Tasmota/sonoff/sonoff.ino.cpp

63483 lines
1.6 MiB

# 1 "/var/folders/vn/7y9hnjrw8xl9lm006s6r35t80000gr/T/tmpQqdpT0"
#include <Arduino.h>
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.ino"
# 29 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.ino"
#include <core_version.h>
#include "sonoff_version.h"
#include "sonoff.h"
#include "my_user_config.h"
#ifdef USE_MQTT_TLS
#include <t_bearssl.h>
#endif
#include "sonoff_post.h"
#include "i18n.h"
#include "sonoff_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"
const char kSleepMode[] PROGMEM = "Dynamic|Normal";
SerialConfig serial_config = SERIAL_8N1;
WiFiUDP PortUdp;
unsigned long feature_drv1;
unsigned long feature_drv2;
unsigned long feature_sns1;
unsigned long feature_sns2;
unsigned long feature5;
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 baudrate = APP_BAUDRATE;
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 tele_period = 1;
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;
char *ota_url;
uint16_t mqtt_cmnd_publish = 0;
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 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_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;
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 ntp_synced_message = 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[33];
char mqtt_topic[33];
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
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* GetFallbackTopic_P(char *stopic, uint32_t prefix, 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 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 PerformEverySecond(void);
void Every100mSeconds(void);
void Every250mSeconds(void);
void ArduinoOTAInit(void);
void SerialInput(void);
void GpioInit(void);
void setup(void);
void loop(void);
uint32_t GetRtcSettingsCrc(void);
void RtcSettingsSave(void);
void RtcSettingsLoad(void);
bool RtcSettingsValid(void);
uint32_t GetRtcRebootCrc(void);
void RtcRebootSave(void);
void RtcRebootLoad(void);
bool RtcRebootValid(void);
void SetFlashModeDout(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);
uint32_t GetSettingsAddress(void);
void SettingsSave(uint8_t rotate);
void SettingsLoad(void);
void SettingsErase(uint8_t type);
bool SettingsEraseConfig(void);
void SettingsSdkErase(void);
void SettingsDefault(void);
void SettingsDefaultSet1(void);
void SettingsDefaultSet2(void);
void SettingsDefaultSet_5_8_1(void);
void SettingsDefaultSet_5_10_1(void);
void SettingsResetStd(void);
void SettingsResetDst(void);
void SettingsDefaultSet_5_13_1c(void);
void SettingsDefaultWebColor(void);
void SettingsDelta(void);
void OsWatchTicker(void);
void OsWatchInit(void);
void OsWatchLoop(void);
String GetResetReason(void);
bool OsWatchBlockedLoop(void);
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);
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* 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* NoAlNumToUnderscore(char* dest, const char* source);
char IndexSeparator();
void SetShortcutDefault(void);
uint8_t Shortcut();
bool ValidIpAddress(const char* str);
bool ParseIp(uint32_t* addr, const char* str);
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);
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);
void SetSerialBaudrate(int baudrate);
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);
uint8_t ModuleNr();
bool ValidTemplateModule(uint32_t index);
bool ValidModule(uint32_t index);
String AnyModuleName(uint32_t index);
String ModuleName();
void ModuleGpios(myio *gp);
gpio_flag ModuleFlag();
void ModuleDefault(uint32_t module);
void SetModuleType();
uint8_t ValidPin(uint32_t pin, uint32_t gpio);
bool ValidGPIO(uint32_t pin, uint32_t gpio);
bool ValidAdc();
bool GetUsedInModule(uint32_t val, uint8_t *arr);
bool JsonTemplate(const char* dataBuf);
void TemplateJson();
long TimeDifference(unsigned long prev, unsigned long next);
long TimePassedSince(unsigned long timestamp);
bool TimeReached(unsigned long timer);
void SetNextTimeInterval(unsigned long& timer, const unsigned long step);
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);
bool I2cDevice(uint8_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 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 ResponseCmndIdxNumber(int value);
void ResponseCmndChar(const char* value);
void ResponseCmndStateText(uint32_t value);
void ResponseCmndDone(void);
void ResponseCmndIdxChar(const char* value);
void ExecuteCommand(char *cmnd, uint32_t source);
void CommandHandler(char* topic, uint8_t* data, uint32_t data_len);
void CmndBacklog(void);
void CmndDelay(void);
void CmndPower(void);
void CmndStatus(void);
void CmndState(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 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 CmndI2cScan(void);
void CmndSensor(void);
void CmndDriver(void);
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 update_position(void);
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 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);
void SwitchPullupFlag(uint16 switch_bit);
uint8_t SwitchLastState(uint8_t index);
void SwitchSetVirtual(uint8_t index, uint8_t state);
uint8_t SwitchGetVirtual(uint8_t index);
void SwitchProbe(void);
void SwitchInit(void);
void SwitchHandler(uint8_t mode);
void SwitchLoop(void);
bool UdpDisconnect(void);
bool UdpConnect(void);
void PollUdp(void);
int WifiGetRssiAsQuality(int rssi);
bool WifiConfigCounter(void);
bool WifiWpsConfigDone(void);
bool WifiWpsConfigBegin(void);
void WifiConfig(uint8_t type);
void WiFiSetSleepMode(void);
void WifiBegin(uint8_t flag, uint8_t channel);
void WifiBeginAfterScan();
uint16_t WifiLinkCount();
String WifiDowntime();
void WifiSetState(uint8_t state);
void WifiCheckIp(void);
void WifiCheck(uint8_t param);
int WifiState(void);
void WifiConnect(void);
void WifiDisconnect(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 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 _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 HandleRoot(void);
bool HandleRootStatusRefresh(void);
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);
uint16_t SendMail(char *buffer);
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);
bool Xdrv01(uint8_t function);
bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value);
void setLongMqttHost(const char *mqtt_host);
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* topic, uint8_t* data, unsigned int data_len);
void MqttRetryCounter(uint8_t value);
void MqttSubscribe(const char *topic);
void MqttUnsubscribe(const char *topic);
void MqttPublishDirect(const char* topic, bool retained);
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 MqttPublishPowerState(uint32_t device);
void MqttPublishAllPowerState();
void MqttPublishPowerBlinkState(uint32_t device);
uint16_t MqttConnectCount();
void MqttDisconnected(int state);
void MqttConnected(void);
void MqttReconnect(void);
void MqttCheck(void);
void CmndMqttFingerprint(void);
void CmndMqttUser(void);
void CmndMqttPassword(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);
void TlsWriteSpiBuffer(uint8_t *buf);
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 EnergyCommandResponse(uint32_t nvalue, uint32_t unit);
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 EnergyDrvInit(void);
void EnergySnsInit(void);
void EnergyShow(bool json);
bool Xdrv03(uint8_t function);
bool Xsns03(uint8_t function);
power_t LightPower(void);
uint8_t LightDevice(void);
static uint32_t min3(uint32_t a, uint32_t b, uint32_t c);
void AriluxRfInterrupt(void);
void AriluxRfHandler(void);
void AriluxRfInit(void);
void AriluxRfDisable(void);
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);
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);
bool SM16716_ModuleSelected(void);
void SM16716_Init(void);
void LightInit(void);
void LightUpdateColorMapping(void);
void LightSetDimmer(uint8_t dimmer);
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 LightPreparePower(void);
void LightFade(void);
void LightWheel(uint8_t wheel_pos);
void LightCycleColor(int8_t direction);
void LightRandomColor(void);
void LightSetPower(void);
void LightAnimate(void);
void calcGammaXiaomiBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]);
void calcGammaMultiChannels(uint8_t cur_col[5], uint16_t cur_col_10bits[5]);
void calcGammaBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[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 CmndLed(void);
void CmndPixels(void);
void CmndRotation(void);
void CmndWidth(void);
void CmndScheme(void);
void CmndWakeup(void);
void CmndColorTemperature(void);
void CmndDimmer(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 IrReceiveInit(void);
void IrReceiveCheck(void);
uint8_t IrHvacToshiba(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp);
uint8_t IrHvacMidea(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp);
uint8_t IrHvacMitsubishi(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp);
uint8_t IrHvacLG(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp);
uint8_t IrHvacFujitsu(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp);
uint32_t IrRemoteCmndIrHvacJson(void);
void CmndIrHvac(void);
uint32_t IrRemoteCmndIrSendRaw(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 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 DomoticzUpdateFanState();
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);
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();
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);
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(uint8_t key, uint8_t device, uint8_t state);
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);
static void FindPrefix(char* s1, char* s2, char* out);
static void Shorten(char** s, char *prefix);
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);
void HAssAnnounceSensors(void);
void HAssAnnounceStatusSensor(void);
void HAssPublishStatus(void);
void HAssDiscovery(void);
void HAssDiscover(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);
void 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 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);
bool TuyaSetPower(void);
bool TuyaSetChannels(void);
void LightSerialDuty(uint8_t duty);
void TuyaRequestState(void);
void TuyaResetWifi(void);
void TuyaPacketProcess(void);
bool TuyaModuleSelected(void);
void TuyaInit(void);
void TuyaSerialInput(void);
bool TuyaButtonPressed(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 PS16DZSerialSendTxBuffer(void);
void PS16DZSerialSendOkCommand(void);
void PS16DZSerialSendUpdateCommand(void);
bool PS16DZSerialSendUpdateCommandIfRequired(void);
bool PS16DZModuleSelected(void);
void PS16DZInit(void);
void PS16DZSerialInput(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);
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();
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);
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_ReceiveDeviceInfo(int32_t res, class SBuffer &buf);
int32_t Z_CheckNVWrite(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_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf);
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_State_Ready(uint8_t value);
uint8_t ZigbeeGetInstructionSize(uint8_t instr);
void ZigbeeGotoLabel(uint8_t label);
void ZigbeeStateMachine_Run(void);
int32_t ZigbeeProcessInput(class SBuffer &buf);
void ZigbeeInput(void);
void ZigbeeInit(void);
void CmndZigbeeZNPSend(void);
void ZigbeeZNPSend(const uint8_t *msg, size_t len);
void CmndZigbeePermitJoin(void);
bool Xdrv23(uint8_t function);
void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune);
void BuzzerBeep(uint32_t count);
void BuzzerEnabledBeep(uint32_t count);
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 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 DebugCfgShow(uint8_t more);
void SetFlashMode(uint8_t mode);
void CmndHelp(void);
void CmndRtcDump(void);
void CmndCfgDump(void);
void CmndCfgPeek(void);
void CmndCfgPoke(void);
void CmndCfgShow(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);
bool Xdrv99(uint8_t function);
void XsnsDriverState(void);
bool XdrvMqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uint16_t sdataBuf);
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 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 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 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 PzemEvery200ms(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 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);
bool XnrgCall(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 Ws2812Init(void);
void Ws2812Clear(void);
void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white);
void Ws2812ForceSuspend (void);
void Ws2812ForceUpdate (void);
char* Ws2812GetColor(uint32_t led, char* scolor);
void Ws2812ShowScheme(uint32_t scheme);
void CounterUpdate(uint8_t index);
void CounterUpdate1(void);
void CounterUpdate2(void);
void CounterUpdate3(void);
void CounterUpdate4(void);
bool CounterPinState(void);
void CounterInit(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 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);
bool OneWireCrc8(uint8_t *addr);
void Ds18b20Convert(void);
bool Ds18b20Read(void);
void Ds18b20EverySecond(void);
void Ds18b20Show(bool json);
bool Xsns05(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 Ds18x20Init(void);
void Ds18x20Search(void);
uint8_t Ds18x20Sensors(void);
String Ds18x20Addresses(uint8_t sensor);
void Ds18x20Convert(void);
bool Ds18x20Read(uint8_t sensor, float &t);
void Ds18x20Type(uint8_t sensor);
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);
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 BmpEverySecond(void);
void BmpShow(bool json);
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 Ads1115GetValues(uint8_t address);
void Ads1115toJSON(char *comma_j);
void Ads1115toString(uint8_t address);
void Ads1115Show(bool json);
bool Xsns12(uint8_t function);
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);
bool 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);
void Sr04Init(void);
void Sr04Show(bool json);
bool Xsns22(uint8_t function);
bool SDM120_ModbusReceiveReady(void);
void SDM120_ModbusSend(uint8_t function_code, uint16_t start_address, uint16_t register_count);
uint8_t SDM120_ModbusReceive(float *value);
uint16_t SDM120_calculateCRC(uint8_t *frame, uint8_t num);
void SDM120250ms(void);
void SDM120Init(void);
void SDM120Show(bool json);
bool Xsns23(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);
void Si1145Update(void);
void Si1145Show(bool json);
bool Xsns24(uint8_t function);
bool SDM630_ModbusReceiveReady(void);
void SDM630_ModbusSend(uint8_t function_code, uint16_t start_address, uint16_t register_count);
uint8_t SDM630_ModbusReceive(float *value);
uint16_t SDM630_calculateCRC(uint8_t *frame, uint8_t num);
void SDM630250ms(void);
void SDM630Init(void);
void SDM630Show(bool json);
bool Xsns25(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);
bool 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);
void Mpr121Show(struct mpr121 *pS, uint8_t function);
bool Xsns30(uint8_t function);
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);
bool Xsns33(uint8_t function);
bool HxIsReady(uint16_t timeout);
long HxRead();
void HxResetPart(void);
void HxReset(void);
void HxCalibrationStateTextJson(uint8_t msg_id);
bool HxCommand(void);
long HxWeight();
void HxInit(void);
void HxEvery100mSecond(void);
void HxSaveBeforeRestart();
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_triggerTele();
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();
bool 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);
bool Scd30Init();
int Scd30Update();
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 SPS30_Every_Second();
void SPS30_Show(bool json);
bool SPS30_cmd(void);
bool Xsns44(byte function);
void Vl53l0Detect();
void Vl53l0Every_250MSecond();
void Vl53l0Show(boolean json);
bool Xsns45(byte function);
void MLX90614_Init();
uint16_t read_irtmp(uint8_t flag);
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);
bool I2cWriteReg(uint8_t addr, uint8_t reg);
void ChirpReset(uint8_t addr);
void ChirpResetAll(void);
void ChirpClockSet();
void ChirpSleep(uint8_t addr);
void ChirpSelect(uint8_t sensor);
bool ChirpMeasureLight(void);
void ChirpReadCapTemp();
bool ChirpReadLight();
uint8_t ChirpReadVersion(uint8_t addr);
bool ChirpSet(uint8_t addr);
bool ChirpScan();
void ChirpDetect(void);
void ChirpEverySecond(void);
void ChirpShow(bool json);
bool ChirpCmd(void);
bool Xsns48(uint8_t function);
bool solaxX1_RS485ReceiveReady(void);
void solaxX1_RS485Send(uint8_t *msg, uint16_t msgLen);
uint8_t solaxX1_RS485Receive(uint8_t *value);
uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen);
void solaxX1_setMessage(uint8_t *message);
void solaxX1_SendInverterAddress();
void solaxX1_QueryLiveData();
uint8_t solaxX1_ParseErrorCode(uint32_t code);
void solaxX1_Update(void);
void solaxX1Init(void);
void solaxX1Show(bool json);
bool Xsns49(uint8_t function);
void PAJ7620SelectBank(uint8_t bank);
void PAJ7620TriggerTele();
void PAJ7620DecodeGesture(void);
void PAJ7620ReadGesture(void);
void PAJ7620Detect(void);
void PAJ7620Init(void);
void PAJ7620SelectMode(uint16_t mode);
void PAJ7620Loop(void);
void PAJ7620Show(bool json);
bool PAJ7620Cmd(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);
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);
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 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 XsnsEnabled(uint32_t sns_index);
void XsnsSensorState(void);
bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index);
bool XsnsCall(uint8_t Function);
#line 180 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.ino"
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(Settings.ota_url, "%04d") != nullptr) {
snprintf(otaurl, otaurl_size, Settings.ota_url, ESP.getChipId() & 0x1fff);
}
else if (strstr(Settings.ota_url, "%d") != nullptr) {
snprintf_P(otaurl, otaurl_size, Settings.ota_url, ESP.getChipId());
}
else {
strlcpy(otaurl, Settings.ota_url, otaurl_size);
}
return otaurl;
}
char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic)
{
char romram[CMDSZ];
String fulltopic;
snprintf_P(romram, sizeof(romram), subtopic);
if (fallback_topic_flag || (prefix > 3)) {
prefix &= 3;
fulltopic = FPSTR(kPrefixes[prefix]);
fulltopic += F("/");
fulltopic += mqtt_client;
fulltopic += F("_fb");
} else {
fulltopic = Settings.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 < 3; i++) {
if ('\0' == Settings.mqtt_prefix[i][0]) {
snprintf_P(Settings.mqtt_prefix[i], sizeof(Settings.mqtt_prefix[i]), kPrefixes[i]);
}
}
fulltopic.replace(FPSTR(MQTT_TOKEN_PREFIX), Settings.mqtt_prefix[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* GetFallbackTopic_P(char *stopic, uint32_t prefix, const char* subtopic)
{
return GetTopic_P(stopic, prefix +4, nullptr, subtopic);
}
char* GetStateText(uint32_t state)
{
if (state > 3) {
state = 1;
}
return Settings.state_text[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);
if (pin[GPIO_REL1 +port] < 99) {
digitalWrite(pin[GPIO_REL1 +port], bitRead(rel_inverted, port) ? !state : state);
}
}
}
void SetDevicePower(power_t rpower, uint32_t source)
{
ShowSource(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) && (pin[GPIO_REL1 +i] < 99)) {
digitalWrite(pin[GPIO_REL1 +i], bitRead(rel_inverted, i) ? !state : state);
}
rpower >>= 1;
}
}
}
void RestorePower(bool publish_power, uint32_t source)
{
if (power != last_power) {
SetDevicePower(last_power, source);
if (publish_power) {
MqttPublishAllPowerState();
}
}
}
void SetAllPower(uint32_t state, uint32_t source)
{
# 394 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.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 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(pin[GPIO_LED1 + led], bitRead(led_inverted, led) ? !state : state);
}
}
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);
}
}
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)
{
# 499 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.ino"
char stopic[TOPSZ];
char scommand[CMDSZ];
char key_topic[sizeof(Settings.button_topic)];
bool result = false;
char *tmp = (key) ? Settings.switch_topic : Settings.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(Settings.mqtt_grptopic, 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)))) {
MqttPublishDirect(stopic, ((key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain) && (state != POWER_HOLD || !Settings.flag3.no_hold_retain));
}
#else
MqttPublishDirect(stopic, ((key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain) && (state != POWER_HOLD || !Settings.flag3.no_hold_retain));
#endif
result = !Settings.flag3.button_switch_force_local;
} else {
Response_P(PSTR("{\"%s%d\":{\"State\":%d}}"), (key) ? "Switch" : "Button", device, state);
result = XdrvRulesProcess();
}
#ifdef USE_KNX
KnxSendButtonPower(key, device, state);
#endif
return result;
}
void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
{
# 553 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.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[33];
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_LINK_COUNT "\":%d,\"" D_JSON_DOWNTIME "\":\"%s\"}}"),
Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active], WiFi.BSSIDstr().c_str(), WiFi.channel(), WifiGetRssiAsQuality(WiFi.RSSI()), WifiLinkCount(), WifiDowntime().c_str());
}
void MqttPublishTeleState(void)
{
mqtt_data[0] = '\0';
MqttShowState();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
#ifdef 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
bool swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i]));
ResponseAppend_P(PSTR(",\"" D_JSON_SWITCH "%d\":\"%s\""), i +1, GetStateText(swm ^ SwitchLastState(i)));
}
}
XsnsCall(FUNC_JSON_APPEND);
#ifdef USE_SCRIPT_JSON_EXPORT
XdrvCall(FUNC_JSON_APPEND);
#endif
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 PerformEverySecond(void)
{
uptime++;
if (ntp_synced_message) {
AddLog_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());
ntp_synced_message = false;
}
if (BOOT_LOOP_TIME == uptime) {
RtcReboot.fast_reboot_count = 0;
RtcRebootSave();
Settings.bootcount++;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BOOT_COUNT " %d"), Settings.bootcount);
}
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) {
tele_period++;
if (tele_period == Settings.tele_period -1) {
XsnsCall(FUNC_PREP_BEFORE_TELEPERIOD);
}
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_EVERY_SECOND);
XsnsCall(FUNC_EVERY_SECOND);
}
# 840 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff.ino"
void Every100mSeconds(void)
{
power_t power_now;
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);
}
}
}
if (TimeReached(backlog_delay)) {
if (!BACKLOG_EMPTY && !backlog_mutex) {
backlog_mutex = true;
#ifdef SUPPORT_IF_STATEMENT
ExecuteCommand((char*)backlog.shift().c_str(), SRC_BACKLOG);
#else
ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG);
backlog_pointer++;
if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; }
#endif
backlog_mutex = false;
}
}
}
void Every250mSeconds(void)
{
uint32_t blinkinterval = 1;
state_250mS++;
state_250mS &= 0x3;
if (mqtt_cmnd_publish) mqtt_cmnd_publish--;
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:
PerformEverySecond();
if (ota_state_flag && BACKLOG_EMPTY) {
ota_state_flag--;
if (2 == ota_state_flag) {
ota_url = Settings.ota_url;
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) {
char *bch = strrchr(mqtt_data, '/');
char *pch = strrchr((bch != nullptr) ? bch : mqtt_data, '-');
char *ech = strrchr((bch != nullptr) ? bch : mqtt_data, '.');
if (!pch) { pch = ech; }
if (pch) {
mqtt_data[pch - mqtt_data] = '\0';
char *ech = strrchr(Settings.ota_url, '.');
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s-" D_JSON_MINIMAL "%s"), mqtt_data, ech);
}
}
#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;
if (ota_result) {
Response_P(PSTR(D_JSON_SUCCESSFUL ". " D_JSON_RESTARTING));
} else {
Response_P(PSTR(D_JSON_FAILED " %s"), ESPhttpUpdate.getLastErrorString().c_str());
}
restart_flag = 2;
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_wifi[sizeof(Settings.sta_ssid) +
sizeof(Settings.sta_pwd)];
char storage_mqtt[sizeof(Settings.mqtt_host) +
sizeof(Settings.mqtt_port) +
sizeof(Settings.mqtt_client) +
sizeof(Settings.mqtt_user) +
sizeof(Settings.mqtt_pwd) +
sizeof(Settings.mqtt_topic)];
memcpy(storage_wifi, Settings.sta_ssid, sizeof(storage_wifi));
if (216 == restart_flag) {
memcpy(storage_mqtt, Settings.mqtt_host, sizeof(storage_mqtt));
}
if ((215 == restart_flag) || (216 == restart_flag)) {
SettingsErase(0);
}
SettingsDefault();
memcpy(Settings.sta_ssid, storage_wifi, sizeof(storage_wifi));
if (216 == restart_flag) {
memcpy(Settings.mqtt_host, storage_mqtt, sizeof(storage_mqtt));
strlcpy(Settings.mqtt_client, MQTT_CLIENT_ID, sizeof(Settings.mqtt_client));
}
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 (Settings.web_password[0] !=0) { ArduinoOTA.setPassword(Settings.web_password); }
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"));
}
#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;
}
}
}
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 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];
ResponseTime_P(PSTR(",\"" D_JSON_SERIALRECEIVED "\":\"%s\"}"),
(Settings.flag.mqtt_serial_raw) ? ToHex_P((unsigned char*)serial_in_buffer, serial_in_byte_counter, hex_char, sizeof(hex_char)) : serial_in_buffer);
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) {
baudrate = APP_BAUDRATE;
}
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 = 1;
light_type = LT_BASIC;
#ifdef USE_LIGHT
if (Settings.flag.pwm_control) {
for (uint32_t i = 0; i < MAX_PWMS; i++) {
if (pin[GPIO_PWM1 +i] < 99) { light_type++; }
}
}
#endif
if (XdrvCall(FUNC_MODULE_INIT)) {
}
else if (YTF_IR_BRIDGE == my_module_type) {
ClaimSerial();
}
else if (SONOFF_DUAL == my_module_type) {
Settings.flag.mqtt_serial = 0;
devices_present = 2;
baudrate = 19200;
}
else if (CH4 == my_module_type) {
Settings.flag.mqtt_serial = 0;
devices_present = 4;
baudrate = 19200;
}
else if (SONOFF_SC == my_module_type) {
Settings.flag.mqtt_serial = 0;
devices_present = 0;
baudrate = 19200;
}
#ifdef USE_LIGHT
else if (SONOFF_BN == my_module_type) {
light_type = LT_PWM1;
}
else if (SONOFF_LED == my_module_type) {
light_type = LT_PWM2;
}
else if (AILIGHT == my_module_type) {
light_type = LT_RGBW;
}
else if (SONOFF_B1 == my_module_type) {
light_type = LT_RGBWC;
}
#endif
else {
if (!light_type) { devices_present = 0; }
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
#ifdef USE_LIGHT
#ifdef USE_WS2812
if (!light_type && (pin[GPIO_WS2812] < 99)) {
devices_present++;
light_type = LT_WS2812;
}
#endif
#ifdef USE_SM16716
if (SM16716_ModuleSelected()) {
light_type += 3;
light_type |= LT_SM16716;
}
#endif
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;
}
#endif
if (!light_type) {
for (uint32_t i = 0; i < MAX_PWMS; i++) {
if (pin[GPIO_PWM1 +i] < 99) {
pwm_present = true;
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]);
}
}
}
SetLedPower(Settings.ledstate &8);
SetLedLink(Settings.ledstate &8);
XdrvCall(FUNC_PRE_INIT);
}
extern "C" {
extern struct rst_info resetInfo;
}
void setup(void)
{
global_state.data = 3;
RtcRebootLoad();
if (!RtcRebootValid()) { RtcReboot.fast_reboot_count = 0; }
RtcReboot.fast_reboot_count++;
RtcRebootSave();
Serial.begin(baudrate);
delay(10);
Serial.println();
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);
}
char code_image[20];
snprintf_P(my_image, sizeof(my_image), PSTR("(%s)"), GetTextIndexed(code_image, sizeof(code_image), CODE_IMAGE, kCodeImage));
SettingsLoad();
SettingsDelta();
OsWatchInit();
GetFeatures();
if (1 == RtcReboot.fast_reboot_count) {
XdrvCall(FUNC_SETTINGS_OVERRIDE);
}
baudrate = Settings.baudrate * 300;
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_DEBUG, PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count);
}
}
Format(mqtt_client, Settings.mqtt_client, sizeof(mqtt_client));
Format(mqtt_topic, Settings.mqtt_topic, sizeof(mqtt_topic));
if (strstr(Settings.hostname, "%") != nullptr) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
snprintf_P(my_hostname, sizeof(my_hostname)-1, Settings.hostname, mqtt_topic, ESP.getChipId() & 0x1FFF);
} else {
snprintf_P(my_hostname, sizeof(my_hostname)-1, Settings.hostname);
}
GpioInit();
SetSerialBaudrate(baudrate);
WifiConnect();
if (MOTOR == my_module_type) { Settings.poweronstate = POWER_ALL_ON; }
if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) {
SetDevicePower(1, SRC_RESTART);
} else {
if ((resetInfo.reason == REASON_DEFAULT_RST) || (resetInfo.reason == 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;
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_ESP8266_RELEASE), PROJECT, Settings.friendlyname[0], my_version, my_image);
#ifdef FIRMWARE_MINIMAL
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION));
#endif
RtcInit();
#ifdef USE_ARDUINO_OTA
ArduinoOTAInit();
#endif
XdrvCall(FUNC_INIT);
XsnsCall(FUNC_INIT);
}
void loop(void)
{
uint32_t my_sleep = millis();
XdrvCall(FUNC_LOOP);
XsnsCall(FUNC_LOOP);
OsWatchLoop();
ButtonLoop();
SwitchLoop();
#ifdef ROTARY_V1
RotaryLoop();
#endif
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 (!serial_local) { SerialInput(); }
#ifdef USE_ARDUINO_OTA
MDNS.update();
ArduinoOTA.handle();
while (arduino_ota_triggered) ArduinoOTA.handle();
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/_changelog.ino"
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/settings.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 IR_RCV_MIN_UNKNOWN_SIZE
#define IR_RCV_MIN_UNKNOWN_SIZE 6
#endif
#ifndef ENERGY_OVERTEMP
#define ENERGY_OVERTEMP 90
#endif
#ifndef TUYA_DIMMER_MAX
#define TUYA_DIMMER_MAX 100
#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_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;
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 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
extern "C" uint32_t _FS_end;
const uint32_t SPIFFS_END = ((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;
#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;
}
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);
}
uint32_t GetSettingsAddress(void)
{
return settings_location * SPI_FLASH_SEC_SIZE;
}
void SettingsSave(uint8_t rotate)
{
# 379 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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();
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 SettingsErase(uint8_t type)
{
#ifndef FIRMWARE_MINIMAL
bool result;
uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1;
uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE;
if (1 == type) {
_sectorStart = SETTINGS_LOCATION +2;
_sectorEnd = SETTINGS_LOCATION +5;
}
bool _serialoutput = (LOG_LEVEL_DEBUG_MORE <= seriallog_level);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart);
for (uint32_t _sector = _sectorStart; _sector < _sectorEnd; _sector++) {
result = ESP.flashEraseSector(_sector);
if (_serialoutput) {
Serial.print(F(D_LOG_APPLICATION D_ERASED_SECTOR " "));
Serial.print(_sector);
if (result) {
Serial.println(F(" " D_OK));
} else {
Serial.println(F(" " D_ERROR));
}
delay(10);
}
OsWatchLoop();
}
#endif
}
bool SettingsEraseConfig(void) {
const size_t cfgSize = 0x4000;
size_t cfgAddr = ESP.getFlashChipSize() - cfgSize;
for (size_t offset = 0; offset < cfgSize; offset += SPI_FLASH_SEC_SIZE) {
if (!ESP.flashEraseSector((cfgAddr + offset) / SPI_FLASH_SEC_SIZE)) {
return false;
}
}
return true;
}
void SettingsSdkErase(void)
{
WiFi.disconnect(true);
SettingsErase(1);
SettingsEraseConfig();
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.save_data = SAVE_DATA;
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);
strlcpy(Settings.friendlyname[0], FRIENDLY_NAME, sizeof(Settings.friendlyname[0]));
strlcpy(Settings.friendlyname[1], FRIENDLY_NAME"2", sizeof(Settings.friendlyname[1]));
strlcpy(Settings.friendlyname[2], FRIENDLY_NAME"3", sizeof(Settings.friendlyname[2]));
strlcpy(Settings.friendlyname[3], FRIENDLY_NAME"4", sizeof(Settings.friendlyname[3]));
strlcpy(Settings.ota_url, OTA_URL, sizeof(Settings.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.baudrate = APP_BAUDRATE / 300;
Settings.sbaudrate = SOFT_BAUDRATE / 300;
Settings.serial_delimiter = 0xff;
Settings.seriallog_level = SERIAL_LOG_LEVEL;
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;
strlcpy(Settings.sta_ssid[0], STA_SSID1, sizeof(Settings.sta_ssid[0]));
strlcpy(Settings.sta_pwd[0], STA_PASS1, sizeof(Settings.sta_pwd[0]));
strlcpy(Settings.sta_ssid[1], STA_SSID2, sizeof(Settings.sta_ssid[1]));
strlcpy(Settings.sta_pwd[1], STA_PASS2, sizeof(Settings.sta_pwd[1]));
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
strlcpy(Settings.syslog_host, SYS_LOG_HOST, sizeof(Settings.syslog_host));
Settings.syslog_port = SYS_LOG_PORT;
Settings.syslog_level = SYS_LOG_LEVEL;
Settings.flag2.emulation = EMULATION;
Settings.webserver = WEB_SERVER;
Settings.weblog_level = WEB_LOG_LEVEL;
strlcpy(Settings.web_password, WEB_PASSWORD, sizeof(Settings.web_password));
Settings.flag3.mdns_enabled = MDNS_ENABLED;
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_power_retain = MQTT_POWER_RETAIN;
Settings.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN;
Settings.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN;
Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL;
Settings.flag3.hass_tele_on_power = TELE_ON_POWER;
strlcpy(Settings.mqtt_host, MQTT_HOST, sizeof(Settings.mqtt_host));
Settings.mqtt_port = MQTT_PORT;
strlcpy(Settings.mqtt_client, MQTT_CLIENT_ID, sizeof(Settings.mqtt_client));
strlcpy(Settings.mqtt_user, MQTT_USER, sizeof(Settings.mqtt_user));
strlcpy(Settings.mqtt_pwd, MQTT_PASS, sizeof(Settings.mqtt_pwd));
strlcpy(Settings.mqtt_topic, MQTT_TOPIC, sizeof(Settings.mqtt_topic));
strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic));
strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic));
strlcpy(Settings.mqtt_grptopic, MQTT_GRPTOPIC, sizeof(Settings.mqtt_grptopic));
strlcpy(Settings.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(Settings.mqtt_fulltopic));
Settings.mqtt_retry = MQTT_RETRY_SECS;
strlcpy(Settings.mqtt_prefix[0], SUB_PREFIX, sizeof(Settings.mqtt_prefix[0]));
strlcpy(Settings.mqtt_prefix[1], PUB_PREFIX, sizeof(Settings.mqtt_prefix[1]));
strlcpy(Settings.mqtt_prefix[2], PUB_PREFIX2, sizeof(Settings.mqtt_prefix[2]));
strlcpy(Settings.state_text[0], MQTT_STATUS_OFF, sizeof(Settings.state_text[0]));
strlcpy(Settings.state_text[1], MQTT_STATUS_ON, sizeof(Settings.state_text[1]));
strlcpy(Settings.state_text[2], MQTT_CMND_TOGGLE, sizeof(Settings.state_text[2]));
strlcpy(Settings.state_text[3], MQTT_CMND_HOLD, sizeof(Settings.state_text[3]));
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.flag2.current_resolution = 3;
Settings.flag2.energy_resolution = ENERGY_RESOLUTION;
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;
# 702 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE;
memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9);
Settings.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER;
# 734 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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.flag2.calc_resolution = CALC_RESOLUTION;
Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE;
Settings.flag.pwm_control = 1;
Settings.pwm_frequency = PWM_FREQ;
Settings.pwm_range = PWM_RANGE;
for (uint32_t i = 0; i < MAX_PWMS; i++) {
Settings.light_color[i] = 255;
}
Settings.light_correction = 1;
Settings.light_dimmer = 10;
Settings.light_speed = 1;
Settings.light_width = 1;
Settings.light_pixels = WS2812_LEDS;
SettingsDefaultSet_5_8_1();
Settings.param[P_TUYA_DIMMER_MAX] = TUYA_DIMMER_MAX;
SettingsDefaultSet_5_10_1();
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);
}
strlcpy(Settings.ntp_server[0], NTP_SERVER1, sizeof(Settings.ntp_server[0]));
strlcpy(Settings.ntp_server[1], NTP_SERVER2, sizeof(Settings.ntp_server[1]));
strlcpy(Settings.ntp_server[2], NTP_SERVER3, sizeof(Settings.ntp_server[2]));
for (uint32_t j = 0; j < 3; j++) {
for (uint32_t i = 0; i < strlen(Settings.ntp_server[j]); i++) {
if (Settings.ntp_server[j][i] == ',') {
Settings.ntp_server[j][i] = '.';
}
}
}
Settings.latitude = (int)((double)LATITUDE * 1000000);
Settings.longitude = (int)((double)LONGITUDE * 1000000);
SettingsDefaultSet_5_13_1c();
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_period = WORKING_PERIOD;
SettingsDefaultWebColor();
memset(&Settings.monitors, 0xFF, 20);
}
void SettingsDefaultSet_5_8_1(void)
{
Settings.ws_width[WS_SECOND] = 1;
Settings.ws_color[WS_SECOND][WS_RED] = 255;
Settings.ws_color[WS_SECOND][WS_GREEN] = 0;
Settings.ws_color[WS_SECOND][WS_BLUE] = 255;
Settings.ws_width[WS_MINUTE] = 3;
Settings.ws_color[WS_MINUTE][WS_RED] = 0;
Settings.ws_color[WS_MINUTE][WS_GREEN] = 255;
Settings.ws_color[WS_MINUTE][WS_BLUE] = 0;
Settings.ws_width[WS_HOUR] = 5;
Settings.ws_color[WS_HOUR][WS_RED] = 255;
Settings.ws_color[WS_HOUR][WS_GREEN] = 0;
Settings.ws_color[WS_HOUR][WS_BLUE] = 0;
}
void SettingsDefaultSet_5_10_1(void)
{
Settings.display_model = 0;
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_rotate = 0;
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;
}
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 SettingsDefaultSet_5_13_1c(void)
{
SettingsResetStd();
SettingsResetDst();
}
void SettingsDefaultWebColor(void)
{
char scolor[10];
for (uint32_t i = 0; i < COL_LAST; i++) {
WebHexCode(i, GetTextIndexed(scolor, sizeof(scolor), i, kWebColors));
}
}
void SettingsDelta(void)
{
if (Settings.version != VERSION) {
if (Settings.version < 0x05050000) {
for (uint32_t i = 0; i < 17; i++) { Settings.rf_code[i][0] = 0; }
memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9);
}
if (Settings.version < 0x05080000) {
Settings.light_pixels = WS2812_LEDS;
Settings.light_width = 1;
Settings.light_color[0] = 255;
Settings.light_color[1] = 0;
Settings.light_color[2] = 0;
Settings.light_dimmer = 10;
Settings.light_correction = 1;
Settings.light_fade = 0;
Settings.light_speed = 1;
Settings.light_scheme = 0;
Settings.light_width = 1;
Settings.light_wakeup = 0;
}
if (Settings.version < 0x0508000A) {
Settings.power = 0;
Settings.altitude = 0;
}
if (Settings.version < 0x0508000B) {
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
if ((Settings.my_gp.io[i] >= 25) && (Settings.my_gp.io[i] <= 32)) {
Settings.my_gp.io[i] += 23;
}
}
for (uint32_t i = 0; i < MAX_PWMS; i++) {
Settings.pwm_value[i] = Settings.pulse_timer[4 +i];
Settings.pulse_timer[4 +i] = 0;
}
}
if (Settings.version < 0x0508000D) {
Settings.pwm_frequency = PWM_FREQ;
Settings.pwm_range = PWM_RANGE;
}
if (Settings.version < 0x0508000E) {
SettingsDefaultSet_5_8_1();
}
if (Settings.version < 0x05090102) {
Settings.flag2.data = Settings.flag.data;
Settings.flag2.data &= 0xFFE80000;
Settings.flag2.voltage_resolution = Settings.flag.not_power_linked;
Settings.flag2.current_resolution = 3;
Settings.ina219_mode = 0;
}
if (Settings.version < 0x050A0009) {
SettingsDefaultSet_5_10_1();
}
if (Settings.version < 0x050B0107) {
Settings.flag.not_power_linked = 0;
}
if (Settings.version < 0x050C0005) {
Settings.light_rotation = 0;
Settings.energy_power_delta = 0;
char fingerprint[60];
memcpy(fingerprint, Settings.mqtt_fingerprint, sizeof(fingerprint));
char *p = fingerprint;
for (uint32_t i = 0; i < 20; i++) {
Settings.mqtt_fingerprint[0][i] = strtol(p, &p, 16);
Settings.mqtt_fingerprint[1][i] = Settings.mqtt_fingerprint[0][i];
}
}
if (Settings.version < 0x050C0007) {
Settings.baudrate = APP_BAUDRATE / 300;
}
if (Settings.version < 0x050C0008) {
Settings.sbaudrate = SOFT_BAUDRATE / 300;
Settings.serial_delimiter = 0xff;
}
if (Settings.version < 0x050C000A) {
Settings.latitude = (int)((double)LATITUDE * 1000000);
Settings.longitude = (int)((double)LONGITUDE * 1000000);
}
if (Settings.version < 0x050C000B) {
Settings.rules[0][0] = '\0';
}
if (Settings.version < 0x050C000D) {
memmove(Settings.rules, Settings.rules -256, sizeof(Settings.rules));
memset(&Settings.timer, 0x00, sizeof(Timer) * MAX_TIMERS);
Settings.knx_GA_registered = 0;
Settings.knx_CB_registered = 0;
memset(&Settings.knx_physsical_addr, 0x00, 0x800 - 0x6b8);
}
if (Settings.version < 0x050C000F) {
Settings.energy_kWhtoday /= 1000;
Settings.energy_kWhyesterday /= 1000;
RtcSettings.energy_kWhtoday /= 1000;
}
if (Settings.version < 0x050D0103) {
SettingsDefaultSet_5_13_1c();
}
if (Settings.version < 0x050E0002) {
for (uint32_t i = 1; i < MAX_RULE_SETS; i++) { Settings.rules[i][0] = '\0'; }
Settings.rule_enabled = Settings.flag.mqtt_serial_raw;
Settings.rule_once = Settings.flag.pressure_conversion;
}
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_period = WORKING_PERIOD;
}
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_dimmer_range_255) {
Settings.param[P_TUYA_DIMMER_MAX] = 100;
} else {
Settings.param[P_TUYA_DIMMER_MAX] = 255;
}
}
if (Settings.version < 0x06060009) {
Settings.baudrate = Settings.ex_baudrate * 4;
Settings.sbaudrate = Settings.ex_sbaudrate * 4;
}
if (Settings.version < 0x0606000A) {
uint8_t tuyaindex = 0;
if (Settings.param[P_ex_TUYA_DIMMER_ID] > 0) {
Settings.tuya_fnid_map[tuyaindex].fnid = 21;
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_DIMMER_ID];
tuyaindex++;
} else if (Settings.flag3.ex_tuya_disable_dimmer == 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];
tuyaindex++;
}
}
if (Settings.version < 0x0606000C) {
memset(&Settings.register8, 0x00, sizeof(Settings.register8));
}
Settings.version = VERSION;
SettingsSave(1);
}
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff_ca.ino"
# 24 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff_ca.ino"
#ifdef USE_MQTT_TLS_CA_CERT
#ifndef USE_MQTT_AWS_IOT
# 38 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff_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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/sonoff_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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support.ino"
IPAddress syslog_host_addr;
uint32_t syslog_host_hash = 0;
#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, last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), last_run);
#endif
if (last_run >= (OSWATCH_RESET_TIME * 1000)) {
RtcSettings.oswatch_blocked_loop = 1;
RtcSettingsSave();
ESP.reset();
}
}
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();
}
String GetResetReason(void)
{
char buff[32];
if (oswatch_blocked_loop) {
strncpy_P(buff, PSTR(D_JSON_BLOCKED_LOOP), sizeof(buff));
return String(buff);
} else {
return ESP.getResetReason();
}
}
bool OsWatchBlockedLoop(void)
{
return oswatch_blocked_loop;
}
#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
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;
int n = 0;
do {
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* 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* 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()
{
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()
{
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);
}
bool NewerVersion(char* version_str)
{
uint32_t version = 0;
uint32_t i = 0;
char *str_ptr;
char* version_dup = strdup(version_str);
if (!version_dup) {
return false;
}
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)) {
free(version_dup);
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++;
}
}
free(version_dup);
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)
{
char sidx[8];
strncpy_P(dest, S_RSLT_POWER, size);
if ((devices_present + option) > 1) {
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);
}
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;
}
return result;
}
float ConvertTempToCelsius(float c)
{
float result = c;
if (!isnan(c) && Settings.flag.temperature_conversion) {
result = (c - 32) / 1.8;
}
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);
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 "|" D_FALSE "|" D_STOP "|" D_CELSIUS "|"
"ON|" D_ON "|" D_TRUE "|" 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,
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;
}
void SetSerialBaudrate(int baudrate)
{
Settings.baudrate = baudrate / 300;
if (Serial.baudRate() != baudrate) {
if (seriallog_level) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SET_BAUDRATE_TO " %d"), baudrate);
}
delay(100);
Serial.flush();
Serial.begin(baudrate, serial_config);
delay(10);
Serial.println();
}
}
void ClaimSerial(void)
{
serial_local = true;
AddLog_P(LOG_LEVEL_INFO, PSTR("SNS: Hardware Serial"));
SetSeriallog(LOG_LEVEL_NONE);
baudrate = Serial.baudRate();
Settings.baudrate = baudrate / 300;
}
void SerialSendRaw(char *codes)
{
char *p;
char stemp[3];
uint8_t code;
int size = strlen(codes);
while (size > 0) {
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);
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 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("}}"));
}
uint8_t ModuleNr()
{
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()
{
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()
{
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()
{
my_module_type = (USER_MODULE == Settings.module) ? Settings.user_template_base : Settings.module;
}
uint8_t ValidPin(uint32_t pin, uint32_t gpio)
{
uint8_t result = gpio;
if (((pin > 5) && (pin < 9)) || (11 == pin)) {
result = GPIO_NONE;
}
if ((WEMOS == Settings.module) && (!Settings.flag3.user_esp8285_enable)) {
if ((pin == 9) || (pin == 10)) { result = GPIO_NONE; }
}
return result;
}
bool ValidGPIO(uint32_t pin, uint32_t gpio)
{
return (GPIO_USER == ValidPin(pin, gpio));
}
bool ValidAdc()
{
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()
{
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);
}
long TimeDifference(unsigned long prev, unsigned long next)
{
long signed_diff = 0;
const unsigned long half_max_unsigned_long = 2147483647u;
if (next >= prev) {
const unsigned long diff = next - prev;
if (diff <= half_max_unsigned_long) {
signed_diff = static_cast<long>(diff);
} else {
signed_diff = static_cast<long>((0xffffffffUL - next) + prev + 1u);
signed_diff = -1 * signed_diff;
}
} else {
const unsigned long diff = prev - next;
if (diff <= half_max_unsigned_long) {
signed_diff = static_cast<long>(diff);
signed_diff = -1 * signed_diff;
} else {
signed_diff = static_cast<long>((0xffffffffUL - prev) + next + 1u);
}
}
return signed_diff;
}
long TimePassedSince(unsigned long timestamp)
{
return TimeDifference(timestamp, millis());
}
bool TimeReached(unsigned long 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);
}
#ifdef USE_I2C
const uint8_t I2C_RETRY_COUNTER = 3;
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 "\"}"));
}
}
bool I2cDevice(uint8_t addr)
{
for (uint8_t address = 1; address <= 127; address++) {
Wire.beginTransmission(address);
if (!Wire.endTransmission() && (address == addr)) {
return true;
}
}
return false;
}
#endif
# 1480 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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)
{
char syslog_preamble[64];
uint32_t current_hash = GetHash(Settings.syslog_host, strlen(Settings.syslog_host));
if (syslog_host_hash != current_hash) {
syslog_host_hash = current_hash;
WiFi.hostByName(Settings.syslog_host, syslog_host_addr);
}
if (PortUdp.beginPacket(syslog_host_addr, Settings.syslog_port)) {
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 (!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 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)
{
# 1627 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_button.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_button.ino"
#define BUTTON_V1
#ifdef BUTTON_V1
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;
}
# 104 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_button.ino"
void ButtonHandler(void)
{
if (uptime < 4) { return; }
uint8_t button = NOT_PRESSED;
uint8_t button_present = 0;
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++) {
button = NOT_PRESSED;
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) {
snprintf_P(scmnd, sizeof(scmnd), kCommands[Button.press_counter[button_index] -3]);
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_command.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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_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 "|"
#ifdef USE_I2C
D_CMND_I2CSCAN "|"
#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,
&CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig,
&CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask,
#ifdef USE_I2C
&CmndI2cScan,
#endif
&CmndSensor, &CmndDriver };
void ResponseCmndNumber(int value)
{
Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, value);
}
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 ExecuteCommand(char *cmnd, uint32_t source)
{
char *start;
char *token;
#ifdef USE_DEBUG_DRIVER
ShowFreeMem(PSTR("ExecuteCommand"));
#endif
ShowSource(source);
token = strtok(cmnd, " ");
if (token != nullptr) {
start = strrchr(token, '/');
if (start) { token = start +1; }
}
uint32_t size = (token != nullptr) ? strlen(token) : 0;
char stopic[size +2];
snprintf_P(stopic, sizeof(stopic), PSTR("/%s"), (token == nullptr) ? "" : token);
token = strtok(nullptr, "");
size = (token != nullptr) ? strlen(token) : 0;
char svalue[size +1];
strlcpy(svalue, (token == nullptr) ? "" : token, sizeof(svalue));
CommandHandler(stopic, (uint8_t*)svalue, strlen(svalue));
}
# 117 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_command.ino"
void CommandHandler(char* topic, uint8_t* data, uint32_t data_len)
{
#ifdef USE_DEBUG_DRIVER
ShowFreeMem(PSTR("CommandHandler"));
#endif
char topicBuf[TOPSZ];
strlcpy(topicBuf, topic, sizeof(topicBuf));
uint32_t i = 0;
for (i = 0; i < data_len; i++) {
if (!isspace(data[i])) { break; }
}
data_len -= i;
char dataBuf[data_len+1];
memcpy(dataBuf, data +i, sizeof(dataBuf));
bool grpflg = (strstr(topicBuf, Settings.mqtt_grptopic) != nullptr);
char stemp1[TOPSZ];
GetFallbackTopic_P(stemp1, CMND, "");
fallback_topic_flag = (!strncmp(topicBuf, stemp1, strlen(stemp1)));
char *type = strrchr(topicBuf, '/');
uint32_t index = 1;
bool user_index = false;
if (type != nullptr) {
type++;
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';
}
DEBUG_CORE_LOG(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() + (100 * MIN_BACKLOG_DELAY);
char command[CMDSZ];
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;
if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) {
if (!XdrvCall(FUNC_COMMAND)) {
if (!XsnsCall(FUNC_COMMAND)) {
type = nullptr;
}
}
}
}
if (type == nullptr) {
blinks = 201;
snprintf_P(topicBuf, sizeof(topicBuf), PSTR(D_JSON_COMMAND));
Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_UNKNOWN "\"}"));
type = (char*)topicBuf;
}
if (mqtt_data[0] != '\0') {
MqttPublishPrefixTopic_P(RESULT_OR_STAT, type);
#ifdef USE_SCRIPT
XdrvRulesProcess();
#endif
}
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) && (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[MAX_FRIENDLYNAMES * (sizeof(Settings.friendlyname[0]) +MAX_FRIENDLYNAMES)];
char stemp2[100];
if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1]) && (!payload)) { option++; }
if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; }
if (!energy_flg && (9 == 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 ? "," : ""), Settings.friendlyname[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,
Settings.button_topic, power, Settings.poweronstate, Settings.ledstate,
Settings.ledmask, Settings.save_data, Settings.flag.save_state, Settings.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_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,\"" D_JSON_SAVECOUNT "\":%d,\"" D_JSON_SAVEADDRESS "\":\"%X\"}}"),
baudrate, Settings.mqtt_grptopic, Settings.ota_url,
GetResetReason().c_str(), GetUptime().c_str(), GetDateAndTime(DT_RESTART).c_str(), Settings.sleep,
Settings.cfg_holder, Settings.bootcount, 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\"}}"),
my_version, my_image, GetBuildDateAndTime().c_str(),
ESP.getBootVersion(), ESP.getSdkVersion());
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_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\"]}}"),
Settings.seriallog_level, Settings.weblog_level, Settings.syslog_level,
Settings.syslog_host, Settings.syslog_port, Settings.sta_ssid[0], Settings.sta_ssid[1], Settings.tele_period,
Settings.flag2.data, Settings.flag.data, ToHex_P((unsigned char*)Settings.param, PARAM8_SIZE, stemp2, sizeof(stemp2)), Settings.flag3.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\"]"),
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);
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}}"),
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);
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "5"));
}
if (((0 == payload) || (6 == payload)) && Settings.flag.mqtt_enabled) {
#ifdef USE_MQTT_AWS_IOT
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_JSON_MASK "\":\"%s\",\""
D_CMND_MQTTCLIENT "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"),
Settings.mqtt_user, Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client,
mqtt_client, MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE);
#else
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}}"),
Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client,
mqtt_client, Settings.mqtt_user, MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE);
#endif
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"));
}
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 CmndSleep(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 251)) {
Settings.sleep = XdrvMailbox.payload;
sleep = XdrvMailbox.payload;
WiFiSetSleepMode();
}
Response_P(S_JSON_COMMAND_NVALUE_UNIT_NVALUE_UNIT, XdrvMailbox.command, sleep, (Settings.flag.value_units) ? " " D_UNIT_MILLISECOND : "", Settings.sleep, (Settings.flag.value_units) ? " " D_UNIT_MILLISECOND : "");
}
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) && (XdrvMailbox.data_len < sizeof(Settings.ota_url))) {
strlcpy(Settings.ota_url, (SC_DEFAULT == Shortcut()) ? OTA_URL : XdrvMailbox.data, sizeof(Settings.ota_url));
}
ResponseCmndChar(Settings.ota_url);
}
void CmndSeriallog(void)
{
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_ALL)) {
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 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)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) {
Settings.pulse_timer[XdrvMailbox.index -1] = XdrvMailbox.payload;
SetPulseTimer(XdrvMailbox.index -1, XdrvMailbox.payload);
}
Response_P(S_JSON_COMMAND_INDEX_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, XdrvMailbox.index, Settings.pulse_timer[XdrvMailbox.index -1], GetPulseTimer(XdrvMailbox.index -1));
}
}
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 < 82) {
uint32_t ptype;
uint32_t pindex;
if (XdrvMailbox.index <= 31) {
ptype = 0;
pindex = XdrvMailbox.index;
}
else if (XdrvMailbox.index <= 49) {
ptype = 2;
pindex = XdrvMailbox.index -32;
}
else {
ptype = 1;
pindex = XdrvMailbox.index -50;
}
if (XdrvMailbox.payload >= 0) {
if (0 == ptype) {
if (XdrvMailbox.payload <= 1) {
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 (1 == ptype) {
if (XdrvMailbox.payload <= 1) {
bitWrite(Settings.flag3.data, pindex, XdrvMailbox.payload);
if (5 == pindex) {
if (0 == XdrvMailbox.payload) {
restart_flag = 2;
}
}
if (10 == pindex) {
WiFiSetSleepMode();
}
if (18 == pindex) {
restart_flag = 2;
}
}
}
else {
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;
switch (pindex) {
#ifdef USE_LIGHT
case P_RGB_REMAP:
LightUpdateColorMapping();
break;
#endif
#if (defined(USE_IR_REMOTE) && defined(USE_IR_RECEIVE)) || defined(USE_IR_REMOTE_FULL)
case P_IR_UNKNOW_THRESHOLD:
IrReceiveUpdateThreshold();
break;
#endif
#ifdef USE_TUYA_MCU
case P_TUYA_DIMMER_MAX:
restart_flag = 2;
break;
#endif
}
}
}
}
if (ptype < 99) {
char stemp1[TOPSZ];
if (2 == ptype) { snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), Settings.param[pindex]); }
ResponseCmndIdxChar((2 == ptype) ? stemp1 : (1 == ptype) ? GetStateText(bitRead(Settings.flag3.data, pindex)) : GetStateText(bitRead(Settings.flag.data, 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])) {
if (jsflg) { ResponseAppend_P(PSTR(",")); }
jsflg = true;
char stemp1[TOPSZ];
ResponseAppend_P(PSTR("\"" D_CMND_GPIO "%d\":{\"%d\":\"%s\"}"), i, Settings.my_gp.io[i], GetTextIndexed(stemp1, sizeof(stemp1), Settings.my_gp.io[i], kSensorNames));
}
}
if (jsflg) {
ResponseJsonEnd();
} else {
ResponseCmndChar(D_JSON_NOT_SUPPORTED);
}
}
}
void CmndGpios(void)
{
myio cmodule;
ModuleGpios(&cmodule);
uint32_t midx;
uint32_t lines = 1;
bool jsflg = false;
for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) {
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;
baudrate = (XdrvMailbox.payload & 0xFFFF) * 300;
SetSerialBaudrate(baudrate);
}
ResponseCmndNumber(Settings.baudrate * 300);
}
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_ALL)) {
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) && (XdrvMailbox.data_len < sizeof(Settings.syslog_host))) {
strlcpy(Settings.syslog_host, (SC_DEFAULT == Shortcut()) ? SYS_LOG_HOST : XdrvMailbox.data, sizeof(Settings.syslog_host));
}
ResponseCmndChar(Settings.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 <= 3)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.ntp_server[0]))) {
strlcpy(Settings.ntp_server[XdrvMailbox.index -1],
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index)?NTP_SERVER1:(2==XdrvMailbox.index)?NTP_SERVER2:NTP_SERVER3 : XdrvMailbox.data,
sizeof(Settings.ntp_server[0]));
for (uint32_t i = 0; i < strlen(Settings.ntp_server[XdrvMailbox.index -1]); i++) {
if (Settings.ntp_server[XdrvMailbox.index -1][i] == ',') Settings.ntp_server[XdrvMailbox.index -1][i] = '.';
}
ntp_force_sync = true;
}
ResponseCmndIdxChar(Settings.ntp_server[XdrvMailbox.index -1]);
}
}
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, Settings.sta_ssid[Settings.sta_active]);
}
void CmndSsid(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.sta_ssid[0]))) {
strlcpy(Settings.sta_ssid[XdrvMailbox.index -1],
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_SSID1 : STA_SSID2 : XdrvMailbox.data,
sizeof(Settings.sta_ssid[0]));
Settings.sta_active = XdrvMailbox.index -1;
restart_flag = 2;
}
ResponseCmndIdxChar(Settings.sta_ssid[XdrvMailbox.index -1]);
}
}
void CmndPassword(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
if ((XdrvMailbox.data_len > 4 || SC_CLEAR == Shortcut() || SC_DEFAULT == Shortcut()) && (XdrvMailbox.data_len < sizeof(Settings.sta_pwd[0]))) {
strlcpy(Settings.sta_pwd[XdrvMailbox.index -1],
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_PASS1 : STA_PASS2 : XdrvMailbox.data,
sizeof(Settings.sta_pwd[0]));
Settings.sta_active = XdrvMailbox.index -1;
restart_flag = 2;
ResponseCmndIdxChar(Settings.sta_pwd[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) && (XdrvMailbox.data_len < sizeof(Settings.hostname))) {
strlcpy(Settings.hostname, (SC_DEFAULT == Shortcut()) ? WIFI_HOSTNAME : XdrvMailbox.data, sizeof(Settings.hostname));
if (strstr(Settings.hostname, "%") != nullptr) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
}
restart_flag = 2;
}
ResponseCmndChar(Settings.hostname);
}
void CmndWifiConfig(void)
{
char stemp1[TOPSZ];
if ((XdrvMailbox.payload >= WIFI_RESTART) && (XdrvMailbox.payload < MAX_WIFI_OPTION)) {
Settings.sta_config = XdrvMailbox.payload;
wifi_state_flag = Settings.sta_config;
snprintf_P(stemp1, sizeof(stemp1), kWifiConfig[Settings.sta_config]);
Response_P(PSTR("{\"" D_CMND_WIFICONFIG "\":\"%s " D_JSON_SELECTED "\"}"), stemp1);
if (WifiState() > WIFI_RESTART) {
restart_flag = 2;
}
} else {
snprintf_P(stemp1, sizeof(stemp1), kWifiConfig[Settings.sta_config]);
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_config, stemp1);
}
}
void CmndFriendlyname(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.friendlyname[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);
}
strlcpy(Settings.friendlyname[XdrvMailbox.index -1], (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data, sizeof(Settings.friendlyname[XdrvMailbox.index -1]));
}
ResponseCmndIdxChar(Settings.friendlyname[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);
}
}
}
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;
}
Response_P(S_JSON_COMMAND_NVALUE_UNIT, XdrvMailbox.command, Settings.tele_period, (Settings.flag.value_units) ? " " D_UNIT_SECOND : "");
}
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;
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);
}
#ifdef USE_I2C
void CmndI2cScan(void)
{
if (i2c_flg) {
I2cScan(mqtt_data, sizeof(mqtt_data));
}
}
#endif
void CmndSensor(void)
{
XsnsCall(FUNC_COMMAND_SENSOR);
}
void CmndDriver(void)
{
XdrvCall(FUNC_COMMAND_DRIVER);
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_features.ino"
# 24 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
# 485 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_features.ino"
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_float.ino"
# 23 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
double y;
for (uint32_t count = 0; count < 10; count++) {
z *= step;
y = (1 / powe) * z;
totalValue = totalValue + y;
powe = powe + 2;
}
totalValue *= 2;
# 145 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_float.ino"
return totalValue;
}
# 155 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
# 194 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 386 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 ((ito_min >= ito_max) || (ifrom_min >= ifrom_max)) {
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));
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_rotary.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
void update_position(void)
{
uint8_t s;
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);
}
#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 (LightPower()) {
update_position();
}
}
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_rtc.ino"
# 25 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 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);
}
# 174 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_rtc.ino"
String GetDateAndTime(uint8_t time_type)
{
uint32_t time = Rtc.local_time;
switch (time_type) {
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;
days = 0;
month = 0;
month_length = 0;
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) {
if ((Rtc.ntp_sync_minute > 59) && (RtcTime.minute > 2)) Rtc.ntp_sync_minute = 1;
uint8_t offset = (uptime < 30) ? RtcTime.second : (((ESP.getChipId() & 0xF) * 3) + 3) ;
if (!global_state.wifi_down && (((offset == RtcTime.second) && ((RtcTime.year < 2016) || (Rtc.ntp_sync_minute == RtcTime.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);
ntp_synced_message = true;
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; }
}
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;
} else {
Rtc.user_time_entry = true;
Rtc.utc_time = epoch -1;
}
RtcSecond();
}
void RtcInit(void)
{
sntp_setservername(0, Settings.ntp_server[0]);
sntp_setservername(1, Settings.ntp_server[1]);
sntp_setservername(2, Settings.ntp_server[2]);
sntp_stop();
sntp_set_timezone(0);
sntp_init();
Rtc.utc_time = 0;
BreakTime(Rtc.utc_time, RtcTime);
TickerRtc.attach(1, RtcSecond);
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_static_buffer.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 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 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;
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_switch.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
uint8_t SwitchLastState(uint8_t index)
{
return Switch.last_state[index];
}
void SwitchSetVirtual(uint8_t index, uint8_t state)
{
Switch.virtual_state[index] = state;
}
uint8_t SwitchGetVirtual(uint8_t index)
{
return Switch.virtual_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; }
uint8_t button = NOT_PRESSED;
uint8_t switchflag;
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)) {
if (Switch.hold_timer[i]) {
Switch.hold_timer[i]--;
if (0 == Switch.hold_timer[i]) {
SendKey(KEY_SWITCH, i +1, POWER_HOLD);
}
}
button = Switch.virtual_state[i];
if (button != Switch.last_state[i]) {
switchflag = POWER_TOGGLE +1;
switch (Settings.switchmode[i]) {
case 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 PUSHBUTTON_TOGGLE:
if (button != 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;
}
if (switchflag <= POWER_TOGGLE) {
if (!SendKey(KEY_SWITCH, i +1, switchflag)) {
ExecuteCommandPower(i +1, switchflag, SRC_SWITCH);
}
}
Switch.last_state[i] = button;
}
}
}
}
void SwitchLoop(void)
{
if (Switch.present) {
if (TimeReached(Switch.debounce)) {
SetNextTimeInterval(Switch.debounce, Settings.switch_debounce);
SwitchHandler(0);
}
}
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_udp.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) {
if (PortUdp.parsePacket()) {
char packet_buffer[UDP_BUFFER_SIZE];
int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
packet_buffer[len] = 0;
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len);
if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
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;
}
}
delay(1);
}
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_wifi.ino"
# 24 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_wifi.ino"
#ifndef WIFI_RSSI_THRESHOLD
#define WIFI_RSSI_THRESHOLD 10
#endif
#ifndef WIFI_RESCAN_MINUTES
#define WIFI_RESCAN_MINUTES 44
#endif
const uint8_t WIFI_CONFIG_SEC = 180;
const uint8_t WIFI_CHECK_SEC = 20;
const uint8_t WIFI_RETRY_OFFSET_SEC = 20;
#include <ESP8266WiFi.h>
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 wps_result;
uint8_t config_type = 0;
uint8_t config_counter = 0;
uint8_t mdns_begun = 0;
uint8_t scan_state;
uint8_t bssid[6];
} 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);
}
extern "C" {
#include "user_interface.h"
}
void WifiWpsStatusCallback(wps_cb_status status);
void WifiWpsStatusCallback(wps_cb_status status)
{
# 92 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/support_wifi.ino"
Wifi.wps_result = status;
if (WPS_CB_ST_SUCCESS == Wifi.wps_result) {
wifi_wps_disable();
} else {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WPS_FAILED_WITH_STATUS " %d"), Wifi.wps_result);
Wifi.config_counter = 2;
}
}
bool WifiWpsConfigDone(void)
{
return (!Wifi.wps_result);
}
bool WifiWpsConfigBegin(void)
{
Wifi.wps_result = 99;
if (!wifi_wps_disable()) { return false; }
if (!wifi_wps_enable(WPS_TYPE_PBC)) { return false; }
if (!wifi_set_wps_cb((wps_st_cb_t) &WifiWpsStatusCallback)) { return false; }
if (!wifi_wps_start()) { return false; }
return true;
}
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_WPS
if (WIFI_WPSCONFIG == Wifi.config_type) { Wifi.config_type = WIFI_MANAGER; }
#endif
#ifndef USE_WEBSERVER
if (WIFI_MANAGER == Wifi.config_type) { Wifi.config_type = WIFI_SMARTCONFIG; }
#endif
#ifndef USE_SMARTCONFIG
if (WIFI_SMARTCONFIG == 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_SMARTCONFIG
else if (WIFI_SMARTCONFIG == Wifi.config_type) {
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_1_SMARTCONFIG " " D_ACTIVE_FOR_3_MINUTES));
WiFi.mode(WIFI_STA);
WiFi.beginSmartConfig();
}
#endif
#ifdef USE_WPS
else if (WIFI_WPSCONFIG == Wifi.config_type) {
if (WifiWpsConfigBegin()) {
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_3_WPSCONFIG " " D_ACTIVE_FOR_3_MINUTES));
} else {
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_3_WPSCONFIG " " D_FAILED_TO_START));
Wifi.config_counter = 3;
}
}
#endif
#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 WiFiSetSleepMode(void)
{
# 186 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
}
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));
WiFi.mode(WIFI_OFF);
#endif
WiFi.persistent(false);
WiFi.disconnect(true);
delay(200);
WiFi.mode(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 ('\0' == Settings.sta_ssid[Settings.sta_active][0]) { 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);
if (channel) {
WiFi.begin(Settings.sta_ssid[Settings.sta_active], Settings.sta_pwd[Settings.sta_active], channel, Wifi.bssid);
} else {
WiFi.begin(Settings.sta_ssid[Settings.sta_active], Settings.sta_pwd[Settings.sta_active]);
}
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECTING_TO_AP "%d %s " D_IN_MODE " 11%c " D_AS " %s..."),
Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active], kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname);
}
void WifiBeginAfterScan()
{
static int8_t best_network_db;
if (0 == Wifi.scan_state) { return; }
if (1 == Wifi.scan_state) {
memset((void*) &Wifi.bssid, 0, sizeof(Wifi.bssid));
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));
best_network_db = WiFi.RSSI();
if (best_network_db < -WIFI_RSSI_THRESHOLD) { best_network_db += WIFI_RSSI_THRESHOLD; }
Wifi.scan_state = 3;
}
if (3 == Wifi.scan_state) {
if (WiFi.scanComplete() != WIFI_SCAN_RUNNING) {
WiFi.scanNetworks(true);
Wifi.scan_state++;
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR("Network (re)scan started..."));
return;
}
}
int8_t wifi_scan_result = WiFi.scanComplete();
if (4 == Wifi.scan_state) {
if (wifi_scan_result != WIFI_SCAN_RUNNING) {
Wifi.scan_state++;
}
}
if (5 == Wifi.scan_state) {
int32_t channel = 0;
int8_t ap = 3;
uint8_t last_bssid[6];
memcpy((void*) &last_bssid, (void*) &Wifi.bssid, sizeof(last_bssid));
if (wifi_scan_result > 0) {
for (uint32_t i = 0; i < wifi_scan_result; ++i) {
String ssid_scan;
int32_t rssi_scan;
uint8_t sec_scan;
uint8_t* bssid_scan;
int32_t chan_scan;
bool hidden_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, bssid_scan, chan_scan, hidden_scan);
bool known = false;
uint32_t j;
for (j = 0; j < 2; j++) {
if (ssid_scan == Settings.sta_ssid[j]) {
known = true;
if (rssi_scan > best_network_db) {
if (sec_scan == ENC_TYPE_NONE || Settings.sta_pwd[j]) {
best_network_db = (int8_t)rssi_scan;
channel = chan_scan;
ap = j;
memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid));
}
}
break;
}
}
char hex_char[18];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "Network %d, AP%c, SSId %s, Channel %d, BSSId %s, RSSI %d, Encryption %d"),
i,
(known) ? (j) ? '2' : '1' : '-',
ssid_scan.c_str(),
chan_scan,
ToHex_P((unsigned char*)bssid_scan, 6, hex_char, sizeof(hex_char), ':'),
rssi_scan,
(sec_scan == ENC_TYPE_NONE) ? 0 : 1);
delay(0);
}
WiFi.scanDelete();
delay(0);
}
Wifi.scan_state = 0;
for (uint32_t i = 0; i < sizeof(Wifi.bssid); i++) {
if (last_bssid[i] != Wifi.bssid[i]) {
WifiBegin(ap, channel);
break;
}
}
}
}
uint16_t WifiLinkCount()
{
return Wifi.link_count;
}
String WifiDowntime()
{
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;
}
void WifiCheckIp(void)
{
if ((WL_CONNECTED == WiFi.status()) && (static_cast<uint32_t>(WiFi.localIP()) != 0)) {
WifiSetState(1);
Wifi.counter = WIFI_CHECK_SEC;
Wifi.retry = Wifi.retry_init;
AddLog_P((Wifi.status != WL_CONNECTED) ? LOG_LEVEL_INFO : LOG_LEVEL_DEBUG_MORE, S_LOG_WIFI, PSTR(D_CONNECTED));
if (Wifi.status != WL_CONNECTED) {
Settings.ip_address[1] = (uint32_t)WiFi.gatewayIP();
Settings.ip_address[2] = (uint32_t)WiFi.subnetMask();
Settings.ip_address[3] = (uint32_t)WiFi.dnsIP();
}
Wifi.status = WL_CONNECTED;
#ifdef USE_DISCOVERY
#ifdef WEBSERVER_ADVERTISE
if (2 == Wifi.mdns_begun) {
MDNS.update();
AddLog_P(LOG_LEVEL_DEBUG_MORE, D_LOG_MDNS, "MDNS.update");
}
#endif
#endif
} else {
WifiSetState(0);
uint8_t wifi_config_tool = Settings.sta_config;
Wifi.status = WiFi.status();
switch (Wifi.status) {
case WL_CONNECTED:
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_NO_IP_ADDRESS));
Wifi.status = 0;
Wifi.retry = Wifi.retry_init;
break;
case WL_NO_SSID_AVAIL:
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_NOT_REACHED));
if (WIFI_WAIT == Settings.sta_config) {
Wifi.retry = Wifi.retry_init;
} else {
if (Wifi.retry > (Wifi.retry_init / 2)) {
Wifi.retry = Wifi.retry_init / 2;
}
else if (Wifi.retry) {
Wifi.retry = 0;
}
}
break;
case WL_CONNECT_FAILED:
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_WRONG_PASSWORD));
if (Wifi.retry > (Wifi.retry_init / 2)) {
Wifi.retry = Wifi.retry_init / 2;
}
else if (Wifi.retry) {
Wifi.retry = 0;
}
break;
default:
if (!Wifi.retry || ((Wifi.retry_init / 2) == Wifi.retry)) {
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_TIMEOUT));
} else {
if (('\0' == Settings.sta_ssid[0][0]) && ('\0' == Settings.sta_ssid[1][0])) {
wifi_config_tool = WIFI_CONFIG_NO_SSID;
Wifi.retry = 0;
} else {
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_ATTEMPTING_CONNECTION));
}
}
}
if (Wifi.retry) {
if (Settings.flag3.use_wifi_scan) {
if (Wifi.retry_init == Wifi.retry) {
Wifi.scan_state = 1;
}
} else {
if (Wifi.retry_init == Wifi.retry) {
WifiBegin(3, 0);
}
if ((Settings.sta_config != WIFI_WAIT) && ((Wifi.retry_init / 2) == Wifi.retry)) {
WifiBegin(2, 0);
}
}
Wifi.counter = 1;
Wifi.retry--;
} else {
WifiConfig(wifi_config_tool);
Wifi.counter = 1;
Wifi.retry = Wifi.retry_init;
}
}
}
void WifiCheck(uint8_t param)
{
Wifi.counter--;
switch (param) {
case WIFI_SERIAL:
case WIFI_SMARTCONFIG:
case WIFI_MANAGER:
case WIFI_WPSCONFIG:
WifiConfig(param);
break;
default:
if (Wifi.config_counter) {
Wifi.config_counter--;
Wifi.counter = Wifi.config_counter +5;
if (Wifi.config_counter) {
#ifdef USE_SMARTCONFIG
if ((WIFI_SMARTCONFIG == Wifi.config_type) && WiFi.smartConfigDone()) {
Wifi.config_counter = 0;
}
#endif
#ifdef USE_WPS
if ((WIFI_WPSCONFIG == Wifi.config_type) && WifiWpsConfigDone()) {
Wifi.config_counter = 0;
}
#endif
if (!Wifi.config_counter) {
if (strlen(WiFi.SSID().c_str())) {
strlcpy(Settings.sta_ssid[0], WiFi.SSID().c_str(), sizeof(Settings.sta_ssid[0]));
}
if (strlen(WiFi.psk().c_str())) {
strlcpy(Settings.sta_pwd[0], WiFi.psk().c_str(), sizeof(Settings.sta_pwd[0]));
}
Settings.sta_active = 0;
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_WCFG_1_SMARTCONFIG D_CMND_SSID "1 %s"), Settings.sta_ssid[0]);
}
}
if (!Wifi.config_counter) {
#ifdef USE_SMARTCONFIG
if (WIFI_SMARTCONFIG == Wifi.config_type) { WiFi.stopSmartConfig(); }
#endif
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 ((WL_CONNECTED == WiFi.status()) && (static_cast<uint32_t>(WiFi.localIP()) != 0) && !Wifi.config_type) {
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;
}
void WifiConnect(void)
{
WifiSetState(0);
WiFi.persistent(false);
Wifi.status = 0;
Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2);
Wifi.retry = Wifi.retry_init;
Wifi.counter = 1;
}
void WifiDisconnect(void)
{
WiFi.persistent(true);
ETS_UART_INTR_DISABLE();
wifi_station_disconnect();
ETS_UART_INTR_ENABLE();
WiFi.persistent(false);
}
void EspRestart(void)
{
delay(100);
if (Settings.flag.mqtt_enabled) MqttDisconnect();
WifiDisconnect();
ESP.reset();
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_01_webserver.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 };
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 =
"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);"
"}"
"}"
#ifdef USE_SCRIPT_WEB_DISPLAY
"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;"
"}"
"}"
#endif
#ifdef USE_JAVASCRIPT_ES6
"lb=p=>la('&d='+p);"
"lc=p=>la('&t='+p);"
#else
"function lb(p){"
"la('&d='+p);"
"}"
"function lc(p){"
"la('&t='+p);"
"}"
#endif
"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=sn;"
"}"
"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;}"
"select{width:100%%;background:#%06x;color:#%06x;}"
"textarea{resize:none;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{text-decoration:none;}"
".p{float:left;text-align:left;}"
".q{float:right;text-align:right;}";
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;'><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_SLIDER1[] PROGMEM =
"<div><span class='p'>" D_COLDLIGHT "</span><span class='q'>" D_WARMLIGHT "</span></div>"
"<div><input type='range' min='153' max='500' value='%d' onchange='lc(value)'></div>";
const char HTTP_MSG_SLIDER2[] PROGMEM =
"<div><span class='p'>" D_DARKLIGHT "</span><span class='q'>" D_BRIGHTLIGHT "</span></div>"
"<div><input type='range' min='1' max='100' value='%d' onchange='lb(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>";
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;'>Sonoff-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_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
;
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);
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) {
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());
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) {
WiFi.mode(WIFI_AP_STA);
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION));
} else {
WiFi.mode(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; }
WiFi.softAP(my_hostname, nullptr, channel);
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 (Settings.web_password[0] != 0 && HTTP_MANAGER_RESET_ONLY != Web.state) {
return WebServer->authenticate(WEB_USERNAME, Settings.web_password);
} 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 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"));
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
#endif
}
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()
{
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);
vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg);
va_end(arg);
_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);
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 && (Settings.web_password[0] != 0) && !WebServer->authenticate(WEB_USERNAME, Settings.web_password)) {
return WebServer->requestAuthentication();
}
WSContentBegin(200, CT_HTML);
if (title != nullptr) {
char ctitle[strlen_P(title) +1];
strcpy_P(ctitle, title);
WSContentSend_P(HTTP_HEADER, Settings.friendlyname[0], 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));
if (formatP != nullptr) {
va_list arg;
va_start(arg, formatP);
vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg);
va_end(arg);
_WSContentSendBuffer();
}
WSContentSend_P(HTTP_HEAD_STYLE3, WebColor(COL_TEXT),
#ifdef FIRMWARE_MINIMAL
WebColor(COL_TEXT_WARNING),
#endif
ModuleName().c_str(), Settings.friendlyname[0]);
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 HandleRoot(void)
{
if (CaptivePortal()) { return; }
if (WebServer->hasArg("rst")) {
WebRestart(0);
return;
}
if (WifiIsInManagerMode()) {
#ifndef FIRMWARE_MINIMAL
if ((Settings.web_password[0] != 0) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1")) && HTTP_MANAGER_RESET_ONLY != Web.state) {
HandleWifiLogin();
} else {
if (!(Settings.web_password[0] != 0) || (((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == Settings.web_password )) || 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[5];
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
WSContentSendStyle();
WSContentSend_P(PSTR("<div id='l1' name='l1'></div>"));
if (devices_present) {
#ifdef USE_LIGHT
if (light_type) {
if ((LST_COLDWARM == (light_type &7)) || (LST_RGBWC == (light_type &7))) {
WSContentSend_P(HTTP_MSG_SLIDER1, LightGetColorTemp());
}
WSContentSend_P(HTTP_MSG_SLIDER2, Settings.light_dimmer);
}
#endif
WSContentSend_P(HTTP_TABLE100);
WSContentSend_P(PSTR("<tr>"));
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, 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, stemp, "");
}
} else {
#endif
for (uint32_t idx = 1; idx <= devices_present; idx++) {
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx);
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : "");
}
#ifdef USE_SONOFF_IFAN
}
#endif
WSContentSend_P(PSTR("</tr></table>"));
}
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++;
WSContentSend_P(PSTR("<td style='width:25%%'><button onclick='la(\"&k=%d\");'>%d</button></td>"), idx, idx);
}
}
WSContentSend_P(PSTR("</tr></table>"));
}
#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];
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
ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE);
#ifdef USE_SONOFF_IFAN
}
#endif
}
WebGetArg("d", tmp, sizeof(tmp));
if (strlen(tmp)) {
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_DIMMER " %s"), tmp);
ExecuteWebCommand(svalue, SRC_WEBGUI);
}
WebGetArg("t", tmp, sizeof(tmp));
if (strlen(tmp)) {
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp);
ExecuteWebCommand(svalue, SRC_WEBGUI);
}
WebGetArg("k", tmp, sizeof(tmp));
if (strlen(tmp)) {
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp);
ExecuteWebCommand(svalue, SRC_WEBGUI);
}
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;
}
#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]);
for (uint32_t j = i + 1; j < n; j++) {
if (cssid == WiFi.SSID(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%%</span></div>"),
HtmlEscape(WiFi.SSID(indices[i])).c_str(),
WiFi.channel(indices[i]),
GetTextIndexed(encryption, sizeof(encryption), auth +1, kEncryptionType),
quality
);
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, Settings.sta_ssid[0], Settings.sta_ssid[1], WIFI_HOSTNAME, WIFI_HOSTNAME, Settings.hostname);
WSContentSend_P(HTTP_FORM_END);
}
if (WifiIsInManagerMode()) {
WSContentSpaceButton(BUTTON_RESTART);
#ifndef FIRMWARE_MINIMAL
WSContentSpaceButton(BUTTON_RESET_CONFIGURATION);
#endif
} else {
WSContentSpaceButton(BUTTON_CONFIGURATION);
}
WSContentStop();
}
void WifiSaveSettings(void)
{
char tmp[sizeof(Settings.sta_pwd[0])];
WebGetArg("h", tmp, sizeof(tmp));
strlcpy(Settings.hostname, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp, sizeof(Settings.hostname));
if (strstr(Settings.hostname, "%") != nullptr) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
}
WebGetArg("s1", tmp, sizeof(tmp));
strlcpy(Settings.sta_ssid[0], (!strlen(tmp)) ? STA_SSID1 : tmp, sizeof(Settings.sta_ssid[0]));
WebGetArg("s2", tmp, sizeof(tmp));
strlcpy(Settings.sta_ssid[1], (!strlen(tmp)) ? STA_SSID2 : tmp, sizeof(Settings.sta_ssid[1]));
WebGetArg("p1", tmp, sizeof(tmp));
strlcpy(Settings.sta_pwd[0], (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? Settings.sta_pwd[0] : tmp, sizeof(Settings.sta_pwd[0]));
WebGetArg("p2", tmp, sizeof(tmp));
strlcpy(Settings.sta_pwd[1], (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? Settings.sta_pwd[1] : tmp, sizeof(Settings.sta_pwd[1]));
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_SSID "2 %s"), Settings.hostname, Settings.sta_ssid[0], Settings.sta_ssid[1]);
}
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[3] = { LOG_LEVEL_INFO, LOG_LEVEL_INFO, LOG_LEVEL_NONE };
for (uint32_t idx = 0; idx < 3; idx++) {
uint32_t llevel = (0==idx)?Settings.seriallog_level:(1==idx)?Settings.weblog_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_ALL; 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, Settings.syslog_host, Settings.syslog_port, Settings.tele_period);
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
}
void LoggingSaveSettings(void)
{
char tmp[sizeof(Settings.syslog_host)];
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));
SetSyslog((!strlen(tmp)) ? SYS_LOG_LEVEL : atoi(tmp));
WebGetArg("lh", tmp, sizeof(tmp));
strlcpy(Settings.syslog_host, (!strlen(tmp)) ? SYS_LOG_HOST : tmp, sizeof(Settings.syslog_host));
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_SYSLOG " %d, " D_CMND_LOGHOST " %s, " D_CMND_LOGPORT " %d, " D_CMND_TELEPERIOD " %d"),
Settings.seriallog_level, Settings.weblog_level, Settings.syslog_level, Settings.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 : "",
Settings.friendlyname[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[128];
char webindex[5];
char friendlyname[sizeof(Settings.friendlyname[0])];
WebGetArg("wp", tmp, sizeof(tmp));
strlcpy(Settings.web_password, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? Settings.web_password : tmp, sizeof(Settings.web_password));
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(log_data, sizeof(log_data), 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);
strlcpy(Settings.friendlyname[i], (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp, sizeof(Settings.friendlyname[i]));
snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", Settings.friendlyname[i]);
}
AddLog(LOG_LEVEL_INFO);
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[100];
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);
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, Settings.friendlyname[i]);
}
WSContentSend_P(PSTR("}1}2&nbsp;"));
WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%)"), Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active], WifiGetRssiAsQuality(WiFi.RSSI()));
WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "");
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) {
#ifdef USE_MQTT_AWS_IOT
WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s%s"), Settings.mqtt_user, Settings.mqtt_host);
WSContentSend_P(PSTR("}1" D_MQTT_PORT "}2%d"), Settings.mqtt_port);
#else
WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s"), Settings.mqtt_host);
WSContentSend_P(PSTR("}1" D_MQTT_PORT "}2%d"), Settings.mqtt_port);
WSContentSend_P(PSTR("}1" D_MQTT_USER "}2%s"), Settings.mqtt_user);
#endif
WSContentSend_P(PSTR("}1" D_MQTT_CLIENT "}2%s"), mqtt_client);
WSContentSend_P(PSTR("}1" D_MQTT_TOPIC "}2%s"), Settings.mqtt_topic);
WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), Settings.mqtt_grptopic);
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, CMND, ""));
} 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, Settings.ota_url);
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[sizeof(Settings.ota_url) + 10];
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
WifiConfigCounter();
char otaurl[sizeof(Settings.ota_url)];
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 < 14) {
#else
if (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);
restart_flag = 2;
}
SettingsBufferFree();
WSContentSend_P(PSTR("</div><br>"));
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
}
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
{
if (upload.buf[0] != 0xE9) {
Web.upload_error = 3;
return;
}
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
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
else {
if (!Update.end(true)) {
if (_serialoutput) { Update.printError(Serial); }
Web.upload_error = 6;
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)
{
WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
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 (Settings.web_password[0] != 0) {
char tmp1[sizeof(Settings.web_password)];
WebGetArg("user", tmp1, sizeof(tmp1));
char tmp2[sizeof(Settings.web_password)];
WebGetArg("password", tmp2, sizeof(tmp2));
if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password))) { 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++);
# 2374 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_01_webserver.ino"
if ((' ' == decodedChar) || ('+' == decodedChar)) {
encoded += '%';
encoded += hex[decodedChar >> 4];
encoded += hex[decodedChar & 0xF];
} else {
encoded += decodedChar;
}
}
return encoded;
}
#ifdef USE_SENDMAIL
#include "sendemail.h"
#define SEND_MAIL_MINRAM 19*1024
uint16_t SendMail(char *buffer) {
uint16_t count;
char *params,*oparams;
char *mserv;
uint16_t port;
char *user;
char *pstr;
char *passwd;
char *from;
char *to;
char *subject;
char *cmd;
char secure=0,auth=0;
uint16_t status=1;
SendEmail *mail=0;
uint16_t mem=ESP.getFreeHeap();
if (mem<SEND_MAIL_MINRAM) {
return 4;
}
while (*buffer==' ') buffer++;
oparams=(char*)calloc(strlen(buffer)+2,1);
if (!oparams) return 4;
params=oparams;
strcpy(params,buffer);
if (*params=='p') {
auth=1;
params++;
}
if (*params!='[') {
goto exit;
}
params++;
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;
}
cmd=subject+strlen(subject)+1;
#ifdef EMAIL_USER
if (*user=='*') {
user=(char*)EMAIL_USER;
}
#endif
#ifdef EMAIL_PASSWORD
if (*passwd=='*') {
passwd=(char*)EMAIL_PASSWORD;
}
#endif
#ifdef EMAIL_SERVER
if (*mserv=='*') {
mserv=(char*)EMAIL_SERVER;
}
#endif
#define MAIL_TIMEOUT 2000
mail = new SendEmail(mserv, port,user,passwd, MAIL_TIMEOUT, auth);
#ifdef EMAIL_FROM
if (*from=='*') {
from=(char*)EMAIL_FROM;
}
#endif
exit:
if (mail) {
bool result=mail->send(from,to,subject,cmd);
delete mail;
if (result==true) status=0;
}
if (oparams) free(oparams);
return status;
}
#endif
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[128];
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;
void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
&CmndEmulation,
#endif
#ifdef USE_SENDMAIL
&CmndSendmail,
#endif
&CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor };
#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) && (XdrvMailbox.data_len < sizeof(Settings.web_password))) {
strlcpy(Settings.web_password, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data, sizeof(Settings.web_password));
ResponseCmndChar(Settings.web_password);
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
void CmndWeblog(void)
{
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_ALL)) {
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();
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_02_mqtt.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT)
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|"
#endif
#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_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
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT)
&CmndMqttUser, &CmndMqttPassword,
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
&CmndTlsKey,
#endif
&CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient,
&CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish,
&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
char AWS_endpoint[65];
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;
}
#ifdef USE_MQTT_AWS_IOT
void setLongMqttHost(const char *mqtt_host) {
if (strlen(mqtt_host) <= sizeof(Settings.mqtt_host)) {
strlcpy(Settings.mqtt_host, mqtt_host, sizeof(Settings.mqtt_host));
Settings.mqtt_user[0] = 0;
} else {
strlcpy(Settings.mqtt_user, mqtt_host, sizeof(Settings.mqtt_user));
strlcpy(Settings.mqtt_host, &mqtt_host[sizeof(Settings.mqtt_user)-1], sizeof(Settings.mqtt_host));
}
strlcpy(AWS_endpoint, mqtt_host, sizeof(AWS_endpoint));
}
#endif
#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
snprintf_P(Settings.mqtt_host, sizeof(Settings.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(), Settings.mqtt_host, Settings.mqtt_port);
}
}
#endif
#endif
# 186 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 1000"
#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
snprintf_P(AWS_endpoint, sizeof(AWS_endpoint), PSTR("%s%s"), Settings.mqtt_user, Settings.mqtt_host);
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)
{
bool result = MqttClient.publish(topic, mqtt_data, retained);
yield();
return result;
}
void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len)
{
#ifdef USE_DEBUG_DRIVER
ShowFreeMem(PSTR("MqttDataHandler"));
#endif
if (data_len >= MQTT_MAX_PACKET_SIZE) { return; }
if (!strcmp(Settings.mqtt_prefix[0], Settings.mqtt_prefix[1])) {
char *str = strstr(topic, Settings.mqtt_prefix[0]);
if ((str == topic) && mqtt_cmnd_publish) {
if (mqtt_cmnd_publish > 3) {
mqtt_cmnd_publish -= 3;
} else {
mqtt_cmnd_publish = 0;
}
return;
}
}
data[data_len] = 0;
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);
if (XdrvMqttData(topic, strlen(topic), (char*)data, data_len)) { 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 MqttPublishDirect(const char* topic, bool retained)
{
char sretained[CMDSZ];
char slog_type[10];
#ifdef USE_DEBUG_DRIVER
ShowFreeMem(PSTR("MqttPublishDirect"));
#endif
sretained[0] = '\0';
snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_RESULT));
if (Settings.flag.mqtt_enabled) {
if (MqttIsConnected()) {
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, bool retained)
{
char *me;
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
if (retained) {
AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained are not supported by AWS IoT, using retained = false."));
}
retained = false;
#endif
if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) {
me = strstr(topic,Settings.mqtt_prefix[0]);
if (me == topic) {
mqtt_cmnd_publish += 3;
}
}
MqttPublishDirect(topic, retained);
}
void MqttPublish(const char* topic)
{
MqttPublish(topic, false);
}
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained)
{
char romram[33];
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);
}
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic)
{
MqttPublishPrefixTopic_P(prefix, subtopic, false);
}
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()
{
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()
{
return Mqtt.connect_count;
}
void MqttDisconnected(int state)
{
Mqtt.connected = false;
Mqtt.retry_counter = Settings.mqtt_retry;
MqttClient.disconnect();
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), AWS_endpoint, Settings.mqtt_port, state, Mqtt.retry_counter);
#else
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), Settings.mqtt_host, Settings.mqtt_port, state, Mqtt.retry_counter);
#endif
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(Settings.mqtt_fulltopic, MQTT_TOKEN_TOPIC) != nullptr) {
GetTopic_P(stopic, CMND, Settings.mqtt_grptopic, PSTR("#"));
MqttSubscribe(stopic);
GetFallbackTopic_P(stopic, CMND, PSTR("#"));
MqttSubscribe(stopic);
}
XdrvCall(FUNC_MQTT_SUBSCRIBE);
}
if (Mqtt.initial_connection_state) {
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, CMND, ""), Settings.mqtt_grptopic);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
#ifdef USE_WEBSERVER
if (Settings.webserver) {
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());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
}
#endif
Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":\"%s\"}"), (GetResetReason() == "Exception") ? ESP.getResetInfo().c_str() : GetResetReason().c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
MqttPublishAllPowerState();
if (Settings.tele_period) { tele_period = Settings.tele_period -9; }
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(Settings.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(Settings.mqtt_user) > 0) mqtt_user = Settings.mqtt_user;
if (strlen(Settings.mqtt_pwd) > 0) mqtt_pwd = Settings.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);
MqttClient.setServer(AWS_endpoint, Settings.mqtt_port);
#else
MqttClient.setServer(Settings.mqtt_host, Settings.mqtt_port);
#endif
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"), AWS_endpoint);
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) {
#ifdef USE_DISCOVERY
#ifdef MQTT_HOST_DISCOVERY
if (!strlen(Settings.mqtt_host) && !Wifi.mdns_begun) { return; }
#endif
#endif
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
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT)
void CmndMqttUser(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_user))) {
strlcpy(Settings.mqtt_user, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data, sizeof(Settings.mqtt_user));
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_user);
}
void CmndMqttPassword(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_pwd))) {
strlcpy(Settings.mqtt_pwd, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data, sizeof(Settings.mqtt_pwd));
ResponseCmndChar(Settings.mqtt_pwd);
restart_flag = 2;
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
#endif
void CmndMqttHost(void)
{
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len <= sizeof(Settings.mqtt_host) + sizeof(Settings.mqtt_user) - 2)) {
setLongMqttHost((SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
restart_flag = 2;
}
ResponseCmndChar(AWS_endpoint);
#else
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_host))) {
strlcpy(Settings.mqtt_host, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data, sizeof(Settings.mqtt_host));
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_host);
#endif
}
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 <= 4)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.state_text[0]))) {
for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) {
if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_';
}
strlcpy(Settings.state_text[XdrvMailbox.index -1], XdrvMailbox.data, sizeof(Settings.state_text[0]));
}
ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1));
}
}
void CmndMqttClient(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_client))) {
strlcpy(Settings.mqtt_client, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data, sizeof(Settings.mqtt_client));
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_client);
}
void CmndFullTopic(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_fulltopic))) {
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, Settings.mqtt_fulltopic)) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : "");
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true);
strlcpy(Settings.mqtt_fulltopic, stemp1, sizeof(Settings.mqtt_fulltopic));
restart_flag = 2;
}
}
ResponseCmndChar(Settings.mqtt_fulltopic);
}
void CmndPrefix(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_prefix[0]))) {
MakeValidMqtt(0, XdrvMailbox.data);
strlcpy(Settings.mqtt_prefix[XdrvMailbox.index -1], (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index)?SUB_PREFIX:(2==XdrvMailbox.index)?PUB_PREFIX:PUB_PREFIX2 : XdrvMailbox.data, sizeof(Settings.mqtt_prefix[0]));
restart_flag = 2;
}
ResponseCmndIdxChar(Settings.mqtt_prefix[XdrvMailbox.index -1]);
}
}
void CmndPublish(void)
{
if (XdrvMailbox.data_len > 0) {
char *mqtt_part = strtok(XdrvMailbox.data, " ");
if (mqtt_part) {
char stemp1[TOPSZ];
strlcpy(stemp1, mqtt_part, sizeof(stemp1));
mqtt_part = strtok(nullptr, " ");
if (mqtt_part) {
strlcpy(mqtt_data, mqtt_part, sizeof(mqtt_data));
} else {
mqtt_data[0] = '\0';
}
MqttPublishDirect(stemp1, (XdrvMailbox.index == 2));
mqtt_data[0] = '\0';
}
}
}
void CmndGroupTopic(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_grptopic))) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
strlcpy(Settings.mqtt_grptopic, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data, sizeof(Settings.mqtt_grptopic));
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_grptopic);
}
void CmndTopic(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_topic))) {
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, Settings.mqtt_topic)) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : "");
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true);
strlcpy(Settings.mqtt_topic, stemp1, sizeof(Settings.mqtt_topic));
restart_flag = 2;
}
}
ResponseCmndChar(Settings.mqtt_topic);
}
void CmndButtonTopic(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.button_topic))) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
case SC_CLEAR: strlcpy(Settings.button_topic, "", sizeof(Settings.button_topic)); break;
case SC_DEFAULT: strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic)); break;
case SC_USER: strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic)); break;
default: strlcpy(Settings.button_topic, XdrvMailbox.data, sizeof(Settings.button_topic));
}
}
ResponseCmndChar(Settings.button_topic);
}
void CmndSwitchTopic(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.switch_topic))) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
case SC_CLEAR: strlcpy(Settings.switch_topic, "", sizeof(Settings.switch_topic)); break;
case SC_DEFAULT: strlcpy(Settings.switch_topic, mqtt_topic, sizeof(Settings.switch_topic)); break;
case SC_USER: strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic)); break;
default: strlcpy(Settings.switch_topic, XdrvMailbox.data, sizeof(Settings.switch_topic));
}
}
ResponseCmndChar(Settings.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 = SPIFFS_END + 4;
const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000);
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);
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);
}
TlsWriteSpiBuffer(spi_buffer);
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);
}
}
extern "C" {
#include "spi_flash.h"
}
void TlsWriteSpiBuffer(uint8_t *buf) {
bool ret = false;
SpiFlashOpResult res;
noInterrupts();
res = spi_flash_erase_sector(tls_spi_start_sector);
if (SPI_FLASH_RESULT_OK == res) {
res = spi_flash_write(tls_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) buf, SPI_FLASH_SEC_SIZE);
if (SPI_FLASH_RESULT_OK == res) {
ret = true;
}
}
interrupts();
}
#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);
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
#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 =
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT)
"<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>"
#endif
"<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[sizeof(Settings.mqtt_client)];
WSContentStart_P(S_CONFIGURE_MQTT);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_MQTT1,
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AWS_endpoint,
#else
Settings.mqtt_host,
#endif
Settings.mqtt_port,
Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, Settings.mqtt_client);
WSContentSend_P(HTTP_FORM_MQTT2,
(Settings.mqtt_user[0] == '\0') ? "0" : Settings.mqtt_user,
Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, Settings.mqtt_topic,
MQTT_FULLTOPIC, MQTT_FULLTOPIC, Settings.mqtt_fulltopic);
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
}
void MqttSaveSettings(void)
{
char tmp[100];
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, Settings.mqtt_topic)) || (strcmp(stemp2, Settings.mqtt_fulltopic))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : "");
MqttPublishPrefixTopic_P(TELE, S_LWT, true);
}
strlcpy(Settings.mqtt_topic, stemp, sizeof(Settings.mqtt_topic));
strlcpy(Settings.mqtt_fulltopic, stemp2, sizeof(Settings.mqtt_fulltopic));
WebGetArg("mh", tmp, sizeof(tmp));
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
setLongMqttHost((!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp);
#else
strlcpy(Settings.mqtt_host, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp, sizeof(Settings.mqtt_host));
#endif
WebGetArg("ml", tmp, sizeof(tmp));
Settings.mqtt_port = (!strlen(tmp)) ? MQTT_PORT : atoi(tmp);
WebGetArg("mc", tmp, sizeof(tmp));
strlcpy(Settings.mqtt_client, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp, sizeof(Settings.mqtt_client));
#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"),
AWS_endpoint, Settings.mqtt_port, Settings.mqtt_client, Settings.mqtt_topic, Settings.mqtt_fulltopic);
#else
WebGetArg("mu", tmp, sizeof(tmp));
strlcpy(Settings.mqtt_user, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp, sizeof(Settings.mqtt_user));
WebGetArg("mp", tmp, sizeof(tmp));
strlcpy(Settings.mqtt_pwd, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? Settings.mqtt_pwd : tmp, sizeof(Settings.mqtt_pwd));
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"),
Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client, Settings.mqtt_user, Settings.mqtt_topic, Settings.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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_03_energy.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
float power_history[3] = { 0 };
uint8_t power_steady_counter = 8;
uint8_t power_delta = 0;
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 tariff1 = Settings.register8[R8_ENERGY_TARIFF1_ST];
uint8_t tariff2 = Settings.register8[R8_ENERGY_TARIFF2_ST];
if (IsDst() && (Settings.register8[R8_ENERGY_TARIFF1_DS] != Settings.register8[R8_ENERGY_TARIFF2_DS])) {
tariff1 = Settings.register8[R8_ENERGY_TARIFF1_DS];
tariff2 = Settings.register8[R8_ENERGY_TARIFF2_DS];
}
if (tariff1 != tariff2) {
return ((RtcTime.hour < tariff2) ||
(RtcTime.hour >= tariff1) ||
(Settings.flag3.energy_weekend && ((RtcTime.day_of_week == 1) ||
(RtcTime.day_of_week == 7)))
);
} 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)){
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();
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NRG: Energy Total updated with hardware value"));
}
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)
{
uint16_t energy_daily_u = 0;
uint16_t energy_power_u = 0;
uint16_t energy_voltage_u = 0;
uint16_t energy_current_u = 0;
bool flag;
bool jsonflg;
if (Energy.power_steady_counter) {
Energy.power_steady_counter--;
return;
}
if (Settings.energy_power_delta) {
float delta = abs(Energy.power_history[0] - Energy.active_power[0]);
float min_power = (Energy.power_history[0] > Energy.active_power[0]) ? Energy.active_power[0] : Energy.power_history[0];
if (((delta / min_power) * 100) > Settings.energy_power_delta) {
Energy.power_delta = 1;
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.active_power[0];
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)) {
energy_power_u = (uint16_t)(Energy.active_power[0]);
energy_voltage_u = (uint16_t)(Energy.voltage[0]);
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("{"));
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%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%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%s\"}"), energy_power_u, (Settings.flag.value_units) ? " " D_UNIT_WATT : "");
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();
}
}
}
}
}
if (Settings.energy_max_energy) {
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;
dtostrfd(Energy.daily, 3, mqtt_data);
ResponseTime_P(PSTR(",\"" D_JSON_MAXENERGYREACHED "\":\"%s%s\"}"), mqtt_data, (Settings.flag.value_units) ? " " D_UNIT_KILOWATTHOUR : "");
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();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
Energy.power_delta = 0;
}
#endif
void EnergyEverySecond()
{
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 EnergyCommandResponse(uint32_t nvalue, uint32_t unit)
{
if (UNIT_MILLISECOND == unit) {
snprintf_P(XdrvMailbox.command, CMDSZ, PSTR("%sCal"), XdrvMailbox.command);
unit = UNIT_MICROSECOND;
}
if (Settings.flag.value_units) {
char sunit[CMDSZ];
Response_P(S_JSON_COMMAND_LVALUE_SPACE_UNIT, XdrvMailbox.command, nvalue, GetTextIndexed(sunit, sizeof(sunit), unit, kUnitNames));
} else {
Response_P(S_JSON_COMMAND_LVALUE, XdrvMailbox.command, 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.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;
Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000;
Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight();
RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 1000);
break;
}
}
}
if ((XdrvMailbox.index > 3) && (XdrvMailbox.index <= 5)) {
char *p;
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
int32_t position = -1;
uint32_t values[2];
while ((str != nullptr) && (position < 1)) {
uint32_t value = strtoul(str, nullptr, 10);
position++;
values[position] = value *100;
str = strtok_r(nullptr, ", ", &p);
}
switch (XdrvMailbox.index)
{
case 4:
if (position > -1) {
RtcSettings.energy_usage.usage1_kWhtotal = values[0];
}
if (position > 0) {
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 > -1) {
RtcSettings.energy_usage.return1_kWhtotal = values[0];
}
if (position > 0) {
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;
}
}
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)) {
char *p;
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
uint32_t time_type = 0;
while ((str != nullptr) && (time_type <= 2)) {
uint8_t value = strtol(str, nullptr, 10);
if ((value >= 0) && (value < 24)) {
Settings.register8[R8_ENERGY_TARIFF1_ST + (XdrvMailbox.index -1) + time_type] = value;
}
str = strtok_r(nullptr, ", ", &p);
time_type += 2;
}
}
else if (XdrvMailbox.index == 9) {
Settings.flag3.energy_weekend = XdrvMailbox.payload & 1;
}
Response_P(PSTR("{\"%s\":{\"Off-Peak\":[%d,%d],\"Standard\":[%d,%d],\"Weekend\":\"%s\"}}"),
XdrvMailbox.command,
Settings.register8[R8_ENERGY_TARIFF1_ST], Settings.register8[R8_ENERGY_TARIFF1_DS],
Settings.register8[R8_ENERGY_TARIFF2_ST], Settings.register8[R8_ENERGY_TARIFF2_DS],
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;
}
EnergyCommandResponse(Settings.energy_power_calibration, UNIT_MICROSECOND);
}
}
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;
}
EnergyCommandResponse(Settings.energy_voltage_calibration, UNIT_MICROSECOND);
}
}
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;
}
EnergyCommandResponse(Settings.energy_current_calibration, UNIT_MICROSECOND);
}
}
void CmndPowerSet(void)
{
Energy.command_code = CMND_POWERSET;
if (XnrgCall(FUNC_COMMAND)) {
EnergyCommandResponse(Settings.energy_power_calibration, UNIT_MILLISECOND);
}
}
void CmndVoltageSet(void)
{
Energy.command_code = CMND_VOLTAGESET;
if (XnrgCall(FUNC_COMMAND)) {
EnergyCommandResponse(Settings.energy_voltage_calibration, UNIT_MILLISECOND);
}
}
void CmndCurrentSet(void)
{
Energy.command_code = CMND_CURRENTSET;
if (XnrgCall(FUNC_COMMAND)) {
EnergyCommandResponse(Settings.energy_current_calibration, UNIT_MILLISECOND);
}
}
void CmndFrequencySet(void)
{
Energy.command_code = CMND_FREQUENCYSET;
if (XnrgCall(FUNC_COMMAND)) {
EnergyCommandResponse(Settings.energy_frequency_calibration, UNIT_MILLISECOND);
}
}
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 < 101)) {
Settings.energy_power_delta = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_power_delta, UNIT_PERCENTAGE);
}
void CmndPowerLow(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_min_power = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_min_power, UNIT_WATT);
}
void CmndPowerHigh(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_power, UNIT_WATT);
}
void CmndVoltageLow(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
Settings.energy_min_voltage = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_min_voltage, UNIT_VOLT);
}
void CmndVoltageHigh(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
Settings.energy_max_voltage = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_voltage, UNIT_VOLT);
}
void CmndCurrentLow(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
Settings.energy_min_current = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_min_current, UNIT_MILLIAMPERE);
}
void CmndCurrentHigh(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
Settings.energy_max_current = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_current, UNIT_MILLIAMPERE);
}
#ifdef USE_ENERGY_POWER_LIMIT
void CmndMaxPower(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_limit = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_power_limit, UNIT_WATT);
}
void CmndMaxPowerHold(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_power_limit_hold, UNIT_SECOND);
}
void CmndMaxPowerWindow(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_power_limit_window, UNIT_SECOND);
}
void CmndSafePower(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_safe_limit = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_power_safe_limit, UNIT_WATT);
}
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;
}
EnergyCommandResponse(Settings.energy_max_power_safe_limit_hold, UNIT_SECOND);
}
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;
}
EnergyCommandResponse(Settings.energy_max_power_safe_limit_window, UNIT_MINUTE);
}
void CmndMaxEnergy(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_energy = XdrvMailbox.payload;
Energy.max_energy_state = 3;
}
EnergyCommandResponse(Settings.energy_max_energy, UNIT_WATTHOUR);
}
void CmndMaxEnergyStart(void)
{
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) {
Settings.energy_max_energy_start = XdrvMailbox.payload;
}
EnergyCommandResponse(Settings.energy_max_energy_start, UNIT_HOUR);
}
#endif
#endif
void EnergyDrvInit(void)
{
energy_flg = ENERGY_NONE;
XnrgCall(FUNC_PRE_INIT);
}
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.register8[R8_ENERGY_TARIFF1_ST] != Settings.register8[R8_ENERGY_TARIFF2_ST]) {
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));
}
}
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(PSTR("{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"),
EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common));
}
if (Energy.current_available) {
WSContentSend_PD(PSTR("{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"),
EnergyFormat(value_chr, current_chr[0], json));
}
WSContentSend_PD(PSTR("{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"),
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));
}
}
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) {
EnergyDrvInit();
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_04_light.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_04_light.ino"
#ifdef USE_LIGHT
# 128 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_04_light.ino"
#define XDRV_04 4
const uint8_t LIGHT_COLOR_SIZE = 25;
const uint8_t WS2812_SCHEMES = 7;
const char kLightCommands[] PROGMEM = "|"
#ifdef USE_WS2812
D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH "|"
#endif
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" 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 = {
#ifdef USE_WS2812
&CmndLed, &CmndPixels, &CmndRotation, &CmndWidth,
#endif
&CmndColor, &CmndColorTemperature, &CmndDimmer, &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 };
#ifdef XFUNC_PTR_IN_ROM
const uint8_t _ledTable[] PROGMEM = {
#else
const uint8_t _ledTable[] = {
#endif
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7,
7, 8, 8, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 24, 25, 26, 28, 29, 30, 32, 33, 35, 37, 38, 40, 42,
22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 36, 37, 38, 39,
41, 42, 44, 45, 47, 48, 50, 51, 53, 55, 56, 58, 60, 62, 64, 65,
67, 69, 71, 73, 75, 78, 80, 82, 84, 86, 89, 91, 93, 96, 98,101,
103,106,108,111,114,116,119,122,125,128,131,134,137,140,143,146,
75, 77, 78, 80, 82, 84, 85, 87, 89, 91, 93, 94, 96, 98,100,102,
104,106,108,110,112,115,117,119,121,123,125,128,130,132,135,137,
140,142,144,147,149,152,155,157,160,163,165,168,171,173,176,179,
182,185,188,191,194,197,200,203,206,209,212,215,219,222,225,229,
116,118,120,121,123,125,127,128,130,132,134,136,138,139,141,143,
145,147,149,151,153,155,157,159,161,163,165,168,170,172,174,176,
178,181,183,185,187,190,192,194,197,199,201,204,206,209,211,214,
216,219,221,224,226,229,232,234,237,240,242,245,248,250,253,255
};
# 241 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_04_light.ino"
struct LIGHT {
unsigned long 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 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;
bool update = true;
bool pwm_multi_channels = false;
} Light;
power_t LightPower(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;
}
# 316 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 = 153;
uint8_t _wc = 255;
uint8_t _ww = 0;
uint8_t _briCT = 255;
uint8_t _color_mode = LCM_RGB;
public:
LightStateClass() {
}
void setSubType(uint8_t sub_type) {
_subtype = sub_type;
}
# 351 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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_RGBWC:
_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 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() {
return BriToDimmer(getBri());
}
inline uint16_t getCT() {
return _ct;
}
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 < 153 ? 153 : (ct > 500 ? 500 : ct));
_ww = changeUIntScale(ct, 153, 500, 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
}
# 534 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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, 153, 500);
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 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);
};
# 640 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
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
_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]);
if (!_pwm_multi_channels) {
uint8_t bri = _state->DimmerToBri(Settings.light_dimmer);
if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) {
_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) {
uint8_t bri = changeUIntScale(dimmer, 0, 100, 0, 255);
changeBri(bri);
}
void changeBri(uint8_t bri) {
_state->setBri(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 r,g,b,c,w,briRGB,briCT;
_state->getActualRGBCW(&r,&g,&b,&c,&w);
if (_pwm_multi_channels) {
Light.current_color[0] = r;
Light.current_color[1] = g;
Light.current_color[2] = b;
Light.current_color[3] = c;
Light.current_color[4] = w;
return;
}
briRGB = _state->getBriRGB();
briCT = _state->getBriCT();
Light.current_color[0] = Light.current_color[1] = Light.current_color[2] = 0;
Light.current_color[3] = Light.current_color[4] = 0;
switch (Light.subtype) {
case LST_NONE:
Light.current_color[0] = 255;
break;
case LST_SINGLE:
Light.current_color[0] = briRGB;
break;
case LST_COLDWARM:
Light.current_color[0] = c;
Light.current_color[1] = w;
break;
case LST_RGBW:
case LST_RGBWC:
if (LST_RGBWC == Light.subtype) {
Light.current_color[3] = c;
Light.current_color[4] = w;
} else {
Light.current_color[3] = briCT;
}
case LST_RGB:
Light.current_color[0] = r;
Light.current_color[1] = g;
Light.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->getActualRGBCW(&Settings.light_color[0], &Settings.light_color[1],
&Settings.light_color[2], &Settings.light_color[3],
&Settings.light_color[4]);
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 (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 ledGamma(uint8_t v, uint16_t bits_out = 8) {
uint16_t result;
uint32_t bits_resolution = 11 - (v / 64);
int32_t bits_correction = bits_out - bits_resolution;
#ifdef XFUNC_PTR_IN_ROM
uint32_t uncorrected_value = pgm_read_byte(_ledTable + v);
#else
uint32_t uncorrected_value = _ledTable[v];
#endif
if (0 == bits_correction) {
result = uncorrected_value;
} else if (bits_correction > 0) {
uint32_t bits_mask = (1 << bits_correction) - 1;
result = (uncorrected_value << bits_correction) | bits_mask;
} else {
uint32_t bits_mask = (1 << -bits_correction) - 1;
result = ((uncorrected_value + bits_mask) >> -bits_correction);
}
return result;
}
#ifdef USE_ARILUX_RF
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);
}
}
#endif
extern "C" {
void os_delay_us(unsigned int);
}
uint8_t light_pdi_pin;
uint8_t light_pdcki_pin;
void LightDiPulse(uint8_t times)
{
for (uint32_t i = 0; i < times; i++) {
digitalWrite(light_pdi_pin, HIGH);
digitalWrite(light_pdi_pin, LOW);
}
}
void LightDckiPulse(uint8_t times)
{
for (uint32_t i = 0; i < times; i++) {
digitalWrite(light_pdcki_pin, HIGH);
digitalWrite(light_pdcki_pin, LOW);
}
}
void LightMy92x1Write(uint8_t data)
{
for (uint32_t i = 0; i < 4; i++) {
digitalWrite(light_pdcki_pin, LOW);
digitalWrite(light_pdi_pin, (data & 0x80));
digitalWrite(light_pdcki_pin, HIGH);
data = data << 1;
digitalWrite(light_pdi_pin, (data & 0x80));
digitalWrite(light_pdcki_pin, LOW);
digitalWrite(light_pdi_pin, LOW);
data = data << 1;
}
}
void LightMy92x1Init(void)
{
uint8_t chips = 1;
if (LT_RGBWC == light_type) {
chips = 2;
}
LightDckiPulse(chips * 32);
os_delay_us(12);
LightDiPulse(12);
os_delay_us(12);
for (uint32_t n = 0; n < chips; 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[2] = { 4, 6 };
uint8_t didx = 0;
if (LT_RGBWC == light_type) {
didx = 1;
}
uint8_t duty[2][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 },
{ duty_w, duty_c, 0, duty_g, duty_r, duty_b }};
os_delay_us(12);
for (uint32_t channel = 0; channel < channels[didx]; channel++) {
LightMy92x1Write(duty[didx][channel]);
}
os_delay_us(12);
LightDiPulse(8);
os_delay_us(12);
}
#ifdef USE_SM16716
# 1281 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_04_light.ino"
#define D_LOG_SM16716 "SM16716: "
uint8_t sm16716_pin_clk = 100;
uint8_t sm16716_pin_dat = 100;
uint8_t sm16716_pin_sel = 100;
uint8_t sm16716_enabled = 0;
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) {
uint8_t sm16716_should_enable = (duty_r | duty_g | duty_b);
if (!sm16716_enabled && sm16716_should_enable) {
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color on"));
sm16716_enabled = 1;
digitalWrite(sm16716_pin_sel, HIGH);
delayMicroseconds(1000);
SM16716_Init();
}
else if (sm16716_enabled && !sm16716_should_enable) {
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color off"));
sm16716_enabled = 0;
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);
}
bool SM16716_ModuleSelected(void)
{
sm16716_pin_clk = pin[GPIO_SM16716_CLK];
sm16716_pin_dat = pin[GPIO_SM16716_DAT];
sm16716_pin_sel = pin[GPIO_SM16716_SEL];
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "ModuleSelected; clk_pin=%d, dat_pin=%d)"), sm16716_pin_clk, sm16716_pin_dat);
return (sm16716_pin_clk < 99) && (sm16716_pin_dat < 99);
}
void SM16716_Init(void)
{
for (uint32_t t_init = 0; t_init < 50; ++t_init) {
SM16716_SendBit(0);
}
}
#endif
void LightInit(void)
{
uint8_t max_scheme = LS_MAX -1;
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 defined(USE_WS2812) && (USE_WS2812_CTYPE > NEO_3LED)
if (LT_WS2812 == light_type) {
Light.subtype++;
}
#endif
if ((LST_SINGLE < Light.subtype) && Light.pwm_multi_channels) {
light_controller.setPWMMultiChannel(true);
Light.device = devices_present - Light.subtype + 1;
}
#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();
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 (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);
}
}
if (pin[GPIO_ARIRFRCV] < 99) {
if (pin[GPIO_ARIRFSEL] < 99) {
pinMode(pin[GPIO_ARIRFSEL], OUTPUT);
digitalWrite(pin[GPIO_ARIRFSEL], 1);
}
}
}
#ifdef USE_WS2812
else if (LT_WS2812 == light_type) {
Ws2812Init();
max_scheme = LS_MAX + WS2812_SCHEMES;
}
#endif
#ifdef USE_SM16716
else if (LT_SM16716 == light_type - Light.subtype) {
for (uint32_t i = 0; i < Light.subtype; i++) {
Settings.pwm_value[i] = 0;
if (pin[GPIO_PWM1 +i] < 99) {
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
}
}
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();
}
}
#endif
else {
light_pdi_pin = pin[GPIO_DI];
light_pdcki_pin = pin[GPIO_DCKI];
pinMode(light_pdi_pin, OUTPUT);
pinMode(light_pdcki_pin, OUTPUT);
digitalWrite(light_pdi_pin, LOW);
digitalWrite(light_pdcki_pin, LOW);
LightMy92x1Init();
}
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];
bool ct_rgb_linked = !(Settings.param[P_RGB_REMAP] & 128);
light_controller.setCTRGBLinked(ct_rgb_linked);
Light.update = true;
}
void LightSetDimmer(uint8_t dimmer) {
light_controller.changeDimmer(dimmer);
}
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 (device == Light.device) {
bri = light_state.getBri();
}
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 (device == Light.device) {
light_controller.changeBri(bri);
}
}
void LightSetColorTemp(uint16_t ct)
{
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
return;
}
light_controller.changeCTB(ct, light_state.getBriCT());
}
uint16_t LightGetColorTemp(void)
{
if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != 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];
if (append) {
ResponseAppend_P(PSTR(","));
} else {
Response_P(PSTR("{"));
}
if (!Light.pwm_multi_channels) {
GetPowerDevice(scommand, Light.device, sizeof(scommand), Settings.flag.device_index_enable);
ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"), scommand, GetStateText(Light.power), light_state.getDimmer());
if (Light.subtype > LST_SINGLE) {
ResponseAppend_P(PSTR(",\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
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);
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 ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
}
if (append) {
if (Light.subtype >= LST_RGB) {
ResponseAppend_P(PSTR(",\"" D_CMND_SCHEME "\":%d"), Settings.light_scheme);
}
if (LT_WS2812 == light_type) {
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(void)
{
#ifdef DEBUG_LIGHT
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower power=%d Light.power=%d", power, Light.power);
#endif
if (Light.pwm_multi_channels) {
# 1698 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_04_light.ino"
} else {
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);
}
#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 LightFade(void)
{
if (0 == Settings.light_fade) {
for (uint32_t i = 0; i < Light.subtype; i++) {
Light.new_color[i] = Light.current_color[i];
}
} else {
uint8_t shift = Settings.light_speed;
if (Settings.light_speed > 6) {
shift = (Light.strip_timer_counter % (Settings.light_speed -6)) ? 0 : 8;
}
if (shift) {
for (uint32_t i = 0; i < Light.subtype; i++) {
if (Light.new_color[i] != Light.current_color[i]) {
if (Light.new_color[i] < Light.current_color[i]) {
Light.new_color[i] += ((Light.current_color[i] - Light.new_color[i]) >> shift) +1;
}
if (Light.new_color[i] > Light.current_color[i]) {
Light.new_color[i] -= ((Light.new_color[i] - Light.current_color[i]) >> shift) +1;
}
}
}
}
}
}
void LightWheel(uint8_t wheel_pos)
{
wheel_pos = 255 - wheel_pos;
if (wheel_pos < 85) {
Light.entry_color[0] = 255 - wheel_pos * 3;
Light.entry_color[1] = 0;
Light.entry_color[2] = wheel_pos * 3;
} else if (wheel_pos < 170) {
wheel_pos -= 85;
Light.entry_color[0] = 0;
Light.entry_color[1] = wheel_pos * 3;
Light.entry_color[2] = 255 - wheel_pos * 3;
} else {
wheel_pos -= 170;
Light.entry_color[0] = wheel_pos * 3;
Light.entry_color[1] = 255 - wheel_pos * 3;
Light.entry_color[2] = 0;
}
Light.entry_color[3] = 0;
Light.entry_color[4] = 0;
float dimmer = 100 / (float)Settings.light_dimmer;
for (uint32_t i = 0; i < LST_RGB; i++) {
float temp = (float)Light.entry_color[i] / dimmer + 0.5f;
Light.entry_color[i] = (uint8_t)temp;
}
}
void LightCycleColor(int8_t direction)
{
if (Light.strip_timer_counter % (Settings.light_speed * 2)) {
return;
}
Light.wheel += direction;
LightWheel(Light.wheel);
memcpy(Light.new_color, Light.entry_color, sizeof(Light.new_color));
}
void LightRandomColor(void)
{
bool update = false;
for (uint32_t i = 0; i < LST_RGB; i++) {
if (Light.new_color[i] != Light.current_color[i]) {
update = true;
}
}
if (!update) {
Light.wheel = random(255);
LightWheel(Light.wheel);
memcpy(Light.current_color, Light.entry_color, sizeof(Light.current_color));
}
LightFade();
}
void LightSetPower(void)
{
Light.old_power = Light.power;
uint32_t mask = 1;
if (Light.pwm_multi_channels) {
mask = (1 << Light.subtype) - 1;
}
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)
{
uint8_t cur_col[LST_MAX];
uint16_t light_still_on = 0;
Light.strip_timer_counter++;
if (!Light.power) {
sleep = Settings.sleep;
Light.strip_timer_counter = 0;
for (uint32_t i = 0; i < Light.subtype; i++) {
light_still_on += Light.new_color[i];
}
if (light_still_on && Settings.light_fade && (Settings.light_scheme < LS_MAX)) {
uint8_t speed = Settings.light_speed;
if (speed > 6) {
speed = 6;
}
for (uint32_t i = 0; i < Light.subtype; i++) {
if (Light.new_color[i] > 0) {
Light.new_color[i] -= (Light.new_color[i] >> speed) +1;
}
}
} else {
for (uint32_t i = 0; i < Light.subtype; i++) {
Light.new_color[i] = 0;
}
}
}
else {
#ifdef PWM_LIGHTSCHEME0_IGNORE_SLEEP
sleep = (LS_POWER == Settings.light_scheme) ? Settings.sleep : 0;
#else
sleep = 0;
#endif
switch (Settings.light_scheme) {
case LS_POWER:
light_controller.calcLevels();
LightFade();
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 "\"}"));
MqttPublishPrefixTopic_P(TELE, PSTR(D_CMND_WAKEUP));
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:
LightRandomColor();
break;
#ifdef USE_WS2812
default:
if (LT_WS2812 == light_type) {
Ws2812ShowScheme(Settings.light_scheme -LS_MAX);
}
#endif
}
}
if ((Settings.light_scheme < LS_MAX) || !Light.power) {
if (Light.pwm_multi_channels) {
for (uint32_t i = 0; i < LST_MAX; i++) {
if (0 == bitRead(Light.power,i)) {
Light.new_color[i] = 0;
}
}
}
if (memcmp(Light.last_color, Light.new_color, Light.subtype)) {
Light.update = true;
}
if (Light.update) {
uint16_t cur_col_10bits[LST_MAX];
Light.update = false;
for (uint32_t i = 0; i < LST_MAX; i++) {
cur_col[i] = Light.last_color[i] = Light.new_color[i];
cur_col_10bits[i] = changeUIntScale(cur_col[i], 0, 255, 0, 1023);
}
if (PHILIPS == my_module_type) {
calcGammaXiaomiBulbs(cur_col, cur_col_10bits);
} else if (Light.pwm_multi_channels) {
calcGammaMultiChannels(cur_col, cur_col_10bits);
} else {
calcGammaBulbs(cur_col, cur_col_10bits);
if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col[3]+cur_col[4])) {
uint32_t min_rgb_10 = min3(cur_col_10bits[0], cur_col_10bits[1], cur_col_10bits[2]);
uint8_t min_rgb = min3(cur_col[0], cur_col[1], cur_col[2]);
for (uint32_t i=0; i<3; i++) {
cur_col_10bits[i] = changeUIntScale(cur_col_10bits[i] - min_rgb_10, 0, 255, 0, Settings.rgbwwTable[i]);
cur_col[i] = changeUIntScale(cur_col[i] - min_rgb, 0, 255, 0, Settings.rgbwwTable[i]);
}
uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 255, 0, Settings.rgbwwTable[3]);
uint32_t white = changeUIntScale(min_rgb, 0, 255, 0, Settings.rgbwwTable[3]);
if (LST_RGBW == Light.subtype) {
cur_col_10bits[3] = white_10;
cur_col[3] = white;
} else {
uint32_t ct = light_state.getCT();
cur_col_10bits[4] = changeUIntScale(ct, 153, 500, 0, white_10);
cur_col_10bits[3] = white_10 - cur_col_10bits[4];
cur_col[4] = changeUIntScale(ct, 153, 500, 0, white);
cur_col[3] = white - cur_col[4];
}
}
}
for (uint32_t i = 0; i < LST_MAX; i++) {
#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)
if ((cur_col_10bits[i] > 1008) && (cur_col_10bits[i] < 1023)) {
cur_col_10bits[i] = 1008;
}
#endif
cur_col_10bits[i] = (cur_col_10bits[i] > 0) ? changeUIntScale(cur_col_10bits[i], 1, 1023, 1, Settings.pwm_range) : 0;
}
uint8_t orig_col[LST_MAX];
uint16_t orig_col_10bits[LST_MAX];
memcpy(orig_col, cur_col, sizeof(orig_col));
memcpy(orig_col_10bits, cur_col_10bits, sizeof(orig_col_10bits));
for (uint32_t i = 0; i < LST_MAX; i++) {
cur_col[i] = orig_col[Light.color_remap[i]];
cur_col_10bits[i] = orig_col_10bits[Light.color_remap[i]];
}
if (light_type < LT_PWM6) {
for (uint32_t i = 0; i < Light.subtype; i++) {
if (pin[GPIO_PWM1 +i] < 99) {
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col_10bits[i] : cur_col_10bits[i]);
}
}
}
char *tmp_data = XdrvMailbox.data;
uint16_t tmp_data_len = XdrvMailbox.data_len;
XdrvMailbox.data = (char*)cur_col;
XdrvMailbox.data_len = sizeof(cur_col);
if (XdrvCall(FUNC_SET_CHANNELS)) {
}
#ifdef USE_WS2812
else if (LT_WS2812 == light_type) {
Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]);
}
#endif
#ifdef USE_SM16716
else if (LT_SM16716 == light_type - Light.subtype) {
for (uint32_t i = 3; i < Light.subtype; i++) {
if (pin[GPIO_PWM1 +i-3] < 99) {
analogWrite(pin[GPIO_PWM1 +i-3], bitRead(pwm_inverted, i-3) ? Settings.pwm_range - cur_col_10bits[i] : cur_col_10bits[i]);
}
}
SM16716_Update(cur_col[0], cur_col[1], cur_col[2]);
}
#endif
else if (light_type > LT_WS2812) {
LightMy92x1Duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]);
}
XdrvMailbox.data = tmp_data;
XdrvMailbox.data_len = tmp_data_len;
}
}
}
void calcGammaXiaomiBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
uint8_t cold;
light_state.getCW(&cold, nullptr);
cur_col[1] = cold;
cur_col_10bits[1] = changeUIntScale(cur_col[1], 0, 255, 0, 1023);
uint8_t pxBri = light_state.getBriCT();
if (Settings.light_correction) {
cur_col[0] = ledGamma(pxBri);
cur_col_10bits[0] = ledGamma(pxBri, 10);
} else {
cur_col[0] = pxBri;
cur_col_10bits[0] = changeUIntScale(pxBri, 0, 255, 0, 1023);
}
}
void calcGammaMultiChannels(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
if (Settings.light_correction) {
for (uint32_t i = 0; i < LST_MAX; i++) {
cur_col_10bits[i] = ledGamma(cur_col[i], 10);
cur_col[i] = ledGamma(cur_col[i]);
}
}
}
void calcGammaBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
if (Settings.light_correction) {
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
uint8_t w_idx[2] = {0, 1};
if (LST_RGBWC == Light.subtype) {
w_idx[0] = 3;
w_idx[1] = 4;
}
uint16_t white_bri = cur_col[w_idx[0]] + cur_col[w_idx[1]];
if (white_bri <= 255) {
uint16_t white_bri_10bits = ledGamma(white_bri, 10);
uint8_t white_bri_8bits = ledGamma(white_bri);
cur_col_10bits[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_10bits);
cur_col_10bits[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_10bits);
cur_col[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_8bits);
cur_col[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_8bits);
} else {
cur_col_10bits[w_idx[0]] = ledGamma(cur_col[w_idx[0]], 10);
cur_col_10bits[w_idx[1]] = ledGamma(cur_col[w_idx[1]], 10);
cur_col[w_idx[0]] = ledGamma(cur_col[w_idx[0]]);
cur_col[w_idx[1]] = ledGamma(cur_col[w_idx[1]]);
}
}
if (LST_RGB <= Light.subtype) {
for (uint32_t i = 0; i < 3; i++) {
cur_col_10bits[i] = ledGamma(cur_col[i], 10);
cur_col[i] = ledGamma(cur_col[i]);
}
}
if (LST_COLDWARM != Light.subtype) {
cur_col_10bits[3] = ledGamma(cur_col[3], 10);
cur_col[3] = ledGamma(cur_col[3]);
}
}
}
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));
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_RGBWC == 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.subtype == LST_RGBW) && (XdrvMailbox.index == 1)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
uint32_t whiteBri = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
char scolor[LIGHT_COLOR_SIZE];
snprintf_P(scolor, sizeof(scolor), PSTR("0,0,0,%d"), whiteBri);
light_state.setBri(whiteBri);
XdrvMailbox.data = scolor;
XdrvMailbox.data_len = strlen(scolor);
} else {
XdrvMailbox.data_len = 0;
}
CmndSupportColor();
}
}
void CmndChannel(void)
{
if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) {
bool coldim = false;
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
Light.current_color[XdrvMailbox.index - Light.device] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
if (Light.pwm_multi_channels) {
} else {
if ((XdrvMailbox.index <= 3) && (light_controller.isCTRGBLinked())) {
Light.current_color[3] = Light.current_color[4] = 0;
}
}
light_controller.changeChannels(Light.current_color);
coldim = true;
}
ResponseCmndIdxNumber(Light.current_color[XdrvMailbox.index -1] * 100 / 255);
if (coldim) {
LightPreparePower();
}
}
}
void CmndHsbColor(void)
{
if (Light.subtype >= LST_RGB) {
bool validHSB = (XdrvMailbox.data_len > 0);
if (validHSB) {
uint16_t HSB[3];
if (strstr(XdrvMailbox.data, ",") != nullptr) {
for (uint32_t i = 0; i < 3; i++) {
char *substr;
if (0 == i) {
substr = strtok(XdrvMailbox.data, ",");
} else {
substr = strtok(nullptr, ",");
}
if (substr != nullptr) {
HSB[i] = atoi(substr);
if (0 < i) {
HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255);
}
} else {
validHSB = false;
}
}
} else {
uint16_t c_hue;
uint8_t c_sat;
light_state.getHSB(&c_hue, &c_sat, nullptr);
HSB[0] = c_hue;
HSB[1] = c_sat;
HSB[2] = light_state.getBri();
if (1 == XdrvMailbox.index) {
HSB[0] = XdrvMailbox.payload;
} else if ((XdrvMailbox.index > 1) && (XdrvMailbox.index < 4)) {
HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
} else {
validHSB = false;
}
}
if (validHSB) {
light_controller.changeHSB(HSB[0], HSB[1], HSB[2]);
LightPreparePower();
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
}
} else {
LightState(0);
}
}
}
#ifdef USE_WS2812
void CmndLed(void)
{
if ((LT_WS2812 == light_type) && (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 (LT_WS2812 == light_type) {
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 (LT_WS2812 == light_type) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings.light_pixels)) {
Settings.light_rotation = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.light_rotation);
}
}
void CmndWidth(void)
{
if ((LT_WS2812 == light_type) && (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]);
}
}
}
#endif
void CmndScheme(void)
{
if (Light.subtype >= LST_RGB) {
uint32_t max_scheme = (LT_WS2812 == light_type) ? LS_MAX + WS2812_SCHEMES : LS_MAX -1;
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)) {
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)) {
Settings.light_dimmer = XdrvMailbox.payload;
}
Light.wakeup_active = 3;
Settings.light_scheme = LS_WAKEUP;
LightPowerOn();
ResponseCmndChar(D_JSON_STARTED);
}
void CmndColorTemperature(void)
{
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
uint32_t ct = light_state.getCT();
if (1 == XdrvMailbox.data_len) {
if ('+' == XdrvMailbox.data[0]) {
XdrvMailbox.payload = (ct > (500-34)) ? 500 : ct + 34;
}
else if ('-' == XdrvMailbox.data[0]) {
XdrvMailbox.payload = (ct < (153+34)) ? 153 : ct - 34;
}
}
if ((XdrvMailbox.payload >= 153) && (XdrvMailbox.payload <= 500)) {
light_controller.changeCTB(XdrvMailbox.payload, light_state.getBri());
LightPreparePower();
} else {
ResponseCmndNumber(ct);
}
}
}
void CmndDimmer(void)
{
uint32_t dimmer = light_state.getDimmer();
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)) {
light_controller.changeDimmer(XdrvMailbox.payload);
Light.update = true;
LightPreparePower();
} else {
ResponseCmndNumber(Settings.light_dimmer);
}
}
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)) {
if (strstr(XdrvMailbox.data, ",") != nullptr) {
for (uint32_t i = 0; i < LST_RGBWC; i++) {
char *substr;
if (0 == i) {
substr = strtok(XdrvMailbox.data, ",");
} else {
substr = strtok(nullptr, ",");
}
if (substr != nullptr) {
Settings.rgbwwTable[i] = atoi(substr);
}
}
}
Light.update = true;
}
char scolor[LIGHT_COLOR_SIZE];
scolor[0] = '\0';
for (uint32_t i = 0; i < LST_RGBWC; i++) {
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
}
ResponseCmndIdxChar(scolor);
}
void CmndFade(void)
{
switch (XdrvMailbox.payload) {
case 0:
case 1:
Settings.light_fade = XdrvMailbox.payload;
break;
case 2:
Settings.light_fade ^= 1;
break;
}
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 < STATES)) {
XdrvMailbox.payload = Settings.light_speed +1;
}
}
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= STATES)) {
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 (light_type) {
switch (function) {
case FUNC_PRE_INIT:
LightInit();
break;
case FUNC_EVERY_50_MSECOND:
LightAnimate();
#ifdef USE_ARILUX_RF
if (pin[GPIO_ARIRFRCV] < 99) { AriluxRfHandler(); }
#endif
break;
#ifdef USE_ARILUX_RF
case FUNC_EVERY_SECOND:
if (10 == uptime) { AriluxRfInit(); }
break;
#endif
case FUNC_SET_POWER:
LightSetPower();
break;
case FUNC_COMMAND:
result = DecodeCommand(kLightCommands, LightCommand);
break;
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_05_irremote.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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, IE_SYNTAX_IRHVAC };
const char kIrRemoteCommands[] PROGMEM = "|"
#ifdef USE_IR_HVAC
D_CMND_IRHVAC "|"
#endif
D_CMND_IRSEND ;
void (* const IrRemoteCommand[])(void) PROGMEM = {
#ifdef USE_IR_HVAC
&CmndIrHvac,
#endif
&CmndIrSend };
static const uint8_t MAX_STANDARD_IR = SHARP;
enum IrVendors { IR_BASE = MAX_STANDARD_IR,
#ifdef USE_IR_SEND_PIONEER
IR_PIONEER,
#endif
};
const char kIrRemoteProtocols[] PROGMEM =
"UNKNOWN|RC5|RC6|NEC|SONY|PANASONIC|JVC|SAMSUNG|WHYNTER|AIWA_RC_T501|LG|SANYO|MITSUBISHI|DISH|SHARP"
#ifdef USE_IR_SEND_PIONEER
"|PIONEER"
#endif
;
#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()
{
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[14];
int8_t iridx = 0;
decode_results results;
if (irrecv->decode(&results)) {
char hvalue[65];
iridx = results.decode_type;
if ((iridx < 0) || (iridx > 14)) { 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));
if (iridx) {
XdrvRulesProcess();
#ifdef USE_DOMOTICZ
unsigned long value = results.value | (iridx << 28);
DomoticzSensor(DZ_COUNT, value);
#endif
}
}
irrecv->resume();
}
}
#endif
#ifdef USE_IR_HVAC
enum IrHvacVendors { VNDR_TOSHIBA, VNDR_MITSUBISHI, VNDR_LG, VNDR_FUJITSU, VNDR_MIDEA };
const char kIrHvacVendors[] PROGMEM = "Toshiba|Mitsubishi|LG|Fujitsu|Midea" ;
const char kFanSpeedOptions[] = "A12345S";
const char kHvacModeOptions[] = "HDCA";
#ifdef USE_IR_HVAC_TOSHIBA
const uint16_t HVAC_TOSHIBA_HDR_MARK = 4400;
const uint16_t HVAC_TOSHIBA_HDR_SPACE = 4300;
const uint16_t HVAC_TOSHIBA_BIT_MARK = 543;
const uint16_t HVAC_TOSHIBA_ONE_SPACE = 1623;
const uint16_t HVAC_MISTUBISHI_ZERO_SPACE = 472;
const uint16_t HVAC_TOSHIBA_RPT_MARK = 440;
const uint16_t HVAC_TOSHIBA_RPT_SPACE = 7048;
const uint8_t HVAC_TOSHIBA_DATALEN = 9;
uint8_t IrHvacToshiba(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp)
{
uint16_t rawdata[2 + 2 * 8 * HVAC_TOSHIBA_DATALEN + 2];
uint8_t data[HVAC_TOSHIBA_DATALEN] = {0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x00, 0x00, 0x00, 0x00};
char *p;
uint8_t mode;
if (HVAC_Mode == nullptr) {
p = (char *)kHvacModeOptions;
}
else {
p = strchr(kHvacModeOptions, toupper(HVAC_Mode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
data[6] = (p - kHvacModeOptions) ^ 0x03;
if (!HVAC_Power) {
data[6] = (uint8_t)0x07;
}
if (HVAC_FanMode == nullptr) {
p = (char *)kFanSpeedOptions;
}
else {
p = strchr(kFanSpeedOptions, toupper(HVAC_FanMode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
mode = p - kFanSpeedOptions + 1;
if ((1 == mode) || (7 == mode)) {
mode = 0;
}
mode = mode << 5;
data[6] = data[6] | mode;
uint8_t Temp;
if (HVAC_Temp > 30) {
Temp = 30;
}
else if (HVAC_Temp < 17) {
Temp = 17;
}
else {
Temp = HVAC_Temp;
}
data[5] = (uint8_t)(Temp - 17) << 4;
data[HVAC_TOSHIBA_DATALEN - 1] = 0;
for (uint32_t x = 0; x < HVAC_TOSHIBA_DATALEN - 1; x++) {
data[HVAC_TOSHIBA_DATALEN - 1] = (uint8_t)data[x] ^ data[HVAC_TOSHIBA_DATALEN - 1];
}
int i = 0;
uint8_t mask = 1;
rawdata[i++] = HVAC_TOSHIBA_HDR_MARK;
rawdata[i++] = HVAC_TOSHIBA_HDR_SPACE;
for (uint32_t b = 0; b < HVAC_TOSHIBA_DATALEN; b++) {
for (mask = B10000000; mask > 0; mask >>= 1) {
if (data[b] & mask) {
rawdata[i++] = HVAC_TOSHIBA_BIT_MARK;
rawdata[i++] = HVAC_TOSHIBA_ONE_SPACE;
}
else {
rawdata[i++] = HVAC_TOSHIBA_BIT_MARK;
rawdata[i++] = HVAC_MISTUBISHI_ZERO_SPACE;
}
}
}
rawdata[i++] = HVAC_TOSHIBA_RPT_MARK;
rawdata[i++] = HVAC_TOSHIBA_RPT_SPACE;
irsend->sendRaw(rawdata, i, 38);
irsend->sendRaw(rawdata, i, 38);
return IE_NO_ERROR;
}
#endif
#ifdef USE_IR_HVAC_MIDEA
const uint16_t HVAC_MIDEA_HDR_MARK = 4420;
const uint16_t HVAC_MIDEA_HDR_SPACE = 4420;
const uint16_t HVAC_MIDEA_BIT_MARK = 553;
const uint16_t HVAC_MIDEA_ONE_SPACE = 1660;
const uint16_t HVAC_MIDEA_ZERO_SPACE = 553;
const uint16_t HVAC_MIDEA_RPT_MARK = 553;
const uint16_t HVAC_MIDEA_RPT_SPACE = 5530;
const uint8_t HVAC_MIDEA_DATALEN = 3;
uint8_t IrHvacMidea(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp)
{
uint16_t rawdata[2 + 2 * 2 * 8 * HVAC_MIDEA_DATALEN + 2];
uint8_t data[HVAC_MIDEA_DATALEN] = {0xB2, 0x00, 0x00};
char *p;
uint8_t mode;
if (!HVAC_Power) {
data[1] = 0x7B;
data[2] = 0xE0;
} else {
if (HVAC_FanMode == nullptr) {
p = (char*)kFanSpeedOptions;
}
else {
p = (char*)HVAC_FanMode;
}
switch(p[0]) {
case '1': data[1] = 0xBF; break;
case '2': data[1] = 0x9F; break;
case '3': data[1] = 0x5F; break;
case '4': data[1] = 0x3F; break;
case '5': data[1] = 0x1F; break;
case 'A': data[1] = 0x1F; break;
default: return IE_SYNTAX_IRHVAC;
}
uint8_t Temp;
if (HVAC_Temp > 30) {
Temp = 30;
}
else if (HVAC_Temp < 17) {
Temp = 17;
}
else {
Temp = HVAC_Temp-17;
}
if (10 == Temp) {
data[2] = 0x90;
} else if (11 == Temp) {
data[2] = 0x80;
} else {
Temp = (Temp >> 1) ^Temp;
data[2] = (Temp << 4);
}
if (HVAC_Mode == nullptr) {
p = (char*)kHvacModeOptions + 3;
}
else {
p = (char*)HVAC_Mode;
}
switch(toupper(p[0])) {
case 'D': data[2] = 0xE4; break;
case 'C': data[2] = 0x0 | data[2]; break;
case 'A': data[2] = 0x8 | data[2]; data[1] = 0x1F; break;
case 'H': data[2] = 0xC | data[2]; break;
default: return IE_SYNTAX_IRHVAC;
}
}
int i = 0;
uint8_t mask = 1;
rawdata[i++] = HVAC_MIDEA_HDR_MARK;
rawdata[i++] = HVAC_MIDEA_HDR_SPACE;
for (int b = 0; b < HVAC_MIDEA_DATALEN; b++) {
for (mask = B10000000; mask > 0; mask >>= 1) {
if (data[b] & mask) {
rawdata[i++] = HVAC_MIDEA_BIT_MARK;
rawdata[i++] = HVAC_MIDEA_ONE_SPACE;
}
else {
rawdata[i++] = HVAC_MIDEA_BIT_MARK;
rawdata[i++] = HVAC_MIDEA_ZERO_SPACE;
}
}
for (mask = B10000000; mask > 0; mask >>= 1) {
if (data[b] & mask) {
rawdata[i++] = HVAC_MIDEA_BIT_MARK;
rawdata[i++] = HVAC_MIDEA_ZERO_SPACE;
}
else {
rawdata[i++] = HVAC_MIDEA_BIT_MARK;
rawdata[i++] = HVAC_MIDEA_ONE_SPACE;
}
}
}
rawdata[i++] = HVAC_MIDEA_RPT_MARK;
rawdata[i++] = HVAC_MIDEA_RPT_SPACE;
irsend->sendRaw(rawdata, i, 38);
irsend->sendRaw(rawdata, i, 38);
return IE_NO_ERROR;
}
#endif
#ifdef USE_IR_HVAC_MITSUBISHI
#include <ir_Mitsubishi.h>
uint8_t IrHvacMitsubishi(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp)
{
char *p;
uint8_t mode;
IRMitsubishiAC mitsubir(pin[GPIO_IRSEND]);
mitsubir.stateReset();
if (HVAC_Mode == nullptr) {
p = (char *)kHvacModeOptions;
}
else {
p = strchr(kHvacModeOptions, toupper(HVAC_Mode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
mode = (p - kHvacModeOptions + 1) << 3;
mitsubir.setMode(mode);
mitsubir.setPower(HVAC_Power);
if (HVAC_FanMode == nullptr) {
p = (char *)kFanSpeedOptions;
}
else {
p = strchr(kFanSpeedOptions, toupper(HVAC_FanMode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
mode = p - kFanSpeedOptions;
mitsubir.setFan(mode);
mitsubir.setTemp(HVAC_Temp);
mitsubir.setVane(MITSUBISHI_AC_VANE_AUTO);
mitsubir.send();
return IE_NO_ERROR;
}
#endif
#ifdef USE_IR_HVAC_LG
const uint8_t HVAC_LG_DATALEN = 7;
uint8_t IrHvacLG(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp)
{
uint32_t LG_Code;
uint8_t data[HVAC_LG_DATALEN];
static bool hvacOn = false;
char *p;
uint8_t mode;
uint8_t Temp;
data[0] = 0x08;
data[1] = 0x08;
data[2] = 0x00;
if (!HVAC_Power) {
data[2] = (uint8_t)0x0C;
data[3] = (uint8_t)0x00;
data[4] = (uint8_t)0x00;
data[5] = (uint8_t)0x05;
data[6] = (uint8_t)0x01;
hvacOn = false;
}
else {
if (HVAC_Mode == nullptr) {
p = (char *)kHvacModeOptions;
}
else {
p = strchr(kHvacModeOptions, toupper(HVAC_Mode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
mode = (p - kHvacModeOptions) ^ 0x03;
switch (mode) {
case 0:
data[3] = 11;
break;
case 1:
data[3] = 8;
break;
case 2:
data[3] = 9;
break;
case 3:
data[3] = 12;
break;
}
if (!hvacOn) {
data[3] = data[3] & 7;
hvacOn = true;
}
if (HVAC_Temp > 30) {
Temp = 30;
}
else if (HVAC_Temp < 18) {
Temp = 18;
}
else {
Temp = HVAC_Temp;
}
data[4] = (uint8_t)(Temp - 15);
if (HVAC_FanMode == nullptr) {
p = (char *)kFanSpeedOptions;
}
else {
p = strchr(kFanSpeedOptions, toupper(HVAC_FanMode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
mode = p - kFanSpeedOptions;
if ((mode == 0) || (mode > 3)) {
data[5] = 5;
}
else {
data[5] = (mode * 2) - 2;
}
data[6] = (data[3] + data[4] + data[5]) & 0x0f;
}
LG_Code = data[0] << 4;
for (uint32_t i = 1; i < 6; i++) {
LG_Code = (LG_Code + data[i]) << 4;
}
LG_Code = LG_Code + data[6];
irsend->sendLG(LG_Code, 28);
return IE_NO_ERROR;
}
#endif
#ifdef USE_IR_HVAC_FUJITSU
#include <ir_Fujitsu.h>
uint8_t IrHvacFujitsu(const char *HVAC_Mode, const char *HVAC_FanMode, bool HVAC_Power, int HVAC_Temp)
{
const char kFujitsuHvacModeOptions[] = "HDCAF";
IRFujitsuAC ac(pin[GPIO_IRSEND]);
if (0 == HVAC_Power) {
ac.off();
ac.send();
return IE_NO_ERROR;
}
uint8_t modes[5] = {FUJITSU_AC_MODE_HEAT, FUJITSU_AC_MODE_DRY, FUJITSU_AC_MODE_COOL, FUJITSU_AC_MODE_AUTO, FUJITSU_AC_MODE_FAN};
uint8_t fanModes[7] = {FUJITSU_AC_FAN_AUTO, FUJITSU_AC_FAN_LOW, FUJITSU_AC_FAN_MED, FUJITSU_AC_FAN_HIGH, FUJITSU_AC_FAN_HIGH, FUJITSU_AC_FAN_HIGH, FUJITSU_AC_FAN_QUIET};
ac.setCmd(FUJITSU_AC_CMD_TURN_ON);
ac.setSwing(FUJITSU_AC_SWING_VERT);
char *p;
if (nullptr == HVAC_Mode) {
p = (char *)kFujitsuHvacModeOptions;
}
else {
p = strchr(kFujitsuHvacModeOptions, toupper(HVAC_Mode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
ac.setMode(modes[p - kFujitsuHvacModeOptions]);
if (HVAC_FanMode == nullptr) {
p = (char *)kFanSpeedOptions;
}
else {
p = strchr(kFanSpeedOptions, toupper(HVAC_FanMode[0]));
}
if (!p) {
return IE_SYNTAX_IRHVAC;
}
ac.setFanSpeed(fanModes[p - kFanSpeedOptions]);
ac.setTemp(HVAC_Temp);
ac.send();
return IE_NO_ERROR;
}
#endif
uint32_t IrRemoteCmndIrHvacJson(void)
{
const char *HVAC_Mode;
const char *HVAC_FanMode;
const char *HVAC_Vendor;
char parm_uc[12];
int HVAC_Temp = 21;
bool HVAC_Power = true;
char dataBufUc[XdrvMailbox.data_len];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) {
return IE_INVALID_JSON;
}
StaticJsonBuffer<164> jsonBufer;
JsonObject &root = jsonBufer.parseObject(dataBufUc);
if (!root.success()) {
return IE_INVALID_JSON;
}
HVAC_Vendor = root[UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR))];
HVAC_Power = root[UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER))];
HVAC_Mode = root[UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE))];
HVAC_FanMode = root[UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED))];
HVAC_Temp = root[UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TEMP))];
char vendor[20];
int vendor_code = GetCommandCode(vendor, sizeof(vendor), HVAC_Vendor, kIrHvacVendors);
irsend_active = true;
switch (vendor_code) {
#ifdef USE_IR_HVAC_TOSHIBA
case VNDR_TOSHIBA:
return IrHvacToshiba(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
#endif
#ifdef USE_IR_HVAC_MITSUBISHI
case VNDR_MITSUBISHI:
return IrHvacMitsubishi(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
#endif
#ifdef USE_IR_HVAC_LG
case VNDR_LG:
return IrHvacLG(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
#endif
#ifdef USE_IR_HVAC_FUJITSU
case VNDR_FUJITSU:
return IrHvacFujitsu(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
#endif
#ifdef USE_IR_HVAC_MIDEA
case VNDR_MIDEA:
return IrHvacMidea(HVAC_Mode, HVAC_FanMode, HVAC_Power, HVAC_Temp);
#endif
default:
irsend_active = false;
}
return IE_SYNTAX_IRHVAC;
}
void CmndIrHvac(void)
{
uint8_t error = IE_SYNTAX_IRHVAC;
if (XdrvMailbox.data_len) {
error = IrRemoteCmndIrHvacJson();
}
IrRemoteCmndResponse(error);
}
#endif
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;
}
uint32_t IrRemoteCmndIrSendJson(void)
{
char dataBufUc[XdrvMailbox.data_len];
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
#ifdef USE_IR_SEND_SONY
case SONY:
irsend->sendSony(data, (bits > SONY_20_BITS) ? SONY_20_BITS : bits, repeat > kSonyMinRepeat ? repeat : kSonyMinRepeat); break;
#endif
#ifdef USE_IR_SEND_PANASONIC
case PANASONIC:
irsend->sendPanasonic64(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_JVC
case JVC:
irsend->sendJVC(data, (bits > JVC_BITS) ? JVC_BITS : bits, repeat > 1 ? repeat : 1); break;
#endif
#ifdef USE_IR_SEND_SAMSUNG
case SAMSUNG:
irsend->sendSAMSUNG(data, (bits > SAMSUNG_BITS) ? SAMSUNG_BITS : bits, repeat); break;
#endif
#ifdef USE_IR_SEND_WHYNTER
case WHYNTER:
irsend->sendWhynter(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_AIWA
case AIWA_RC_T501:
irsend->sendAiwaRCT501(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_LG
case LG:
irsend->sendLG(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_SANYO
case SANYO:
irsend->sendSanyoLC7461(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_MITSUBISHI
case MITSUBISHI:
irsend->sendMitsubishi(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_DISH
case DISH:
irsend->sendDISH(data, (bits > DISH_BITS) ? DISH_BITS : bits, repeat > kDishMinRepeat ? repeat : kDishMinRepeat); break;
#endif
#ifdef USE_IR_SEND_SHARP
case SHARP:
irsend->sendSharpRaw(data, bits, repeat); break;
#endif
#ifdef USE_IR_SEND_PIONEER
case IR_PIONEER:
irsend->sendPioneer(data, 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 = 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_PROTOCOL ", " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}"));
break;
#ifdef USE_IR_HVAC
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;
#endif
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_05_irremote_full.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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()
{
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 += "\"";
json += hvalue;
json += "\",\"" D_JSON_IR_DATALSB "\":\"";
Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits);
json += hvalue;
json += "\"";
} else {
Uint64toHex(results.value, hvalue, 32);
json += "\"";
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)
{
char sirtype[14];
int8_t iridx = 0;
decode_results results;
if (irrecv->decode(&results)) {
uint32_t now = millis();
if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) {
ir_lasttime = now;
ResponseTime_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));
if (iridx) {
XdrvRulesProcess();
#ifdef USE_DOMOTICZ
unsigned long value = results.value | (iridx << 28);
DomoticzSensor(DZ_COUNT, value);
#endif
}
}
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];
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];
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 %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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_06_snfbridge.ino"
# 24 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_06_snfbridge.ino"
#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("\"#%X\""), 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:
Settings.flag.mqtt_serial = 0;
baudrate = 19200;
break;
}
}
return result;
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_07_domoticz.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 ;
char domoticz_in_topic[] = DOMOTICZ_IN_TOPIC;
char domoticz_out_topic[] = DOMOTICZ_OUT_TOPIC;
int domoticz_update_timer = 0;
uint32_t domoticz_fan_debounce = 0;
bool domoticz_subscribe = false;
bool domoticz_update_flag = true;
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()
{
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()
{
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_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
}
}
}
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_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("%s/#"), domoticz_out_topic);
MqttSubscribe(stopic);
}
}
# 200 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_07_domoticz.ino"
bool DomoticzMqttData(void)
{
char stemp1[10];
unsigned long idx = 0;
int16_t nvalue = -1;
bool found = false;
domoticz_update_flag = true;
if (!strncmp(XdrvMailbox.topic, domoticz_out_topic, strlen(domoticz_out_topic))) {
if (XdrvMailbox.data_len < 20) {
return true;
}
StaticJsonBuffer<400> jsonBuf;
JsonObject& domoticz = jsonBuf.parseObject(XdrvMailbox.data);
if (!domoticz.success()) {
return true;
}
idx = domoticz["idx"];
if (domoticz.containsKey("nvalue")) {
nvalue = domoticz["nvalue"];
}
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ "idx %d, nvalue %d"), idx, nvalue);
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;
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
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;
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;
}
# 334 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 {
Response_P(DOMOTICZ_MESSAGE,
Settings.domoticz_sensor_idx[idx], 0, 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();
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_08_serial_bridge.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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];
ResponseTime_P(PSTR(",\"" D_JSON_SSERIALRECEIVED "\":\"%s\"}"),
(serial_bridge_raw) ? ToHex_P((unsigned char*)serial_bridge_buffer, serial_bridge_in_byte_counter, hex_char, sizeof(hex_char)) : serial_bridge_buffer);
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 > 0) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_09_timers.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_09_timers.ino"
#ifdef USE_TIMERS
# 39 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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];
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);
}
char lbuff[33];
dtostrfd(((float)Settings.longitude) /1000000, 6, lbuff);
ResponseCmndChar(lbuff);
}
void CmndLatitude(void)
{
if (XdrvMailbox.data_len) {
Settings.latitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
}
char lbuff[33];
dtostrfd(((float)Settings.latitude) /1000000, 6, lbuff);
ResponseCmndChar(lbuff);
}
#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').disabled='';"
"if(e>12){for(i=12;i<=23;i++){o.removeChild(o.lastElementChild);}}"
"}else{"
"qs('#dr').disabled='disabled';"
"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){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];
Timer timer;
Settings.flag3.timers_enable = WebServer->hasArg("e0");
WebGetArg("t0", tmp, sizeof(tmp));
char *p = tmp;
snprintf_P(log_data, sizeof(log_data), 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(log_data, sizeof(log_data), PSTR("%s,0x%08X"), log_data, Settings.timer[i].data);
}
AddLog(LOG_LEVEL_DEBUG);
}
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_rules.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_rules.ino"
#ifdef USE_RULES
#ifndef USE_SCRIPT
# 67 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 = "+-*/%^";
#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>5)
#error MAX_RULE_MEMS is bigger than 5
#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[CMDSZ] = { 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 = Settings.mems[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);
}
}
StaticJsonBuffer<1024> jsonBuf;
JsonObject &root = jsonBuf.parseObject(event);
if (!root.success()) { return false; }
const char* str_value;
if ((pos = rule_name.indexOf("[")) > 0) {
int 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);
str_value = root[rule_task][rule_name][rule_name_idx -1];
} else {
str_value = root[rule_task][rule_name];
}
if (!root[rule_task][rule_name].success()) { return false; }
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;
}
# 348 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
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);
if (plen == plen2) { stop_all_rules = true; }
String commands = rules.substring(pevt +4, plen);
plen += 6;
Rules.event_value = "";
String event = event_saved;
if (RulesRuleMatch(rule_set, event, event_trigger)) {
commands.trim();
String ucommand = commands;
ucommand.toUpperCase();
if (ucommand.indexOf("EVENT ") != -1) { commands = "backlog " + commands; }
commands.replace(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);
commands.replace(stemp, rules_vars[i]);
}
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%%mem%d%%"), i +1);
commands.replace(stemp, Settings.mems[i]);
}
commands.replace(F("%time%"), String(MinutesPastMidnight()));
commands.replace(F("%uptime%"), String(MinutesUptime()));
commands.replace(F("%timestamp%"), GetDateAndTime(DT_LOCAL).c_str());
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
commands.replace(F("%sunrise%"), String(SunMinutes(0)));
commands.replace(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; }
}
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
bool swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i]));
snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (swm ^ SwitchLastState(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, Settings.mems[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;
}
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
# 695 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_rules.ino"
bool RulesMqttData(void)
{
bool serviced = false;
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;
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;
}
# 757 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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());
}
# 837 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
# 879 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
}
# 918 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_rules.ino"
bool findNextNumber(char * &pNumber, float &value)
{
bool bSucceed = false;
String sNumber = "";
while (*pNumber) {
if (isdigit(*pNumber) || (*pNumber == '.')) {
sNumber += *pNumber;
pNumber++;
} else {
break;
}
}
if (sNumber.length() > 0) {
value = CharToFloat(sNumber.c_str());
bSucceed = true;
}
return bSucceed;
}
# 950 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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(Settings.mems[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;
}
# 1012 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_rules.ino"
bool findNextObjectValue(char * &pointer, float &value)
{
bool bSucceed = false;
while (*pointer)
{
if (isspace(*pointer)) {
pointer++;
continue;
}
if (isdigit(*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 - 2);
pointer = closureBracket + 1;
bSucceed = true;
}
break;
} else {
break;
}
}
return bSucceed;
}
# 1056 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_rules.ino"
bool findNextOperator(char * &pointer, int8_t &op)
{
bool bSucceed = false;
while (*pointer)
{
if (isspace(*pointer)) {
pointer++;
continue;
}
if (char *pch = strchr(kExpressionOperators, *pointer)) {
op = (int8_t)(pch - kExpressionOperators);
pointer++;
bSucceed = true;
}
break;
}
return bSucceed;
}
# 1087 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 1140 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 == 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()
{
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();
}
# 1214 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 1270 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 1307 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 - 2);
pointer = closureBracket + 1;
bSucceed = true;
}
break;
}
pointer++;
}
if (!bSucceed && pointer > pExpr) {
value = evaluateComparisonExpression(pExpr, pointer - pExpr);
bSucceed = true;
}
return bSucceed;
}
# 1356 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
# 1428 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 1485 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 1556 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
}
}
}
# 1620 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) {
mqtt_data[0] = '\0';
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
ResponseAppend_P(PSTR("%c\"Mem%d\":\"%s\""), (i) ? ',' : '{', i +1, Settings.mems[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, Settings.mems[XdrvMailbox.index -1]);
} else {
strlcpy(Settings.mems[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[XdrvMailbox.index -1]));
}
#else
strlcpy(Settings.mems[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[XdrvMailbox.index -1]));
#endif
bitSet(Rules.mems_event, XdrvMailbox.index -1);
}
ResponseCmndIdxChar(Settings.mems[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_PRE_INIT:
RulesInit();
break;
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
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_scripter.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_scripter.ino"
#ifdef USE_SCRIPT
#ifndef USE_RULES
# 40 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_10_scripter.ino"
#define XDRV_10 10
#define SCRIPT_DEBUG 0
#define MAXVARS 50
#define MAXNVARS 45
#define MAXSVARS 5
#define MAXFILT 5
#define SCRIPT_SVARSIZE 20
#define SCRIPT_MAXSSIZE 48
#define SCRIPT_EOL '\n'
#define SCRIPT_FLOAT_PRECISION 2
#define SCRIPT_MAXPERM (MAX_RULE_MEMS*10)-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>
#define FAT_SCRIPT_SIZE 4096
#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;
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
#define EEP_SCRIPT_SIZE 4095
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++;
}
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
#define FLT_MAX 99999999
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;
} else if (!strncmp(str_value,"OFF",3)) {
if (fp) *fp=0;
} 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);
}
}
*vtype=NUM_RES;
tind->bits.constant=1;
tind->bits.is_string=0;
return lp+len;
}
}
}
}
}
chknext:
switch (vname[0]) {
case 'b':
if (!strncmp(vname,"boot",4)) {
if (rules_flag.system_boot) {
rules_flag.system_boot=0;
fvar=1;
}
goto exit;
}
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,Settings.mqtt_grptopic,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,Settings.mqtt_prefix[0],glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix2",7)) {
if (sp) strlcpy(sp,Settings.mqtt_prefix[1],glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix3",7)) {
if (sp) strlcpy(sp,Settings.mqtt_prefix[2],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)+(MAX_RULE_MEMS*10);
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((uint8_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
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,Settings.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];
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 {
}
strcpy(&dstbuf[count],string);
count+=strlen(string)-1;
cp++;
} else {
strcpy(&dstbuf[count],"???");
count+=2;
while (*cp!='%') {
if (*cp==0 || *cp==SCRIPT_EOL) {
dstbuf[count+1]=0;
return;
}
cp++;
}
cp++;
}
}
} else {
dstbuf[count]=*cp;
if (*cp==0) {
break;
}
cp++;
}
}
}
void toLog(const char *str) {
if (!str) return;
snprintf_P(log_data, sizeof(log_data), PSTR("%s"),str);
AddLog(LOG_LEVEL_INFO);
}
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;
SCRIPT_SKIP_SPACES
if (*lp=='(') {
lp++;
lp=Evaluate_expression(lp,and_or,result,jo);
lp++;
SCRIPT_SKIP_SPACES
if (!strncmp(lp,"or",2)) {
lp+=2;
and_or=1;
SCRIPT_SKIP_SPACES
lp=Evaluate_expression(lp,and_or,result,jo);
} else if (!strncmp(lp,"and",3)) {
lp+=3;
and_or=2;
SCRIPT_SKIP_SPACES
lp=Evaluate_expression(lp,and_or,result,jo);
}
return lp;
}
dfvar=&fvar;
glob_script_mem.glob_error=0;
char *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) {
uint8_t res=0;
res=strcmp(cmpstr,str);
if (lastop==OPER_EQUEQU) res=!res;
if (!and_or) {
*result=res;
} else if (and_or==1) {
*result|=res;
} else {
*result&=res;
}
}
} 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;
}
if (!and_or) {
*result=res;
} else if (and_or==1) {
*result|=res;
} else {
*result&=res;
}
}
exit:
#if SCRIPT_DEBUG>0
char tbuff[128];
sprintf(tbuff,"p1=%d,p2=%d,cmpres=%d line: ",(int32_t)*dfvar,(int32_t)fvar1,*result);
toLogEOL(tbuff,lp);
#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,"print",5)) {
uint8_t sflag=0,pflg=0,svmqtt,swll;
if (*lp=='p') {
pflg=1;
lp+=5;
}
else {
if (*lp=='-') sflag=1;
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-1; 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) {
snprintf_P(log_data, sizeof(log_data), PSTR("Script: performs \"%s\""), tmp);
AddLog(glob_script_mem.script_loglevel&0x7f);
} else {
svmqtt=Settings.flag.mqtt_enabled;
swll=Settings.weblog_level;
Settings.flag.mqtt_enabled=0;
Settings.weblog_level=0;
}
tasm_cmd_activ=1;
ExecuteCommand((char*)tmp, SRC_RULE);
tasm_cmd_activ=0;
if (sflag) {
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) {
dfvar=&sysvar;
sysv_type=ind.index;
} 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>MAX_RULE_MEMS*10) {
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>MAX_RULE_MEMS*10) {
vtp[count].bits.is_permanent=0;
return;
}
strcpy(cp,sp);
cp+=slen+1;
}
}
}
#ifdef USE_WEBSERVER
#define WEB_HANDLE_SCRIPT "s10"
#define D_CONFIGURE_SCRIPT "Edit script"
#define D_SCRIPT "edit script"
#define D_SDCARD_UPLOAD "file upload"
#define D_SDCARD_DIR "sd card directory"
#define D_UPL_DONE "Done"
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>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 textarea=qs('textarea');"
"textarea.addEventListener('input',function(){"
"var ml=this.getAttribute('maxlength');"
"var cl=this.value.length;"
"if(cl>=ml){"
"eb('charNum').innerHTML='no more chars';"
"}else{"
"eb('charNum').innerHTML=ml-cl+' chars left';"
"}"
"});"
"</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'>Download '%s'</button>";
#ifdef SDCARD_DIR
const char HTTP_FORM_SCRIPT1d[] PROGMEM =
"<button method='post' name='upl' type='submit' class='button bgrn'>SD card directory</button>";
#else
const char HTTP_FORM_SCRIPT1d[] PROGMEM =
"<button method='post' name='upl' type='submit' class='button bgrn'>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, "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);
WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",glob_script_mem.script_size);
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");
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) {
snprintf_P(log_data, sizeof(log_data), PSTR("script init error: %d"),res);
AddLog(LOG_LEVEL_INFO);
return;
}
Run_Scripter(">B",2,0);
fast_script=Run_Scripter(">F",-2,0);
}
}
#endif
#ifdef USE_SCRIPT_SUB_COMMAND
bool Script_SubCmd(void) {
if (!bitRead(Settings.rule_enabled, 0)) return false;
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 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;
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kScriptCommands);
if (-1 == command_code) {
#ifdef USE_SCRIPT_SUB_COMMAND
strlcpy(command,XdrvMailbox.topic,CMDSZ);
uint32_t pl=XdrvMailbox.payload;
char pld[64];
strlcpy(pld,XdrvMailbox.data,sizeof(pld));
if (Script_SubCmd()) {
if (pl>=0) {
Response_P(S_JSON_COMMAND_NVALUE, command, pl);
} else {
Response_P(S_JSON_COMMAND_SVALUE, command, pld);
}
return serviced;
}
#endif
serviced = false;
}
else if ((CMND_SCRIPT == command_code) && (index > 0)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 2)) {
switch (XdrvMailbox.payload) {
case 0:
case 1:
bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload);
}
} 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
# 3469 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 3544 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 3624 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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_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.mems[0];
glob_script_mem.script_pram_size=MAX_RULE_MEMS*10;
#ifdef USE_24C256
#ifndef USE_SCRIPT_FATFS
if (i2c_flg) {
if (I2cDevice(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;
}
}
#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);
}
break;
case FUNC_EVERY_100_MSECOND:
ScripterEvery100ms();
break;
case FUNC_EVERY_SECOND:
ScriptEverySecond();
break;
case FUNC_COMMAND:
result = ScriptCommand();
break;
case FUNC_SET_POWER:
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:
ScriptJsonAppend();
break;
#endif
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_11_knx.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_11_knx.ino"
#ifdef USE_KNX
# 51 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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; }
if (GetUsedInModule(GPIO_DSB, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
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 ( 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;
}
#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(uint8_t key, uint8_t device, uint8_t state)
{
if (!(Settings.flag.knx_enabled)) { return; }
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_PRE_INIT:
KNX_INIT();
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_EVERY_50_MSECOND:
if (toggle_inhibit) {
toggle_inhibit--;
}
break;
case FUNC_COMMAND:
result = DecodeCommand(kKnxCommands, KnxCommand);
break;
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_12_home_assistant.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_12_home_assistant.ino"
#ifdef USE_HOME_ASSISTANT
#define XDRV_12 12
const char HASS_DISCOVER_RELAY[] PROGMEM =
"{\"name\":\"%s\","
"\"cmd_t\":\"%s\","
"\"stat_t\":\"%s\","
"\"val_tpl\":\"{{value_json.%s}}\","
"\"pl_off\":\"%s\","
"\"pl_on\":\"%s\","
"\"avty_t\":\"%s\","
"\"pl_avail\":\"" D_ONLINE "\","
"\"pl_not_avail\":\"" D_OFFLINE "\"";
const char HASS_DISCOVER_BUTTON_SWITCH[] PROGMEM =
"{\"name\":\"%s\","
"\"stat_t\":\"%s\","
"\"pl_on\":\"%s\","
"\"avty_t\":\"%s\","
"\"pl_avail\":\"" D_ONLINE "\","
"\"pl_not_avail\":\"" D_OFFLINE "\"";
const char HASS_DISCOVER_BUTTON_SWITCH_TOGGLE[] PROGMEM =
",\"off_delay\":1";
const char HASS_DISCOVER_BUTTON_SWITCH_ONOFF[] PROGMEM =
",\"frc_upd\":true,"
"\"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[] PROGMEM =
"{\"name\":\"%s\","
"\"stat_t\":\"%s\","
"\"avty_t\":\"%s\","
"\"pl_avail\":\"" D_ONLINE "\","
"\"pl_not_avail\":\"" D_OFFLINE "\"";
const char HASS_DISCOVER_SENSOR_TEMP[] PROGMEM =
",\"unit_of_meas\":\"°%c\","
"\"val_tpl\":\"{{value_json['%s'].Temperature}}\","
"\"dev_cla\":\"temperature\"";
const char HASS_DISCOVER_SENSOR_HUM[] PROGMEM =
",\"unit_of_meas\":\"%%\","
"\"val_tpl\":\"{{value_json['%s'].Humidity}}\","
"\"dev_cla\":\"humidity\"";
const char HASS_DISCOVER_SENSOR_PRESS[] PROGMEM =
",\"unit_of_meas\":\"%s\","
"\"val_tpl\":\"{{value_json['%s'].Pressure}}\","
"\"dev_cla\":\"pressure\"";
const char HASS_DISCOVER_SENSOR_KWH[] PROGMEM =
",\"unit_of_meas\":\"kWh\","
"\"val_tpl\":\"{{value_json['%s'].%s}}\","
"\"dev_cla\":\"power\"";
const char HASS_DISCOVER_SENSOR_WATT[] PROGMEM =
",\"unit_of_meas\":\"W\","
"\"val_tpl\":\"{{value_json['%s'].%s}}\","
"\"dev_cla\":\"power\"";
const char HASS_DISCOVER_SENSOR_VOLTAGE[] PROGMEM =
",\"unit_of_meas\":\"V\","
"\"val_tpl\":\"{{value_json['%s'].%s}}\","
"\"dev_cla\":\"power\"";
const char HASS_DISCOVER_SENSOR_AMPERE[] PROGMEM =
",\"unit_of_meas\":\"A\","
"\"val_tpl\":\"{{value_json['%s'].%s}}\","
"\"dev_cla\":\"power\"";
const char HASS_DISCOVER_SENSOR_ILLUMINANCE[] PROGMEM =
",\"unit_of_meas\":\"LX\","
"\"val_tpl\":\"{{value_json['%s'].Illuminance}}\","
"\"dev_cla\":\"illuminance\"";
const char HASS_DISCOVER_SENSOR_ANY[] PROGMEM =
",\"unit_of_meas\":\" \","
"\"val_tpl\":\"{{value_json['%s'].%s}}\"";
const char HASS_DISCOVER_SENSOR_HASS_STATUS[] PROGMEM =
",\"json_attributes_topic\":\"%s\","
"\"unit_of_meas\":\" \","
"\"val_tpl\":\"{{value_json['" D_JSON_RSSI "']}}\"";
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\"]]}";
const char HASS_DISCOVER_TOPIC_PREFIX[] PROGMEM =
",\"~\":\"%s\"";
uint8_t hass_init_step = 0;
uint8_t hass_mode = 0;
int hass_tele_period = 0;
static void FindPrefix(char* s1, char* s2, char* out)
{
int prefixlen = 0;
while (s1[prefixlen] != '\0' && s2[prefixlen] != '\0' && s1[prefixlen] == s2[prefixlen]) {
prefixlen++;
}
strlcpy(out, s1, prefixlen+1);
}
static void Shorten(char** s, char *prefix)
{
size_t len = strlen(*s);
size_t prefixlen = strlen(prefix);
if (len > prefixlen && prefixlen != 0 && !strncmp(*s, prefix, prefixlen)) {
*s += prefixlen-1;
*s[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"), Settings.friendlyname[0], i);
} else {
snprintf_P(name, sizeof(name), Settings.friendlyname[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);
FindPrefix(command_topic, state_topic, prefix);
Shorten(&command_topic, prefix);
Shorten(&state_topic, prefix);
Shorten(&availability_topic, prefix);
Response_P(HASS_DISCOVER_RELAY, name, command_topic, state_topic, value_template, Settings.state_text[0], Settings.state_text[1], availability_topic);
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
#ifdef USE_LIGHT
if (is_light) {
char *brightness_command_topic = stemp1;
GetTopic_P(brightness_command_topic, CMND, mqtt_topic, D_CMND_DIMMER);
Shorten(&brightness_command_topic, prefix);
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);
Shorten(&rgb_command_topic, prefix);
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);
Shorten(&effect_command_topic, prefix);
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);
Shorten(&white_temp_command_topic, prefix);
TryResponseAppend_P(HASS_DISCOVER_LIGHT_WHITE, white_temp_command_topic, state_topic);
}
if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
char *color_temp_command_topic = stemp1;
GetTopic_P(color_temp_command_topic, CMND, mqtt_topic, D_CMND_COLORTEMPERATURE);
Shorten(&color_temp_command_topic, prefix);
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;
if (device+1 > MAX_FRIENDLYNAMES) {
snprintf_P(name, sizeof(name), PSTR("%s %s %d"), Settings.friendlyname[0], key?"SW":"BTN", device+1);
} else {
snprintf_P(name, sizeof(name), PSTR("%s %s"), Settings.friendlyname[device], key?"SW":"BTN");
}
GetPowerDevice(value_template, device+1, sizeof(value_template),
key + Settings.flag.device_index_enable);
GetTopic_P(state_topic, CMND, topic, value_template);
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
FindPrefix(state_topic, availability_topic, prefix);
Shorten(&state_topic, prefix);
Shorten(&availability_topic, prefix);
Response_P(HASS_DISCOVER_BUTTON_SWITCH, name, state_topic, Settings.state_text[toggle?2:1], availability_topic);
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
if (strlen(prefix) > 0 ) TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
if (toggle) TryResponseAppend_P(HASS_DISCOVER_BUTTON_SWITCH_TOGGLE);
else TryResponseAppend_P(HASS_DISCOVER_BUTTON_SWITCH_ONOFF, Settings.state_text[0]);
TryResponseAppend_P(PSTR("}"));
}
MqttPublish(stopic, true);
}
void HAssAnnounceSwitches(void)
{
char sw_topic[sizeof(Settings.switch_topic)];
char *tmp = Settings.switch_topic;
Format(sw_topic, tmp, sizeof(sw_topic));
if ((strlen(sw_topic) != 0) && strcmp(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(Settings.mqtt_grptopic, sw_topic))
{
toggle = 0;
}
HAssAnnounceButtonSwitch(switch_index, sw_topic, switch_present, 1, toggle);
}
}
}
void HAssAnnounceButtons(void)
{
char key_topic[sizeof(Settings.button_topic)];
char *tmp = Settings.button_topic;
Format(key_topic, tmp, sizeof(key_topic));
if ((strlen(key_topic) != 0) && strcmp(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(Settings.mqtt_grptopic, key_topic))
{
toggle = 0;
}
HAssAnnounceButtonSwitch(button_index, key_topic, button_present, 0, toggle);
}
}
}
void HAssAnnounceSensor(const char* sensorname, const char* subsensortype)
{
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_%s"), ESP.getChipId(), sensorname, subsensortype);
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(name, sizeof(name), PSTR("%s %s %s"), Settings.friendlyname[0], sensorname, subsensortype);
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR));
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
FindPrefix(state_topic, availability_topic, prefix);
Shorten(&state_topic, prefix);
Shorten(&availability_topic, prefix);
Response_P(HASS_DISCOVER_SENSOR, name, state_topic, availability_topic);
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
if (!strcmp_P(subsensortype, PSTR(D_JSON_TEMPERATURE))) {
TryResponseAppend_P(HASS_DISCOVER_SENSOR_TEMP, TempUnit(), sensorname);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_HUMIDITY))) {
TryResponseAppend_P(HASS_DISCOVER_SENSOR_HUM, sensorname);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_PRESSURE))) {
TryResponseAppend_P(HASS_DISCOVER_SENSOR_PRESS, PressureUnit().c_str(), sensorname);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_TOTAL))
|| !strcmp_P(subsensortype, PSTR(D_JSON_TODAY))
|| !strcmp_P(subsensortype, PSTR(D_JSON_YESTERDAY))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_KWH, sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_POWERUSAGE))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_WATT, sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_VOLTAGE))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_VOLTAGE, sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_CURRENT))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_AMPERE, sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_ILLUMINANCE))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_ILLUMINANCE, sensorname, subsensortype);
}
else {
TryResponseAppend_P(HASS_DISCOVER_SENSOR_ANY, sensorname, subsensortype);
}
TryResponseAppend_P(PSTR("}"));
}
MqttPublish(stopic, true);
}
void HAssAnnounceSensors(void)
{
uint8_t hass_xsns_index = 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[256];
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: 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: failed to parse '%s'"), sensordata);
continue;
}
for (auto subsensor : sensors) {
HAssAnnounceSensor(sensorname, subsensor.key);
}
}
}
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"), Settings.friendlyname[0]);
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_HASS_STATE));
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
FindPrefix(state_topic, availability_topic, prefix);
Shorten(&state_topic, prefix);
Shorten(&availability_topic, prefix);
Response_P(HASS_DISCOVER_SENSOR, 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(),
Settings.friendlyname[0], ModuleName().c_str(), my_version, my_image);
TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
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;
if (strcmp_P(Settings.mqtt_fulltopic, PSTR("%topic%/%prefix%/"))) {
strncpy_P(Settings.mqtt_fulltopic, PSTR("%topic%/%prefix%/"), sizeof(Settings.mqtt_fulltopic));
restart_flag = 2;
return;
}
}
if (Settings.flag.hass_discovery || (1 == hass_mode)) {
HAssAnnounceRelayLight();
HAssAnnounceButtons();
HAssAnnounceSwitches();
HAssAnnounceSensors();
HAssAnnounceStatusSensor();
}
}
void HAssDiscover(void)
{
hass_mode = 1;
hass_init_step = 1;
}
bool Xdrv12(uint8_t function)
{
bool result = false;
if (Settings.flag.mqtt_enabled) {
switch (function) {
case FUNC_MQTT_INIT:
hass_mode = 0;
hass_init_step = 2;
break;
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;
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_13_display.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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];
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 '~'
void decode_te(char *line) {
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));
} else {
if (strlen(cp)<2) {
return;
}
sbuf[0]=*(cp);
sbuf[1]=*(cp+1);
sbuf[2]=0;
*line=strtol(sbuf,0,16);
memmove(cp,cp+2,strlen(cp)-1);
}
}
line++;
}
}
#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:
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());
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"), Settings.sta_ssid[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" D_UNIT_PRESSURE), value);
}
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)
{
# 1133 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_13_display.ino"
const char *tempunit;
String jsonStr = json;
StaticJsonBuffer<1024> jsonBuf;
JsonObject &root = jsonBuf.parseObject(jsonStr);
if (root.success()) {
tempunit = root[D_JSON_TEMPERATURE_UNIT];
if (tempunit) {
snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%s"), tempunit);
}
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, Settings.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, Settings.mqtt_prefix[2], 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/"), Settings.mqtt_prefix[2]);
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);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DSP: Power %d"), disp_power);
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) {
# 1416 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_14_mp3.ino"
# 64 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_15_pca9685.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_15_pca9685.ino"
#ifdef USE_I2C
#ifdef USE_PCA9685
#define XDRV_15 15
#define PCA9685_REG_MODE1 0x00
#define PCA9685_REG_LED0_ON_L 0x06
#define PCA9685_REG_PRE_SCALE 0xFE
#ifndef USE_PCA9685_FREQ
#define USE_PCA9685_FREQ 50
#endif
uint8_t pca9685_detected = 0;
uint16_t pca9685_freq = USE_PCA9685_FREQ;
uint16_t pca9685_pin_pwm_value[16];
void PCA9685_Detect(void)
{
if (pca9685_detected) { 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 = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "PCA9685", USE_PCA9685_ADDR);
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) {
if (0 == pca9685_detected) { return; }
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) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
}
bool Xdrv15(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_EVERY_SECOND:
PCA9685_Detect();
if (tele_period == 0) {
PCA9685_OutputTelemetry(true);
}
break;
case FUNC_COMMAND_DRIVER:
if (XDRV_15 == XdrvMailbox.index) {
result = PCA9685_Command();
}
break;
default:
break;
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_16_tuyamcu.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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_TYPE_BOOL 0x01
#define TUYA_TYPE_VALUE 0x02
#define TUYA_BUFFER_SIZE 256
#include <TasmotaSerial.h>
TasmotaSerial *TuyaSerial = nullptr;
struct TUYA {
uint8_t new_dim = 0;
bool ignore_dim = false;
uint8_t cmd_status = 0;
uint8_t cmd_checksum = 0;
uint8_t data_len = 0;
int8_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;
} 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_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_LAST = 255
};
const char kTuyaCommand[] PROGMEM = "|"
D_CMND_TUYA_MCU;
void (* const TuyaCommand[])(void) PROGMEM = {
&CmndTuyaMcu
};
# 108 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_16_tuyamcu.ino"
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("["));
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);
}
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:
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));
}
bool TuyaSetPower(void)
{
bool status = false;
uint8_t rpower = XdrvMailbox.index;
int16_t source = XdrvMailbox.payload;
if (source != SRC_SWITCH && TuyaSerial) {
TuyaSendBool(active_device, 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(uint8_t duty)
{
uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_DIMMER);
if (duty > 0 && !Tuya.ignore_dim && TuyaSerial && dpid > 0) {
if (Settings.flag3.tuya_dimmer_min_limit) {
if (duty < 25) { duty = 25; }
}
duty = changeUIntScale(duty, 0, 255, 0, Settings.param[P_TUYA_DIMMER_MAX]);
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.param[P_TUYA_DIMMER_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 TuyaPacketProcess(void)
{
char scmnd[20];
uint8_t fnId = TUYA_MCU_FUNC_NONE;
switch (Tuya.buffer[3]) {
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:
fnId = TuyaGetFuncId(Tuya.buffer[6]);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: FnId=%d is set for dpId=%d"), fnId, Tuya.buffer[6]);
if (Tuya.buffer[5] == 5) {
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[10]?"On":"Off",bitRead(power, fnId - TUYA_MCU_FUNC_REL1)?"On":"Off");
if ((power || Settings.light_dimmer > 0) && (Tuya.buffer[10] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1))) {
ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[10], 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[10]?"Off":"On",bitRead(power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1?"Off":"On");
if (Tuya.buffer[10] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1) {
ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[10] ^ 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[10], SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1));
if (SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1) != Tuya.buffer[10]) {
SwitchSetVirtual(fnId - TUYA_MCU_FUNC_SWT1, Tuya.buffer[10]);
SwitchHandler(1);
}
}
}
else if (Tuya.buffer[5] == 8) {
bool tuya_energy_enabled = (XNRG_16 == energy_flg);
if (fnId == TUYA_MCU_FUNC_DIMMER) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Dim State=%d"), Tuya.buffer[13]);
Tuya.new_dim = changeUIntScale((uint8_t) Tuya.buffer[13], 0, Settings.param[P_TUYA_DIMMER_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)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Voltage=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
} else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_CURRENT) {
Energy.current[0] = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 1000;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Current=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
} else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_POWER) {
Energy.active_power[0] = (float)(Tuya.buffer[12] << 8 | Tuya.buffer[13]) / 10;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Active_Power=%d"), Tuya.buffer[6], (Tuya.buffer[12] << 8 | Tuya.buffer[13]));
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
}
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 = WifiState();
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;
devices_present--;
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;
}
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_MCU_CONF);
}
}
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;
snprintf_P(log_data, sizeof(log_data), PSTR("TYA: RX Packet: \""));
for (uint32_t i = 0; i < Tuya.byte_counter; i++) {
snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, Tuya.buffer[i]);
}
snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data);
AddLog(LOG_LEVEL_DEBUG);
TuyaPacketProcess();
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;
}
void TuyaSetWifiLed(void)
{
uint8_t wifi_state = 0x02;
switch(WifiState()){
case WIFI_SMARTCONFIG:
wifi_state = 0x00;
break;
case WIFI_MANAGER:
case WIFI_WPSCONFIG:
wifi_state = 0x01;
break;
case WIFI_RESTART:
wifi_state = 0x03;
break;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Set WiFi LED %d (%d)"), wifi_state, WifiState());
TuyaSendCmd(TUYA_CMD_WIFI_STATE, &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_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 != WifiState()) { TuyaSetWifiLed(); }
Tuya.heartbeat_timer++;
if (Tuya.heartbeat_timer > 10) {
Tuya.heartbeat_timer = 0;
TuyaSendCmd(TUYA_CMD_HEARTBEAT);
}
break;
case FUNC_SET_CHANNELS:
result = TuyaSetChannels();
break;
case FUNC_COMMAND:
result = DecodeCommand(kTuyaCommand, TuyaCommand);
break;
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_17_rcswitch.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) {
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];
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_18_armtronix_dimmers.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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)
{
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_SMARTCONFIG:
wifi_state = 0x00;
break;
case WIFI_MANAGER:
case WIFI_WPSCONFIG:
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_19_ps16dz_dimmer.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_19_ps16dz_dimmer.ino"
#ifdef USE_LIGHT
#ifdef USE_PS_16_DZ
#define XDRV_19 19
#define PS16DZ_BUFFER_SIZE 140
#define PS16DZ_SONOFF_L1_MODE_COLORFUL 1
#define PS16DZ_SONOFF_L1_MODE_COLORFUL_GRADIENT 2
#define PS16DZ_SONOFF_L1_MODE_COLORFUL_BREATH 3
#define PS16DZ_SONOFF_L1_MODE_DIY_GRADIENT 4
#define PS16DZ_SONOFF_L1_MODE_DIY_PULSE 5
#define PS16DZ_SONOFF_L1_MODE_DIY_BREATH 6
#define PS16DZ_SONOFF_L1_MODE_DIY_STROBE 7
#define PS16DZ_SONOFF_L1_MODE_RGB_GRADIENT 8
#define PS16DZ_SONOFF_L1_MODE_RGB_PULSE 9
#define PS16DZ_SONOFF_L1_MODE_RGB_BREATH 10
#define PS16DZ_SONOFF_L1_MODE_RGB_STROBE 11
#define PS16DZ_SONOFF_L1_MODE_SYNC_TO_MUSIC 12
#include <TasmotaSerial.h>
TasmotaSerial *PS16DZSerial = nullptr;
struct PS16DZ {
char *tx_buffer = nullptr;
char *rx_buffer = nullptr;
int byte_counter = 0;
uint8_t color[3];
uint8_t dimmer = 0;
bool supports_color = false;
bool switch_state = false;
} Ps16dz;
void PS16DZSerialSendTxBuffer(void)
{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Send %s"), Ps16dz.tx_buffer);
PS16DZSerial->print(Ps16dz.tx_buffer);
PS16DZSerial->write(0x1B);
PS16DZSerial->flush();
}
void PS16DZSerialSendOkCommand(void)
{
snprintf_P(Ps16dz.tx_buffer, PS16DZ_BUFFER_SIZE, PSTR("AT+SEND=ok"));
PS16DZSerialSendTxBuffer();
}
void PS16DZSerialSendUpdateCommand(void)
{
uint8_t light_state_dimmer = light_state.getDimmer();
light_state_dimmer = (light_state_dimmer < 10) ? 10 : light_state_dimmer;
snprintf_P(Ps16dz.tx_buffer, PS16DZ_BUFFER_SIZE, PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"bright\":%d"),
LocalTime(), millis()%1000, power?"on":"off", light_state_dimmer);
if (Ps16dz.supports_color) {
uint8_t light_state_rgb[3];
light_state.getRGB(&light_state_rgb[0], &light_state_rgb[1], &light_state_rgb[2]);
snprintf_P(Ps16dz.tx_buffer, PS16DZ_BUFFER_SIZE, PSTR("%s,\"mode\":%d,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"light_types\":1"),
Ps16dz.tx_buffer, PS16DZ_SONOFF_L1_MODE_COLORFUL, light_state_rgb[0], light_state_rgb[1], light_state_rgb[2]);
}
PS16DZSerialSendTxBuffer();
}
bool PS16DZSerialSendUpdateCommandIfRequired(void)
{
if (!PS16DZSerial) { return true; }
bool is_switch_change = (XdrvMailbox.payload != SRC_SWITCH);
bool is_brightness_change = (light_state.getDimmer() != Ps16dz.dimmer);
uint8_t light_state_rgb[3];
light_state.getRGB(&light_state_rgb[0], &light_state_rgb[1], &light_state_rgb[2]);
bool is_color_change = (Ps16dz.supports_color && (memcmp(light_state_rgb, Ps16dz.color, 3) != 0));
if (is_switch_change || is_brightness_change || is_color_change) {
PS16DZSerialSendUpdateCommand();
}
return true;
}
bool PS16DZModuleSelected(void)
{
switch (my_module_type)
{
case PS_16_DZ:
light_type = LT_SERIAL1;
break;
case SONOFF_L1:
light_type = LT_PWM3;
break;
}
return true;
}
void PS16DZInit(void)
{
Ps16dz.supports_color = (light_state.getColorMode() == LCM_RGB);
Ps16dz.tx_buffer = (char*)(malloc(PS16DZ_BUFFER_SIZE));
if (Ps16dz.tx_buffer != nullptr) {
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(); }
}
}
}
}
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;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Received %s"), Ps16dz.rx_buffer);
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 color_updated[3] = { false, false, false };
memcpy(Ps16dz.color, Settings.light_color, 3);
bool is_switch_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, "\"switch\"", 8)) {
Ps16dz.switch_state = !strncmp(token3, "\"on\"", 4) ? true : false;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Switch %d"), Ps16dz.switch_state);
is_switch_change = (Ps16dz.switch_state != power);
if (is_switch_change) {
ExecuteCommandPower(1, Ps16dz.switch_state, SRC_SWITCH);
}
}
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);
Ps16dz.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) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Color R:%d, G:%d, B:%d"), Ps16dz.color[0], Ps16dz.color[1], Ps16dz.color[2]);
is_color_change = (memcmp(Ps16dz.color, Settings.light_color, 3) != 0);
}
if (power && is_color_change) {
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_COLOR "2 %02x%02x%02x"), Ps16dz.color[0], Ps16dz.color[1], Ps16dz.color[2]);
ExecuteCommand(scmnd, SRC_SWITCH);
}
}
else if (!strncmp(token2, "\"bright\"", 8)) {
Ps16dz.dimmer = atoi(token3);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Brightness %d"), Ps16dz.dimmer);
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)) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Sequence %s"), token3);
}
token = strtok_r(nullptr, ",", &end_str);
}
if (!is_color_change && !is_brightness_change) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PSZ: Update"));
PS16DZSerialSendOkCommand();
}
}
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 Xdrv19(uint8_t function)
{
bool result = false;
if ((PS_16_DZ == my_module_type) || (SONOFF_L1 == my_module_type)) {
switch (function) {
case FUNC_LOOP:
if (PS16DZSerial) { PS16DZSerialInput(); }
break;
case FUNC_MODULE_INIT:
result = PS16DZModuleSelected();
break;
case FUNC_INIT:
PS16DZInit();
break;
case FUNC_SET_DEVICE_POWER:
case FUNC_SET_CHANNELS:
result = PS16DZSerialSendUpdateCommandIfRequired();
break;
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_20_hue.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_20_hue.ino"
#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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.17.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));
}
AddLog_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>"
"<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;
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);
}
void HueLightStatus2(uint8_t device, String *response)
{
*response += FPSTR(HUE_LIGHTS_STATUS_JSON2);
if (device <= MAX_FRIENDLYNAMES) {
response->replace("{j1", Settings.friendlyname[device-1]);
} else {
char fname[33];
strcpy(fname, Settings.friendlyname[MAX_FRIENDLYNAMES-1]);
uint32_t fname_len = strlen(fname);
if (fname_len >= 33-3) {
fname[33-3] = 0x00;
fname_len = 33-3;
}
fname[fname_len++] = '-';
fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
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\":{\"");
for (uint32_t i = 1; i <= maxhue; i++) {
response += EncodeLightId(i);
response += F("\":{\"state\":");
HueLightStatus1(i, &response);
HueLightStatus2(i, &response);
if (i < maxhue) {
response += ",\"";
}
}
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 = "{\"";
for (uint32_t i = 1; i <= maxhue; i++) {
response += EncodeLightId(i);
response += F("\":{\"state\":");
HueLightStatus1(i, &response);
HueLightStatus2(i, &response);
if (i < maxhue) {
response += ",\"";
}
}
response += "}";
}
else if (path->endsWith("/state")) {
path->remove(0,8);
path->remove(path->indexOf("/state"));
device = DecodeLightId(atoi(path->c_str()));
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");
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;
}
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) {
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()));
if ((device < 1) || (device > maxhue)) {
device = 1;
}
response += F("{\"state\":");
HueLightStatus1(device, &response);
HueLightStatus2(device, &response);
}
else {
response = "{}";
code = 406;
}
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)
{
# 723 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
if (devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
switch (function) {
case FUNC_WEB_ADD_HANDLER:
WebServer->on("/description.xml", HandleUpnpSetupHue);
break;
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_21_wemo.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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));
}
AddLog_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", Settings.friendlyname[0]);
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_22_sonoff_ifan.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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()
{
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(1);
#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(3);
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) {
Settings.flag.mqtt_serial = 0;
baudrate = 9600;
SetSeriallog(LOG_LEVEL_NONE);
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_0_constants.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_0_constants.ino"
#ifdef USE_ZIGBEE
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
};
# 208 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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,
# 260 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_0_constants.ino"
};
# 273 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
};
enum class ZclGlobalCommandId : uint8_t {
};
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_4_converters.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_4_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 = 0, uint16_t groupid = 0):
_cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq),
_payload(buf_len ? buf_len : 250),
_cluster_id(clusterid), _group_id(groupid)
{
_frame_control.d8 = frame_control;
_payload.addBuffer(buf, buf_len);
};
void publishMQTTReceived(uint16_t groupid, uint16_t clusterid, Z_ShortAddress srcaddr,
uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
uint32_t timestamp) {
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_ZIGBEEZCLRECEIVED "\":{"
"\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\","
"\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d,"
"\"linkquality\":%d," "\"securityuse\":%d," "\"seqnumber\":%d,"
"\"timestamp\":%d,"
"\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d,"
"\"cmdid\":\"0x%02X\",\"payload\":\"%s\""),
groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp,
_frame_control, _manuf_code, _transact_seq, _cmd_id,
hex_char);
ResponseJsonEnd();
ResponseJsonEnd();
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLSENT));
XdrvRulesProcess();
}
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) {
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);
return zcl_frame;
}
bool isClusterSpecificCommand(void) {
return _frame_control.b.frame_type & 1;
}
void parseRawAttributes(JsonObject& json, uint8_t offset = 0);
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
void postProcessAttributes(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;
}
const SBuffer &getPayload(void) const {
return _payload;
}
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;
};
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:
i += attrtype - 0x1F;
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:
i += attrtype - 0x27;
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 ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
else {
for (uint32_t j = 0; j < len; j++) {
if (0x00 == buf.get8(i+j)) {
parse_as_string = false;
break;
}
}
}
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:
i++;
break;
case 0x18:
i++;
break;
case 0x19:
i += 2;
break;
case 0x1B:
i += 4;
break;
case 0x30:
case 0x31:
i += attrtype - 0x2F;
break;
case 0x39:
i += 4;
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 0x09:
case 0x0A:
case 0x0B:
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:
i += 8;
break;
}
return i - offset;
}
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
uint32_t i = offset;
uint32_t len = _payload.len();
uint32_t attrid = _cluster_id << 16;
while (len + offset - i >= 3) {
attrid = (attrid & 0xFFFF0000) | _payload.get16(i);
i += 2;
char shortaddr[12];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%08X"), attrid);
if (0x0000FF01 == attrid) {
if (0x42 == _payload.get8(i)) {
_payload.set8(i, 0x41);
}
}
i += parseSingleAttribute(json, shortaddr, _payload, i, len);
}
}
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
uint32_t i = offset;
uint32_t len = _payload.len();
uint32_t attrid = _cluster_id << 8 | _cmd_id;
char attrid_str[12];
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("0x%06X"), attrid);
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;
}
#define ZCL_MODELID "0x00000005"
#define ZCL_TEMPERATURE "0x04020000"
#define ZCL_PRESSURE "0x04030000"
#define ZCL_PRESSURE_SCALED "0x04030010"
#define ZCL_PRESSURE_SCALE "0x04030014"
#define ZCL_HUMIDITY "0x04050000"
#define ZCL_LUMI_WEATHER "0x0000FF01"
#define ZCL_OO_OFF "0x000600"
#define ZCL_OO_ON "0x000601"
#define ZCL_COLORTEMP_MOVE "0x03000A"
#define ZCL_LC_MOVE "0x000800"
#define ZCL_LC_MOVE_1 "0x000801"
#define ZCL_LC_STEP "0x000802"
#define ZCL_LC_STOP "0x000803"
#define ZCL_LC_MOVE_WOO "0x000804"
#define ZCL_LC_MOVE_1_WOO "0x000805"
#define ZCL_LC_STEP_WOO "0x000806"
#define ZCL_LC_STOP_WOO "0x000807"
void ZCLFrame::postProcessAttributes(JsonObject& json) {
const __FlashStringHelper *key;
key = F(ZCL_MODELID);
if (json.containsKey(key)) {
json[F(D_JSON_MODEL D_JSON_ID)] = json[key];
json.remove(key);
}
key = F(ZCL_TEMPERATURE);
if (json.containsKey(key)) {
int32_t temperature = json[key];
json.remove(key);
json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
}
key = F(ZCL_PRESSURE);
if (json.containsKey(key)) {
json[F(D_JSON_PRESSURE)] = json[key];
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE);
json.remove(key);
}
json.remove(F(ZCL_PRESSURE_SCALE));
json.remove(F(ZCL_PRESSURE_SCALED));
key = F(ZCL_HUMIDITY);
if (json.containsKey(key)) {
uint32_t humidity = json[key];
json.remove(key);
json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
}
key = F(ZCL_OO_OFF);
if (json.containsKey(key)) {
json.remove(key);
json[F(D_CMND_POWER)] = F("Off");
}
key = F(ZCL_OO_ON);
if (json.containsKey(key)) {
json.remove(key);
json[F(D_CMND_POWER)] = F("On");
}
key = F(ZCL_COLORTEMP_MOVE);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint16_t color_temp = buf2.get16(0);
uint16_t transition_time = buf2.get16(2);
json.remove(key);
json[F("ColorTemp")] = color_temp;
json[F("TransitionTime")] = transition_time / 10.0f;
}
key = F(ZCL_LC_MOVE_WOO);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t level = buf2.get8(0);
uint16_t transition_time = buf2.get16(1);
json.remove(key);
json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100);
json[F("TransitionTime")] = transition_time / 10.0f;
if (0 == level) {
json[F(D_CMND_POWER)] = F("Off");
} else {
json[F(D_CMND_POWER)] = F("On");
}
}
key = F(ZCL_LC_MOVE);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t level = buf2.get8(0);
uint16_t transition_time = buf2.get16(1);
json.remove(key);
json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100);
json[F("TransitionTime")] = transition_time / 10.0f;
}
key = F(ZCL_LC_MOVE_1);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t move_mode = buf2.get8(0);
uint8_t move_rate = buf2.get8(1);
json.remove(key);
json[F("Move")] = move_mode ? F("Down") : F("Up");
json[F("Rate")] = move_rate;
}
key = F(ZCL_LC_MOVE_1_WOO);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t move_mode = buf2.get8(0);
uint8_t move_rate = buf2.get8(1);
json.remove(key);
json[F("Move")] = move_mode ? F("Down") : F("Up");
json[F("Rate")] = move_rate;
if (0 == move_mode) {
json[F(D_CMND_POWER)] = F("On");
}
}
key = F(ZCL_LC_STEP);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t step_mode = buf2.get8(0);
uint8_t step_size = buf2.get8(1);
uint16_t transition_time = buf2.get16(2);
json.remove(key);
json[F("Step")] = step_mode ? F("Down") : F("Up");
json[F("StepSize")] = step_size;
json[F("TransitionTime")] = transition_time / 10.0f;
}
key = F(ZCL_LC_STEP_WOO);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint8_t step_mode = buf2.get8(0);
uint8_t step_size = buf2.get8(1);
uint16_t transition_time = buf2.get16(2);
json.remove(key);
json[F("Step")] = step_mode ? F("Down") : F("Up");
json[F("StepSize")] = step_size;
json[F("TransitionTime")] = transition_time / 10.0f;
if (0 == step_mode) {
json[F(D_CMND_POWER)] = F("On");
}
}
key = F(ZCL_LC_STOP);
if (json.containsKey(key)) {
json.remove(key);
json[F("Stop")] = 1;
}
key = F(ZCL_LC_STOP_WOO);
if (json.containsKey(key)) {
json.remove(key);
json[F("Stop")] = 1;
}
key = F(ZCL_LUMI_WEATHER);
if (json.containsKey(key)) {
String hex = json[key];
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
DynamicJsonBuffer jsonBuffer;
JsonObject& json_lumi = jsonBuffer.createObject();
uint32_t i = 0;
uint32_t len = buf2.len();
char shortaddr[8];
while (len - i >= 2) {
uint8_t attrid = buf2.get8(i++);
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
i += parseSingleAttribute(json_lumi, shortaddr, buf2, i, len);
}
if (json_lumi.containsKey("0x64")) {
int32_t temperature = json_lumi["0x64"];
json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
}
if (json_lumi.containsKey("0x65")) {
uint32_t humidity = json_lumi["0x65"];
json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
}
if (json_lumi.containsKey("0x66")) {
int32_t pressure = json_lumi["0x66"];
json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE);
}
if (json_lumi.containsKey("0x01")) {
uint32_t voltage = json_lumi["0x01"];
json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
json[F("Battery")] = toPercentageCR2032(voltage);
}
json.remove(key);
}
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_9_impl.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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_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_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;
#ifdef Z_USE_SOFTWARE_SERIAL
#include <SoftwareSerial.h>
SoftwareSerial *ZigbeeSerial = nullptr;
#else
#include <TasmotaSerial.h>
TasmotaSerial *ZigbeeSerial = nullptr;
#endif
const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN;
void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin };
typedef int32_t (*ZB_Func)(uint8_t value);
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, 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_STATUS,
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_STATUS(x,m) { .i = { ZGB_INSTR_MQTT_STATUS, (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 )
# 287 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_9_impl.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_NODEDESCREQ, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP)
# 305 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_9_impl.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_XX, 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 )
ZBM(ZBR_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG)
ZBM(ZBR_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND)
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(15000)
ZI_ON_ERROR_GOTO(50)
ZI_MQTT_STATUS(ZIGBEE_STATUS_BOOT, "Booting")
ZI_SEND(ZBS_RESET)
ZI_WAIT_RECV(5000, ZBR_RESET)
ZI_LOG(LOG_LEVEL_INFO, "ZIG: 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_STATUS(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(5000, 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_NODEDESCREQ)
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_STATUS(ZIGBEE_STATUS_OK, "Started")
ZI_LOG(LOG_LEVEL_INFO, "ZIG: zigbee device ready, listening...")
ZI_CALL(&Z_State_Ready, 1)
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
ZI_WAIT_FOREVER()
ZI_GOTO(ZIGBEE_LABEL_READY)
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
ZI_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_CLOSE, "Disable Pairing mode")
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_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_60, "Enable Pairing mode for 60 seconds")
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_MQTT_STATUS(ZIGBEE_STATUS_PERMITJOIN_OPEN_XX, "Enable Pairing mode until next boot")
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
ZI_LABEL(50)
ZI_MQTT_STATUS(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_STATUS(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
ZI_GOTO(ZIGBEE_LABEL_ABORT)
ZI_LABEL(ZIGBEE_LABEL_ABORT)
ZI_MQTT_STATUS(ZIGBEE_STATUS_ABORT, "Abort")
ZI_LOG(LOG_LEVEL_ERROR, "ZIG: Abort")
ZI_STOP(ZIGBEE_LABEL_ABORT)
};
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);
char hex[20];
Uint64toHex(long_adr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"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_STATUS));
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;
}
}
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
# 543 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_9_impl.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_STATUS "\":{"
"\"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_STATUS));
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_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);
char hex[20];
Uint64toHex(ieeeAddr, hex, 64);
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{"
"\"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_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
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);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp);
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
DynamicJsonBuffer jsonBuffer;
JsonObject& json_root = jsonBuffer.createObject();
JsonObject& json = json_root.createNestedObject(shortaddr);
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseRawAttributes(json);
} else if (zcl_received.isClusterSpecificCommand()) {
zcl_received.parseClusterSpecificCommand(json);
}
zcl_received.postProcessAttributes(json);
String msg("");
msg.reserve(100);
json_root.printTo(msg);
Response_P(PSTR("%s"), msg.c_str());
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCLRECEIVED));
XdrvRulesProcess();
return -1;
}
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Z_Recv_Default"));
if (zigbee.init_phase) {
return -1;
} else {
if (Z_ReceiveMatchPrefix(buf, ZBR_AF_INCOMING_MESSAGE)) {
return Z_ReceiveAfIncomingMessage(res, buf);
} else if (Z_ReceiveMatchPrefix(buf, ZBR_END_DEVICE_ANNCE_IND)) {
return Z_ReceiveEndDeviceAnnonce(res, buf);
}
return -1;
}
}
int32_t Z_State_Ready(uint8_t value) {
zigbee.init_phase = false;
return 0;
}
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("ZIG: 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("ZIG: 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("ZIG: 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("ZIG: Invalid pc: %d, aborting"), zigbee.pc);
zigbee.pc = -1;
}
if (zigbee.pc < 0) {
zigbee.state_machine = false;
return;
}
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZIG: Executing instruction pc=%d"), zigbee.pc);
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("ZIG: 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_STATUS:
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATUS "\":{\"Status\":%d,\"Message\":\"%s\"}}"),
cur_d8, (char*) cur_ptr1);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATUS));
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;
}
}
}
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;
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: ZigbeeProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match);
}
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, PSTR("ZIG: ZigbeeProcessInput: 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;
# 932 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_23_zigbee_9_impl.ino"
while (ZigbeeSerial->available()) {
yield();
uint8_t zigbee_in_byte = ZigbeeSerial->read();
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZigbeeInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len());
if (0 == zigbee_buffer->len()) {
zigbee_frame_len = 5;
fcs = ZIGBEE_SOF;
}
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInput 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));
#ifndef Z_USE_SOFTWARE_SERIAL
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: Bytes follor_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
#endif
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 {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received correct frame %s"), hex_char);
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);
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
XdrvRulesProcess();
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("Zigbee: GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]);
#ifdef Z_USE_SOFTWARE_SERIAL
ZigbeeSerial = new SoftwareSerial();
ZigbeeSerial->begin(115200, pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], SWSERIAL_8N1, false, 256);
ZigbeeSerial->enableIntTx(false);
zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE);
#else
ZigbeeSerial = new TasmotaSerial(pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], 0, 0, 256);
ZigbeeSerial->begin(115200);
if (ZigbeeSerial->hardwareSerial()) {
ClaimSerial();
zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer), serial_in_buffer);
} else {
zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE);
}
#endif
zigbee.active = true;
zigbee.init_phase = true;
zigbee.state_machine = true;
ZigbeeSerial->flush();
}
}
void CmndZigbeeZNPSend(void)
{
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 > 0) {
char stemp[3];
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, nullptr, 16);
buf.add8(code);
size -= 2;
codes += 2;
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
}
ResponseCmndDone();
}
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);
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF);
ZigbeeSerial->write(data_len);
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len);
for (uint32_t i = 0; i < len; i++) {
uint8_t b = pgm_read_byte(msg + i);
ZigbeeSerial->write(b);
fcs ^= b;
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b);
}
ZigbeeSerial->write(fcs);
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
char hex_char[(len * 2) + 2];
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPSENT "\":\"%s\"}"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPSENT));
XdrvRulesProcess();
}
void CmndZigbeePermitJoin(void)
{
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();
}
bool Xdrv23(uint8_t function)
{
bool result = false;
if (zigbee.active) {
switch (function) {
case FUNC_LOOP:
if (ZigbeeSerial) { ZigbeeInput(); }
if (zigbee.state_machine) {
ZigbeeStateMachine_Run();
}
break;
case FUNC_PRE_INIT:
ZigbeeInit();
break;
case FUNC_COMMAND:
result = DecodeCommand(kZigbeeCommands, ZigbeeCommand);
break;
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_24_buzzer.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_24_buzzer.ino"
#ifdef USE_BUZZER
#define XDRV_24 24
struct BUZZER {
uint32_t tune = 0;
bool active = true;
bool enable = false;
uint8_t inverted = 0;
uint8_t count = 0;
uint8_t set[2];
uint8_t duration;
uint8_t state = 0;
} Buzzer;
void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune)
{
Buzzer.set[0] = off;
Buzzer.set[1] = on;
Buzzer.duration = 1;
Buzzer.tune = 0;
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 <<= 1;
Buzzer.tune |= tune1 & 1;
tune1 >>= 1;
}
}
Buzzer.count = 1;
} else {
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 = true;
}
void BuzzerBeep(uint32_t count) {
BuzzerBeep(count, 1, 1, 0);
}
void BuzzerEnabledBeep(uint32_t count)
{
if (Settings.flag3.buzzer_enable) {
BuzzerBeep(count);
}
}
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);
digitalWrite(pin[GPIO_BUZZER], Buzzer.inverted);
} else {
Buzzer.active = false;
}
}
void BuzzerEvery100mSec(void)
{
if (Buzzer.enable) {
if (Buzzer.count) {
if (Buzzer.duration) {
Buzzer.duration--;
if (!Buzzer.duration) {
if (Buzzer.tune) {
Buzzer.state = Buzzer.tune & 1;
Buzzer.tune >>= 1;
} else {
Buzzer.count--;
Buzzer.state = Buzzer.count & 1;
}
Buzzer.duration = Buzzer.set[Buzzer.state];
}
}
digitalWrite(pin[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)
{
# 147 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_24_buzzer.ino"
if (XdrvMailbox.data_len > 0) {
char *p;
uint32_t i = 0;
uint32_t parm[4] = { 0 };
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 4; str = strtok_r(nullptr, ", ", &p)) {
parm[i] = strtoul(str, nullptr, 0);
i++;
}
for (uint32_t i = 0; i < 3; i++) {
if (parm[i] < 1) { parm[i] = 1; }
}
BuzzerBeep(parm[0], parm[1], parm[2], parm[3]);
} 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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_25_A4988_Stepper.ino"
# 21 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_25_A4988_Stepper.ino"
#ifdef USE_A4988_Stepper
#include <A4988_Stepper.h>
#define XDRV_25 25
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_99_debug.ino"
# 22 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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"
const char kDebugCommands[] PROGMEM = "|"
D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|"
#ifdef USE_DEBUG_SETTING_NAMES
D_CMND_CFGSHOW "|"
#endif
#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 ;
void (* const DebugCommand[])(void) PROGMEM = {
&CmndCfgDump, &CmndCfgPeek, &CmndCfgPoke,
#ifdef USE_DEBUG_SETTING_NAMES
&CmndCfgShow,
#endif
#ifdef USE_WEBSERVER
&CmndCfgXor,
#endif
&CmndCpuCheck,
#ifdef DEBUG_THEO
&CmndException,
#endif
&CmndFlashDump, &CmndFlashMode, &CmndFreemem, &CmndHelp, &CmndRtcDump, &CmndSetSensor };
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)
{
# 141 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_99_debug.ino"
if (1 == type) {
char svalue[10];
snprintf_P(svalue, sizeof(svalue), PSTR("%s"), 7);
}
# 155 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
# 242 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
#ifdef USE_DEBUG_SETTING_NAMES
void DebugCfgShow(uint8_t more)
{
uint8_t *SetAddr;
SetAddr = (uint8_t *)&Settings;
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Hostname (%d) [%s]"), (uint8_t *)&Settings.hostname - SetAddr, sizeof(Settings.hostname)-1, Settings.hostname);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: SSids (%d) [%s], [%s]"), (uint8_t *)&Settings.sta_ssid - SetAddr, sizeof(Settings.sta_ssid[0])-1, Settings.sta_ssid[0], Settings.sta_ssid[1]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Friendlynames (%d) [%s], [%s], [%s], [%s]"), (uint8_t *)&Settings.friendlyname - SetAddr, sizeof(Settings.friendlyname[0])-1, Settings.friendlyname[0], Settings.friendlyname[1], Settings.friendlyname[2], Settings.friendlyname[3]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: OTA Url (%d) [%s]"), (uint8_t *)&Settings.ota_url - SetAddr, sizeof(Settings.ota_url)-1, Settings.ota_url);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: StateText (%d) [%s], [%s], [%s], [%s]"), (uint8_t *)&Settings.state_text - SetAddr, sizeof(Settings.state_text[0])-1, Settings.state_text[0], Settings.state_text[1], Settings.state_text[2], Settings.state_text[3]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Syslog Host (%d) [%s]"), (uint8_t *)&Settings.syslog_host - SetAddr, sizeof(Settings.syslog_host)-1, Settings.syslog_host);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: NTP Servers (%d) [%s], [%s], [%s]"), (uint8_t *)&Settings.ntp_server - SetAddr, sizeof(Settings.ntp_server[0])-1, Settings.ntp_server[0], Settings.ntp_server[1], Settings.ntp_server[2]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Host (%d) [%s]"), (uint8_t *)&Settings.mqtt_host - SetAddr, sizeof(Settings.mqtt_host)-1, Settings.mqtt_host);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Client (%d) [%s]"), (uint8_t *)&Settings.mqtt_client - SetAddr, sizeof(Settings.mqtt_client)-1, Settings.mqtt_client);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT User (%d) [%s]"), (uint8_t *)&Settings.mqtt_user - SetAddr, sizeof(Settings.mqtt_user)-1, Settings.mqtt_user);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT FullTopic (%d) [%s]"), (uint8_t *)&Settings.mqtt_fulltopic - SetAddr, sizeof(Settings.mqtt_fulltopic)-1, Settings.mqtt_fulltopic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Topic (%d) [%s]"), (uint8_t *)&Settings.mqtt_topic - SetAddr, sizeof(Settings.mqtt_topic)-1, Settings.mqtt_topic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT GroupTopic (%d) [%s]"), (uint8_t *)&Settings.mqtt_grptopic - SetAddr, sizeof(Settings.mqtt_grptopic)-1, Settings.mqtt_grptopic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT ButtonTopic (%d) [%s]"), (uint8_t *)&Settings.button_topic - SetAddr, sizeof(Settings.button_topic)-1, Settings.button_topic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT SwitchTopic (%d) [%s]"), (uint8_t *)&Settings.switch_topic - SetAddr, sizeof(Settings.switch_topic)-1, Settings.switch_topic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Prefixes (%d) [%s], [%s], [%s]"), (uint8_t *)&Settings.mqtt_prefix - SetAddr, sizeof(Settings.mqtt_prefix[0])-1, Settings.mqtt_prefix[0], Settings.mqtt_prefix[1], Settings.mqtt_prefix[2]);
if (17 == more) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: AP Passwords (%d) [%s], [%s]"), (uint8_t *)&Settings.sta_pwd - SetAddr, sizeof(Settings.sta_pwd[0])-1, Settings.sta_pwd[0], Settings.sta_pwd[1]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Password (%d) [%s]"), (uint8_t *)&Settings.mqtt_pwd - SetAddr, sizeof(Settings.mqtt_pwd)-1, Settings.mqtt_pwd);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Web Password (%d) [%s]"), (uint8_t *)&Settings.web_password - SetAddr, sizeof(Settings.web_password)-1, Settings.web_password);
}
}
#endif
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_DEBUG_SETTING_NAMES
void CmndCfgShow(void)
{
DebugCfgShow(XdrvMailbox.payload);
ResponseCmndDone();
}
#endif
#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();
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdrv_interface.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 XdrvMqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uint16_t sdataBuf)
{
XdrvMailbox.index = stopicBuf;
XdrvMailbox.data_len = sdataBuf;
XdrvMailbox.topic = topicBuf;
XdrvMailbox.data = dataBuf;
return XdrvCall(FUNC_MQTT_DATA);
}
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;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_01_lcd.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_01_lcd.ino"
#ifdef USE_I2C
#ifdef USE_DISPLAY
#ifdef USE_DISPLAY_LCD
#define XDSP_01 1
#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 (I2cDevice(LCD_ADDRESS1)) {
Settings.display_address[0] = LCD_ADDRESS1;
Settings.display_model = XDSP_01;
}
else if (I2cDevice(LCD_ADDRESS2)) {
Settings.display_address[0] = LCD_ADDRESS2;
Settings.display_model = XDSP_01;
}
}
if (XDSP_01 == Settings.display_model) {
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)
{
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)
{
bool result = false;
if (i2c_flg) {
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;
# 230 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_02_ssd1306.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_02_ssd1306.ino"
#ifdef USE_I2C
#ifdef USE_DISPLAY
#ifdef USE_DISPLAY_SSD1306
#define XDSP_02 2
#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()
{
if (!Settings.display_model) {
if (I2cDevice(OLED_ADDRESS1)) {
Settings.display_address[0] = OLED_ADDRESS1;
Settings.display_model = XDSP_02;
}
else if (I2cDevice(OLED_ADDRESS2)) {
Settings.display_address[0] = OLED_ADDRESS2;
Settings.display_model = XDSP_02;
}
}
if (XDSP_02 == Settings.display_model) {
if ((Settings.display_width != 96) && (Settings.display_width != 128)) {
Settings.display_width = 128;
}
if ((Settings.display_height != 16) && (Settings.display_height != 32) && (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], 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);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
AddLog(LOG_LEVEL_DEBUG);
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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_03_matrix.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_03_matrix.ino"
#ifdef USE_I2C
#ifdef USE_DISPLAY
#ifdef USE_DISPLAY_MATRIX
#define XDSP_03 3
#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 (I2cDevice(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]) {
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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_04_ili9341.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_05_epaper_29.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_06_epaper_42.ino"
# 21 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_07_sh1106.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 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 (I2cDevice(OLED_ADDRESS1)) {
Settings.display_address[0] = OLED_ADDRESS1;
Settings.display_model = XDSP_07;
}
else if (I2cDevice(OLED_ADDRESS2)) {
Settings.display_address[0] = OLED_ADDRESS2;
Settings.display_model = XDSP_07;
}
}
if (XDSP_07 == Settings.display_model) {
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);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
AddLog(LOG_LEVEL_DEBUG);
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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_08_ILI9488.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_08_ILI9488.ino"
#ifdef USE_SPI
#ifdef USE_DISPLAY
#ifdef USE_DISPLAY_ILI9488
#define XDSP_08 8
#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 (i2c_flg && I2cDevice(FT6236_address)) {
FT6236begin(FT6236_address);
FT6236_found=1;
} 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);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_09_SSD1351.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
AddLog(LOG_LEVEL_DEBUG);
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_10_RA8876.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_10_RA8876.ino"
#ifdef USE_SPI
#ifdef USE_DISPLAY
#ifdef USE_DISPLAY_RA8876
#define XDSP_10 10
#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 (i2c_flg && I2cDevice(FT5316_address)) {
FT6236begin(FT5316_address);
FT5316_found=1;
} 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);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
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;
# 168 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
# 424 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_interface.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xdsp_interface.ino"
uint8_t XdspPresent(void)
{
return xdsp_present;
}
bool XdspCall(uint8_t Function)
{
bool result = false;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_01_hlw8012.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
if (pin[GPIO_NRG_SEL] < 99) {
digitalWrite(pin[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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_02_cse7766.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
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;
uint8_t power_invalid = 0;
bool received = false;
} Cse;
void CseReceived(void)
{
uint8_t header = serial_in_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 = serial_in_buffer[2] << 16 | serial_in_buffer[3] << 8 | serial_in_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 = serial_in_buffer[8] << 16 | serial_in_buffer[9] << 8 | serial_in_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 = serial_in_buffer[14] << 16 | serial_in_buffer[15] << 8 | serial_in_buffer[16];
}
Settings.energy_power_calibration = power_coefficient / CSE_PREF;
}
uint8_t adjustement = serial_in_buffer[20];
Cse.voltage_cycle = serial_in_buffer[5] << 16 | serial_in_buffer[6] << 8 | serial_in_buffer[7];
Cse.current_cycle = serial_in_buffer[11] << 16 | serial_in_buffer[12] << 8 | serial_in_buffer[13];
Cse.power_cycle = serial_in_buffer[17] << 16 | serial_in_buffer[18] << 8 | serial_in_buffer[19];
Cse.cf_pulses = serial_in_buffer[21] << 8 | serial_in_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)
{
if (Cse.received) {
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
if (24 == serial_in_byte_counter) {
AddLogSerial(LOG_LEVEL_DEBUG_MORE);
uint8_t checksum = 0;
for (uint32_t i = 2; i < 23; i++) { checksum += serial_in_buffer[i]; }
if (checksum == serial_in_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(serial_in_buffer, serial_in_buffer +1, 24);
serial_in_byte_counter--;
} while ((serial_in_byte_counter > 2) && (0x5A != serial_in_buffer[1]));
if (0x5A != serial_in_buffer[1]) {
Cse.received = false;
serial_in_byte_counter = 0;
}
}
}
} else {
if ((0x5A == serial_in_byte) && (1 == serial_in_byte_counter)) {
Cse.received = true;
} else {
serial_in_byte_counter = 0;
}
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
}
serial_in_byte = 0;
return false;
}
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 <= (3680*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 CseDrvInit(void)
{
if ((3 == pin[GPIO_CSE7766_RX]) && (1 == pin[GPIO_CSE7766_TX])) {
baudrate = 4800;
serial_config = SERIAL_8E1;
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];
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_SERIAL:
result = CseSerialInput();
break;
case FUNC_ENERGY_EVERY_SECOND:
CseEverySecond();
break;
case FUNC_COMMAND:
result = CseCommand();
break;
case FUNC_PRE_INIT:
CseDrvInit();
break;
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_03_pzem004t.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_03_pzem004t.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_PZEM004T
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_03_pzem004t.ino"
#define XNRG_03 3
#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;
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] = 0;
pzem.addr[1] = 0;
pzem.addr[2] = 0;
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)
{
# 120 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 (!c && !len) {
continue;
}
if ((1 == len) && (buffer[0] == c)) {
len--;
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 PzemEvery200ms(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) {
EnergyUpdateTotal(Pzem.energy, false);
Pzem.energy = 0;
}
break;
}
Pzem.read_state++;
if (5 == Pzem.read_state) {
Pzem.read_state = 1;
}
}
}
if (0 == Pzem.send_retry || data_ready) {
Pzem.phase++;
if (Pzem.phase >= Energy.phase_count) {
Pzem.phase = 0;
}
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 < 30)) {
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 = 2;
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) {
Pzem.address = XdrvMailbox.payload;
}
else serviced = false;
return serviced;
}
bool Xnrg03(uint8_t function)
{
bool result = false;
switch (function) {
case FUNC_EVERY_200_MSECOND:
if (PzemSerial) { PzemEvery200ms(); }
break;
case FUNC_COMMAND:
result = PzemCommand();
break;
case FUNC_INIT:
PzemSnsInit();
break;
case FUNC_PRE_INIT:
PzemDrvInit();
break;
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_04_mcp39f501.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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));
}
if (pin[GPIO_MCP39F5_RST] < 99) {
digitalWrite(pin[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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_05_pzem_ac.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_05_pzem_ac.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_PZEM_AC
# 32 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_05_pzem_ac.ino"
#define XNRG_05 5
#define PZEM_AC_DEVICE_ADDRESS 0x01
#include <TasmotaModbus.h>
TasmotaModbus *PzemAcModbus;
struct PZEMAC {
float 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) {
EnergyUpdateTotal(PzemAc.energy, false);
PzemAc.energy = 0;
}
}
}
}
if (0 == PzemAc.send_retry || data_ready) {
PzemAc.phase++;
if (PzemAc.phase >= Energy.phase_count) {
PzemAc.phase = 0;
}
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 < 30)) {
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 = 2;
} 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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_06_pzem_dc.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_06_pzem_dc.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_PZEM_DC
# 32 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_06_pzem_dc.ino"
#define XNRG_06 6
#define PZEM_DC_DEVICE_ADDRESS 0x01
#include <TasmotaModbus.h>
TasmotaModbus *PzemDcModbus;
struct PZEMDC {
float 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) {
EnergyUpdateTotal(PzemDc.energy, false);
PzemDc.energy = 0;
}
}
}
}
if (0 == PzemDc.send_retry || data_ready) {
PzemDc.channel++;
if (PzemDc.channel >= Energy.phase_count) {
PzemDc.channel = 0;
}
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 < 30)) {
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 = 2;
} 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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_07_ade7953.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_07_ade7953.ino"
#ifdef USE_I2C
#ifdef USE_ENERGY_SENSOR
#ifdef USE_ADE7953
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_07_ade7953.ino"
#define XNRG_07 7
#define ADE7953_PREF 1540
#define ADE7953_UREF 26000
#define ADE7953_IREF 10000
#define ADE7953_ADDR 0x38
const uint8_t Ade7953Registers[] {
0x1B,
0x13,
0x11,
0x15,
0x1A,
0x12,
0x10,
0x14,
0x1C
};
struct Ade7953 {
uint32_t voltage_rms = 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); i++) {
int32_t value = Ade7953Read(0x300 + Ade7953Registers[i]);
if (8 == i) {
Ade7953.voltage_rms = 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]"),
Ade7953.voltage_rms, 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, I %d + %d = %d, P %d + %d = %d"),
Ade7953.voltage_rms, 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;
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()
{
if (Ade7953.init_step) {
if (1 == Ade7953.init_step) {
Ade7953Init();
}
Ade7953.init_step--;
} else {
Ade7953GetData();
}
}
void Ade7953DrvInit(void)
{
if (i2c_flg && (pin[GPIO_ADE7953_IRQ] < 99)) {
delay(100);
if (I2cDevice(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;
}
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "ADE7953", ADE7953_ADDR);
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)
{
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_08_sdm120.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_08_sdm120.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_SDM120_2
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_09_dds2382.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
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;
Energy.export_active = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[13] << 8) + buffer[14]) / 100.0;
float import_active = (float)((buffer[15] << 24) + (buffer[16] << 16) + (buffer[17] << 8) + buffer[18]) / 100.0;
EnergyUpdateTotal(import_active, false);
}
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_10_sdm630.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_10_sdm630.ino"
#ifdef USE_ENERGY_SENSOR
#ifdef USE_SDM630_2
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xnrg_interface.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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)
{
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xplg_ws2812.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xplg_ws2812.ino"
#ifdef USE_LIGHT
#ifdef USE_WS2812
#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] = {
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 };
uint8_t ws_show_next = 1;
bool ws_suspend_update = false;
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 Ws2812Init(void)
{
strip = new NeoPixelBus<selectedNeoFeatureType, selectedNeoSpeedType>(WS2812_MAX_LEDS, pin[GPIO_WS2812]);
strip->Begin();
Ws2812Clear();
}
void Ws2812Clear(void)
{
strip->ClearTo(0);
strip->Show();
ws_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 (!ws_suspend_update) {
strip->Show();
ws_show_next = 1;
}
}
void Ws2812ForceSuspend (void) {
ws_suspend_update = true;
}
void Ws2812ForceUpdate (void) {
ws_suspend_update = false;
strip->Show();
ws_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 Ws2812ShowScheme(uint32_t scheme)
{
switch (scheme) {
case 0:
if ((1 == state_250mS) || (ws_show_next)) {
Ws2812Clock();
ws_show_next = 0;
}
break;
default:
if (1 == Settings.light_fade) {
Ws2812Gradient(scheme -1);
} else {
Ws2812Bars(scheme -1);
}
ws_show_next = 1;
break;
}
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_01_counter.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 };
unsigned long last_counter_timer[MAX_COUNTERS];
uint8_t counter_no_pullup = 0;
#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)
{
unsigned long counter_debounce_time = micros() - last_counter_timer[index -1];
if (counter_debounce_time > Settings.pulse_counter_debounce * 1000) {
last_counter_timer[index -1] = micros();
if (bitRead(Settings.pulse_counter_type, index -1)) {
RtcSettings.pulse_counter[index -1] = counter_debounce_time;
} else {
RtcSettings.pulse_counter[index -1]++;
}
}
}
void CounterUpdate1(void)
{
CounterUpdate(1);
}
void CounterUpdate2(void)
{
CounterUpdate(2);
}
void CounterUpdate3(void)
{
CounterUpdate(3);
}
void CounterUpdate4(void)
{
CounterUpdate(4);
}
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) {
pinMode(pin[GPIO_CNTR1 +i], bitRead(counter_no_pullup, i) ? INPUT : INPUT_PULLUP);
attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING);
}
}
}
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
#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 (bitRead(Settings.pulse_counter_type, i)) {
RtcSettings.pulse_counter[i] = 0xFFFFFFFF;
}
}
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;
switch (function) {
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;
case FUNC_INIT:
CounterInit();
break;
case FUNC_PIN_STATE:
result = CounterPinState();
break;
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_02_analog.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
uint16_t adc_last_value = 0;
float adc_temp = 0;
void AdcInit(void)
{
if ((Settings.adc_param_type != my_adc0) || (Settings.adc_param1 > 1000000) || (Settings.adc_param1 < 100)) {
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;
}
}
}
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()
{
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;
}
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_temp = ConvertTemp(TO_CELSIUS(T));
}
}
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_temp, 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_temp);
}
#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
}
}
}
#define D_CMND_ADCPARAM "AdcParam"
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)) {
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);
Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000);
} else {
Settings.adc_param_type = 0;
AdcInit();
}
}
}
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);
Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"),
Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2, param3);
}
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)) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_04_snfsc.ino"
# 56 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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_INIT:
SonoffScInit();
break;
case FUNC_JSON_APPEND:
SonoffScShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
SonoffScShow(0);
break;
#endif
}
}
return result;
}
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_05_ds18b20.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_05_ds18b20.ino"
#ifdef USE_DS18B20
#define XSNS_05 5
#define W1_SKIP_ROM 0xCC
#define W1_CONVERT_TEMP 0x44
#define W1_READ_SCRATCHPAD 0xBE
float ds18b20_temperature = 0;
uint8_t ds18b20_valid = 0;
uint8_t ds18x20_pin = 0;
char ds18b20_types[] = "DS18B20";
uint8_t OneWireReset(void)
{
uint8_t retries = 125;
#ifdef DS18B20_INTERNAL_PULLUP
pinMode(ds18x20_pin, INPUT_PULLUP);
#else
pinMode(ds18x20_pin, INPUT);
#endif
do {
if (--retries == 0) {
return 0;
}
delayMicroseconds(2);
} while (!digitalRead(ds18x20_pin));
pinMode(ds18x20_pin, OUTPUT);
digitalWrite(ds18x20_pin, LOW);
delayMicroseconds(480);
#ifdef DS18B20_INTERNAL_PULLUP
pinMode(ds18x20_pin, INPUT_PULLUP);
#else
pinMode(ds18x20_pin, INPUT);
#endif
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;
digitalWrite(ds18x20_pin, LOW);
pinMode(ds18x20_pin, OUTPUT);
delayMicroseconds(delay_low[v]);
digitalWrite(ds18x20_pin, HIGH);
delayMicroseconds(delay_high[v]);
}
uint8_t OneWireReadBit(void)
{
pinMode(ds18x20_pin, OUTPUT);
digitalWrite(ds18x20_pin, LOW);
delayMicroseconds(3);
#ifdef DS18B20_INTERNAL_PULLUP
pinMode(ds18x20_pin, INPUT_PULLUP);
#else
pinMode(ds18x20_pin, INPUT);
#endif
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;
}
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 Ds18b20Convert(void)
{
OneWireReset();
OneWireWrite(W1_SKIP_ROM);
OneWireWrite(W1_CONVERT_TEMP);
}
bool Ds18b20Read(void)
{
uint8_t data[9];
int8_t sign = 1;
if (ds18b20_valid) { ds18b20_valid--; }
for (uint32_t retry = 0; retry < 3; retry++) {
OneWireReset();
OneWireWrite(W1_SKIP_ROM);
OneWireWrite(W1_READ_SCRATCHPAD);
for (uint32_t i = 0; i < 9; i++) {
data[i] = OneWireRead();
}
if (OneWireCrc8(data)) {
uint16_t temp12 = (data[1] << 8) + data[0];
if (temp12 > 2047) {
temp12 = (~temp12) +1;
sign = -1;
}
ds18b20_temperature = ConvertTemp(sign * temp12 * 0.0625);
ds18b20_valid = SENSOR_MAX_MISS;
return true;
}
}
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSOR_CRC_ERROR));
return false;
}
void Ds18b20EverySecond(void)
{
ds18x20_pin = pin[GPIO_DSB];
if (uptime &1) {
Ds18b20Convert();
} else {
if (!Ds18b20Read()) {
AddLogMissed(ds18b20_types, ds18b20_valid);
}
}
}
void Ds18b20Show(bool json)
{
if (ds18b20_valid) {
char temperature[33];
dtostrfd(ds18b20_temperature, Settings.flag2.temperature_resolution, temperature);
if(json) {
ResponseAppend_P(JSON_SNS_TEMP, ds18b20_types, temperature);
#ifdef USE_DOMOTICZ
if (0 == tele_period) {
DomoticzSensor(DZ_TEMP, temperature);
}
#endif
#ifdef USE_KNX
if (0 == tele_period) {
KnxSensor(KNX_TEMPERATURE, ds18b20_temperature);
}
#endif
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_TEMP, ds18b20_types, temperature, TempUnit());
#endif
}
}
}
bool Xsns05(uint8_t function)
{
bool result = false;
if (pin[GPIO_DSB] < 99) {
switch (function) {
case FUNC_EVERY_SECOND:
Ds18b20EverySecond();
break;
case FUNC_JSON_APPEND:
Ds18b20Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Ds18b20Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_05_ds18x20.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
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;
pinMode(ds18x20_pin, 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, INPUT);
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;
digitalWrite(ds18x20_pin, LOW);
pinMode(ds18x20_pin, OUTPUT);
delayMicroseconds(delay_low[v]);
digitalWrite(ds18x20_pin, HIGH);
delayMicroseconds(delay_high[v]);
}
uint8_t OneWireReadBit(void)
{
pinMode(ds18x20_pin, OUTPUT);
digitalWrite(ds18x20_pin, LOW);
delayMicroseconds(3);
pinMode(ds18x20_pin, INPUT);
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];
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) {
if (1 == ds18x20_sensors) {
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, temperature);
} else {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_05_ds18x20_legacy.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_05_ds18x20_legacy.ino"
#ifdef USE_DS18x20_LEGACY
#define XSNS_05 5
#define DS18S20_CHIPID 0x10
#define DS18B20_CHIPID 0x28
#define MAX31850_CHIPID 0x3B
#define W1_SKIP_ROM 0xCC
#define W1_CONVERT_TEMP 0x44
#define W1_READ_SCRATCHPAD 0xBE
#define DS18X20_MAX_SENSORS 8
#include <OneWire.h>
OneWire *ds = nullptr;
uint8_t ds18x20_address[DS18X20_MAX_SENSORS][8];
uint8_t ds18x20_index[DS18X20_MAX_SENSORS];
uint8_t ds18x20_sensors = 0;
char ds18x20_types[9];
void Ds18x20Init(void)
{
ds = new OneWire(pin[GPIO_DSB]);
}
void Ds18x20Search(void)
{
uint8_t num_sensors=0;
uint8_t sensor = 0;
ds->reset_search();
for (num_sensors = 0; num_sensors < DS18X20_MAX_SENSORS; num_sensors) {
if (!ds->search(ds18x20_address[num_sensors])) {
ds->reset_search();
break;
}
if ((OneWire::crc8(ds18x20_address[num_sensors], 7) == ds18x20_address[num_sensors][7]) &&
((ds18x20_address[num_sensors][0]==DS18S20_CHIPID) || (ds18x20_address[num_sensors][0]==DS18B20_CHIPID) || (ds18x20_address[num_sensors][0]==MAX31850_CHIPID))) {
num_sensors++;
}
}
for (uint32_t i = 0; i < num_sensors; i++) {
ds18x20_index[i] = i;
}
for (uint32_t i = 0; i < num_sensors; i++) {
for (uint32_t j = i + 1; j < num_sensors; j++) {
if (uint32_t(ds18x20_address[ds18x20_index[i]]) > uint32_t(ds18x20_address[ds18x20_index[j]])) {
std::swap(ds18x20_index[i], ds18x20_index[j]);
}
}
}
ds18x20_sensors = num_sensors;
}
uint8_t Ds18x20Sensors(void)
{
return ds18x20_sensors;
}
String Ds18x20Addresses(uint8_t sensor)
{
char address[20];
for (uint32_t i = 0; i < 8; i++) {
sprintf(address+2*i, "%02X", ds18x20_address[ds18x20_index[sensor]][i]);
}
return String(address);
}
void Ds18x20Convert(void)
{
ds->reset();
ds->write(W1_SKIP_ROM);
ds->write(W1_CONVERT_TEMP);
}
bool Ds18x20Read(uint8_t sensor, float &t)
{
uint8_t data[12];
int8_t sign = 1;
uint16_t temp12 = 0;
int16_t temp14 = 0;
float temp9 = 0.0;
uint8_t present = 0;
t = NAN;
ds->reset();
ds->select(ds18x20_address[ds18x20_index[sensor]]);
ds->write(W1_READ_SCRATCHPAD);
for (uint32_t i = 0; i < 9; i++) {
data[i] = ds->read();
}
if (OneWire::crc8(data, 8) == data[8]) {
switch(ds18x20_address[ds18x20_index[sensor]][0]) {
case DS18S20_CHIPID:
if (data[1] > 0x80) {
data[0] = (~data[0]) +1;
sign = -1;
}
temp9 = (float)(data[0] >> 1) * sign;
t = ConvertTemp((temp9 - 0.25) + ((16.0 - data[6]) / 16.0));
break;
case DS18B20_CHIPID:
temp12 = (data[1] << 8) + data[0];
if (temp12 > 2047) {
temp12 = (~temp12) +1;
sign = -1;
}
t = ConvertTemp(sign * temp12 * 0.0625);
break;
case MAX31850_CHIPID:
temp14 = (data[1] << 8) + (data[0] & 0xFC);
t = ConvertTemp(temp14 * 0.0625);
break;
}
}
return (!isnan(t));
}
void Ds18x20Type(uint8_t sensor)
{
strcpy_P(ds18x20_types, PSTR("DS18x20"));
switch(ds18x20_address[ds18x20_index[sensor]][0]) {
case DS18S20_CHIPID:
strcpy_P(ds18x20_types, PSTR("DS18S20"));
break;
case DS18B20_CHIPID:
strcpy_P(ds18x20_types, PSTR("DS18B20"));
break;
case MAX31850_CHIPID:
strcpy_P(ds18x20_types, PSTR("MAX31850"));
break;
}
}
void Ds18x20Show(bool json)
{
char stemp[10];
float t;
uint8_t dsxflg = 0;
for (uint32_t i = 0; i < Ds18x20Sensors(); i++) {
if (Ds18x20Read(i, t)) {
Ds18x20Type(i);
char temperature[33];
dtostrfd(t, Settings.flag2.temperature_resolution, temperature);
if (json) {
if (!dsxflg) {
ResponseAppend_P(PSTR(",\"DS18x20\":{"));
stemp[0] = '\0';
}
dsxflg++;
ResponseAppend_P(PSTR("%s\"DS%d\":{\"" D_JSON_TYPE "\":\"%s\",\"" D_JSON_ADDRESS "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"),
stemp, i +1, ds18x20_types, Ds18x20Addresses(i).c_str(), temperature);
strlcpy(stemp, ",", sizeof(stemp));
#ifdef USE_DOMOTICZ
if ((0 == tele_period) && (1 == dsxflg)) {
DomoticzSensor(DZ_TEMP, temperature);
}
#endif
#ifdef USE_KNX
if ((0 == tele_period) && (1 == dsxflg)) {
KnxSensor(KNX_TEMPERATURE, t);
}
#endif
#ifdef USE_WEBSERVER
} else {
snprintf_P(stemp, sizeof(stemp), PSTR("%s%c%d"), ds18x20_types, IndexSeparator(), i +1);
WSContentSend_PD(HTTP_SNS_TEMP, stemp, temperature, TempUnit());
#endif
}
}
}
if (json) {
if (dsxflg) {
ResponseJsonEnd();
}
}
Ds18x20Search();
Ds18x20Convert();
}
bool Xsns05(uint8_t function)
{
bool result = false;
if (pin[GPIO_DSB] < 99) {
switch (function) {
case FUNC_INIT:
Ds18x20Init();
break;
case FUNC_PREP_BEFORE_TELEPERIOD:
Ds18x20Search();
Ds18x20Convert();
break;
case FUNC_JSON_APPEND:
Ds18x20Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Ds18x20Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_06_dht.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_06_dht.ino"
#ifdef USE_DHT
# 29 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_06_dht.ino"
#define XSNS_06 6
#define DHT_MAX_SENSORS 3
#define DHT_MAX_RETRY 8
uint32_t dht_max_cycles;
uint8_t dht_data[5];
uint8_t dht_sensors = 0;
bool dht_active = true;
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++) {
digitalWrite(Dht[i].pin, 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;
digitalWrite(Dht[sensor].pin, HIGH);
delay(250);
}
pinMode(Dht[sensor].pin, OUTPUT);
digitalWrite(Dht[sensor].pin, LOW);
if (GPIO_SI7021 == Dht[sensor].type) {
delayMicroseconds(500);
} else {
delay(20);
}
noInterrupts();
digitalWrite(Dht[sensor].pin, HIGH);
delayMicroseconds(40);
pinMode(Dht[sensor].pin, INPUT_PULLUP);
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);
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);
}
}
} 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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_07_sht1x.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_07_sht1x.ino"
#ifdef USE_I2C
#ifdef USE_SHT
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_07_sht1x.ino"
#define XSNS_07 7
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) {
sht_type = 0;
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));
sht_type = 0;
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)
{
if (sht_type) {
return;
}
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 (sht_type && !(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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
ShtDetect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_08_htu21.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_08_htu21.ino"
#ifdef USE_I2C
#ifdef USE_HTU
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_08_htu21.ino"
#define XSNS_08 8
#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)
{
if (htu_type) { return; }
htu_address = HTU21_ADDR;
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);
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, htu_types, htu_address);
}
}
void HtuEverySecond(void)
{
if (92 == (uptime %100)) {
HtuDetect();
}
else if (uptime &1) {
if (htu_type) {
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
HtuDetect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_09_bmp.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_09_bmp.ino"
#ifdef USE_I2C
#ifdef USE_BMP
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_09_bmp.ino"
#define XSNS_09 9
#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_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)
{
if (bmp_count) return;
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++) {
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);
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, bmp_sensors[bmp_count].bmp_name, bmp_sensors[bmp_count].bmp_address);
bmp_count++;
}
}
}
}
void BmpRead(void)
{
if (!bmp_sensors) { return; }
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 BmpEverySecond(void)
{
if (91 == (uptime %100)) {
BmpDetect();
}
else {
BmpRead();
}
}
void BmpShow(bool json)
{
if (!bmp_sensors) { return; }
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
}
}
}
}
bool Xsns09(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
BmpDetect();
break;
case FUNC_EVERY_SECOND:
BmpEverySecond();
break;
case FUNC_JSON_APPEND:
BmpShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
BmpShow(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_10_bh1750.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_10_bh1750.ino"
#ifdef USE_I2C
#ifdef USE_BH1750
#define XSNS_10 10
#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)
{
if (bh1750_type) {
return;
}
for (uint32_t i = 0; i < sizeof(bh1750_addresses); i++) {
bh1750_address = bh1750_addresses[i];
Wire.beginTransmission(bh1750_address);
Wire.write(BH1750_CONTINUOUS_HIGH_RES_MODE);
if (!Wire.endTransmission()) {
bh1750_type = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, bh1750_types, bh1750_address);
break;
}
}
}
void Bh1750EverySecond(void)
{
if (90 == (uptime %100)) {
Bh1750Detect();
}
else {
if (bh1750_type) {
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
Bh1750Detect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_11_veml6070.ino"
# 89 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_11_veml6070.ino"
#ifdef USE_I2C
#ifdef USE_VEML6070
#define XSNS_11 11
#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;
uint8_t veml6070_valid = 0;
char veml6070_name[9];
char str_uvrisk_text[10];
void Veml6070Detect(void)
{
if (veml6070_type) {
return;
}
Wire.beginTransmission(VEML6070_ADDR_L);
Wire.write((itime << 2) | 0x02);
uint8_t status = Wire.endTransmission();
if (!status) {
veml6070_type = 1;
uint8_t veml_model = 0;
GetTextIndexed(veml6070_name, sizeof(veml6070_name), veml_model, kVemlTypes);
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "VEML6070", VEML6070_ADDR_L);
}
}
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)
{
if (11 == (uptime %100)) {
Veml6070ModeCmd(1);
Veml6070Detect();
Veml6070ModeCmd(0);
} else {
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, S_LOG_I2C_FOUND_AT, "VEML6070 mode_cmd", VEML6070_ADDR_L);
}
}
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)
{
if (veml6070_type) {
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)
{
bool result = false;
if (i2c_flg && !(pin[GPIO_ADE7953_IRQ] < 99)) {
switch (function) {
case FUNC_INIT:
Veml6070Detect();
Veml6070UvTableInit();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_12_ads1115.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_12_ads1115.ino"
#ifdef USE_I2C
#ifdef USE_ADS1115
# 43 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_12_ads1115.ino"
#define XSNS_12 12
#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)
uint8_t ads1115_type = 0;
uint8_t ads1115_address;
uint8_t ads1115_addresses[] = { ADS1115_ADDRESS_ADDR_GND, ADS1115_ADDRESS_ADDR_VDD, ADS1115_ADDRESS_ADDR_SDA, ADS1115_ADDRESS_ADDR_SCL };
uint8_t ads1115_found[] = {false,false,false,false};
int16_t ads1115_values[4];
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)
{
uint16_t buffer;
for (uint32_t i = 0; i < sizeof(ads1115_addresses); i++) {
if (!ads1115_found[i]) {
ads1115_address = ads1115_addresses[i];
if (I2cValidRead16(&buffer, ads1115_address, ADS1115_REG_POINTER_CONVERT) &&
I2cValidRead16(&buffer, ads1115_address, ADS1115_REG_POINTER_CONFIG)) {
Ads1115StartComparator(i, ADS1115_REG_CONFIG_MODE_CONTIN);
ads1115_type = 1;
ads1115_found[i] = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "ADS1115", ads1115_address);
}
}
}
}
void Ads1115GetValues(uint8_t address)
{
uint8_t old_address = ads1115_address;
ads1115_address = address;
for (uint32_t i = 0; i < 4; i++) {
ads1115_values[i] = Ads1115GetConversion(i);
}
ads1115_address = old_address;
}
void Ads1115toJSON(char *comma_j)
{
ResponseAppend_P(PSTR("%s{"), comma_j);
char *comma = (char*)"";
for (uint32_t i = 0; i < 4; i++) {
ResponseAppend_P(PSTR("%s\"A%d\":%d"), comma, i, ads1115_values[i]);
comma = (char*)",";
}
ResponseJsonEnd();
}
void Ads1115toString(uint8_t address)
{
char label[15];
snprintf_P(label, sizeof(label), "ADS1115(%02x)", address);
for (uint32_t i = 0; i < 4; i++) {
WSContentSend_PD(HTTP_SNS_ANALOG, label, i, ads1115_values[i]);
}
}
void Ads1115Show(bool json)
{
if (!ads1115_type) { return; }
if (json) {
ResponseAppend_P(PSTR(",\"ADS1115\":"));
}
char *comma = (char*)"";
for (uint32_t t = 0; t < sizeof(ads1115_addresses); t++) {
if (ads1115_found[t]) {
Ads1115GetValues(ads1115_addresses[t]);
if (json) {
Ads1115toJSON(comma);
comma = (char*)",";
}
#ifdef USE_WEBSERVER
else {
Ads1115toString(ads1115_addresses[t]);
}
#endif
}
}
}
bool Xsns12(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_PREP_BEFORE_TELEPERIOD:
Ads1115Detect();
break;
case FUNC_JSON_APPEND:
Ads1115Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Ads1115Show(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_12_ads1115_i2cdev.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_12_ads1115_i2cdev.ino"
#ifdef USE_I2C
#ifdef USE_ADS1115_I2CDEV
# 43 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_12_ads1115_i2cdev.ino"
#define XSNS_12 12
#include <ADS1115.h>
ADS1115 adc0;
uint8_t ads1115_type = 0;
uint8_t ads1115_address;
uint8_t ads1115_addresses[] = {
ADS1115_ADDRESS_ADDR_GND,
ADS1115_ADDRESS_ADDR_VDD,
ADS1115_ADDRESS_ADDR_SDA,
ADS1115_ADDRESS_ADDR_SCL
};
int16_t Ads1115GetConversion(uint8_t channel)
{
switch (channel) {
case 0:
adc0.getConversionP0GND();
break;
case 1:
adc0.getConversionP1GND();
break;
case 2:
adc0.getConversionP2GND();
break;
case 3:
adc0.getConversionP3GND();
break;
}
}
void Ads1115Detect(void)
{
if (ads1115_type) {
return;
}
for (uint32_t i = 0; i < sizeof(ads1115_addresses); i++) {
ads1115_address = ads1115_addresses[i];
ADS1115 adc0(ads1115_address);
if (adc0.testConnection()) {
adc0.initialize();
adc0.setGain(ADS1115_PGA_6P144);
adc0.setRate(ADS1115_RATE_860);
adc0.setMode(ADS1115_MODE_CONTINUOUS);
ads1115_type = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "ADS1115", ads1115_address);
break;
}
}
}
void Ads1115Show(bool json)
{
if (ads1115_type) {
uint8_t dsxflg = 0;
for (uint32_t i = 0; i < 4; i++) {
int16_t adc_value = Ads1115GetConversion(i);
if (json) {
if (!dsxflg ) {
ResponseAppend_P(PSTR(",\"ADS1115\":{"));
}
ResponseAppend_P(PSTR("%s\"A%d\":%d"), (dsxflg) ? "," : "", i, adc_value);
dsxflg++;
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_ANALOG, "ADS1115", i, adc_value);
#endif
}
}
if (json) {
if (dsxflg) {
ResponseJsonEnd();
}
}
}
}
bool Xsns12(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_PREP_BEFORE_TELEPERIOD:
Ads1115Detect();
break;
case FUNC_JSON_APPEND:
Ads1115Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Ads1115Show(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_13_ina219.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_13_ina219.ino"
#ifdef USE_I2C
#ifdef USE_INA219
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_13_ina219.ino"
#define XSNS_13 13
#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";
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;
}
# 183 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_13_ina219.ino"
bool Ina219CommandSensor(void)
{
bool serviced = true;
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 serviced;
}
void Ina219Detect(void)
{
for (int i=0; i<sizeof(ina219_type); i++) {
if (ina219_type[i])
continue;
uint16_t addr = ina219_addresses[i];
if (Ina219SetCalibration(Settings.ina219_mode, addr)) {
ina219_type[i] = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, ina219_types, addr);
}
}
}
void Ina219EverySecond(void)
{
if (87 == (uptime %100)) {
Ina219Detect();
}
else {
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_COMMAND_SENSOR:
if ((XSNS_13 == XdrvMailbox.index) && (ina219_type)) {
result = Ina219CommandSensor();
}
break;
case FUNC_INIT:
Ina219Detect();
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_14_sht3x.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_14_sht3x.ino"
#ifdef USE_I2C
#ifdef USE_SHT3X
#define XSNS_14 14
#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)
{
if (sht3x_count) return;
float t;
float h;
for (uint32_t i = 0; i < SHT3X_MAX_SENSORS; i++) {
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);
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, sht3x_sensors[sht3x_count].types, sht3x_sensors[sht3x_count].address);
sht3x_count++;
}
}
}
void Sht3xShow(bool json)
{
if (sht3x_count) {
float t;
float h;
char types[11];
for (uint32_t i = 0; i < sht3x_count; i++) {
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);
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
Sht3xDetect();
break;
case FUNC_JSON_APPEND:
Sht3xShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Sht3xShow(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_15_mhz19.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_15_mhz19.ino"
#ifdef USE_MHZ19
# 33 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_16_tsl2561.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_16_tsl2561.ino"
#ifdef USE_I2C
#ifdef USE_TSL2561
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_16_tsl2561.ino"
#define XSNS_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 (tsl2561_type) { return; }
uint8_t id;
if (I2cDevice(0x29) || I2cDevice(0x39) || I2cDevice(0x49)) {
Tsl.begin();
if (!Tsl.id(id)) return;
if (Tsl.on()) {
tsl2561_type = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, tsl2561_types, Tsl.address(), id);
}
}
}
void Tsl2561EverySecond(void)
{
if (90 == (uptime %100)) {
Tsl2561Detect();
}
else if (!(uptime %2)) {
if (tsl2561_type) {
if (!Tsl2561Read()) {
AddLogMissed(tsl2561_types, tsl2561_valid);
if (!tsl2561_valid) { tsl2561_type = 0; }
}
}
}
}
#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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
Tsl2561Detect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_17_senseair.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_17_senseair.ino"
#ifdef USE_SENSEAIR
# 29 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_18_pms5003.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_18_pms5003.ino"
#ifdef USE_PMS5003
#define XSNS_18 18
#include <TasmotaSerial.h>
TasmotaSerial *PmsSerial;
uint8_t pms_type = 1;
uint8_t pms_valid = 0;
struct pms5003data {
uint16_t framelen;
uint16_t pm10_standard, pm25_standard, pm100_standard;
uint16_t pm10_env, pm25_env, pm100_env;
uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
uint16_t unused;
uint16_t checksum;
} pms_data;
bool PmsReadData(void)
{
if (! PmsSerial->available()) {
return false;
}
while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) {
PmsSerial->read();
}
if (PmsSerial->available() < 32) {
return false;
}
uint8_t buffer[32];
uint16_t sum = 0;
PmsSerial->readBytes(buffer, 32);
PmsSerial->flush();
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 32);
for (uint32_t i = 0; i < 30; i++) {
sum += buffer[i];
}
uint16_t buffer_u16[15];
for (uint32_t i = 0; i < 15; i++) {
buffer_u16[i] = buffer[2 + i*2 + 1];
buffer_u16[i] += (buffer[2 + i*2] << 8);
}
if (sum != buffer_u16[14]) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR("PMS: " D_CHECKSUM_FAILURE));
return false;
}
memcpy((void *)&pms_data, (void *)buffer_u16, 30);
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
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
void PmsShow(bool json)
{
if (pms_valid) {
if (json) {
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);
#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 {
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
}
}
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_19_mgs.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_19_mgs.ino"
#ifdef USE_I2C
#ifdef USE_MGS
#define XSNS_19 19
#ifndef MGS_SENSOR_ADDR
#define MGS_SENSOR_ADDR 0x04
#endif
#include "MutichannelGasSensor.h"
void MGSInit(void) {
gas.begin(MGS_SENSOR_ADDR);
}
bool MGSPrepare(void)
{
gas.begin(MGS_SENSOR_ADDR);
if (!gas.isError()) {
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "MultiGasSensor", MGS_SENSOR_ADDR);
return true;
} else {
return false;
}
}
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)
{
bool result = false;
static int detected = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
break;
case FUNC_PREP_BEFORE_TELEPERIOD:
detected = MGSPrepare();
break;
case FUNC_JSON_APPEND:
if (detected) MGSShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
if (detected) MGSShow(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_20_novasds.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_20_novasds.ino"
#ifdef USE_NOVA_SDS
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_20_novasds.ino"
#define XSNS_20 20
#include <TasmotaSerial.h>
#ifndef WORKING_PERIOD
#define WORKING_PERIOD 5
#endif
#ifndef NOVA_SDS_REINIT_CHECK
#define NOVA_SDS_REINIT_CHECK 80
#endif
#ifndef NOVA_SDS_QUERY_INTERVAL
#define NOVA_SDS_QUERY_INTERVAL 3
#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;
struct sds011data {
uint16_t pm100;
uint16_t pm25;
} novasds_data;
#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_WORK 0
#define NOVA_SDS_SLEEP 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, Settings.novasds_period, 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 (0 == (uptime % NOVA_SDS_REINIT_CHECK)) {
if (!novasds_valid) {
NovaSdsSetWorkPeriod();
}
} else if (0 == (uptime % NOVA_SDS_QUERY_INTERVAL)) {
if (NovaSdsReadData()) {
novasds_valid = 10;
} else {
if (novasds_valid) {
novasds_valid--;
}
}
}
}
bool NovaSdsCommandSensor(void)
{
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 256)) {
Settings.novasds_period = XdrvMailbox.payload;
NovaSdsSetWorkPeriod();
}
Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_20, Settings.novasds_period);
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_21_sgp30.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_21_sgp30.ino"
#ifdef USE_I2C
#ifdef USE_SGP30
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_21_sgp30.ino"
#define XSNS_21 21
#include "Adafruit_SGP30.h"
Adafruit_SGP30 sgp;
uint8_t sgp30_type = 0;
uint8_t sgp30_ready = 0;
float sgp30_abshum;
void sgp30_Init(void) {
if (sgp.begin()) {
sgp30_type = 1;
snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "SGP30", 0x58);
AddLog(LOG_LEVEL_DEBUG);
}
}
#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 = 0;
if (!sgp.IAQmeasure() || !sgp30_type) {
if (21 == (uptime %100)) {
sgp30_Init();
}
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 = 1;
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
sgp30_Init();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_22_sr04.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_22_sr04.ino"
#ifdef USE_SR04
#include <NewPing.h>
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_22_sr04.ino"
#define XSNS_22 22
uint8_t sr04_echo_pin = 0;
uint8_t sr04_trig_pin = 0;
real64_t distance;
NewPing* sonar = nullptr;
void Sr04Init(void)
{
sr04_echo_pin = pin[GPIO_SR04_ECHO];
sr04_trig_pin = pin[GPIO_SR04_TRIG];
sonar = new NewPing(sr04_trig_pin, sr04_echo_pin, 300);
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_DISTANCE[] PROGMEM =
"{s}SR04 " D_DISTANCE "{m}%s" D_UNIT_CENTIMETER "{e}";
#endif
void Sr04Show(bool json)
{
distance = (real64_t)(sonar->ping_median(5))/ US_ROUNDTRIP_CM;
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 ((pin[GPIO_SR04_ECHO] < 99) && (pin[GPIO_SR04_TRIG] < 99)) {
switch (function) {
case FUNC_INIT:
Sr04Init();
break;
case FUNC_JSON_APPEND:
Sr04Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Sr04Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_23_sdm120.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_23_sdm120.ino"
#ifdef USE_SDM120
#define XSNS_23 23
#ifndef SDM120_SPEED
#define SDM120_SPEED 2400
#endif
#ifndef SDM120_ADDR
#define SDM120_ADDR 1
#endif
#include <TasmotaSerial.h>
enum SDM120_Error {SDM120_ERR_NO_ERROR=0, SDM120_ERR_CRC_ERROR, SDM120_ERR_WRONG_BYTES, SDM120_ERR_NOT_ENOUGHT_BYTES};
TasmotaSerial *SDM120Serial;
uint8_t sdm120_type = 1;
float sdm120_voltage = 0;
float sdm120_current = 0;
float sdm120_active_power = 0;
float sdm120_apparent_power = 0;
float sdm120_reactive_power = 0;
float sdm120_power_factor = 0;
float sdm120_frequency = 0;
float sdm120_energy_total = 0;
float sdm120_phase_angle = 0;
float sdm120_import_active = 0;
float sdm120_export_active = 0;
float sdm120_import_reactive = 0;
float sdm120_export_reactive = 0;
float sdm120_total_reactive = 0;
bool SDM120_ModbusReceiveReady(void)
{
return (SDM120Serial->available() > 1);
}
void SDM120_ModbusSend(uint8_t function_code, uint16_t start_address, uint16_t register_count)
{
uint8_t frame[8];
frame[0] = SDM120_ADDR;
frame[1] = function_code;
frame[2] = (uint8_t)(start_address >> 8);
frame[3] = (uint8_t)(start_address);
frame[4] = (uint8_t)(register_count >> 8);
frame[5] = (uint8_t)(register_count);
uint16_t crc = SDM120_calculateCRC(frame, 6);
frame[6] = lowByte(crc);
frame[7] = highByte(crc);
while (SDM120Serial->available() > 0) {
SDM120Serial->read();
}
SDM120Serial->flush();
SDM120Serial->write(frame, sizeof(frame));
}
uint8_t SDM120_ModbusReceive(float *value)
{
uint8_t buffer[9];
*value = NAN;
uint8_t len = 0;
while (SDM120Serial->available() > 0) {
buffer[len++] = (uint8_t)SDM120Serial->read();
}
if (len < 9) {
return SDM120_ERR_NOT_ENOUGHT_BYTES;
}
if (9 == len) {
if (0x01 == buffer[0] && 0x04 == buffer[1] && 4 == buffer[2]) {
if((SDM120_calculateCRC(buffer, 7)) == ((buffer[8] << 8) | buffer[7])) {
((uint8_t*)value)[3] = buffer[3];
((uint8_t*)value)[2] = buffer[4];
((uint8_t*)value)[1] = buffer[5];
((uint8_t*)value)[0] = buffer[6];
} else {
return SDM120_ERR_CRC_ERROR;
}
} else {
return SDM120_ERR_WRONG_BYTES;
}
}
return SDM120_ERR_NO_ERROR;
}
uint16_t SDM120_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;
}
const uint16_t sdm120_start_addresses[] {
0x0000,
0x0006,
0x000C,
0x0012,
0x0018,
0x001E,
0x0046,
#ifdef USE_SDM220
0x0156,
0X0024,
0X0048,
0X004A,
0X004C,
0X004E,
0X0158
#else
0x0156
#endif
};
uint8_t sdm120_read_state = 0;
uint8_t sdm120_send_retry = 0;
uint8_t sdm120_nodata_count = 0;
void SDM120250ms(void)
{
float value = 0;
bool data_ready = SDM120_ModbusReceiveReady();
if (data_ready) {
sdm120_nodata_count = 0;
uint8_t error = SDM120_ModbusReceive(&value);
if (error) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SDM120 response error %d"), error);
} else {
switch(sdm120_read_state) {
case 0:
sdm120_voltage = value;
break;
case 1:
sdm120_current = value;
break;
case 2:
sdm120_active_power = value;
break;
case 3:
sdm120_apparent_power = value;
break;
case 4:
sdm120_reactive_power = value;
break;
case 5:
sdm120_power_factor = value;
break;
case 6:
sdm120_frequency = value;
break;
case 7:
sdm120_energy_total = value;
break;
#ifdef USE_SDM220
case 8:
sdm120_phase_angle = value;
break;
case 9:
sdm120_import_active = value;
break;
case 10:
sdm120_export_active = value;
break;
case 11:
sdm120_import_reactive = value;
break;
case 12:
sdm120_export_reactive = value;
break;
case 13:
sdm120_total_reactive = value;
break;
#endif
}
sdm120_read_state++;
if (sizeof(sdm120_start_addresses)/2 == sdm120_read_state) {
sdm120_read_state = 0;
}
}
}
else {
if (sdm120_nodata_count <= (1000/250) * 4) {
sdm120_nodata_count++;
} else if (sdm120_nodata_count != 255) {
sdm120_nodata_count = 255;
sdm120_voltage = sdm120_current = sdm120_active_power = sdm120_apparent_power = sdm120_reactive_power = sdm120_power_factor = sdm120_frequency = sdm120_energy_total = 0;
#ifdef USE_SDM220
sdm120_phase_angle = sdm120_import_active = sdm120_export_active = sdm120_import_reactive = sdm120_export_reactive = sdm120_total_reactive = 0;
#endif
}
}
if (0 == sdm120_send_retry || data_ready) {
sdm120_send_retry = 5;
SDM120_ModbusSend(0x04, sdm120_start_addresses[sdm120_read_state], 2);
} else {
sdm120_send_retry--;
}
}
void SDM120Init(void)
{
sdm120_type = 0;
if ((pin[GPIO_SDM120_RX] < 99) && (pin[GPIO_SDM120_TX] < 99)) {
SDM120Serial = new TasmotaSerial(pin[GPIO_SDM120_RX], pin[GPIO_SDM120_TX], 1);
if (SDM120Serial->begin(SDM120_SPEED)) {
if (SDM120Serial->hardwareSerial()) { ClaimSerial(); }
sdm120_type = 1;
}
}
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_SDM120_DATA[] PROGMEM =
"{s}SDM120 " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
"{s}SDM120 " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
"{s}SDM120 " D_POWERUSAGE_ACTIVE "{m}%s " D_UNIT_WATT "{e}"
"{s}SDM120 " D_POWERUSAGE_APPARENT "{m}%s " D_UNIT_VA "{e}"
"{s}SDM120 " D_POWERUSAGE_REACTIVE "{m}%s " D_UNIT_VAR "{e}"
"{s}SDM120 " D_POWER_FACTOR "{m}%s{e}"
"{s}SDM120 " D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"
"{s}SDM120 " D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
#ifdef USE_SDM220
"{s}SDM120 " D_PHASE_ANGLE "{m}%s " D_UNIT_ANGLE "{e}"
"{s}SDM120 " D_IMPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
"{s}SDM120 " D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
"{s}SDM120 " D_IMPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
"{s}SDM120 " D_EXPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
"{s}SDM120 " D_TOTAL_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
#endif
;
#endif
void SDM120Show(bool json)
{
char voltage[33];
dtostrfd(sdm120_voltage, Settings.flag2.voltage_resolution, voltage);
char current[33];
dtostrfd(sdm120_current, Settings.flag2.current_resolution, current);
char active_power[33];
dtostrfd(sdm120_active_power, Settings.flag2.wattage_resolution, active_power);
char apparent_power[33];
dtostrfd(sdm120_apparent_power, Settings.flag2.wattage_resolution, apparent_power);
char reactive_power[33];
dtostrfd(sdm120_reactive_power, Settings.flag2.wattage_resolution, reactive_power);
char power_factor[33];
dtostrfd(sdm120_power_factor, 2, power_factor);
char frequency[33];
dtostrfd(sdm120_frequency, Settings.flag2.frequency_resolution, frequency);
char energy_total[33];
dtostrfd(sdm120_energy_total, Settings.flag2.energy_resolution, energy_total);
#ifdef USE_SDM220
char phase_angle[33];
dtostrfd(sdm120_phase_angle, 2, phase_angle);
char import_active[33];
dtostrfd(sdm120_import_active, Settings.flag2.wattage_resolution, import_active);
char export_active[33];
dtostrfd(sdm120_export_active, Settings.flag2.wattage_resolution, export_active);
char import_reactive[33];
dtostrfd(sdm120_import_reactive,Settings.flag2.wattage_resolution, import_reactive);
char export_reactive[33];
dtostrfd(sdm120_export_reactive,Settings.flag2.wattage_resolution, export_reactive);
char total_reactive[33];
dtostrfd(sdm120_total_reactive, Settings.flag2.wattage_resolution, total_reactive);
#endif
if (json) {
#ifdef USE_SDM220
ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_ACTIVE_POWERUSAGE "\":%s,\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_FREQUENCY "\":%s,\"" D_JSON_POWERFACTOR "\":%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_PHASE_ANGLE "\":%s,\"" D_JSON_IMPORT_ACTIVE "\":%s,\"" D_JSON_EXPORT_ACTIVE "\":%s,\"" D_JSON_IMPORT_REACTIVE "\":%s,\"" D_JSON_EXPORT_REACTIVE "\":%s,\"" D_JSON_TOTAL_REACTIVE "\":%s}"),
energy_total, active_power, apparent_power, reactive_power, frequency, power_factor, voltage, current, phase_angle, import_active, export_active, import_reactive, export_reactive, total_reactive);
#else
ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_ACTIVE_POWERUSAGE "\":%s,\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_FREQUENCY "\":%s,\"" D_JSON_POWERFACTOR "\":%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s}"),
energy_total, active_power, apparent_power, reactive_power, frequency, power_factor, voltage, current);
#endif
#ifdef USE_DOMOTICZ
if (0 == tele_period) {
char energy_total_chr[33];
dtostrfd(sdm120_energy_total * 1000, 1, energy_total_chr);
DomoticzSensor(DZ_VOLTAGE, voltage);
DomoticzSensor(DZ_CURRENT, current);
DomoticzSensorPowerEnergy((int)sdm120_active_power, energy_total_chr);
}
#endif
#ifdef USE_WEBSERVER
} else {
#ifdef USE_SDM220
WSContentSend_PD(HTTP_SNS_SDM120_DATA, voltage, current, active_power, apparent_power, reactive_power, power_factor, frequency, energy_total, phase_angle,import_active,export_active,import_reactive,export_reactive,total_reactive);
#else
WSContentSend_PD(HTTP_SNS_SDM120_DATA, voltage, current, active_power, apparent_power, reactive_power, power_factor, frequency, energy_total);
#endif
#endif
}
}
bool Xsns23(uint8_t function)
{
bool result = false;
if (sdm120_type) {
switch (function) {
case FUNC_INIT:
SDM120Init();
break;
case FUNC_EVERY_250_MSECOND:
SDM120250ms();
break;
case FUNC_JSON_APPEND:
SDM120Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
SDM120Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_24_si1145.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_24_si1145.ino"
#ifdef USE_I2C
#ifdef USE_SI1145
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_24_si1145.ino"
#define XSNS_24 24
#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
uint8_t si1145_type = 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);
}
void Si1145Update(void)
{
if (!si1145_type) {
if (Si1145Begin()) {
si1145_type = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "SI1145", SI114X_ADDR);
}
}
}
#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_type && Si1145Present()) {
uint16_t visible = Si1145ReadVisible();
uint16_t infrared = Si1145ReadIR();
uint16_t uvindex = Si1145ReadUV();
if (json) {
ResponseAppend_P(PSTR(",\"SI1145\":{\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_INFRARED "\":%d,\"" D_JSON_UV_INDEX "\":%d.%d}"),
visible, infrared, uvindex /100, uvindex %100);
#ifdef USE_DOMOTICZ
if (0 == tele_period) DomoticzSensor(DZ_ILLUMINANCE, visible);
#endif
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_SI1145, visible, infrared, uvindex /100, uvindex %100);
#endif
}
} else {
si1145_type = 0;
}
}
bool Xsns24(uint8_t function)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_25_sdm630.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_25_sdm630.ino"
#ifdef USE_SDM630
#define XSNS_25 25
#include <TasmotaSerial.h>
TasmotaSerial *SDM630Serial;
uint8_t sdm630_type = 1;
float sdm630_voltage[] = {0,0,0};
float sdm630_current[] = {0,0,0};
float sdm630_active_power[] = {0,0,0};
float sdm630_reactive_power[] = {0,0,0};
float sdm630_power_factor[] = {0,0,0};
float sdm630_energy_total = 0;
bool SDM630_ModbusReceiveReady(void)
{
return (SDM630Serial->available() > 1);
}
void SDM630_ModbusSend(uint8_t function_code, uint16_t start_address, uint16_t register_count)
{
uint8_t frame[8];
frame[0] = 0x01;
frame[1] = function_code;
frame[2] = (uint8_t)(start_address >> 8);
frame[3] = (uint8_t)(start_address);
frame[4] = (uint8_t)(register_count >> 8);
frame[5] = (uint8_t)(register_count);
uint16_t crc = SDM630_calculateCRC(frame, 6);
frame[6] = lowByte(crc);
frame[7] = highByte(crc);
while (SDM630Serial->available() > 0) {
SDM630Serial->read();
}
SDM630Serial->flush();
SDM630Serial->write(frame, sizeof(frame));
}
uint8_t SDM630_ModbusReceive(float *value)
{
uint8_t buffer[9];
*value = NAN;
uint8_t len = 0;
while (SDM630Serial->available() > 0) {
buffer[len++] = (uint8_t)SDM630Serial->read();
}
if (len < 9)
return 3;
if (len == 9) {
if (buffer[0] == 0x01 && buffer[1] == 0x04 && buffer[2] == 4) {
if((SDM630_calculateCRC(buffer, 7)) == ((buffer[8] << 8) | buffer[7])) {
((uint8_t*)value)[3] = buffer[3];
((uint8_t*)value)[2] = buffer[4];
((uint8_t*)value)[1] = buffer[5];
((uint8_t*)value)[0] = buffer[6];
} else return 1;
} else return 2;
}
return 0;
}
uint16_t SDM630_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;
}
const uint16_t sdm630_start_addresses[] {
0x0000,
0x0002,
0x0004,
0x0006,
0x0008,
0x000A,
0x000C,
0x000E,
0x0010,
0x0018,
0x001A,
0x001C,
0x001E,
0x0020,
0x0022,
0x0156
};
uint8_t sdm630_read_state = 0;
uint8_t sdm630_send_retry = 0;
void SDM630250ms(void)
{
float value = 0;
bool data_ready = SDM630_ModbusReceiveReady();
if (data_ready) {
uint8_t error = SDM630_ModbusReceive(&value);
if (error) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SDM630 response error %d"), error);
} else {
switch(sdm630_read_state) {
case 0:
sdm630_voltage[0] = value;
break;
case 1:
sdm630_voltage[1] = value;
break;
case 2:
sdm630_voltage[2] = value;
break;
case 3:
sdm630_current[0] = value;
break;
case 4:
sdm630_current[1] = value;
break;
case 5:
sdm630_current[2] = value;
break;
case 6:
sdm630_active_power[0] = value;
break;
case 7:
sdm630_active_power[1] = value;
break;
case 8:
sdm630_active_power[2] = value;
break;
case 9:
sdm630_reactive_power[0] = value;
break;
case 10:
sdm630_reactive_power[1] = value;
break;
case 11:
sdm630_reactive_power[2] = value;
break;
case 12:
sdm630_power_factor[0] = value;
break;
case 13:
sdm630_power_factor[1] = value;
break;
case 14:
sdm630_power_factor[2] = value;
break;
case 15:
sdm630_energy_total = value;
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;
SDM630_ModbusSend(0x04, sdm630_start_addresses[sdm630_read_state], 2);
} else {
sdm630_send_retry--;
}
}
void SDM630Init(void)
{
sdm630_type = 0;
if ((pin[GPIO_SDM630_RX] < 99) && (pin[GPIO_SDM630_TX] < 99)) {
SDM630Serial = new TasmotaSerial(pin[GPIO_SDM630_RX], pin[GPIO_SDM630_TX], 1);
#ifdef SDM630_SPEED
if (SDM630Serial->begin(SDM630_SPEED)) {
#else
if (SDM630Serial->begin(2400)) {
#endif
if (SDM630Serial->hardwareSerial()) { ClaimSerial(); }
sdm630_type = 1;
}
}
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_SDM630_DATA[] PROGMEM =
"{s}SDM630 " D_VOLTAGE "{m}%s/%s/%s " D_UNIT_VOLT "{e}"
"{s}SDM630 " D_CURRENT "{m}%s/%s/%s " D_UNIT_AMPERE "{e}"
"{s}SDM630 " D_POWERUSAGE_ACTIVE "{m}%s/%s/%s " D_UNIT_WATT "{e}"
"{s}SDM630 " D_POWERUSAGE_REACTIVE "{m}%s/%s/%s " D_UNIT_VAR "{e}"
"{s}SDM630 " D_POWER_FACTOR "{m}%s/%s/%s{e}"
"{s}SDM630 " D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
#endif
void SDM630Show(bool json)
{
char voltage_l1[33];
dtostrfd(sdm630_voltage[0], Settings.flag2.voltage_resolution, voltage_l1);
char voltage_l2[33];
dtostrfd(sdm630_voltage[1], Settings.flag2.voltage_resolution, voltage_l2);
char voltage_l3[33];
dtostrfd(sdm630_voltage[2], Settings.flag2.voltage_resolution, voltage_l3);
char current_l1[33];
dtostrfd(sdm630_current[0], Settings.flag2.current_resolution, current_l1);
char current_l2[33];
dtostrfd(sdm630_current[1], Settings.flag2.current_resolution, current_l2);
char current_l3[33];
dtostrfd(sdm630_current[2], Settings.flag2.current_resolution, current_l3);
char active_power_l1[33];
dtostrfd(sdm630_active_power[0], Settings.flag2.wattage_resolution, active_power_l1);
char active_power_l2[33];
dtostrfd(sdm630_active_power[1], Settings.flag2.wattage_resolution, active_power_l2);
char active_power_l3[33];
dtostrfd(sdm630_active_power[2], Settings.flag2.wattage_resolution, active_power_l3);
char reactive_power_l1[33];
dtostrfd(sdm630_reactive_power[0], Settings.flag2.wattage_resolution, reactive_power_l1);
char reactive_power_l2[33];
dtostrfd(sdm630_reactive_power[1], Settings.flag2.wattage_resolution, reactive_power_l2);
char reactive_power_l3[33];
dtostrfd(sdm630_reactive_power[2], Settings.flag2.wattage_resolution, reactive_power_l3);
char power_factor_l1[33];
dtostrfd(sdm630_power_factor[0], 2, power_factor_l1);
char power_factor_l2[33];
dtostrfd(sdm630_power_factor[1], 2, power_factor_l2);
char power_factor_l3[33];
dtostrfd(sdm630_power_factor[2], 2, power_factor_l3);
char energy_total[33];
dtostrfd(sdm630_energy_total, Settings.flag2.energy_resolution, energy_total);
if (json) {
ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL "\":%s,\""
D_JSON_ACTIVE_POWERUSAGE "\":[%s,%s,%s],\"" D_JSON_REACTIVE_POWERUSAGE "\":[%s,%s,%s],\""
D_JSON_POWERFACTOR "\":[%s,%s,%s],\"" D_JSON_VOLTAGE "\":[%s,%s,%s],\"" D_JSON_CURRENT "\":[%s,%s,%s]}"),
energy_total, active_power_l1, active_power_l2, active_power_l3,
reactive_power_l1, reactive_power_l2, reactive_power_l3,
power_factor_l1, power_factor_l2, power_factor_l3,
voltage_l1, voltage_l2, voltage_l3,
current_l1, current_l2, current_l3);
#ifdef USE_DOMOTICZ
if (0 == tele_period) {
char energy_total_chr[33];
dtostrfd(sdm630_energy_total * 1000, 1, energy_total_chr);
DomoticzSensor(DZ_VOLTAGE, voltage_l1);
DomoticzSensor(DZ_CURRENT, current_l1);
DomoticzSensorPowerEnergy((int)sdm630_active_power[0], energy_total_chr);
}
#endif
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_SDM630_DATA,
voltage_l1, voltage_l2, voltage_l3, current_l1, current_l2, current_l3,
active_power_l1, active_power_l2, active_power_l3,
reactive_power_l1, reactive_power_l2, reactive_power_l3,
power_factor_l1, power_factor_l2, power_factor_l3, energy_total);
#endif
}
}
bool Xsns25(uint8_t function)
{
bool result = false;
if (sdm630_type) {
switch (function) {
case FUNC_INIT:
SDM630Init();
break;
case FUNC_EVERY_250_MSECOND:
SDM630250ms();
break;
case FUNC_JSON_APPEND:
SDM630Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
SDM630Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_26_lm75ad.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_26_lm75ad.ino"
#ifdef USE_I2C
#ifdef USE_LM75AD
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_26_lm75ad.ino"
#define XSNS_26 26
#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
uint8_t lm75ad_type = 0;
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)
{
if (lm75ad_type) { return; }
uint16_t buffer;
for (uint32_t i = 0; i < sizeof(lm75ad_addresses); i++) {
lm75ad_address = lm75ad_addresses[i];
if (I2cValidRead16(&buffer, lm75ad_address, LM75_THYST_REGISTER)) {
if (buffer == 0x4B00) {
lm75ad_type = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "LM75AD", lm75ad_address);
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)
{
if (lm75ad_type) {
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_EVERY_SECOND:
LM75ADDetect();
break;
case FUNC_JSON_APPEND:
LM75ADShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
LM75ADShow(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
# 28 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
#ifdef USE_I2C
#ifdef USE_APDS9960
# 39 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
#define XSNS_27 27
#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[9];
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
# 96 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
# 305 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 324 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 391 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
# 447 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getLEDDrive(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ;
val = (val >> 6) & 0b00000011;
return val;
}
# 470 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
# 499 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getProximityGain(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ;
val = (val >> 2) & 0b00000011;
return val;
}
# 522 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
# 563 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
}
# 590 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getLEDBoost(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ;
val = (val >> 4) & 0b00000011;
return val;
}
# 614 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) ;
}
# 682 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getProxPhotoMask(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ;
val &= 0b00001111;
return val;
}
# 707 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) ;
}
# 785 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getGestureGain(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
val = (val >> 5) & 0b00000011;
return val;
}
# 809 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) ;
}
# 837 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getGestureLEDDrive(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
val = (val >> 3) & 0b00000011;
return val;
}
# 861 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) ;
}
# 893 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
uint8_t getGestureWaitTime(void)
{
uint8_t val;
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
val &= 0b00000111;
return val;
}
# 921 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) ;
}
# 1054 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
}
# 1332 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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) ;
}
# 1599 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_27_apds9960.ino"
void readAllColorAndProximityData(void)
{
if (I2cReadBuffer(APDS9960_I2C_ADDR, APDS9960_CDATAL, (uint8_t *) &color_data, (uint16_t)9))
{
}
}
# 1615 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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"));
}
}
mqtt_data[0] = '\0';
if (MqttShowSensor()) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
#ifdef USE_RULES
RulesTeleperiod();
#endif
}
}
}
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;
}
}
}
}
bool APDS9960_detect(void)
{
if (APDS9960type) {
return true;
}
bool success = false;
APDS9960type = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ID);
if (APDS9960type == APDS9960_CHIPID_1 || APDS9960type == APDS9960_CHIPID_2) {
strcpy_P(APDS9960stype, PSTR("APDS9960"));
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, APDS9960stype, APDS9960_I2C_ADDR);
if (APDS9960_init()) {
success = true;
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "APDS9960 initialized"));
enableProximitySensor();
enableGestureSensor();
}
}
else {
if (APDS9960type == APDS9930_CHIPID_1 || APDS9960type == APDS9930_CHIPID_2) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("APDS9930 found at address 0x%x, unsupported chip"), APDS9960_I2C_ADDR);
}
else{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("APDS9960 not found at address 0x%x"), APDS9960_I2C_ADDR);
}
}
currentGesture[0] = '\0';
return success;
}
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';
}
}
}
# 1979 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_28_tm1638.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_28_tm1638.ino"
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_29_mcp230xx.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_29_mcp230xx.ino"
#ifdef USE_I2C
#ifdef USE_MCP230xx
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_29_mcp230xx.ino"
#define XSNS_29 29
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 (mcp230xx_type) {
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;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "MCP23008", USE_MCP230xx_ADDR);
mcp230xx_pincount = 8;
MCP230xx_ApplySettings();
} else {
if (0x80 == buffer) {
mcp230xx_type = 2;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "MCP23017", USE_MCP230xx_ADDR);
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 (mcp230xx_type) {
if (json) {
if (mcp230xx_type > 0) {
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")) {
MCP230xx_SetOutPin(pin,abs(pincmd-1));
return serviced;
}
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "OFF")) {
MCP230xx_SetOutPin(pin,pincmd);
return serviced;
}
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "T")) {
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)) {
#else
if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 5) && (pullup < 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) {
if (0 == mcp230xx_type) { return; }
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}}"));
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
}
#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}}"));
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
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);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
bool Xsns29(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_EVERY_SECOND:
MCP230xx_Detect();
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
if (tele_period == 0) {
MCP230xx_OutputTelemetry();
}
#endif
break;
case FUNC_EVERY_50_MSECOND:
if ((mcp230xx_int_en) && (mcp230xx_type)) {
mcp230xx_int_prio_counter++;
if ((mcp230xx_int_prio_counter) >= (Settings.mcp230xx_int_prio)) {
MCP230xx_CheckForInterrupt();
mcp230xx_int_prio_counter=0;
}
}
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
default:
break;
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_30_mpr121.ino"
# 43 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_30_mpr121.ino"
#ifdef USE_I2C
#ifdef USE_MPR121
#define XSNS_30 30
#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)
# 191 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 };
};
# 211 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_30_mpr121.ino"
void Mpr121Init(struct mpr121 *pS)
{
for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) {
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]) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_I2C "MPR121(%c) " D_FOUND_AT " 0x%X"), pS->id[i], pS->i2c_addr[i]);
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"));
}
}
# 316 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
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);
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();
}
}
}
}
# 400 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_30_mpr121.ino"
bool Xsns30(uint8_t function)
{
bool result = false;
static struct mpr121 mpr121;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
Mpr121Init(&mpr121);
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_31_ccs811.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_31_ccs811.ino"
#ifdef USE_I2C
#ifdef USE_CCS811
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_31_ccs811.ino"
#define XSNS_31 31
#include "Adafruit_CCS811.h"
Adafruit_CCS811 ccs;
uint8_t CCS811_ready;
uint8_t CCS811_type;
uint16_t eCO2;
uint16_t TVOC;
uint8_t tcnt = 0;
uint8_t ecnt = 0;
#define EVERYNSECONDS 5
void CCS811Update(void)
{
tcnt++;
if (tcnt >= EVERYNSECONDS) {
tcnt = 0;
CCS811_ready = 0;
if (!CCS811_type) {
sint8_t res = ccs.begin(CCS811_ADDRESS);
if (!res) {
CCS811_type = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "CCS811", 0x5A);
} else {
}
} else {
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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_32_mpu6050.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_32_mpu6050.ino"
#ifdef USE_I2C
#ifdef USE_MPU6050
# 30 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_32_mpu6050.ino"
#define XSNS_32 32
#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];
} 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);
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();
}
# 116 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_32_mpu6050.ino"
void MPU_6050Detect(void)
{
if (MPU_6050_found)
{
return;
}
for (uint32_t i = 0; i < sizeof(MPU_6050_addresses); i++)
{
if(!I2cDevice(MPU_6050_addresses[i]))
{
break;
}
MPU_6050_address = MPU_6050_addresses[i];
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)
{
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, D_SENSOR_MPU6050, MPU_6050_address);
}
}
#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}";
#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"
void MPU_6050Show(bool json)
{
if (MPU_6050_found) {
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);
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);
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);
#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);
#endif
}
}
}
bool Xsns32(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_PREP_BEFORE_TELEPERIOD:
MPU_6050Detect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_33_ds3231.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_33_ds3231.ino"
#ifdef USE_I2C
#ifdef USE_DS3231
# 35 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_33_ds3231.ino"
#define XSNS_33 33
#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)
{
DS3231chipDetected = false;
if (I2cValidRead(USE_RTC_ADDR, RTC_STATUS, 1)) {
AddLog_P2(LOG_LEVEL_INFO, S_LOG_I2C_FOUND_AT, "DS3231", USE_RTC_ADDR);
DS3231chipDetected = true;
} else {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_I2C "DS3231 NOT " D_FOUND_AT " 0x%x"), USE_RTC_ADDR);
}
}
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));
}
bool Xsns33(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
DS3231Detect();
break;
case FUNC_EVERY_SECOND:
TIME_T tmpTime;
if (!ds3231ReadStatus && DS3231chipDetected && 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 && DS3231chipDetected && 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;
}
break;
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_34_hx711.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_34_hx711.ino"
#ifdef USE_HX711
# 35 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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"
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 last_weight = 0;
long sum_weight = 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;
} 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()
{
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")); }
}
# 168 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
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\"}}"),
Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, item, GetStateText(Settings.SensorBits1.hx711_json_weight_change));
}
return serviced;
}
long HxWeight()
{
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);
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)
{
Hx.sum_weight += HxRead();
Hx.sample_count++;
if (HX_SAMPLES == Hx.sample_count) {
long average = Hx.sum_weight / Hx.sample_count;
long value = average - Hx.offset;
Hx.weight = value / 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) > 4) {
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();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
Hx.weight_changed = false;
}
}
}
Hx.sum_weight = 0;
Hx.sample_count = 0;
}
}
void HxSaveBeforeRestart()
{
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}"), weight_chr, scount);
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_35_tx20.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_35_tx20.ino"
#ifdef USE_TX20_WIND_SENSOR
# 29 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_36_mgc3130.ino"
# 22 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_36_mgc3130.ino"
#ifdef USE_I2C
#ifdef USE_MGC3130
# 35 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_36_mgc3130.ino"
#define XSNS_36 36
#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[8];
#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_triggerTele(){
mqtt_data[0] = '\0';
if (MqttShowSensor()) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
#ifdef USE_RULES
RulesTeleperiod();
#endif
}
}
void MGC3130_handleSensorData(){
if ( MGC_data.out.outputConfigMask.touchInfo && MGC3130_touchTimeout == 0){
if (MGC3130_handleTouch()){
MGC3130_triggeredByTouch = true;
MGC3130_triggerTele();
}
}
if(MGC3130_mode == 1){
if( MGC_data.out.outputConfigMask.gestureInfo && MGC_data.out.gestureInfo.gestureCode > 0){
MGC3130_handleGesture();
MGC3130_triggerTele();
}
}
if(MGC3130_mode == 2){
if(MGC_data.out.outputConfigMask.airWheelInfo && MGC_data.out.systemInfo.airWheelValid){
MGC3130_handleAirWheel();
MGC3130_triggerTele();
}
}
if(MGC3130_mode == 3){
if(MGC_data.out.systemInfo.positionValid && (MGC_data.out.z > MGC3130_MIN_ZVALUE)){
MGC3130_triggerTele();
}
}
}
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();
}
bool MGC3130_detect(void)
{
if (MGC3130_type){
return true;
}
pinMode(MGC3130_xfer, INPUT_PULLUP);
pinMode(MGC3130_reset, OUTPUT);
digitalWrite(MGC3130_reset, LOW);
delay(10);
digitalWrite(MGC3130_reset, HIGH);
delay(50);
bool success = false;
success = MGC3130_receiveMessage();
if (success) {
strcpy_P(MGC3130stype, PSTR("MGC3130"));
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, MGC3130stype, MGC3130_I2C_ADDR);
MGC3130_currentGesture[0] = '\0';
MGC3130_type = true;
} else {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MGC3130 did not respond at address 0x%x"), MGC3130_I2C_ADDR);
}
return success;
}
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
}
}
# 575 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_37_rfsensor.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_37_rfsensor.ino"
#ifdef USE_RF_SENSOR
# 33 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_38_az7798.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_38_az7798.ino"
#ifdef USE_AZ7798
#define XSNS_38 38
# 112 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_39_max31855.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 (Settings.flag.temperature_conversion) ? ConvertTemp(result) : 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 (Settings.flag.temperature_conversion) ? ConvertTemp(result) : 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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_40_pn532.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
#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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_41_max44009.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_41_max44009.ino"
#ifdef USE_I2C
#ifdef USE_MAX44009
#define XSNS_41 41
#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 reg[8];
bool failed = false;
if (max44009_found) {
return;
}
uint8_t buffer1;
uint8_t buffer2;
for (uint32_t i = 0; 0 != max44009_addresses[i]; i++) {
max44009_address = max44009_addresses[i];
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()) {
max44009_found = 1;
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, max44009_types, max44009_address);
break;
}
}
}
}
}
void Max4409EverySecond(void)
{
if (max44009_found) {
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
Max4409Detect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_42_scd30.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_42_scd30.ino"
#ifdef USE_I2C
#ifdef USE_SCD30
#define XSNS_42 42
#define SCD30_MAX_MISSED_READS 3
#define SONOFF_SCD30_STATE_NO_ERROR 0
#define SONOFF_SCD30_STATE_ERROR_DATA_CRC 1
#define SONOFF_SCD30_STATE_ERROR_READ_MEAS 2
#define SONOFF_SCD30_STATE_ERROR_SOFT_RESET 3
#define SONOFF_SCD30_STATE_ERROR_I2C_RESET 4
#define SONOFF_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 = SONOFF_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;
bool Scd30Init()
{
int error;
bool i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99));
if (i2c_flg)
{
uint8_t major = 0;
uint8_t minor = 0;
uint16_t interval_sec;
scd30.begin();
error = scd30.getFirmwareVersion(&major, &minor);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: did not find an SCD30: 0x%lX", error);
AddLog(LOG_LEVEL_DEBUG);
#endif
return false;
}
else
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: found an SCD30: FW v%d.%d", major, minor);
AddLog(LOG_LEVEL_INFO);
#endif
}
error = scd30.getMeasurementInterval(&scd30Interval_sec);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: error getMeasurementInterval: 0x%lX", error);
AddLog(LOG_LEVEL_ERROR);
#endif
return false;
}
error = scd30.beginMeasuring();
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "Error: Scd30BeginMeasuring: 0x%lX", error);
AddLog(LOG_LEVEL_ERROR);
#endif
return false;
}
return true;
}
}
int Scd30Update()
{
int error = 0;
int16_t delta = 0;
scd30Loop_count++;
if (!scd30Found)
{
scd30Found = Scd30Init();
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "Scd30Update: found: %d ", scd30Found);
AddLog(LOG_LEVEL_INFO);
#endif
if (!scd30Found)
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "Scd30Update: found: %d ", scd30Found);
AddLog(LOG_LEVEL_INFO);
#endif
return (ERROR_SCD30_NOT_FOUND_ERROR);
}
}
else
{
if (scd30Loop_count > (scd30Interval_sec - 1))
{
switch (scd30ErrorState)
{
case SONOFF_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 = SONOFF_SCD30_STATE_ERROR_DATA_CRC;
scd30CrcError_count++;
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "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);
AddLog(LOG_LEVEL_ERROR);
#endif
break;
case ERROR_SCD30_CO2_ZERO:
scd30Co2Zero_count++;
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "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);
AddLog(LOG_LEVEL_ERROR);
#endif
break;
default:
{
scd30ErrorState = SONOFF_SCD30_STATE_ERROR_READ_MEAS;
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld", error, scd30Loop_count);
AddLog(LOG_LEVEL_ERROR);
#endif
return (error);
}
break;
}
}
break;
case SONOFF_SCD30_STATE_ERROR_DATA_CRC:
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "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(LOG_LEVEL_ERROR);
snprintf_P(log_data, sizeof(log_data), "SCD30: got CRC error, try again, counter: %ld", scd30Loop_count);
AddLog(LOG_LEVEL_ERROR);
#endif
scd30ErrorState = ERROR_SCD30_NO_ERROR;
}
break;
case SONOFF_SCD30_STATE_ERROR_READ_MEAS:
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "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(LOG_LEVEL_ERROR);
snprintf_P(log_data, sizeof(log_data), "SCD30: not answering, sending soft reset, counter: %ld", scd30Loop_count);
AddLog(LOG_LEVEL_ERROR);
#endif
scd30Reset_count++;
error = scd30.softReset();
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: resetting got error: 0x%lX", error);
AddLog(LOG_LEVEL_ERROR);
#endif
error >>= 8;
if (error == 4)
{
scd30ErrorState = SONOFF_SCD30_STATE_ERROR_SOFT_RESET;
}
else
{
scd30ErrorState = SONOFF_SCD30_STATE_ERROR_UNKNOWN;
}
}
else
{
scd30ErrorState = ERROR_SCD30_NO_ERROR;
}
}
break;
case SONOFF_SCD30_STATE_ERROR_SOFT_RESET:
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "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(LOG_LEVEL_ERROR);
snprintf_P(log_data, sizeof(log_data), "SCD30: clearing i2c bus");
AddLog(LOG_LEVEL_ERROR);
#endif
i2cReset_count++;
error = scd30.clearI2CBus();
if (error)
{
scd30ErrorState = SONOFF_SCD30_STATE_ERROR_I2C_RESET;
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: error clearing i2c bus: 0x%lX", error);
AddLog(LOG_LEVEL_ERROR);
#endif
}
else
{
scd30ErrorState = ERROR_SCD30_NO_ERROR;
}
}
break;
default:
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: unknown error state: 0x%lX", scd30ErrorState);
AddLog(LOG_LEVEL_ERROR);
#endif
scd30ErrorState = SONOFF_SCD30_STATE_ERROR_SOFT_RESET;
}
}
if (scd30Loop_count > (SCD30_MAX_MISSED_READS * scd30Interval_sec))
{
scd30IsDataValid = false;
}
}
}
return (ERROR_SCD30_NO_ERROR);
}
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
snprintf_P(log_data, sizeof(log_data), "SCD30: error getting FW version: 0x%lX", error);
AddLog(LOG_LEVEL_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)
{
char humidity[10];
char temperature[10];
if (scd30Found && scd30IsDataValid)
{
dtostrfd(ConvertHumidity(scd30_Humid), Settings.flag2.humidity_resolution, humidity);
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)
{
bool result = false;
if (i2c_flg) {
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_43_hre.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_43_hre.ino"
#ifdef USE_HRE
# 49 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_44_sps30.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_44_sps30.ino"
#ifdef USE_I2C
#ifdef USE_SPS30
#define XSNS_44 44
#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() {
if (!I2cDevice(SPS30_ADDR)) {
return;
}
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_ready) return;
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) {
char str[64];
if (!sps30_ready) {
return;
}
if (!sps30_running) {
return;
}
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\"}}"));
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
SPS30_Detect();
break;
case FUNC_EVERY_SECOND:
SPS30_Every_Second();
break;
case FUNC_JSON_APPEND:
SPS30_Show(1);
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_44 == XdrvMailbox.index) {
result = SPS30_cmd();
}
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
SPS30_Show(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_45_vl53l0x.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_45_vl53l0x.ino"
#ifdef USE_I2C
#ifdef USE_VL53L0X
#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()
{
if (!I2cDevice(0x29)) {
return;
}
if (vl53l0x_ready) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR("VL53L1X is ready"));
return;
}
if (sensor.init()==true) {
snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "VL53L0X", sensor.getAddress());
AddLog(LOG_LEVEL_DEBUG);
} else {
return;
}
sensor.setTimeout(500);
sensor.startContinuous();
vl53l0x_ready = 1;
Vl53l0_index=0;
}
#define D_UNIT_MILLIMETER "mm"
#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() {
uint16_t tbuff[5],tmp;
uint8_t flag;
if (!vl53l0x_ready) return;
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 (!vl53l0x_ready) {
return;
}
if (json) {
ResponseAppend_P(PSTR(",\"VL53L0X\":{\"" D_JSON_DISTANCE "\":%d}"), vl53l0x_distance);
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_VL53L0X, vl53l0x_distance);
#endif
}
}
#define XSNS_45 45
bool Xsns45(byte function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
Vl53l0Detect();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_46_MLX90614.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_46_MLX90614.ino"
#ifdef USE_I2C
#ifdef USE_MLX90614
#define XSNS_46 46
#define I2_ADR_IRT 0x5a
uint8_t mlx_ready;
float obj_temp;
float amb_temp;
void MLX90614_Init() {
if (!I2cDevice(I2_ADR_IRT)) {
return;
}
mlx_ready=1;
}
#define MLX90614_RAWIR1 0x04
#define MLX90614_RAWIR2 0x05
#define MLX90614_TA 0x06
#define MLX90614_TOBJ1 0x07
#define MLX90614_TOBJ2 0x08
uint16_t read_irtmp(uint8_t flag) {
uint8_t hig,low;
uint16_t val;
Wire.beginTransmission(I2_ADR_IRT);
if (!flag) Wire.write(MLX90614_TA);
else Wire.write(MLX90614_TOBJ1);
Wire.endTransmission(false);
Wire.requestFrom(I2_ADR_IRT, (uint8_t)3);
low=Wire.read();
hig=Wire.read();
Wire.read();
val=((uint16_t)hig<<8)|low;
return val;
}
void MLX90614_Every_Second(void) {
if (!mlx_ready) return;
uint16_t uval=read_irtmp(1);
if (uval&0x8000) {
obj_temp=-999;
} else {
obj_temp=((float)uval*0.02)-273.15;
}
uval=read_irtmp(0);
if (uval&0x8000) {
amb_temp=-999;
} else {
amb_temp=((float)uval*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}";
void MLX90614_Show(uint8_t json) {
if (!mlx_ready) return;
char obj_tstr[16];
dtostrfd(obj_temp, Settings.flag2.temperature_resolution, obj_tstr);
char amb_tstr[16];
dtostrfd(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
}
}
#endif
bool Xsns46(byte function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
MLX90614_Init();
break;
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_47_max31865.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_48_chirp.ino"
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_48_chirp.ino"
#ifdef USE_I2C
#ifdef USE_CHIRP
#define XSNS_48 48
#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
bool I2cWriteReg(uint8_t addr, uint8_t reg)
{
return I2cWrite(addr, reg, 0, 0);
}
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) {
I2cWriteReg(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) {
I2cWriteReg(addr, CHIRP_SLEEP);
}
# 168 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_48_chirp.ino"
void ChirpSelect(uint8_t sensor) {
if(sensor < chirp_found_sensors) {
chirp_current = sensor;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: Sensor %u now active."), chirp_current);
}
if (sensor == 255) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: Sensor %u active at address 0x%x."), chirp_current, chirp_sensor[chirp_current].address);
}
}
bool ChirpMeasureLight(void) {
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) {
uint8_t lightReady = I2cRead8(chirp_sensor[i].address, CHIRP_GET_BUSY);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: busy status for light for sensor %u"), lightReady);
if (lightReady == 1) {
return false;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: init measure light for sensor %u"), i);
I2cWriteReg(chirp_sensor[i].address, CHIRP_MEASURE_LIGHT);
}
}
return true;
}
void ChirpReadCapTemp() {
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: now really read CapTemp for sensor at address 0x%x"), chirp_sensor[i].address);
chirp_sensor[i].moisture = I2cRead16(chirp_sensor[i].address, CHIRP_GET_CAPACITANCE);
chirp_sensor[i].temperature = I2cRead16(chirp_sensor[i].address, CHIRP_GET_TEMPERATURE);
}
}
}
bool ChirpReadLight() {
bool success = false;
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: will read light for sensor %u"), i);
if (chirp_sensor[i].version) {
if (I2cValidRead16(&chirp_sensor[i].light, chirp_sensor[i].address, CHIRP_GET_LIGHT)){
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: light read success"));
success = true;
}
if(!chirp_sensor[i].explicitSleep){ success = true;}
}
}
return success;
}
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)){
I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: Wrote adress %u "), addr);
ChirpReset(chirp_sensor[chirp_current].address);
chirp_sensor[chirp_current].address = addr;
return true;
}
}
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) {
AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, "CHIRP:", address);
if(chirp_found_sensors<CHIRP_MAX_SENSOR_COUNT){
chirp_sensor[chirp_found_sensors].address = address;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: fw %u"), chirp_sensor[chirp_found_sensors].version);
}
chirp_found_sensors++;
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Found %u CHIRP sensor(s)."), chirp_found_sensors);
if (chirp_found_sensors == 0) {return false;}
else {return true;}
}
void ChirpDetect(void)
{
if (chirp_next_job > 0) {
return;
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: scan will start ..."));
if (ChirpScan()) {
uint8_t chirp_model = 0;
GetTextIndexed(chirp_name, sizeof(chirp_name), chirp_model, kChirpTypes);
}
}
void ChirpEverySecond(void)
{
if(chirp_timeout_count == 0) {
switch(chirp_next_job) {
case 0:
AddLog_P2(LOG_LEVEL_DEBUG,PSTR( "CHIRP: reset all"));
ChirpResetAll();
chirp_timeout_count = 1;
chirp_next_job++;
break;
case 1:
chirp_next_job++;
break;
case 2:
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: call CapTemp twice"));
ChirpReadCapTemp();
ChirpReadCapTemp();
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: call measure light"));
ChirpMeasureLight();
chirp_timeout_count = 2;
chirp_next_job++;
break;
case 3:
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: call read light"));
if (ChirpReadLight()){
chirp_next_job++;
}
break;
case 4:
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: paused, waiting for TELE"));
break;
case 5:
if (Settings.tele_period > 9){
chirp_timeout_count = Settings.tele_period - 10;
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: timeout: %u, tele: %u"), chirp_timeout_count, Settings.tele_period);
}
chirp_next_job = 1;
break;
}
}
else {
chirp_timeout_count--;
}
}
#define D_JSON_MOISTURE "Moisture"
#ifdef USE_WEBSERVER
const char HTTP_SNS_MOISTURE[] PROGMEM = "{s} " D_JSON_MOISTURE ": {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_moisture[33];
dtostrfd(chirp_sensor[i].moisture, 0, str_moisture);
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[33];
dtostrfd(chirp_sensor[i].version, 0, str_version);
if (json) {
if(!chirp_sensor[i].explicitSleep){
ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%s,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_ILLUMINANCE "\":\"%s}"),
chirp_name, i, str_moisture, str_temperature, str_light);}
else {
ResponseAppend_P(PSTR(",\"%s%u\":{\"sleeping\"}"),
chirp_name, i);
}
#ifdef USE_DOMOTICZ
if (0 == tele_period) {
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, str_moisture);
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, " ", chirp_sensor[i].light);
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)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
ChirpDetect();
break;
case FUNC_EVERY_SECOND:
if(chirp_found_sensors > 0){
ChirpEverySecond();
}
break;
case FUNC_COMMAND:
result = ChirpCmd();
break;
case FUNC_JSON_APPEND:
ChirpShow(1);
chirp_next_job = 5;
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
ChirpShow(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_49_solaxX1.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_49_solaxX1.ino"
#ifdef USE_SOLAX_X1
#define XSNS_49 49
#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;
uint8_t solaxX1_status = 0;
uint32_t solaxX1_errorCode = 0;
float solaxX1_temperature = 0;
float solaxX1_energy_today = 0;
float solaxX1_dc1_voltage = 0;
float solaxX1_dc2_voltage = 0;
float solaxX1_dc1_current = 0;
float solaxX1_dc2_current = 0;
float solaxX1_ac_current = 0;
float solaxX1_ac_voltage = 0;
float solaxX1_frequency = 0;
float solaxX1_power = 0;
float solaxX1_energy_total = 0;
float solaxX1_runtime_total = 0;
float solaxX1_dc1_power = 0;
float solaxX1_dc2_power = 0;
bool queryOffline = false;
bool queryOfflineSend = false;
bool hasAddress = true;
bool inverterAddressSend = false;
bool inverterSnReceived = false;
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(uint8_t *msg, uint16_t msgLen)
{
uint16_t crc = solaxX1_calculateCRC(msg, msgLen - 1);
while (solaxX1Serial->available() > 0)
{
solaxX1Serial->read();
}
solaxX1Serial->flush();
solaxX1Serial->write(msg, msgLen);
solaxX1Serial->write(highByte(crc));
solaxX1Serial->write(lowByte(crc));
}
uint8_t solaxX1_RS485Receive(uint8_t *value)
{
uint8_t len = 0;
while (solaxX1Serial->available() > 0)
{
value[len++] = (uint8_t)solaxX1Serial->read();
}
uint16_t crc = solaxX1_calculateCRC(value, len - 3);
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_setMessage(uint8_t *message)
{
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));
}
void solaxX1_SendInverterAddress()
{
source[0] = 0x00;
destination[0] = 0x00;
destination[1] = 0x00;
controlCode[0] = 0x10;
functionCode[0] = 0x01;
dataLength[0] = 0x0F;
data[14] = INVERTER_ADDRESS;
solaxX1_setMessage(message);
solaxX1_RS485Send(message, 24);
}
void solaxX1_QueryLiveData()
{
source[0] = 0x01;
destination[0] = 0x00;
destination[1] = INVERTER_ADDRESS;
controlCode[0] = 0x11;
functionCode[0] = 0x02;
dataLength[0] = 0x00;
solaxX1_setMessage(message);
solaxX1_RS485Send(message, 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 solaxX1_Update(void)
{
uint8_t value[61] = {0};
bool data_ready = solaxX1_RS485ReceiveReady();
DEBUG_SENSOR_LOG(PSTR("SX1: queryOffline: %d , queryOfflineSend: %d, hasAddress: %d, inverterAddressSend: %d, solaxX1_send_retry: %d"),
queryOffline, queryOfflineSend, hasAddress, inverterAddressSend, solaxX1_send_retry);
if (!hasAddress && (data_ready || solaxX1_send_retry == 0))
{
if (data_ready)
{
if (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)
{
inverterAddressSend = false;
queryOfflineSend = false;
hasAddress = true;
}
}
}
if (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 && inverterSnReceived == false)
{
for (uint8_t i = 9; i <= 22; i++)
{
data[i - 9] = value[i];
}
inverterSnReceived = true;
}
solaxX1_SendInverterAddress();
inverterAddressSend = true;
queryOfflineSend = false;
queryOffline = false;
}
}
}
if (queryOffline)
{
source[0] = 0x01;
destination[1] = 0x00;
controlCode[0] = 0x10;
functionCode[0] = 0x00;
dataLength[0] = 0x00;
solaxX1_setMessage(message);
solaxX1_RS485Send(message, 9);
queryOfflineSend = true;
queryOffline = false;
}
if (solaxX1_send_retry == 0)
{
if (inverterAddressSend)
{
solaxX1_SendInverterAddress();
}
if (queryOfflineSend)
{
queryOffline = true;
queryOfflineSend = false;
}
solaxX1_send_retry = 2;
}
}
if (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 = 2;
uint32_t temporal = 0;
temporal = (value[9] << 8) | value[10];
solaxX1_temperature = temporal;
temporal = (value[11] << 8) | value[12];
solaxX1_energy_today = temporal * 0.1f;
temporal = (value[13] << 8) | value[14];
solaxX1_dc1_voltage = temporal * 0.1f;
temporal = (value[15] << 8) | value[16];
solaxX1_dc2_voltage = temporal * 0.1f;
temporal = (value[17] << 8) | value[18];
solaxX1_dc1_current = temporal * 0.1f;
temporal = (value[19] << 8) | value[20];
solaxX1_dc2_current = temporal * 0.1f;
temporal = (value[21] << 8) | value[22];
solaxX1_ac_current = temporal * 0.1f;
temporal = (value[23] << 8) | value[24];
solaxX1_ac_voltage = temporal * 0.1f;
temporal = (value[25] << 8) | value[26];
solaxX1_frequency = temporal * 0.01f;
temporal = (value[27] << 8) | value[28];
solaxX1_power = temporal;
temporal = (value[31] << 8) | (value[32] << 8) | (value[33] << 8) | value[34];
solaxX1_energy_total = temporal * 0.1f;
temporal = (value[35] << 8) | (value[36] << 8) | (value[37] << 8) | value[38];
solaxX1_runtime_total = temporal;
temporal = (value[39] << 8) | value[40];
solaxX1_status = (uint8_t)temporal;
# 412 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_49_solaxX1.ino"
temporal = (value[58] << 8) | (value[57] << 8) | (value[56] << 8) | value[55];
solaxX1_errorCode = (uint32_t)temporal;
solaxX1_dc1_power = solaxX1_dc1_voltage * solaxX1_dc1_current;
solaxX1_dc2_power = solaxX1_dc2_voltage * solaxX1_dc2_current;
solaxX1_QueryLiveData();
}
}
if (solaxX1_send_retry == 0)
{
solaxX1_send_retry = 2;
solaxX1_QueryLiveData();
}
}
else
{
if (solaxX1_nodata_count <= 10)
{
solaxX1_nodata_count++;
}
else if (solaxX1_nodata_count != 255)
{
solaxX1_nodata_count = 255;
queryOffline = true;
queryOfflineSend = false;
hasAddress = false;
inverterAddressSend = false;
inverterSnReceived = false;
solaxX1_temperature = solaxX1_dc1_voltage = solaxX1_dc2_voltage = solaxX1_dc1_current = solaxX1_dc2_current = solaxX1_ac_current = 0;
solaxX1_ac_voltage = solaxX1_frequency = solaxX1_power = solaxX1_dc1_power = solaxX1_dc2_power = solaxX1_status = 0;
}
}
if (!data_ready)
solaxX1_send_retry--;
}
void solaxX1Init(void)
{
AddLog_P(LOG_LEVEL_DEBUG, PSTR("Solax X1 Inverter Init"));
DEBUG_SENSOR_LOG(PSTR("SX1: RX pin: %d, TX pin: %d"), pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX]);
solaxX1_Init = 0;
if ((pin[GPIO_SOLAXX1_RX] < 99) && (pin[GPIO_SOLAXX1_TX] < 99))
{
solaxX1Serial = new TasmotaSerial(pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX], 1);
if (solaxX1Serial->begin(SOLAXX1_SPEED))
{
if (solaxX1Serial->hardwareSerial())
{
ClaimSerial();
}
solaxX1_Init = 1;
}
}
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_solaxX1_DATA1[] PROGMEM =
"{s}" D_SOLAX_X1 " " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
"{s}" D_SOLAX_X1 " " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
"{s}" D_SOLAX_X1 " " D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"
"{s}" D_SOLAX_X1 " " D_INVERTER_POWER "{m}%s " D_UNIT_WATT "{e}"
"{s}" D_SOLAX_X1 " " D_SOLAR_POWER "{m}%s " D_UNIT_WATT "{e}"
"{s}" D_SOLAX_X1 " " D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
"{s}" D_SOLAX_X1 " " D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{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 voltage[33];
dtostrfd(solaxX1_ac_voltage, Settings.flag2.voltage_resolution, voltage);
char current[33];
dtostrfd(solaxX1_ac_current, Settings.flag2.current_resolution, current);
char inverter_power[33];
dtostrfd(solaxX1_power, Settings.flag2.wattage_resolution, inverter_power);
char solar_power[33];
dtostrfd(solaxX1_dc1_power + solaxX1_dc2_power, Settings.flag2.wattage_resolution, solar_power);
char frequency[33];
dtostrfd(solaxX1_frequency, Settings.flag2.frequency_resolution, frequency);
char energy_total[33];
dtostrfd(solaxX1_energy_total, Settings.flag2.energy_resolution, energy_total);
char energy_today[33];
dtostrfd(solaxX1_energy_today, Settings.flag2.energy_resolution, energy_today);
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_RSLT_ENERGY "\":{\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_ACTIVE_POWERUSAGE "\":%s,\""
D_JSON_SOLAR_POWER "\":%s,\"" D_JSON_FREQUENCY "\":%s,\"" D_JSON_TOTAL "\":%s,\"" D_JSON_TODAY "\":%s,\""
D_JSON_PV1_VOLTAGE "\":%s,\"" D_JSON_PV1_CURRENT "\":%s,\"" D_JSON_PV1_POWER "\":%s"),
voltage, current, inverter_power,
solar_power, frequency, energy_total, energy_today,
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_DOMOTICZ
if (0 == tele_period)
{
char energy_total_chr[33];
dtostrfd(solaxX1_energy_total * 1000, 1, energy_total_chr);
DomoticzSensor(DZ_VOLTAGE, voltage);
DomoticzSensor(DZ_CURRENT, current);
if (solaxX1_temperature > 0) DomoticzSensor(DZ_TEMP, temperature);
if (solaxX1_energy_total > 0) DomoticzSensorPowerEnergy((int)solaxX1_power, energy_total_chr);
}
#endif
#ifdef USE_WEBSERVER
}
else
{
WSContentSend_PD(HTTP_SNS_solaxX1_DATA1, voltage, current, frequency, inverter_power, solar_power, energy_total, energy_today, 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 Xsns49(uint8_t function)
{
bool result = false;
if (solaxX1_Init)
{
switch (function)
{
case FUNC_INIT:
solaxX1Init();
break;
case FUNC_EVERY_SECOND:
solaxX1_Update();
break;
case FUNC_JSON_APPEND:
solaxX1Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
solaxX1Show(0);
break;
#endif
}
}
return result;
}
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_50_paj7620.ino"
# 31 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_50_paj7620.ino"
#ifdef USE_I2C
#ifdef USE_PAJ7620
#define XSNS_50 50
#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}
};
#define D_CMND_PAJ7620 "PAJ7620"
const char S_JSON_PAJ7620_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_PAJ7620 "%s\":%d}";
const char kPAJ7620Types[] PROGMEM = "PAJ7620";
const uint8_t PAJ7620_PIN[]= {1,2,3,4};
void PAJ7620SelectBank(uint8_t bank)
{
switch(bank){
case 0:
I2cWrite(PAJ7620_ADDR, PAJ7620_BANK_SEL, 0, 1);
break;
case 1:
I2cWrite(PAJ7620_ADDR, PAJ7620_BANK_SEL, 1, 1);
break;
default:
break;
}
}
void PAJ7620TriggerTele(){
mqtt_data[0] = '\0';
if (MqttShowSensor()) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
#ifdef USE_RULES
RulesTeleperiod();
#endif
}
}
char PAJ7620_name[9];
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 PAJ7620DecodeGesture(void)
{
switch (PAJ7620_gesture.current) {
case PAJ7620_DOWN:
DEBUG_SENSOR_LOG(PSTR("DOWN"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("Down"));
if(PAJ7620_gesture.unfinished){
PAJ7620_finished_gesture = true;
break;
}
PAJ7620_gesture.unfinished = PAJ7620_gesture.current;
PAJ7620_timeout_counter = 5;
break;
case PAJ7620_UP:
DEBUG_SENSOR_LOG(PSTR("UP"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("Up"));
if(PAJ7620_gesture.unfinished){
PAJ7620_finished_gesture = true;
break;
}
PAJ7620_gesture.unfinished = PAJ7620_gesture.current;
PAJ7620_timeout_counter = 5;
break;
case PAJ7620_RIGHT:
DEBUG_SENSOR_LOG(PSTR("RIGHT"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("Right"));
if(PAJ7620_gesture.unfinished){
PAJ7620_finished_gesture = true;
break;
}
PAJ7620_gesture.unfinished = PAJ7620_gesture.current;
PAJ7620_timeout_counter = 5;
break;
case PAJ7620_LEFT:
DEBUG_SENSOR_LOG(PSTR("LEFT"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("Left"));
if(PAJ7620_gesture.unfinished){
PAJ7620_finished_gesture = true;
break;
}
PAJ7620_gesture.unfinished = PAJ7620_gesture.current;
PAJ7620_timeout_counter = 5;
break;
case PAJ7620_NEAR:
DEBUG_SENSOR_LOG(PSTR("NEAR"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("Near"));
PAJ7620_finished_gesture = true;
PAJ7620_timeout_counter = 25;
break;
case PAJ7620_FAR:
DEBUG_SENSOR_LOG(PSTR("FAR"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("Far"));
PAJ7620_finished_gesture = true;
PAJ7620_timeout_counter = 25;
break;
case PAJ7620_CW:
DEBUG_SENSOR_LOG(PSTR("ClockWise"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("CW"));
PAJ7620_finished_gesture = true;
break;
case PAJ7620_CCW:
DEBUG_SENSOR_LOG(PSTR("CounterClockWise"));
snprintf_P(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), PSTR("CCW"));
PAJ7620_finished_gesture = true;
break;
default:
if(PAJ7620_gesture.unfinished){
PAJ7620_finished_gesture = true;
break;
}
break;
}
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;
PAJ7620TriggerTele();
}
}
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("PAJ7620: 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("PAJ7620: Proximity: %u"),PAJ7620_state.proximity );
PAJ7620TriggerTele();
}
}
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("PAJ7620: 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;
default:
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("PAJ7620: 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("PAJ7620: PIN valid!!"));
PAJ7620_state.PIN.countdown = 0;
}
PAJ7620TriggerTele();
}
}
break;
default:
break;
}
}
void PAJ7620Detect(void)
{
DEBUG_SENSOR_LOG(PSTR("PAJ7620: scan will start ..."));
PAJ7620SelectBank(0);
PAJ7620SelectBank(0);
uint16_t PAJ7620_id = I2cRead16LE(PAJ7620_ADDR,0);
uint8_t PAJ7620_ver = I2cRead8(PAJ7620_ADDR,2);
if (PAJ7620_id == 0x7620) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAJ7620: sensor found with ID: 0x%x and VER: %u"), PAJ7620_id, PAJ7620_ver);
uint8_t PAJ7620_model = 0;
GetTextIndexed(PAJ7620_name, sizeof(PAJ7620_name), PAJ7620_model, kPAJ7620Types);
PAJ7620_next_job = 1;
}
else {
DEBUG_SENSOR_LOG(PSTR("PAJ7620: sensor not found, false ID 0x%x"), PAJ7620_id);
PAJ7620_next_job = 255;
}
}
void PAJ7620Init(void)
{
DEBUG_SENSOR_LOG(PSTR("PAJ7620: 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("%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("PAJ7620: init sensor done %u"),millis());
PAJ7620_next_job = 2;
}
void PAJ7620SelectMode(uint16_t mode){
DEBUG_SENSOR_LOG(PSTR("PAJ7620: set mode to %u"),mode);
switch(mode){
case 0:
PAJ7620_mode = 0;
break;
case 1:
PAJ7620_mode = 1;
break;
case 2:
PAJ7620_mode = 2;
break;
case 3:
PAJ7620_mode = 3;
break;
case 4:
PAJ7620_mode = 4;
break;
case 5:
PAJ7620_mode = 5;
break;
default:
break;
}
}
void PAJ7620Loop(void)
{
if(PAJ7620_timeout_counter == 0){
switch(PAJ7620_next_job){
case 0:
PAJ7620Detect();
break;
case 1:
PAJ7620Init();
break;
case 2:
if(PAJ7620_mode != 0){
PAJ7620ReadGesture();
}
break;
default:
break;
}
}
else {
PAJ7620_timeout_counter--;
}
}
#define D_JSON_PAJ7620 "PAJ7620"
#ifdef USE_WEBSERVER
const char HTTP_SNS_PAJ7620[] PROGMEM = "{s} " D_JSON_PAJ7620 ": {m}%s {e}";
const char HTTP_SNS_PAJ7620VER[] PROGMEM = "{s} PAJ7620 at address: {m}0x73{e}"
"{s} version: {m}1 {e}";
#endif
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:
if(PAJ7620_mode>1){
ResponseAppend_P(PSTR(",\"%s\":{\"Proximity\":%u}"), PAJ7620_name, PAJ7620_state.proximity);
}
break;
case 3:
if(PAJ7620_mode>1 && PAJ7620_state.corner>0){
ResponseAppend_P(PSTR(",\"%s\":{\"Corner\":%u}"), PAJ7620_name, PAJ7620_state.corner);
}
break;
case 4:
if(PAJ7620_mode>1 && PAJ7620_state.PIN.valid){
ResponseAppend_P(PSTR(",\"%s\":{\"PIN\":%u}"), PAJ7620_name, 1);
PAJ7620_state.PIN.valid = 0;
}
break;
case 5:
if(PAJ7620_mode>1){
ResponseAppend_P(PSTR(",\"%s\":{\"x\":%u,\"y\":%u}"), PAJ7620_name, PAJ7620_state.x, PAJ7620_state.y);
}
break;
default:
break;
}
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_PAJ7620VER);
#endif
}
}
bool PAJ7620Cmd(void) {
bool serviced = true;
if (XdrvMailbox.data_len > 0) {
DEBUG_SENSOR_LOG(PSTR("PAJ7620: got argument for mode"));
PAJ7620SelectMode(XdrvMailbox.payload);
Response_P(S_JSON_PAJ7620_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload);
}
else {
DEBUG_SENSOR_LOG(PSTR("PAJ7620: show mode"));
Response_P(S_JSON_PAJ7620_COMMAND_NVALUE, XdrvMailbox.command, PAJ7620_mode);
}
return serviced;
}
bool Xsns50(uint8_t function)
{
bool result = false;
if (i2c_flg) {
switch (function) {
case FUNC_INIT:
DEBUG_SENSOR_LOG(PSTR("PAJ7620: 1 second until init"));
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_50 == XdrvMailbox.index){
result = PAJ7620Cmd();
}
break;
case FUNC_EVERY_100_MSECOND:
if(PAJ7620_next_job <255) {
PAJ7620Loop();
}
break;
case FUNC_JSON_APPEND:
PAJ7620Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
PAJ7620Show(0);
break;
#endif
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_51_rdm6300.ino"
# 21 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_52_ibeacon.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_52_ibeacon.ino"
#ifdef USE_IBEACON
#define XSNS_52 52
#include <TasmotaSerial.h>
#define HM17_BAUDRATE 9600
#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+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_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)) {
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
# 466 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_53_sml.ino"
# 24 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_53_sml.ino"
#ifdef USE_SML_M
#define XSNS_53 53
#define SML_BAUDRATE 9600
# 45 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_53_sml.ino"
#include <TasmotaSerial.h>
#define SPECIAL_SS
#if DMY_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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_53_sml.ino"
#define USE_SML_MEDIAN_FILTER
#define MAX_VARS 20
#define MAX_METERS 5
double meter_vars[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[MAX_VARS];
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);
# 597 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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();
}
void Dump2log(void) {
int16_t index=0,hcnt=0;
uint32_t d_lastms;
uint8_t dchars[16];
if (!SML_SAVAILABLE) return;
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 {
index=0;
log_data[index]=':';
index++;
log_data[index]=' ';
index++;
d_lastms=millis();
while ((millis()-d_lastms)<40) {
if (SML_SAVAILABLE) {
if (meter_desc_p[(dump2log&7)-1].type=='o') {
char c=SML_SREAD&0x7f;
if (c=='\n' || c=='\r') break;
log_data[index]=c;
index++;
} else {
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>0) {
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!='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') {
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!='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>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>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>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[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++;
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);
ebus_dval=val;
mp+=4;
cp+=2;
}
else if (*mp=='s' && *(mp+1)=='s') {
int8_t val = *cp++;
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!='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<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);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
}
}
}
}
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<MAX_VARS-1) {
index++;
}
mp = strchr(cp, '|');
if (mp) mp++;
}
if (json) {
ResponseAppend_P(PSTR("}"));
} else {
}
#ifdef USE_DOMOTICZ
if (json && !tele_period) {
char str[16];
dtostrfd(meter_vars[0], 1, str);
DomoticzSensorPowerEnergy(meter_vars[1], str);
dtostrfd(meter_vars[2], 1, str);
DomoticzSensor(DZ_VOLTAGE, str);
dtostrfd(meter_vars[3], 1, str);
DomoticzSensor(DZ_CURRENT, str);
}
#endif
}
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) {
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]);
}
}
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
#define METER_DEF_SIZE 2000
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<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], FALLING);
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=='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()) { ClaimSerial(); }
}
}
}
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') {
*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=='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);
}
# 2277 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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++;
dump2log=atoi(cp);
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];
}
}
# 2336 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_54_ina226.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_54_ina226.ino"
#ifdef USE_I2C
#ifdef USE_INA226
# 69 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_54_ina226.ino"
#define XSNS_54 54
#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_NONE, PSTR("%s: %s"), str, fstr );
}
# 136 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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 Ina226Init()
{
uint32_t i;
slavesFound = 0;
Ina226SlaveInfo_t *p = slaveInfo;
AddLog_P2( LOG_LEVEL_NONE, "Size of Settings: %d bytes", sizeof(Settings));
if (!i2c_flg)
AddLog_P2(LOG_LEVEL_DEBUG, "INA226: Initialization failed: No I2C support");
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 (!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);
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:
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\":%02x,\"" 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
}
}
}
# 532 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_54_ina226.ino"
bool Xsns54(byte callback_id) {
bool result = false;
if(i2c_flg) {
switch (callback_id) {
case FUNC_EVERY_SECOND:
Ina226EverySecond();
break;
case FUNC_INIT:
Ina226Init();
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;
}
}
return result;
}
#endif
#endif
# 1 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/xsns_interface.ino"
# 20 "/Users/gerhardmutz1/Desktop/Smart-Home/Tasmota/Development/Sonoff-Tasmota/sonoff/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;
#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;
}