# 1 "C:\\Users\\martin\\AppData\\Local\\Temp\\tmpeqetuoko" #include # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino" # 29 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino" #include #include "tasmota_version.h" #include "tasmota.h" #include "my_user_config.h" #ifdef USE_MQTT_TLS #include #endif #include "tasmota_post.h" #include "i18n.h" #include "tasmota_template.h" #ifdef ARDUINO_ESP8266_RELEASE_2_4_0 #include "lwip/init.h" #if LWIP_VERSION_MAJOR != 1 #error Please use stable lwIP v1.4 #endif #endif #include #include #include #include #ifdef USE_ARDUINO_OTA #include #ifndef USE_DISCOVERY #define USE_DISCOVERY #endif #endif #ifdef USE_DISCOVERY #include #endif #ifdef USE_I2C #include #endif #ifdef USE_SPI #include #endif #include "settings.h" WiFiUDP PortUdp; unsigned long feature_drv1; unsigned long feature_drv2; unsigned long feature_sns1; unsigned long feature_sns2; unsigned long feature5; unsigned long feature6; unsigned long serial_polling_window = 0; unsigned long state_second = 0; unsigned long state_50msecond = 0; unsigned long state_100msecond = 0; unsigned long state_250msecond = 0; unsigned long pulse_timer[MAX_PULSETIMERS] = { 0 }; unsigned long blink_timer = 0; unsigned long backlog_delay = 0; power_t power = 0; power_t last_power = 0; power_t blink_power; power_t blink_mask = 0; power_t blink_powersave; power_t latching_power = 0; power_t rel_inverted = 0; int serial_in_byte_counter = 0; int ota_state_flag = 0; int ota_result = 0; int restart_flag = 0; int wifi_state_flag = WIFI_RESTART; int blinks = 201; uint32_t uptime = 0; uint32_t loop_load_avg = 0; uint32_t global_update = 0; uint32_t web_log_index = 1; float global_temperature = 9999; float global_humidity = 0; float global_pressure = 0; uint16_t tele_period = 9999; uint16_t blink_counter = 0; uint16_t seriallog_timer = 0; uint16_t syslog_timer = 0; int16_t save_data_counter; RulesBitfield rules_flag; uint8_t mqtt_cmnd_blocked = 0; uint8_t mqtt_cmnd_blocked_reset = 0; uint8_t state_250mS = 0; uint8_t latching_relay_pulse = 0; uint8_t sleep; uint8_t blinkspeed = 1; uint8_t pin[GPIO_MAX]; uint8_t active_device = 1; uint8_t leds_present = 0; uint8_t led_inverted = 0; uint8_t led_power = 0; uint8_t ledlnk_inverted = 0; uint8_t pwm_inverted = 0; uint8_t energy_flg = 0; uint8_t light_flg = 0; uint8_t light_type = 0; uint8_t serial_in_byte; uint8_t ota_retry_counter = OTA_ATTEMPTS; uint8_t devices_present = 0; uint8_t seriallog_level; uint8_t syslog_level; uint8_t my_module_type; uint8_t my_adc0; uint8_t last_source = 0; uint8_t shutters_present = 0; uint8_t prepped_loglevel = 0; bool serial_local = false; bool fallback_topic_flag = false; bool backlog_mutex = false; bool interlock_mutex = false; bool stop_flash_rotate = false; bool blinkstate = false; bool pwm_present = false; bool i2c_flg = false; bool spi_flg = false; bool soft_spi_flg = false; bool ntp_force_sync = false; bool is_8285 = false; myio my_module; gpio_flag my_module_flag; StateBitfield global_state; char my_version[33]; char my_image[33]; char my_hostname[33]; char mqtt_client[TOPSZ]; char mqtt_topic[TOPSZ]; char serial_in_buffer[INPUT_BUFFER_SIZE]; char mqtt_data[MESSZ]; char log_data[LOGSZ]; char web_log[WEB_LOG_SIZE] = {'\0'}; #ifdef SUPPORT_IF_STATEMENT #include LinkedList backlog; #define BACKLOG_EMPTY (backlog.size() == 0) #else uint8_t backlog_index = 0; uint8_t backlog_pointer = 0; String backlog[MAX_BACKLOG]; #define BACKLOG_EMPTY (backlog_pointer == backlog_index) #endif void setup(void); void BacklogLoop(void); void loop(void); uint16_t SendMail(char *buffer); uint32_t GetRtcSettingsCrc(void); void RtcSettingsSave(void); void RtcSettingsLoad(void); bool RtcSettingsValid(void); uint32_t GetRtcRebootCrc(void); void RtcRebootSave(void); void RtcRebootReset(void); void RtcRebootLoad(void); bool RtcRebootValid(void); void SetFlashModeDout(void); bool VersionCompatible(void); void SettingsBufferFree(void); bool SettingsBufferAlloc(void); uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size); uint16_t GetSettingsCrc(void); uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size); uint32_t GetSettingsCrc32(void); void SettingsSaveAll(void); void UpdateQuickPowerCycle(bool update); uint32_t GetSettingsTextLen(void); bool SettingsUpdateText(uint32_t index, const char* replace_me); char* SettingsText(uint32_t index); uint32_t GetSettingsAddress(void); void SettingsSave(uint8_t rotate); void SettingsLoad(void); void EspErase(uint32_t start_sector, uint32_t end_sector); void SettingsErase(uint8_t type); void SettingsSdkErase(void); void SettingsDefault(void); void SettingsDefaultSet1(void); void SettingsDefaultSet2(void); void SettingsResetStd(void); void SettingsResetDst(void); void SettingsDefaultWebColor(void); void SettingsEnableAllI2cDrivers(void); void SettingsDelta(void); void OsWatchTicker(void); void OsWatchInit(void); void OsWatchLoop(void); bool OsWatchBlockedLoop(void); uint32_t ResetReason(void); String GetResetReason(void); size_t strchrspn(const char *str1, int character); char* subStr(char* dest, char* str, const char *delim, int index); float CharToFloat(const char *str); int TextToInt(char *str); char* ulltoa(unsigned long long value, char *str, int radix); char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween); char* Uint64toHex(uint64_t value, char *str, uint16_t bits); char* dtostrfd(double number, unsigned char prec, char *s); char* Unescape(char* buffer, uint32_t* size); char* RemoveSpace(char* p); char* ReplaceCommaWithDot(char* p); char* LowerCase(char* dest, const char* source); char* UpperCase(char* dest, const char* source); char* UpperCase_P(char* dest, const char* source); char* Trim(char* p); char* RemoveAllSpaces(char* p); char* NoAlNumToUnderscore(char* dest, const char* source); char IndexSeparator(void); void SetShortcutDefault(void); uint8_t Shortcut(void); bool ValidIpAddress(const char* str); bool ParseIp(uint32_t* addr, const char* str); uint32_t ParseParameters(uint32_t count, uint32_t *params); bool NewerVersion(char* version_str); char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option); char* GetPowerDevice(char* dest, uint32_t idx, size_t size); void GetEspHardwareType(void); String GetDeviceHardware(void); float ConvertTemp(float c); float ConvertTempToCelsius(float c); char TempUnit(void); float ConvertHumidity(float h); float ConvertPressure(float p); String PressureUnit(void); void ResetGlobalValues(void); uint32_t SqrtInt(uint32_t num); uint32_t RoundSqrtInt(uint32_t num); char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack); int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack); int GetStateNumber(char *state_text); String GetSerialConfig(void); void SetSerialBegin(); void SetSerialConfig(uint32_t serial_config); void SetSerialBaudrate(uint32_t baudrate); void SetSerial(uint32_t baudrate, uint32_t serial_config); void ClaimSerial(void); void SerialSendRaw(char *codes); uint32_t GetHash(const char *buffer, size_t size); void ShowSource(uint32_t source); void WebHexCode(uint32_t i, const char* code); uint32_t WebColor(uint32_t i); char* ResponseGetTime(uint32_t format, char* time_str); int Response_P(const char* format, ...); int ResponseTime_P(const char* format, ...); int ResponseAppend_P(const char* format, ...); int ResponseAppendTimeFormat(uint32_t format); int ResponseAppendTime(void); int ResponseJsonEnd(void); int ResponseJsonEndEnd(void); void DigitalWrite(uint32_t gpio_pin, uint32_t state); uint8_t ModuleNr(void); bool ValidTemplateModule(uint32_t index); bool ValidModule(uint32_t index); String AnyModuleName(uint32_t index); String ModuleName(void); void ModuleGpios(myio *gp); gpio_flag ModuleFlag(void); void ModuleDefault(uint32_t module); void SetModuleType(void); bool FlashPin(uint32_t pin); uint8_t ValidPin(uint32_t pin, uint32_t gpio); bool ValidGPIO(uint32_t pin, uint32_t gpio); bool ValidAdc(void); bool GetUsedInModule(uint32_t val, uint8_t *arr); bool JsonTemplate(const char* dataBuf); void TemplateJson(void); inline int32_t TimeDifference(uint32_t prev, uint32_t next); int32_t TimePassedSince(uint32_t timestamp); bool TimeReached(uint32_t timer); void SetNextTimeInterval(unsigned long& timer, const unsigned long step); int32_t TimePassedSinceUsec(uint32_t timestamp); bool TimeReachedUsec(uint32_t timer); bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size); bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg); bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg); bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg); bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg); bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg); bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg); uint8_t I2cRead8(uint8_t addr, uint8_t reg); uint16_t I2cRead16(uint8_t addr, uint8_t reg); int16_t I2cReadS16(uint8_t addr, uint8_t reg); uint16_t I2cRead16LE(uint8_t addr, uint8_t reg); int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg); int32_t I2cRead24(uint8_t addr, uint8_t reg); bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size); bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val); bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val); int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len); int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len); void I2cScan(char *devs, unsigned int devs_len); void I2cSetActiveFound(uint32_t addr, const char *types); bool I2cActive(uint32_t addr); bool I2cSetDevice(uint32_t addr); void SetSeriallog(uint32_t loglevel); void SetSyslog(uint32_t loglevel); void GetLog(uint32_t idx, char** entry_pp, size_t* len_p); void Syslog(void); void AddLog(uint32_t loglevel); void AddLog_P(uint32_t loglevel, const char *formatP); void AddLog_P(uint32_t loglevel, const char *formatP, const char *formatP2); void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...); void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...); void AddLog_Debug(PGM_P formatP, ...); void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count); void AddLogSerial(uint32_t loglevel); void AddLogMissed(char *sensor, uint32_t misses); void ButtonPullupFlag(uint8 button_bit); void ButtonInvertFlag(uint8 button_bit); void ButtonInit(void); uint8_t ButtonSerial(uint8_t serial_in_byte); void ButtonHandler(void); void ButtonLoop(void); void ResponseCmndNumber(int value); void ResponseCmndFloat(float value, uint32_t decimals); void ResponseCmndIdxNumber(int value); void ResponseCmndChar(const char* value); void ResponseCmndStateText(uint32_t value); void ResponseCmndDone(void); void ResponseCmndIdxChar(const char* value); void ResponseCmndAll(uint32_t text_index, uint32_t count); void ExecuteCommand(const char *cmnd, uint32_t source); void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len); void CmndBacklog(void); void CmndDelay(void); void CmndPower(void); void CmndStatus(void); void CmndState(void); void CmndTempOffset(void); void CmndSleep(void); void CmndUpgrade(void); void CmndOtaUrl(void); void CmndSeriallog(void); void CmndRestart(void); void CmndPowerOnState(void); void CmndPulsetime(void); void CmndBlinktime(void); void CmndBlinkcount(void); void CmndSavedata(void); void CmndSetoption(void); void CmndTemperatureResolution(void); void CmndHumidityResolution(void); void CmndPressureResolution(void); void CmndPowerResolution(void); void CmndVoltageResolution(void); void CmndFrequencyResolution(void); void CmndCurrentResolution(void); void CmndEnergyResolution(void); void CmndWeightResolution(void); void CmndModule(void); void CmndModules(void); void CmndGpio(void); void CmndGpios(void); void CmndTemplate(void); void CmndPwm(void); void CmndPwmfrequency(void); void CmndPwmrange(void); void CmndButtonDebounce(void); void CmndSwitchDebounce(void); void CmndBaudrate(void); void CmndSerialConfig(void); void CmndSerialSend(void); void CmndSerialDelimiter(void); void CmndSyslog(void); void CmndLoghost(void); void CmndLogport(void); void CmndIpAddress(void); void CmndNtpServer(void); void CmndAp(void); void CmndSsid(void); void CmndPassword(void); void CmndHostname(void); void CmndWifiConfig(void); void CmndFriendlyname(void); void CmndSwitchMode(void); void CmndInterlock(void); void CmndTeleperiod(void); void CmndReset(void); void CmndTime(void); void CmndTimezone(void); void CmndTimeStdDst(uint32_t ts); void CmndTimeStd(void); void CmndTimeDst(void); void CmndAltitude(void); void CmndLedPower(void); void CmndLedState(void); void CmndLedMask(void); void CmndWifiPower(void); void CmndI2cScan(void); void CmndI2cDriver(void); void CmndSensor(void); void CmndDriver(void); void CmndCrash(void); void CmndWDT(void); void CmndBlockedLoop(void); void CrashDumpClear(void); bool CrashFlag(void); void CrashDump(void); static bool spiflash_is_ready(void); static void spi_write_enable(void); bool EsptoolEraseSector(uint32_t sector); void EsptoolErase(uint32_t start_sector, uint32_t end_sector); void GetFeatures(void); float fmodf(float x, float y); double FastPrecisePow(double a, double b); float FastPrecisePowf(const float x, const float y); double TaylorLog(double x); inline float sinf(float x); inline float cosf(float x); inline float tanf(float x); inline float atanf(float x); inline float asinf(float x); inline float acosf(float x); inline float sqrtf(float x); inline float powf(float x, float y); float cos_52s(float x); float cos_52(float x); float sin_52(float x); float tan_56s(float x); float tan_56(float x); float atan_66s(float x); float atan_66(float x); float asinf1(float x); float acosf1(float x); float sqrt1(const float x); uint16_t changeUIntScale(uint16_t inum, uint16_t ifrom_min, uint16_t ifrom_max, uint16_t ito_min, uint16_t ito_max); void* memchr(const void* ptr, int value, size_t num); size_t strcspn(const char *str1, const char *str2); char* strpbrk(const char *s1, const char *s2); void* memmove_P(void *dest, const void *src, size_t n); void update_rotary(void); bool RotaryButtonPressed(void); void RotaryInit(void); void RotaryHandler(void); void RotaryLoop(void); uint32_t UtcTime(void); uint32_t LocalTime(void); int32_t DriftTime(void); uint32_t Midnight(void); bool MidnightNow(void); bool IsDst(void); String GetBuildDateAndTime(void); String GetMinuteTime(uint32_t minutes); String GetTimeZone(void); String GetDuration(uint32_t time); String GetDT(uint32_t time); String GetDateAndTime(uint8_t time_type); String GetTime(int type); uint32_t UpTime(void); uint32_t MinutesUptime(void); String GetUptime(void); uint32_t MinutesPastMidnight(void); void BreakTime(uint32_t time_input, TIME_T &tm); uint32_t MakeTime(TIME_T &tm); uint32_t RuleToTime(TimeRule r, int yr); void RtcSecond(void); void RtcSetTime(uint32_t epoch); void RtcInit(void); String GetStatistics(void); String GetStatistics(void); void SwitchPullupFlag(uint16 switch_bit); void SwitchSetVirtual(uint32_t index, uint8_t state); uint8_t SwitchGetVirtual(uint32_t index); uint8_t SwitchLastState(uint32_t index); bool SwitchState(uint32_t index); void SwitchProbe(void); void SwitchInit(void); void SwitchHandler(uint8_t mode); void SwitchLoop(void); char* Format(char* output, const char* input, int size); char* GetOtaUrl(char *otaurl, size_t otaurl_size); char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic); char* GetGroupTopic_P(char *stopic, const char* subtopic); char* GetFallbackTopic_P(char *stopic, const char* subtopic); char* GetStateText(uint32_t state); void SetLatchingRelay(power_t lpower, uint32_t state); void SetDevicePower(power_t rpower, uint32_t source); void RestorePower(bool publish_power, uint32_t source); void SetAllPower(uint32_t state, uint32_t source); void SetPowerOnState(void); void SetLedPowerIdx(uint32_t led, uint32_t state); void SetLedPower(uint32_t state); void SetLedPowerAll(uint32_t state); void SetLedLink(uint32_t state); void SetPulseTimer(uint32_t index, uint32_t time); uint32_t GetPulseTimer(uint32_t index); bool SendKey(uint32_t key, uint32_t device, uint32_t state); void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source); void StopAllPowerBlink(void); void MqttShowPWMState(void); void MqttShowState(void); void MqttPublishTeleState(void); bool MqttShowSensor(void); void MqttPublishSensor(void); void PerformEverySecond(void); void Every100mSeconds(void); void Every250mSeconds(void); void ArduinoOTAInit(void); void ArduinoOtaLoop(void); void SerialInput(void); void GpioInit(void); bool UdpDisconnect(void); bool UdpConnect(void); void PollUdp(void); int WifiGetRssiAsQuality(int rssi); bool WifiConfigCounter(void); void WifiConfig(uint8_t type); void WifiSetMode(WiFiMode_t wifi_mode); void WiFiSetSleepMode(void); void WifiBegin(uint8_t flag, uint8_t channel); void WifiBeginAfterScan(void); uint16_t WifiLinkCount(void); String WifiDowntime(void); void WifiSetState(uint8_t state); bool WifiCheckIPv6(void); String WifiGetIPv6(void); bool WifiCheckIPAddrStatus(void); void WifiCheckIp(void); void WifiCheck(uint8_t param); int WifiState(void); String WifiGetOutputPower(void); void WifiSetOutputPower(void); void WifiConnect(void); void WifiDisconnect(void); void WifiShutdown(void); void EspRestart(void); static void WebGetArg(const char* arg, char* out, size_t max); static bool WifiIsInManagerMode(); void ShowWebSource(uint32_t source); void ExecuteWebCommand(char* svalue, uint32_t source); void StartWebserver(int type, IPAddress ipweb); void StopWebserver(void); void WifiManagerBegin(bool reset_only); void PollDnsWebserver(void); bool WebAuthenticate(void); void HttpHeaderCors(void); void WSHeaderSend(void); void WSSend(int code, int ctype, const String& content); void WSContentBegin(int code, int ctype); void _WSContentSend(const String& content); void WSContentFlush(void); void _WSContentSendBuffer(void); void WSContentSend_P(const char* formatP, ...); void WSContentSend_PD(const char* formatP, ...); void WSContentStart_P(const char* title, bool auth); void WSContentStart_P(const char* title); void WSContentSendStyle_P(const char* formatP, ...); void WSContentSendStyle(void); void WSContentButton(uint32_t title_index); void WSContentSpaceButton(uint32_t title_index); void WSContentEnd(void); void WSContentStop(void); void WebRestart(uint32_t type); void HandleWifiLogin(void); void WebSliderColdWarm(void); void HandleRoot(void); bool HandleRootStatusRefresh(void); int32_t IsShutterWebButton(uint32_t idx); void HandleConfiguration(void); void HandleTemplateConfiguration(void); void TemplateSaveSettings(void); void HandleModuleConfiguration(void); void ModuleSaveSettings(void); String HtmlEscape(const String unescaped); void HandleWifiConfiguration(void); void WifiSaveSettings(void); void HandleLoggingConfiguration(void); void LoggingSaveSettings(void); void HandleOtherConfiguration(void); void OtherSaveSettings(void); void HandleBackupConfiguration(void); void HandleResetConfiguration(void); void HandleRestoreConfiguration(void); void HandleInformation(void); void HandleUpgradeFirmware(void); void HandleUpgradeFirmwareStart(void); void HandleUploadDone(void); void HandleUploadLoop(void); void HandlePreflightRequest(void); void HandleHttpCommand(void); void HandleConsole(void); void HandleConsoleRefresh(void); void HandleNotFound(void); bool CaptivePortal(void); String UrlEncode(const String& text); int WebSend(char *buffer); bool JsonWebColor(const char* dataBuf); void CmndEmulation(void); void CmndSendmail(void); void CmndWebServer(void); void CmndWebPassword(void); void CmndWeblog(void); void CmndWebRefresh(void); void CmndWebSend(void); void CmndWebColor(void); void CmndWebSensor(void); void CmndWebButton(void); void CmndCors(void); bool Xdrv01(uint8_t function); bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value); void MakeValidMqtt(uint32_t option, char* str); void MqttDiscoverServer(void); void MqttInit(void); bool MqttIsConnected(void); void MqttDisconnect(void); void MqttSubscribeLib(const char *topic); void MqttUnsubscribeLib(const char *topic); bool MqttPublishLib(const char* topic, bool retained); void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len); void MqttRetryCounter(uint8_t value); void MqttSubscribe(const char *topic); void MqttUnsubscribe(const char *topic); void MqttPublishLogging(const char *mxtime); void MqttPublish(const char* topic, bool retained); void MqttPublish(const char* topic); void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained); void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic); void MqttPublishTeleSensor(void); void MqttPublishPowerState(uint32_t device); void MqttPublishAllPowerState(void); void MqttPublishPowerBlinkState(uint32_t device); uint16_t MqttConnectCount(void); void MqttDisconnected(int state); void MqttConnected(void); void MqttReconnect(void); void MqttCheck(void); void CmndMqttFingerprint(void); void CmndMqttUser(void); void CmndMqttPassword(void); void CmndMqttlog(void); void CmndMqttHost(void); void CmndMqttPort(void); void CmndMqttRetry(void); void CmndStateText(void); void CmndMqttClient(void); void CmndFullTopic(void); void CmndPrefix(void); void CmndPublish(void); void CmndGroupTopic(void); void CmndTopic(void); void CmndButtonTopic(void); void CmndSwitchTopic(void); void CmndButtonRetain(void); void CmndSwitchRetain(void); void CmndPowerRetain(void); void CmndSensorRetain(void); inline void TlsEraseBuffer(uint8_t *buffer); void loadTlsDir(void); void CmndTlsKey(void); uint32_t bswap32(uint32_t x); void CmndTlsDump(void); void HandleMqttConfiguration(void); void MqttSaveSettings(void); bool Xdrv02(uint8_t function); bool EnergyTariff1Active(); void EnergyUpdateToday(void); void EnergyUpdateTotal(float value, bool kwh); void Energy200ms(void); void EnergySaveState(void); bool EnergyMargin(bool type, uint16_t margin, uint16_t value, bool &flag, bool &save_flag); void EnergyMarginCheck(void); void EnergyMqttShow(void); void EnergyEverySecond(void); void EnergyCommandCalResponse(uint32_t nvalue); void CmndEnergyReset(void); void CmndTariff(void); void CmndPowerCal(void); void CmndVoltageCal(void); void CmndCurrentCal(void); void CmndPowerSet(void); void CmndVoltageSet(void); void CmndCurrentSet(void); void CmndFrequencySet(void); void CmndModuleAddress(void); void CmndPowerDelta(void); void CmndPowerLow(void); void CmndPowerHigh(void); void CmndVoltageLow(void); void CmndVoltageHigh(void); void CmndCurrentLow(void); void CmndCurrentHigh(void); void CmndMaxPower(void); void CmndMaxPowerHold(void); void CmndMaxPowerWindow(void); void CmndSafePower(void); void CmndSafePowerHold(void); void CmndSafePowerWindow(void); void CmndMaxEnergy(void); void CmndMaxEnergyStart(void); void EnergySnsInit(void); void EnergyShow(bool json); bool Xdrv03(uint8_t function); bool Xsns03(uint8_t function); power_t LightPower(void); power_t LightPowerIRAM(void); uint8_t LightDevice(void); static uint32_t min3(uint32_t a, uint32_t b, uint32_t c); uint16_t change8to10(uint8_t v); uint8_t change10to8(uint16_t v); uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr); uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr); uint16_t ledGamma10_10(uint16_t v); uint16_t ledGamma10(uint8_t v); uint8_t ledGamma(uint8_t v); void LightPwmOffset(uint32_t offset); bool LightModuleInit(void); void LightInit(void); void LightUpdateColorMapping(void); uint8_t LightGetDimmer(uint8_t dimmer); void LightSetDimmer(uint8_t dimmer); void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri); void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b); uint8_t LightGetBri(uint8_t device); void LightSetBri(uint8_t device, uint8_t bri); void LightSetColorTemp(uint16_t ct); uint16_t LightGetColorTemp(void); void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value); void LightPowerOn(void); void LightState(uint8_t append); void LightCycleColor(int8_t direction); void LightSetPower(void); void LightAnimate(void); bool isChannelGammaCorrected(uint32_t channel); uint16_t fadeGamma(uint32_t channel, uint16_t v); uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg); bool LightApplyFade(void); void LightApplyPower(uint8_t new_color[LST_MAX], power_t power); void LightSetOutputs(const uint16_t *cur_col_10); void calcGammaMultiChannels(uint16_t cur_col_10[5]); void calcGammaBulbs(uint16_t cur_col_10[5]); bool LightColorEntry(char *buffer, uint32_t buffer_length); void CmndSupportColor(void); void CmndColor(void); void CmndWhite(void); void CmndChannel(void); void CmndHsbColor(void); void CmndScheme(void); void CmndWakeup(void); void CmndColorTemperature(void); void CmndDimmer(void); void CmndDimmerRange(void); void CmndLedTable(void); void CmndRgbwwTable(void); void CmndFade(void); void CmndSpeed(void); void CmndWakeupDuration(void); void CmndUndocA(void); bool Xdrv04(uint8_t function); void IrSendInit(void); void IrReceiveUpdateThreshold(void); void IrReceiveInit(void); void IrReceiveCheck(void); uint32_t IrRemoteCmndIrSendJson(void); void CmndIrSend(void); void IrRemoteCmndResponse(uint32_t error); bool Xdrv05(uint8_t function); void IrSendInit(void); uint8_t reverseBitsInByte(uint8_t b); uint64_t reverseBitsInBytes64(uint64_t b); void IrReceiveUpdateThreshold(void); void IrReceiveInit(void); String sendIRJsonState(const struct decode_results &results); void IrReceiveCheck(void); String listSupportedProtocols(bool hvac); uint32_t IrRemoteCmndIrHvacJson(void); void CmndIrHvac(void); uint32_t IrRemoteCmndIrSendJson(void); uint32_t IrRemoteCmndIrSendRaw(void); void CmndIrSend(void); void IrRemoteCmndResponse(uint32_t error); bool Xdrv05(uint8_t function); ssize_t rf_find_hex_record_start(uint8_t *buf, size_t size); ssize_t rf_find_hex_record_end(uint8_t *buf, size_t size); ssize_t rf_glue_remnant_with_new_data_and_write(const uint8_t *remnant_data, uint8_t *new_data, size_t new_data_len); ssize_t rf_decode_and_write(uint8_t *record, size_t size); ssize_t rf_search_and_write(uint8_t *buf, size_t size); uint8_t rf_erase_flash(void); uint8_t SnfBrUpdateInit(void); void SonoffBridgeReceivedRaw(void); void SonoffBridgeLearnFailed(void); void SonoffBridgeReceived(void); bool SonoffBridgeSerialInput(void); void SonoffBridgeSendCommand(uint8_t code); void SonoffBridgeSendAck(void); void SonoffBridgeSendCode(uint32_t code); void SonoffBridgeSend(uint8_t idx, uint8_t key); void SonoffBridgeLearn(uint8_t key); void CmndRfBridge(void); void CmndRfKey(void); void CmndRfRaw(void); bool Xdrv06(uint8_t function); int DomoticzBatteryQuality(void); int DomoticzRssiQuality(void); void MqttPublishDomoticzFanState(void); void DomoticzUpdateFanState(void); void MqttPublishDomoticzPowerState(uint8_t device); void DomoticzUpdatePowerState(uint8_t device); void DomoticzMqttUpdate(void); void DomoticzMqttSubscribe(void); bool DomoticzMqttData(void); bool DomoticzSendKey(uint8_t key, uint8_t device, uint8_t state, uint8_t svalflg); uint8_t DomoticzHumidityState(char *hum); void DomoticzSensor(uint8_t idx, char *data); void DomoticzSensor(uint8_t idx, uint32_t value); void DomoticzTempHumSensor(char *temp, char *hum); void DomoticzTempHumPressureSensor(char *temp, char *hum, char *baro); void DomoticzSensorPowerEnergy(int power, char *energy); void DomoticzSensorP1SmartMeter(char *usage1, char *usage2, char *return1, char *return2, int power); void CmndDomoticzIdx(void); void CmndDomoticzKeyIdx(void); void CmndDomoticzSwitchIdx(void); void CmndDomoticzSensorIdx(void); void CmndDomoticzUpdateTimer(void); void HandleDomoticzConfiguration(void); void DomoticzSaveSettings(void); bool Xdrv07(uint8_t function); void SerialBridgeInput(void); void SerialBridgeInit(void); void CmndSSerialSend(void); void CmndSBaudrate(void); bool Xdrv08(uint8_t function); float JulianischesDatum(void); float InPi(float x); float eps(float T); float BerechneZeitgleichung(float *DK,float T); void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down); void ApplyTimerOffsets(Timer *duskdawn); String GetSun(uint32_t dawn); uint16_t SunMinutes(uint32_t dawn); void TimerSetRandomWindow(uint32_t index); void TimerSetRandomWindows(void); void TimerEverySecond(void); void PrepShowTimer(uint32_t index); void CmndTimer(void); void CmndTimers(void); void CmndLongitude(void); void CmndLatitude(void); void HandleTimerConfiguration(void); void TimerSaveSettings(void); bool Xdrv09(uint8_t function); bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule); int8_t parseCompareExpression(String &expr, String &leftExpr, String &rightExpr); void RulesVarReplace(String &commands, const String &sfind, const String &replace); bool RuleSetProcess(uint8_t rule_set, String &event_saved); bool RulesProcessEvent(char *json_event); bool RulesProcess(void); void RulesInit(void); void RulesEvery50ms(void); void RulesEvery100ms(void); void RulesEverySecond(void); void RulesSaveBeforeRestart(void); void RulesSetPower(void); void RulesTeleperiod(void); bool RulesMqttData(void); void CmndSubscribe(void); void CmndUnsubscribe(void); bool findNextNumber(char * &pNumber, float &value); bool findNextVariableValue(char * &pVarname, float &value); bool findNextObjectValue(char * &pointer, float &value); bool findNextOperator(char * &pointer, int8_t &op); float calculateTwoValues(float v1, float v2, uint8_t op); float evaluateExpression(const char * expression, unsigned int len); void CmndIf(void); bool evaluateComparisonExpression(const char *expression, int len); bool findNextLogicOperator(char * &pointer, int8_t &op); bool findNextLogicObjectValue(char * &pointer, bool &value); bool evaluateLogicalExpression(const char * expression, int len); int8_t findIfBlock(char * &pointer, int &lenWord, int8_t block_type); void ExecuteCommandBlock(const char * commands, int len); void ProcessIfStatement(const char* statements); void RulesPreprocessCommand(char *pCommands); void CmndRule(void); void CmndRuleTimer(void); void CmndEvent(void); void CmndVariable(void); void CmndMemory(void); void CmndCalcResolution(void); void CmndAddition(void); void CmndSubtract(void); void CmndMultiply(void); void CmndScale(void); float map_double(float x, float in_min, float in_max, float out_min, float out_max); bool Xdrv10(uint8_t function); void ScriptEverySecond(void); void RulesTeleperiod(void); int16_t Init_Scripter(void); void ws2812_set_array(float *array ,uint8_t len); float median_array(float *array,uint8_t len); float Get_MFVal(uint8_t index,uint8_t bind); void Set_MFVal(uint8_t index,uint8_t bind,float val); float Get_MFilter(uint8_t index); void Set_MFilter(uint8_t index, float invar); float DoMedian5(uint8_t index, float in); uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value); uint16_t GetStack(void); uint16_t GetStack(void); void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize); void toLog(const char *str); void toLogN(const char *cp,uint8_t len); void toLogEOL(const char *s1,const char *str); void toSLog(const char *str); int16_t Run_Scripter(const char *type, int8_t tlen, char *js); void ScripterEvery100ms(void); void Scripter_save_pvars(void); void ListDir(char *path, uint8_t depth); void Script_FileUploadConfiguration(void); void ScriptFileUploadSuccess(void); void script_upload(void); uint8_t DownloadFile(char *file); void HandleScriptTextareaConfiguration(void); void HandleScriptConfiguration(void); void ScriptSaveSettings(void); void Script_HueStatus(String *response, uint16_t hue_devs); void Script_Check_Hue(String *response); void Script_Handle_Hue(String *path); bool Script_SubCmd(void); void execute_script(char *script); bool ScriptCommand(void); uint16_t xFAT_DATE(uint16_t year, uint8_t month, uint8_t day); uint16_t xFAT_TIME(uint8_t hour, uint8_t minute, uint8_t second); void dateTime(uint16_t* date, uint16_t* time); bool ScriptMqttData(void); String ScriptSubscribe(const char *data, int data_len); String ScriptUnsubscribe(const char * data, int data_len); void Script_Check_HTML_Setvars(void); void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen); void ScriptWebShow(void); void ScriptJsonAppend(void); bool Xdrv10(uint8_t function); void KNX_ADD_GA( uint8_t GAop, uint8_t GA_FNUM, uint8_t GA_AREA, uint8_t GA_FDEF ); void KNX_DEL_GA( uint8_t GAnum ); void KNX_ADD_CB( uint8_t CBop, uint8_t CB_FNUM, uint8_t CB_AREA, uint8_t CB_FDEF ); void KNX_DEL_CB( uint8_t CBnum ); bool KNX_CONFIG_NOT_MATCH(void); void KNXStart(void); void KNX_INIT(void); void KNX_CB_Action(message_t const &msg, void *arg); void KnxUpdatePowerState(uint8_t device, power_t state); void KnxSendButtonPower(void); void KnxSensor(uint8_t sensor_type, float value); void HandleKNXConfiguration(void); void KNX_Save_Settings(void); void CmndKnxTxCmnd(void); void CmndKnxTxVal(void); void CmndKnxEnabled(void); void CmndKnxEnhanced(void); void CmndKnxPa(void); void CmndKnxGa(void); void CmndKnxCb(void); bool Xdrv11(uint8_t function); void TryResponseAppend_P(const char *format, ...); void HAssAnnounceRelayLight(void); void HAssAnnounceButtonSwitch(uint8_t device, char *topic, uint8_t present, uint8_t key, uint8_t toggle); void HAssAnnounceSwitches(void); void HAssAnnounceButtons(void); void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const char *MultiSubName, uint8_t subqty, uint8_t subidx); void HAssAnnounceSensors(void); void HAssAnnounceStatusSensor(void); void HAssPublishStatus(void); void HAssDiscovery(void); void HAssDiscover(void); void HAssAnyKey(void); bool Xdrv12(uint8_t function); void DisplayInit(uint8_t mode); void DisplayClear(void); void DisplayDrawHLine(uint16_t x, uint16_t y, int16_t len, uint16_t color); void DisplayDrawVLine(uint16_t x, uint16_t y, int16_t len, uint16_t color); void DisplayDrawLine(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color); void DisplayDrawCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color); void DisplayDrawFilledCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color); void DisplayDrawRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color); void DisplayDrawFilledRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color); void DisplayDrawFrame(void); void DisplaySetSize(uint8_t size); void DisplaySetFont(uint8_t font); void DisplaySetRotation(uint8_t rotation); void DisplayDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag); void DisplayOnOff(uint8_t on); uint8_t fatoiv(char *cp,float *res); uint8_t atoiv(char *cp, int16_t *res); uint8_t atoiV(char *cp, uint16_t *res); void alignright(char *string); uint32_t decode_te(char *line); void DisplayText(void); void DisplayClearScreenBuffer(void); void DisplayFreeScreenBuffer(void); void DisplayAllocScreenBuffer(void); void DisplayReAllocScreenBuffer(void); void DisplayFillScreen(uint32_t line); void DisplayClearLogBuffer(void); void DisplayFreeLogBuffer(void); void DisplayAllocLogBuffer(void); void DisplayReAllocLogBuffer(void); void DisplayLogBufferAdd(char* txt); char* DisplayLogBuffer(char temp_code); void DisplayLogBufferInit(void); void DisplayJsonValue(const char* topic, const char* device, const char* mkey, const char* value); void DisplayAnalyzeJson(char *topic, char *json); void DisplayMqttSubscribe(void); bool DisplayMqttData(void); void DisplayLocalSensor(void); void DisplayInitDriver(void); void DisplaySetPower(void); void CmndDisplay(void); void CmndDisplayModel(void); void CmndDisplayWidth(void); void CmndDisplayHeight(void); void CmndDisplayMode(void); void CmndDisplayDimmer(void); void CmndDisplaySize(void); void CmndDisplayFont(void); void CmndDisplayRotate(void); void CmndDisplayText(void); void CmndDisplayAddress(void); void CmndDisplayRefresh(void); void CmndDisplayColumns(void); void CmndDisplayRows(void); void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp); void DrawAClock(uint16_t rad); void ClrGraph(uint16_t num); void DefineGraph(uint16_t num,uint16_t xp,uint16_t yp,int16_t xs,uint16_t ys,int16_t dec,float ymin, float ymax,uint8_t icol); void DisplayCheckGraph(); void Save_graph(uint8_t num, char *path); void Restore_graph(uint8_t num, char *path); void RedrawGraph(uint8_t num, uint8_t flags); void AddGraph(uint8_t num,uint8_t val); void AddValue(uint8_t num,float fval); bool Xdrv13(uint8_t function); uint16_t MP3_Checksum(uint8_t *array); void MP3PlayerInit(void); void MP3_CMD(uint8_t mp3cmd,uint16_t val); bool MP3PlayerCmd(void); bool Xdrv14(uint8_t function); void PCA9685_Detect(void); void PCA9685_Reset(void); void PCA9685_SetPWMfreq(double freq); void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off); void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted); bool PCA9685_Command(void); void PCA9685_OutputTelemetry(bool telemetry); bool Xdrv15(uint8_t function); void CmndTuyaSend(void); void CmndTuyaMcu(void); void TuyaAddMcuFunc(uint8_t fnId, uint8_t dpId); void UpdateDevices(); inline bool TuyaFuncIdValid(uint8_t fnId); uint8_t TuyaGetFuncId(uint8_t dpid); uint8_t TuyaGetDpId(uint8_t fnId); void TuyaSendState(uint8_t id, uint8_t type, uint8_t* value); void TuyaSendBool(uint8_t id, bool value); void TuyaSendValue(uint8_t id, uint32_t value); void TuyaSendEnum(uint8_t id, uint32_t value); void TuyaSendString(uint8_t id, char data[]); bool TuyaSetPower(void); bool TuyaSetChannels(void); void LightSerialDuty(uint16_t duty); void TuyaRequestState(void); void TuyaResetWifi(void); void TuyaProcessStatePacket(void); void TuyaLowPowerModePacketProcess(void); void TuyaHandleProductInfoPacket(void); void TuyaSendLowPowerSuccessIfNeeded(void); void TuyaNormalPowerModePacketProcess(void); bool TuyaModuleSelected(void); void TuyaInit(void); void TuyaSerialInput(void); bool TuyaButtonPressed(void); uint8_t TuyaGetTuyaWifiState(void); void TuyaSetWifiLed(void); bool Xnrg16(uint8_t function); bool Xdrv16(uint8_t function); void RfReceiveCheck(void); void RfInit(void); void CmndRfSend(void); bool Xdrv17(uint8_t function); bool ArmtronixSetChannels(void); void LightSerial2Duty(uint8_t duty1, uint8_t duty2); void ArmtronixRequestState(void); bool ArmtronixModuleSelected(void); void ArmtronixInit(void); void ArmtronixSerialInput(void); void ArmtronixSetWifiLed(void); bool Xdrv18(uint8_t function); void PS16DZSerialSend(const char *tx_buffer); void PS16DZSerialSendOk(void); void PS16DZSerialSendUpdateCommand(void); void PS16DZSerialInput(void); bool PS16DZSerialSendUpdateCommandIfRequired(void); void PS16DZInit(void); bool PS16DZModuleSelected(void); bool Xdrv19(uint8_t function); String HueBridgeId(void); String HueSerialnumber(void); String HueUuid(void); void HueRespondToMSearch(void); String GetHueDeviceId(uint8_t id); String GetHueUserId(void); void HandleUpnpSetupHue(void); void HueNotImplemented(String *path); void HueConfigResponse(String *response); void HueConfig(String *path); uint8_t getLocalLightSubtype(uint8_t device); void HueLightStatus1(uint8_t device, String *response); bool HueActive(uint8_t device); void HueLightStatus2(uint8_t device, String *response); uint32_t EncodeLightId(uint8_t relay_id); uint32_t DecodeLightId(uint32_t hue_id); uint32_t findEchoGeneration(void); void HueGlobalConfig(String *path); void HueAuthentication(String *path); void HueLights(String *path); void HueGroups(String *path); void HandleHueApi(String *path); bool Xdrv20(uint8_t function); String WemoSerialnumber(void); String WemoUuid(void); void WemoRespondToMSearch(int echo_type); void HandleUpnpEvent(void); void HandleUpnpService(void); void HandleUpnpMetaService(void); void HandleUpnpSetupWemo(void); bool Xdrv21(uint8_t function); bool IsModuleIfan(void); uint8_t MaxFanspeed(void); uint8_t GetFanspeed(void); void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence); void SonoffIfanReceived(void); bool SonoffIfanSerialInput(void); void CmndFanspeed(void); bool SonoffIfanInit(void); void SonoffIfanUpdate(void); bool Xdrv22(uint8_t function); void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val); void CopyJsonArray(JsonArray &to, const JsonArray &arr); void CopyJsonObject(JsonObject &to, const JsonObject &from); uint16_t fromClusterCode(uint8_t c); uint8_t toClusterCode(uint16_t c); class SBuffer hibernateDevice(const struct Z_Device &device); class SBuffer hibernateDevices(void); void hidrateDevices(const SBuffer &buf); void loadZigbeeDevices(void); void saveZigbeeDevices(void); void eraseZigbeeDevices(void); uint8_t toPercentageCR2032(uint32_t voltage); uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf, uint32_t offset, uint32_t len); int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint); const __FlashStringHelper* zigbeeFindCommand(const char *command); inline bool isXYZ(char c); inline char hexDigit(uint32_t h); String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z); uint32_t ZigbeeAliasOrNumber(const char *state_text); uint8_t ZigbeeGetInstructionSize(uint8_t instr); void ZigbeeGotoLabel(uint8_t label); void ZigbeeStateMachine_Run(void); int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf); int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf); int32_t Z_Reboot(int32_t res, class SBuffer &buf); int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf); bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match); int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf); void Z_SendActiveEpReq(uint16_t shortaddr); void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint); int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf); int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf); void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid); int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf); int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf); int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf); void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json); int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf); int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf); int32_t Z_Load_Devices(uint8_t value); int32_t Z_State_Ready(uint8_t value); int32_t ZigbeeProcessInput(class SBuffer &buf); void ZigbeeInput(void); void ZigbeeInit(void); uint32_t strToUInt(const JsonVariant &val); void CmndZbReset(void); void CmndZbZNPSendOrReceive(bool send); void CmndZbZNPReceive(void); void CmndZbZNPSend(void); void ZigbeeZNPSend(const uint8_t *msg, size_t len); void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId); inline int8_t hexValue(char c); void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data); void CmndZbSend(void); void CmndZbBind(void); void CmndZbProbe(void); void CmndZbName(void); void CmndZbForget(void); void CmndZbSave(void); void CmndZbRead(void); void CmndZbPermitJoin(void); void CmndZbStatus(void); bool Xdrv23(uint8_t function); void BuzzerOff(void); void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32_t mode); void BuzzerSetStateToLed(uint32_t state); void BuzzerBeep(uint32_t count); void BuzzerEnabledBeep(uint32_t count, uint32_t duration); bool BuzzerPinState(void); void BuzzerInit(void); void BuzzerEvery100mSec(void); void CmndBuzzer(void); bool Xdrv24(uint8_t function); void A4988Init(void); void CmndDoMove(void); void CmndDoRotate(void); void CmndDoTurn(void); void CmndSetMIS(void); void CmndSetSPR(void); void CmndSetRPM(void); bool Xdrv25(uint8_t function); void AriluxRfInterrupt(void); void AriluxRfHandler(void); void AriluxRfInit(void); void AriluxRfDisable(void); bool Xdrv26(uint8_t function); void ShutterLogPos(uint32_t i); void ShutterRtc50mS(void); int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index); uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index); void ShutterInit(void); void ShutterReportPosition(bool always); void ShutterLimitRealAndTargetPositions(uint32_t i); void ShutterUpdatePosition(void); bool ShutterState(uint32_t device); void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos); void ShutterWaitForMotorStop(uint32_t i); int32_t ShutterCounterBasedPosition(uint32_t i); void ShutterRelayChanged(void); bool ShutterButtonIsSimultaneousHold(uint32_t button_index, uint32_t shutter_index); void ShutterButtonHandler(void); void ShutterSetPosition(uint32_t device, uint32_t position); void CmndShutterOpen(void); void CmndShutterClose(void); void CmndShutterStop(void); void CmndShutterPosition(void); void CmndShutterOpenTime(void); void CmndShutterCloseTime(void); void CmndShutterMotorDelay(void); void CmndShutterRelay(void); void CmndShutterButton(void); void CmndShutterSetHalfway(void); void CmndShutterFrequency(void); void CmndShutterSetClose(void); void CmndShutterInvert(void); void CmndShutterCalibration(void); void CmndShutterLock(void); void CmndShutterEnableEndStopTime(void); bool Xdrv27(uint8_t function); void Pcf8574SwitchRelay(void); void Pcf8574Init(void); void HandlePcf8574(void); void Pcf8574SaveSettings(void); bool Xdrv28(uint8_t function); bool DeepSleepEnabled(void); void DeepSleepReInit(void); void DeepSleepPrepare(void); void DeepSleepStart(void); void DeepSleepEverySecond(void); void CmndDeepsleepTime(void); bool Xdrv29(uint8_t function); uint8_t crc8(const uint8_t *p, uint8_t len); void ExsSendCmd(uint8_t cmd, uint8_t value); uint8_t ExsSetPower(uint8_t device, uint8_t power); uint8_t ExsSetBri(uint8_t device, uint8_t bri); uint8_t ExsSyncState(uint8_t device); bool ExsSyncState(); void ExsDebugState(); void ExsPacketProcess(void); bool ExsModuleSelected(void); bool ExsSetChannels(void); bool ExsSetPower(void); void EsxMcuStart(void); void ExsInit(void); void ExsSerialInput(void); void CmndExsDimm(void); void CmndExsDimmTbl(void); void CmndExsDimmVal(void); void CmndExsDimms(void); void CmndExsChLock(void); void CmndExsState(void); bool Xdrv30(uint8_t function); uint32_t TasmotaSlave_FlashStart(void); uint8_t TasmotaSlave_UpdateInit(void); void TasmotaSlave_Reset(void); uint8_t TasmotaSlave_waitForSerialData(int dataCount, int timeout); uint8_t TasmotaSlave_sendBytes(uint8_t* bytes, int count); uint8_t TasmotaSlave_execCmd(uint8_t cmd); uint8_t TasmotaSlave_execParam(uint8_t cmd, uint8_t* params, int count); uint8_t TasmotaSlave_exitProgMode(void); uint8_t TasmotaSlave_SetupFlash(void); uint8_t TasmotaSlave_loadAddress(uint8_t adrHi, uint8_t adrLo); void TasmotaSlave_FlashPage(uint8_t addr_h, uint8_t addr_l, uint8_t* data); void TasmotaSlave_Flash(void); void TasmotaSlave_SetFlagFlashing(bool value); bool TasmotaSlave_GetFlagFlashing(void); void TasmotaSlave_WriteBuffer(uint8_t *buf, size_t size); void TasmotaSlave_Init(void); void TasmotaSlave_Show(void); void TasmotaSlave_sendCmnd(uint8_t cmnd, uint8_t param); void CmndTasmotaSlaveReset(void); void CmndTasmotaSlaveSend(void); void TasmotaSlave_ProcessIn(void); bool Xdrv31(uint8_t function); void HotPlugInit(void); void HotPlugEverySecond(void); void CmndHotPlugTime(void); bool Xdrv32(uint8_t function); bool NRF24initRadio(); bool NRF24Detect(void); bool Xdrv33(uint8_t function); void ExceptionTest(uint8_t type); void CpuLoadLoop(void); void DebugFreeMem(void); void DebugFreeMem(void); void DebugRtcDump(char* parms); void DebugCfgDump(char* parms); void DebugCfgPeek(char* parms); void DebugCfgPoke(char* parms); void SetFlashMode(uint8_t mode); void CmndHelp(void); void CmndRtcDump(void); void CmndCfgDump(void); void CmndCfgPeek(void); void CmndCfgPoke(void); void CmndCfgXor(void); void CmndException(void); void CmndCpuCheck(void); void CmndFreemem(void); void CmndSetSensor(void); void CmndFlashMode(void); uint32_t DebugSwap32(uint32_t x); void CmndFlashDump(void); void CmndI2cWrite(void); void CmndI2cRead(void); void CmndI2cStretch(void); void CmndI2cClock(void); bool Xdrv99(uint8_t function); void XsnsDriverState(void); bool XdrvRulesProcess(void); void ShowFreeMem(const char *where); bool XdrvCallDriver(uint32_t driver, uint8_t Function); bool XdrvCall(uint8_t Function); void LcdInitMode(void); void LcdInit(uint8_t mode); void LcdInitDriver(void); void LcdDrawStringAt(void); void LcdDisplayOnOff(uint8_t on); void LcdCenter(uint8_t row, char* txt); bool LcdPrintLog(void); void LcdTime(void); void LcdRefresh(void); bool Xdsp01(uint8_t function); void SSD1306InitDriver(void); void Ssd1306PrintLog(void); void Ssd1306Time(void); void Ssd1306Refresh(void); bool Xdsp02(byte function); void MatrixWrite(void); void MatrixClear(void); void MatrixFixed(char* txt); void MatrixCenter(char* txt); void MatrixScrollLeft(char* txt, int loop); void MatrixScrollUp(char* txt, int loop); void MatrixInitMode(void); void MatrixInit(uint8_t mode); void MatrixInitDriver(void); void MatrixOnOff(void); void MatrixDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag); void MatrixPrintLog(uint8_t direction); void MatrixRefresh(void); bool Xdsp03(uint8_t function); void Ili9341InitMode(void); void Ili9341Init(uint8_t mode); void Ili9341InitDriver(void); void Ili9341Clear(void); void Ili9341DrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag); void Ili9341DisplayOnOff(uint8_t on); void Ili9341OnOff(void); void Ili9341PrintLog(void); void Ili9341Refresh(void); bool Xdsp04(uint8_t function); void EpdInitDriver29(); void EpdPrintLog29(void); void EpdRefresh29(void); bool Xdsp05(uint8_t function); void EpdInitDriver42(); void EpdRefresh42(); bool Xdsp06(uint8_t function); void SH1106InitDriver(); void SH1106PrintLog(void); void SH1106Time(void); void SH1106Refresh(void); bool Xdsp07(uint8_t function); void ILI9488_InitDriver(); void ILI9488_MQTT(uint8_t count,const char *cp); void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr); void FT6236Check(); bool Xdsp08(uint8_t function); void SSD1351_InitDriver(); void SSD1351PrintLog(void); void SSD1351Time(void); void SSD1351Refresh(void); bool Xdsp09(uint8_t function); void RA8876_InitDriver(); void RA8876_MQTT(uint8_t count,const char *cp); void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr); void FT5316Check(); bool Xdsp10(uint8_t function); uint8_t XdspPresent(void); bool XdspCall(uint8_t Function); void Ws2812StripShow(void); int mod(int a, int b); void Ws2812UpdatePixelColor(int position, struct WsColor hand_color, float offset); void Ws2812UpdateHand(int position, uint32_t index); void Ws2812Clock(void); void Ws2812GradientColor(uint32_t schemenr, struct WsColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i); void Ws2812Gradient(uint32_t schemenr); void Ws2812Bars(uint32_t schemenr); void Ws2812Clear(void); void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white); char* Ws2812GetColor(uint32_t led, char* scolor); void Ws2812ForceSuspend (void); void Ws2812ForceUpdate (void); bool Ws2812SetChannels(void); void Ws2812ShowScheme(void); void Ws2812ModuleSelected(void); void CmndLed(void); void CmndPixels(void); void CmndRotation(void); void CmndWidth(void); bool Xlgt01(uint8_t function); void LightDiPulse(uint8_t times); void LightDckiPulse(uint8_t times); void LightMy92x1Write(uint8_t data); void LightMy92x1Init(void); void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c); bool My92x1SetChannels(void); void My92x1ModuleSelected(void); bool Xlgt02(uint8_t function); void SM16716_SendBit(uint8_t v); void SM16716_SendByte(uint8_t v); void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b); void SM16716_Init(void); bool Sm16716SetChannels(void); void Sm16716ModuleSelected(void); bool Xlgt03(uint8_t function); uint8_t Sm2135Write(uint8_t data); void Sm2135Send(uint8_t *buffer, uint8_t size); bool Sm2135SetChannels(void); void Sm2135ModuleSelected(void); bool Xlgt04(uint8_t function); void SnfL1Send(const char *buffer); void SnfL1SerialSendOk(void); bool SnfL1SerialInput(void); bool SnfL1SetChannels(void); void SnfL1ModuleSelected(void); bool Xlgt05(uint8_t function); bool XlgtCall(uint8_t function); void HlwCfInterrupt(void); void HlwCf1Interrupt(void); void HlwEvery200ms(void); void HlwEverySecond(void); void HlwSnsInit(void); void HlwDrvInit(void); bool HlwCommand(void); bool Xnrg01(uint8_t function); void CseReceived(void); bool CseSerialInput(void); void CseEverySecond(void); void CseSnsInit(void); void CseDrvInit(void); bool CseCommand(void); bool Xnrg02(uint8_t function); uint8_t PzemCrc(uint8_t *data); void PzemSend(uint8_t cmd); bool PzemReceiveReady(void); bool PzemRecieve(uint8_t resp, float *data); void PzemEvery250ms(void); void PzemSnsInit(void); void PzemDrvInit(void); bool PzemCommand(void); bool Xnrg03(uint8_t function); uint8_t McpChecksum(uint8_t *data); unsigned long McpExtractInt(char *data, uint8_t offset, uint8_t size); void McpSetInt(unsigned long value, uint8_t *data, uint8_t offset, size_t size); void McpSend(uint8_t *data); void McpGetAddress(void); void McpAddressReceive(void); void McpGetCalibration(void); void McpParseCalibration(void); bool McpCalibrationCalc(struct mcp_cal_registers_type *cal_registers, uint8_t range_shift); void McpSetCalibration(struct mcp_cal_registers_type *cal_registers); void McpSetSystemConfiguration(uint16 interval); void McpGetFrequency(void); void McpParseFrequency(void); void McpSetFrequency(uint16_t line_frequency_ref, uint16_t gain_line_frequency); void McpGetData(void); void McpParseData(void); void McpSerialInput(void); void McpEverySecond(void); void McpSnsInit(void); void McpDrvInit(void); bool McpCommand(void); bool Xnrg04(uint8_t function); void PzemAcEverySecond(void); void PzemAcSnsInit(void); void PzemAcDrvInit(void); bool PzemAcCommand(void); bool Xnrg05(uint8_t function); void PzemDcEverySecond(void); void PzemDcSnsInit(void); void PzemDcDrvInit(void); bool PzemDcCommand(void); bool Xnrg06(uint8_t function); int Ade7953RegSize(uint16_t reg); void Ade7953Write(uint16_t reg, uint32_t val); int32_t Ade7953Read(uint16_t reg); void Ade7953Init(void); void Ade7953GetData(void); void Ade7953EnergyEverySecond(void); void Ade7953DrvInit(void); bool Ade7953Command(void); bool Xnrg07(uint8_t function); void SDM120Every250ms(void); void Sdm120SnsInit(void); void Sdm120DrvInit(void); void Sdm220Reset(void); void Sdm220Show(bool json); bool Xnrg08(uint8_t function); void Dds2382EverySecond(void); void Dds2382SnsInit(void); void Dds2382DrvInit(void); bool Xnrg09(uint8_t function); void SDM630Every250ms(void); void Sdm630SnsInit(void); void Sdm630DrvInit(void); bool Xnrg10(uint8_t function); void DDSU666Every250ms(void); void Ddsu666SnsInit(void); void Ddsu666DrvInit(void); bool Xnrg11(uint8_t function); bool solaxX1_RS485ReceiveReady(void); void solaxX1_RS485Send(uint16_t msgLen); uint8_t solaxX1_RS485Receive(uint8_t *value); uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen); void solaxX1_SendInverterAddress(void); void solaxX1_QueryLiveData(void); uint8_t solaxX1_ParseErrorCode(uint32_t code); void solaxX1250MSecond(void); void solaxX1SnsInit(void); void solaxX1DrvInit(void); void solaxX1Show(bool json); bool Xnrg12(uint8_t function); void FifLEEvery250ms(void); void FifLESnsInit(void); void FifLEDrvInit(void); void FifLEReset(void); void FifLEShow(bool json); bool Xnrg13(uint8_t function); bool XnrgCall(uint8_t function); void CounterUpdate(uint8_t index); void CounterUpdate1(void); void CounterUpdate2(void); void CounterUpdate3(void); void CounterUpdate4(void); bool CounterPinState(void); void CounterInit(void); void CounterEverySecond(void); void CounterSaveState(void); void CounterShow(bool json); void CmndCounter(void); void CmndCounterType(void); void CmndCounterDebounce(void); bool Xsns01(uint8_t function); void AdcInit(void); uint16_t AdcRead(uint8_t factor); void AdcEvery250ms(void); uint16_t AdcGetLux(void); uint16_t AdcGetRange(void); void AdcGetCurrentPower(uint8_t factor); void AdcEverySecond(void); void AdcShow(bool json); void CmndAdc(void); void CmndAdcs(void); void CmndAdcParam(void); bool Xsns02(uint8_t function); void SonoffScSend(const char *data); void SonoffScInit(void); void SonoffScSerialInput(char *rcvstat); void SonoffScShow(bool json); bool Xsns04(uint8_t function); uint8_t OneWireReset(void); void OneWireWriteBit(uint8_t v); uint8_t OneWireReadBit(void); void OneWireWrite(uint8_t v); uint8_t OneWireRead(void); void OneWireSelect(const uint8_t rom[8]); void OneWireResetSearch(void); uint8_t OneWireSearch(uint8_t *newAddr); bool OneWireCrc8(uint8_t *addr); void Ds18x20Init(void); void Ds18x20Convert(void); bool Ds18x20Read(uint8_t sensor); void Ds18x20Name(uint8_t sensor); void Ds18x20EverySecond(void); void Ds18x20Show(bool json); bool Xsns05(uint8_t function); void DhtReadPrep(void); int32_t DhtExpectPulse(uint8_t sensor, bool level); bool DhtRead(uint8_t sensor); void DhtReadTempHum(uint8_t sensor); bool DhtPinState(); void DhtInit(void); void DhtEverySecond(void); void DhtShow(bool json); bool Xsns06(uint8_t function); void DhtReadPrep(void); int32_t DhtExpectPulse(uint8_t sensor, bool level); bool DhtRead(uint8_t sensor); void DhtReadTempHum(uint8_t sensor); bool DhtPinState(); void DhtInit(void); void DhtEverySecond(void); void DhtShow(bool json); bool Xsns06(uint8_t function); bool DhtExpectPulse(uint8_t sensor, int level); int DhtReadDat(uint8_t sensor); bool DhtRead(uint8_t sensor); void DhtReadTempHum(uint8_t sensor); bool DhtPinState(); void DhtInit(void); void DhtEverySecond(void); void DhtShow(bool json); bool Xsns06(uint8_t function); bool DhtExpectPulse(uint32_t sensor, uint32_t level); bool DhtRead(uint32_t sensor); void DhtReadTempHum(uint32_t sensor); bool DhtPinState(); void DhtInit(void); void DhtEverySecond(void); void DhtShow(bool json); bool Xsns06(uint8_t function); bool DhtWaitState(uint32_t sensor, uint32_t level); bool DhtRead(uint32_t sensor); bool DhtPinState(); void DhtInit(void); void DhtEverySecond(void); void DhtShow(bool json); bool Xsns06(uint8_t function); bool ShtReset(void); bool ShtSendCommand(const uint8_t cmd); bool ShtAwaitResult(void); int ShtReadData(void); bool ShtRead(void); void ShtDetect(void); void ShtEverySecond(void); void ShtShow(bool json); bool Xsns07(uint8_t function); uint8_t HtuCheckCrc8(uint16_t data); uint8_t HtuReadDeviceId(void); void HtuSetResolution(uint8_t resolution); void HtuReset(void); void HtuHeater(uint8_t heater); void HtuInit(void); bool HtuRead(void); void HtuDetect(void); void HtuEverySecond(void); void HtuShow(bool json); bool Xsns08(uint8_t function); bool Bmp180Calibration(uint8_t bmp_idx); void Bmp180Read(uint8_t bmp_idx); bool Bmx280Calibrate(uint8_t bmp_idx); void Bme280Read(uint8_t bmp_idx); static void BmeDelayMs(uint32_t ms); bool Bme680Init(uint8_t bmp_idx); void Bme680Read(uint8_t bmp_idx); void BmpDetect(void); void BmpRead(void); void BmpShow(bool json); void BMP_EnterSleep(void); bool Xsns09(uint8_t function); bool Bh1750Read(void); void Bh1750Detect(void); void Bh1750EverySecond(void); void Bh1750Show(bool json); bool Xsns10(uint8_t function); void Veml6070Detect(void); void Veml6070UvTableInit(void); void Veml6070EverySecond(void); void Veml6070ModeCmd(bool mode_cmd); uint16_t Veml6070ReadUv(void); double Veml6070UvRiskLevel(uint16_t uv_level); double Veml6070UvPower(double uvrisk); void Veml6070Show(bool json); bool Xsns11(uint8_t function); void Ads1115StartComparator(uint8_t channel, uint16_t mode); int16_t Ads1115GetConversion(uint8_t channel); void Ads1115Detect(void); void Ads1115Show(bool json); bool Xsns12(uint8_t function); bool Ina219SetCalibration(uint8_t mode, uint16_t addr); float Ina219GetShuntVoltage_mV(uint16_t addr); float Ina219GetBusVoltage_V(uint16_t addr); float Ina219GetCurrent_mA(uint16_t addr); bool Ina219Read(void); bool Ina219CommandSensor(void); void Ina219Detect(void); void Ina219EverySecond(void); void Ina219Show(bool json); bool Xsns13(uint8_t function); bool Sht3xRead(float &t, float &h, uint8_t sht3x_address); void Sht3xDetect(void); void Sht3xShow(bool json); bool Xsns14(uint8_t function); uint8_t MhzCalculateChecksum(uint8_t *array); size_t MhzSendCmd(uint8_t command_id); bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s); void MhzEverySecond(void); bool MhzCommandSensor(void); void MhzInit(void); void MhzShow(bool json); bool Xsns15(uint8_t function); bool Tsl2561Read(void); void Tsl2561Detect(void); void Tsl2561EverySecond(void); void Tsl2561Show(bool json); bool Xsns16(uint8_t function); void Senseair250ms(void); void SenseairInit(void); void SenseairShow(bool json); bool Xsns17(uint8_t function); bool PmsReadData(void); void PmsSecond(void); void PmsInit(void); void PmsShow(bool json); bool Xsns18(uint8_t function); void MGSInit(void); void MGSPrepare(void); char* measure_gas(int gas_type, char* buffer); void MGSShow(bool json); bool Xsns19(uint8_t function); bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, uint8_t *buffer); void NovaSdsSetWorkPeriod(void); bool NovaSdsReadData(void); void NovaSdsSecond(void); bool NovaSdsCommandSensor(void); void NovaSdsInit(void); void NovaSdsShow(bool json); bool Xsns20(uint8_t function); void sgp30_Init(void); float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit); void Sgp30Update(void); void Sgp30Show(bool json); bool Xsns21(uint8_t function); uint8_t Sr04TModeDetect(void); uint16_t Sr04TMiddleValue(uint16_t first, uint16_t second, uint16_t third); uint16_t Sr04TMode3Distance(); uint16_t Sr04TMode2Distance(void); void Sr04TReading(void); void Sr04Show(bool json); bool Xsns22(uint8_t function); uint8_t Si1145ReadByte(uint8_t reg); uint16_t Si1145ReadHalfWord(uint8_t reg); bool Si1145WriteByte(uint8_t reg, uint16_t val); uint8_t Si1145WriteParamData(uint8_t p, uint8_t v); bool Si1145Present(void); void Si1145Reset(void); void Si1145DeInit(void); bool Si1145Begin(void); uint16_t Si1145ReadUV(void); uint16_t Si1145ReadVisible(void); uint16_t Si1145ReadIR(void); bool Si1145Read(void); void Si1145Detect(void); void Si1145Update(void); void Si1145Show(bool json); bool Xsns24(uint8_t function); void LM75ADDetect(void); float LM75ADGetTemp(void); void LM75ADShow(bool json); bool Xsns26(uint8_t function); int8_t wireReadDataBlock( uint8_t reg, uint8_t *val, uint16_t len); void calculateColorTemperature(void); bool APDS9960_init(void); uint8_t getMode(void); void setMode(uint8_t mode, uint8_t enable); void enableLightSensor(void); void disableLightSensor(void); void enableProximitySensor(void); void disableProximitySensor(void); void enableGestureSensor(void); void disableGestureSensor(void); bool isGestureAvailable(void); int16_t readGesture(void); void enablePower(void); void disablePower(void); void readAllColorAndProximityData(void); void resetGestureParameters(void); bool processGestureData(void); bool decodeGesture(void); void handleGesture(void); void APDS9960_adjustATime(void); void APDS9960_loop(void); void APDS9960_detect(void); void APDS9960_show(bool json); bool APDS9960CommandSensor(void); bool Xsns27(uint8_t function); void Tm16XXSend(uint8_t data); void Tm16XXSendCommand(uint8_t cmd); void TM16XXSendData(uint8_t address, uint8_t data); uint8_t Tm16XXReceive(void); void Tm16XXClearDisplay(void); void Tm1638SetLED(uint8_t color, uint8_t pos); void Tm1638SetLEDs(word leds); uint8_t Tm1638GetButtons(void); void TmInit(void); void TmLoop(void); bool Xsns28(uint8_t function); void MCP230xx_CheckForIntCounter(void); void MCP230xx_CheckForIntRetainer(void); const char* IntModeTxt(uint8_t intmo); uint8_t MCP230xx_readGPIO(uint8_t port); void MCP230xx_ApplySettings(void); void MCP230xx_Detect(void); void MCP230xx_CheckForInterrupt(void); void MCP230xx_Show(bool json); void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate); void MCP230xx_Reset(uint8_t pinmode); bool MCP230xx_Command(void); void MCP230xx_UpdateWebData(void); void MCP230xx_OutputTelemetry(void); void MCP230xx_Interrupt_Counter_Report(void); void MCP230xx_Interrupt_Retain_Report(void); bool Xsns29(uint8_t function); void Mpr121Init(struct mpr121 *pS, bool initial); void Mpr121Show(struct mpr121 *pS, uint8_t function); bool Xsns30(uint8_t function); void CCS811Detect(void); void CCS811Update(void); void CCS811Show(bool json); bool Xsns31(uint8_t function); void MPU_6050PerformReading(void); void MPU_6050Detect(void); void MPU_6050Show(bool json); bool Xsns32(uint8_t function); void DS3231Detect(void); uint8_t bcd2dec(uint8_t n); uint8_t dec2bcd(uint8_t n); uint32_t ReadFromDS3231(void); void SetDS3231Time (uint32_t epoch_time); void DS3231EverySecond(void); bool Xsns33(uint8_t function); bool HxIsReady(uint16_t timeout); long HxRead(void); void HxResetPart(void); void HxReset(void); void HxCalibrationStateTextJson(uint8_t msg_id); void SetWeightDelta(); bool HxCommand(void); long HxWeight(void); void HxInit(void); void HxEvery100mSecond(void); void HxSaveBeforeRestart(void); void HxShow(bool json); void HandleHxAction(void); void HxSaveSettings(void); void HxLogUpdates(void); bool Xsns34(uint8_t function); void Tx20StartRead(void); void Tx20Read(void); void Tx20Init(void); void Tx20Show(bool json); bool Xsns35(uint8_t function); void MGC3130_handleSensorData(); void MGC3130_sendMessage(uint8_t data[], uint8_t length); void MGC3130_handleGesture(); bool MGC3130_handleTouch(); void MGC3130_handleAirWheel(); void MGC3130_handleSystemStatus(); bool MGC3130_receiveMessage(); bool MGC3130_readData(); void MGC3130_nextMode(); void MGC3130_loop(); void MGC3130_detect(void); void MGC3130_show(bool json); bool MGC3130CommandSensor(); bool Xsns36(uint8_t function); bool RfSnsFetchSignal(uint8_t DataPin, bool StateSignal); void RfSnsInitTheoV2(void); void RfSnsAnalyzeTheov2(void); void RfSnsTheoV2Show(bool json); void RfSnsInitAlectoV2(void); void RfSnsAnalyzeAlectov2(); void RfSnsAlectoResetRain(void); uint8_t RfSnsAlectoCRC8(uint8_t *addr, uint8_t len); void RfSnsAlectoV2Show(bool json); void RfSnsInit(void); void RfSnsAnalyzeRawSignal(void); void RfSnsEverySecond(void); void RfSnsShow(bool json); bool Xsns37(uint8_t function); void AzEverySecond(void); void AzInit(void); void AzShow(bool json); bool Xsns38(uint8_t function); void MAX31855_Init(void); void MAX31855_GetResult(void); float MAX31855_GetProbeTemperature(int32_t RawData); float MAX31855_GetReferenceTemperature(int32_t RawData); int32_t MAX31855_ShiftIn(uint8_t Length); void MAX31855_Show(bool Json); bool Xsns39(uint8_t function); void PN532_Init(void); int8_t PN532_receive(uint8_t *buf, int len, uint16_t timeout); int8_t PN532_readAckFrame(void); uint32_t PN532_getFirmwareVersion(void); void PN532_wakeup(void); bool PN532_setPassiveActivationRetries(uint8_t maxRetries); bool PN532_SAMConfig(void); uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData); uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data); uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data); void PN532_ScanForTag(void); bool PN532_Command(void); bool Xsns40(uint8_t function); bool Max4409Read_lum(void); void Max4409Detect(void); void Max4409EverySecond(void); void Max4409Show(bool json); bool Xsns41(uint8_t function); void Scd30Detect(void); void Scd30Update(void); int Scd30GetCommand(int command_code, uint16_t *pvalue); int Scd30SetCommand(int command_code, uint16_t value); bool Scd30CommandSensor(); void Scd30Show(bool json); bool Xsns42(byte function); int hreReadBit(); char hreReadChar(int &parity_errors); void hreInit(void); void hreEvery50ms(void); void hreShow(boolean json); bool Xsns43(byte function); uint8_t sps30_calc_CRC(uint8_t *data); void sps30_get_data(uint16_t cmd, uint8_t *data, uint8_t dlen); void sps30_cmd(uint16_t cmd); void SPS30_Detect(void); void SPS30_Every_Second(); void SPS30_Show(bool json); bool SPS30_cmd(void); bool Xsns44(byte function); void Vl53l0Detect(void); void Vl53l0Every_250MSecond(void); void Vl53l0Show(boolean json); bool Xsns45(byte function); void MLX90614_Init(void); void MLX90614_Every_Second(void); void MLX90614_Show(uint8_t json); bool Xsns46(byte function); void MAX31865_Init(void); void MAX31865_GetResult(void); void MAX31865_Show(bool Json); bool Xsns47(uint8_t function); void ChirpWriteI2CRegister(uint8_t addr, uint8_t reg); uint16_t ChirpFinishReadI2CRegister16bit(uint8_t addr); void ChirpReset(uint8_t addr); void ChirpResetAll(void); void ChirpClockSet(); void ChirpSleep(uint8_t addr); void ChirpSelect(uint8_t sensor); uint8_t ChirpReadVersion(uint8_t addr); bool ChirpSet(uint8_t addr); bool ChirpScan(); void ChirpDetect(void); void ChirpServiceAllSensors(uint8_t job); void ChirpEvery100MSecond(void); void ChirpShow(bool json); bool ChirpCmd(void); bool Xsns48(uint8_t function); void PAJ7620SelectBank(uint8_t bank); void PAJ7620DecodeGesture(void); void PAJ7620ReadGesture(void); void PAJ7620Detect(void); void PAJ7620Init(void); void PAJ7620Loop(void); void PAJ7620Show(bool json); bool PAJ7620CommandSensor(void); bool Xsns50(uint8_t function); void RDM6300_Init(); void RDM6300_ScanForTag(); uint8_t rm6300_hexnibble(char chr); void rm6300_hstring_to_array(uint8_t array[], uint8_t len, char buffer[]); void RDM6300_Show(void); bool Xsns51(byte function); void IBEACON_Init(); void hm17_every_second(void); void hm17_sbclr(void); void hm17_sendcmd(uint8_t cmd); uint32_t ibeacon_add(struct IBEACON *ib); void hm17_decode(void); void IBEACON_loop(); void IBEACON_Show(void); bool xsns52_cmd(void); bool ibeacon_cmd(void); void ib_sendbeep(void); void ibeacon_mqtt(const char *mac,const char *rssi); bool Xsns52(byte function); double sml_median_array(double *array,uint8_t len); double sml_median(struct SML_MEDIAN_FILTER* mf, double in); void ADS1115_init(void); bool Serial_available(); uint8_t Serial_read(); uint8_t Serial_peek(); void Dump2log(void); double sml_getvalue(unsigned char *cp,uint8_t index); uint8_t hexnibble(char chr); double CharToDouble(const char *str); void ebus_esc(uint8_t *ebus_buffer, unsigned char len); uint8_t ebus_crc8(uint8_t data, uint8_t crc_init); uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen ); void sml_empty_receiver(uint32_t meters); void sml_shift_in(uint32_t meters,uint32_t shard); void SML_Poll(void); void SML_Decode(uint8_t index); void SML_Immediate_MQTT(const char *mp,uint8_t index,uint8_t mindex); void SML_Show(boolean json); void SML_CounterUpd(uint8_t index); void SML_CounterUpd1(void); void SML_CounterUpd2(void); void SML_CounterUpd3(void); void SML_CounterUpd4(void); bool Gpio_used(uint8_t gpiopin); void SML_Init(void); uint32_t SML_SetBaud(uint32_t meter, uint32_t br); uint32_t SML_Write(uint32_t meter,char *hstr); void SetDBGLed(uint8_t srcpin, uint8_t ledpin); void SML_Counter_Poll(void); void SML_Check_Send(void); uint8_t sml_hexnibble(char chr); void SML_Send_Seq(uint32_t meter,char *seq); uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num); uint8_t SML_PzemCrc(uint8_t *data, uint8_t len); uint8_t CalcEvenParity(uint8_t data); bool XSNS_53_cmd(void); void InjektCounterValue(uint8_t meter,uint32_t counter); void SML_CounterSaveState(void); bool Xsns53(byte function); static uint32_t _expand_r_shunt(uint16_t compact_r_shunt); void Ina226SetCalibration(uint8_t slaveIndex); bool Ina226TestPresence(uint8_t device); void Ina226ResetActive(void); void Ina226Init(); float Ina226ReadBus_v(uint8_t device); float Ina226ReadShunt_i(uint8_t device); float Ina226ReadPower_w(uint8_t device); void Ina226Read(uint8_t device); void Ina226EverySecond(); bool Ina226CommandSensor(); void Ina226Show(bool json); bool Xsns54(byte callback_id); bool Hih6Read(void); void Hih6Detect(void); void Hih6EverySecond(void); void Hih6Show(bool json); bool Xsns55(uint8_t function); void HpmaSecond(void); void HpmaInit(void); void HpmaShow(bool json); bool Xsns56(uint8_t function); void Tsl2591Init(void); bool Tsl2591Read(void); void Tsl2591EverySecond(void); void Tsl2591Show(bool json); bool Xsns57(uint8_t function); bool Dht12Read(void); void Dht12Detect(void); void Dht12EverySecond(void); void Dht12Show(bool json); bool Xsns58(uint8_t function); uint32_t DS1624_Idx2Addr(uint32_t idx); int DS1624_Restart(uint8_t config, uint32_t idx); void DS1624_HotPlugUp(uint32_t idx); void DS1624_HotPlugDown(int idx); bool DS1624GetTemp(float *value, int idx); void DS1624HotPlugScan(void); void DS1624EverySecond(void); void DS1624Show(bool json); bool Xsns59(uint8_t function); void UBXcalcChecksum(char* CK, size_t msgSize); bool UBXcompareMsgHeader(const char* msgHeader); void UBXinitCFG(void); void UBXTriggerTele(void); void UBXDetect(void); uint32_t UBXprocessGPS(); void UBXsendHeader(void); void UBXsendRecord(uint8_t *buf); void UBXsendFooter(void); void UBXsendFile(void); void UBXSetRate(uint16_t interval); void UBXSelectMode(uint16_t mode); bool UBXHandlePOSLLH(); void UBXHandleSTATUS(); void UBXHandleTIME(); void UBXHandleOther(void); void UBXLoop50msec(void); void UBXLoop(void); void UBXShow(bool json); bool UBXCmd(void); bool Xsns60(uint8_t function); bool MINRFinitBLE(uint8_t _mode); void MINRFhopChannel(); bool MINRFreceivePacket(void); void MINRFswapbuf(uint8_t len); void MINRFwhiten(uint8_t *buf, uint8_t len, uint8_t lfsr); void MINRFreverseMAC(uint8_t _mac[]); void MINRFchangePacketModeTo(uint8_t _mode); void MINRFpurgeFakeSensors(void); void MINRFhandleFloraPacket(void); void MINRFhandleMJ_HT_V1Packet(void); void MINRFhandleLYWSD02Packet(void); void MINRFhandleLYWSD03Packet(void); void MINRF_EVERY_50_MSECOND(); void MINRFShow(bool json); bool Xsns61(uint8_t function); void HM10_Launchtask(uint8_t task, uint8_t slot, uint8_t delay); void HM10_TaskReplaceInSlot(uint8_t task, uint8_t slot); void HM10_Reset(void); void HM10_Discovery_Scan(void); void HM10_Read_LYWSD03(void); void HM10_Read_LYWSD02(void); void HM10_Time_LYWSD02(void); void HM10SerialInit(void); void HM10MACStringToBytes(const char* string, uint8_t _mac[]); void HM10ParseResponse(char *buf); void HM10readTempHum(char *_buf); bool HM10readBat(char *_buf); bool HM10SerialHandleFeedback(); void HM10_TaskEvery100ms(); void HM10EverySecond(); bool HM10Cmd(void); void HM10Show(bool json); bool Xsns62(uint8_t function); bool begin(); bool AHT10Read(void); unsigned char readStatus(void); void AHT10Detect(void); void AHT10EverySecond(void); void AHT10Show(bool json); bool Xsns64(uint8_t function); void HandleMetrics(void); bool Xsns91(uint8_t function); bool XsnsEnabled(uint32_t sns_index); void XsnsSensorState(void); bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index); bool XsnsCall(uint8_t Function); bool I2cEnabled(uint32_t i2c_index); void I2cDriverState(void); #line 184 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino" void setup(void) { global_state.data = 3; RtcRebootLoad(); if (!RtcRebootValid()) { RtcReboot.fast_reboot_count = 0; } RtcReboot.fast_reboot_count++; RtcRebootSave(); Serial.begin(APP_BAUDRATE); seriallog_level = LOG_LEVEL_INFO; snprintf_P(my_version, sizeof(my_version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); if (VERSION & 0xff) { snprintf_P(my_version, sizeof(my_version), PSTR("%s.%d"), my_version, VERSION & 0xff); } snprintf_P(my_image, sizeof(my_image), PSTR("(%s)"), CODE_IMAGE_STR); SettingsLoad(); SettingsDelta(); OsWatchInit(); GetFeatures(); if (1 == RtcReboot.fast_reboot_count) { UpdateQuickPowerCycle(true); XdrvCall(FUNC_SETTINGS_OVERRIDE); } seriallog_level = Settings.seriallog_level; seriallog_timer = SERIALLOG_TIMER; syslog_level = Settings.syslog_level; stop_flash_rotate = Settings.flag.stop_flash_rotate; save_data_counter = Settings.save_data; sleep = Settings.sleep; #ifndef USE_EMULATION Settings.flag2.emulation = 0; #else #ifndef USE_EMULATION_WEMO if (EMUL_WEMO == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } #endif #ifndef USE_EMULATION_HUE if (EMUL_HUE == Settings.flag2.emulation) { Settings.flag2.emulation = 0; } #endif #endif if (Settings.param[P_BOOT_LOOP_OFFSET]) { if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET]) { Settings.flag3.user_esp8285_enable = 0; if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +1) { for (uint32_t i = 0; i < MAX_RULE_SETS; i++) { if (bitRead(Settings.rule_stop, i)) { bitWrite(Settings.rule_enabled, i, 0); } } } if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +2) { Settings.rule_enabled = 0; } if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +3) { for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { Settings.my_gp.io[i] = GPIO_NONE; } Settings.my_adc0 = ADC0_NONE; } if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +4) { Settings.module = SONOFF_BASIC; } AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count); } } Format(mqtt_client, SettingsText(SET_MQTT_CLIENT), sizeof(mqtt_client)); Format(mqtt_topic, SettingsText(SET_MQTT_TOPIC), sizeof(mqtt_topic)); if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) { SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME), mqtt_topic, ESP.getChipId() & 0x1FFF); } else { snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME)); } GetEspHardwareType(); GpioInit(); WifiConnect(); SetPowerOnState(); AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_ESP8266_RELEASE), PROJECT, SettingsText(SET_FRIENDLYNAME1), my_version, my_image); #ifdef FIRMWARE_MINIMAL AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION)); #endif memcpy_P(log_data, VERSION_MARKER, 1); RtcInit(); #ifdef USE_ARDUINO_OTA ArduinoOTAInit(); #endif XdrvCall(FUNC_INIT); XsnsCall(FUNC_INIT); } void BacklogLoop(void) { if (TimeReached(backlog_delay)) { if (!BACKLOG_EMPTY && !backlog_mutex) { #ifdef SUPPORT_IF_STATEMENT backlog_mutex = true; String cmd = backlog.shift(); backlog_mutex = false; ExecuteCommand((char*)cmd.c_str(), SRC_BACKLOG); #else backlog_mutex = true; ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG); backlog_pointer++; if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; } backlog_mutex = false; #endif } } } void loop(void) { uint32_t my_sleep = millis(); XdrvCall(FUNC_LOOP); XsnsCall(FUNC_LOOP); OsWatchLoop(); ButtonLoop(); SwitchLoop(); #ifdef ROTARY_V1 RotaryLoop(); #endif BacklogLoop(); if (TimeReached(state_50msecond)) { SetNextTimeInterval(state_50msecond, 50); XdrvCall(FUNC_EVERY_50_MSECOND); XsnsCall(FUNC_EVERY_50_MSECOND); } if (TimeReached(state_100msecond)) { SetNextTimeInterval(state_100msecond, 100); Every100mSeconds(); XdrvCall(FUNC_EVERY_100_MSECOND); XsnsCall(FUNC_EVERY_100_MSECOND); } if (TimeReached(state_250msecond)) { SetNextTimeInterval(state_250msecond, 250); Every250mSeconds(); XdrvCall(FUNC_EVERY_250_MSECOND); XsnsCall(FUNC_EVERY_250_MSECOND); } if (TimeReached(state_second)) { SetNextTimeInterval(state_second, 1000); PerformEverySecond(); XdrvCall(FUNC_EVERY_SECOND); XsnsCall(FUNC_EVERY_SECOND); } if (!serial_local) { SerialInput(); } #ifdef USE_ARDUINO_OTA ArduinoOtaLoop(); #endif uint32_t my_activity = millis() - my_sleep; if (Settings.flag3.sleep_normal) { delay(sleep); } else { if (my_activity < (uint32_t)sleep) { delay((uint32_t)sleep - my_activity); } else { if (global_state.wifi_down) { delay(my_activity /2); } } } if (!my_activity) { my_activity++; } uint32_t loop_delay = sleep; if (!loop_delay) { loop_delay++; } uint32_t loops_per_second = 1000 / loop_delay; uint32_t this_cycle_ratio = 100 * my_activity / loop_delay; loop_load_avg = loop_load_avg - (loop_load_avg / loops_per_second) + (this_cycle_ratio / loops_per_second); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/sendemail.ino" #ifdef USE_SENDMAIL #include "sendemail.h" # 25 "C:/shared/sonoff/Git/Tasmota/tasmota/sendemail.ino" #ifndef SEND_MAIL_MINRAM #define SEND_MAIL_MINRAM 12*1024 #endif #define xPSTR(a) a uint16_t SendMail(char *buffer) { char *params,*oparams; const char *mserv; uint16_t port; const char *user; const char *pstr; const char *passwd; const char *from; const char *to; const char *subject; const char *cmd; char auth=0; uint16_t status=1; SendEmail *mail=0; uint16_t blen; char *endcmd; uint16_t mem=ESP.getFreeHeap(); if (memsend(from,to,subject,cmd); delete mail; if (result==true) status=0; } exit: if (oparams) free(oparams); return status; } void script_send_email_body(BearSSL::WiFiClientSecure_light *client); SendEmail::SendEmail(const String& host, const int port, const String& user, const String& passwd, const int timeout, const int auth_used) : host(host), port(port), user(user), passwd(passwd), timeout(timeout), ssl(ssl), auth_used(auth_used), client(new BearSSL::WiFiClientSecure_light(1024,1024)) { } String SendEmail::readClient() { delay(0); String r = client->readStringUntil('\n'); r.trim(); while (client->available()) { delay(0); r += client->readString(); } return r; } bool SendEmail::send(const String& from, const String& to, const String& subject, const char *msg) { bool status=false; String buffer; if (!host.length()) { return status; } client->setTimeout(timeout); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("Connecting: %s on port %d"),host.c_str(),port); #endif if (!client->connect(host.c_str(), port)) { #ifdef DEBUG_EMAIL_PORT AddLog_P(LOG_LEVEL_INFO, PSTR("Connection failed")); #endif goto exit; } buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("220"))) { goto exit; } buffer = F("EHLO "); buffer += client->localIP().toString(); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("250"))) { goto exit; } if (user.length()>0 && passwd.length()>0 ) { buffer = F("AUTH LOGIN"); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("334"))) { goto exit; } base64 b; buffer = b.encode(user); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("334"))) { goto exit; } buffer = b.encode(passwd); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("235"))) { goto exit; } } buffer = F("MAIL FROM:"); buffer += from; client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("250"))) { goto exit; } buffer = F("RCPT TO:"); buffer += to; client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("250"))) { goto exit; } buffer = F("DATA"); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = readClient(); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif if (!buffer.startsWith(F("354"))) { goto exit; } buffer = F("From: "); buffer += from; client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = F("To: "); buffer += to; client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = F("Subject: "); buffer += subject; buffer += F("\r\n"); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif #ifdef USE_SCRIPT if (*msg=='*' && *(msg+1)==0) { script_send_email_body(client); } else { client->println(msg); } #else client->println(msg); #endif client->println('.'); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif buffer = F("QUIT"); client->println(buffer); #ifdef DEBUG_EMAIL_PORT AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str()); #endif status=true; exit: return status; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" #ifndef DOMOTICZ_UPDATE_TIMER #define DOMOTICZ_UPDATE_TIMER 0 #endif #ifndef EMULATION #define EMULATION EMUL_NONE #endif #ifndef MTX_ADDRESS1 #define MTX_ADDRESS1 0 #endif #ifndef MTX_ADDRESS2 #define MTX_ADDRESS2 0 #endif #ifndef MTX_ADDRESS3 #define MTX_ADDRESS3 0 #endif #ifndef MTX_ADDRESS4 #define MTX_ADDRESS4 0 #endif #ifndef MTX_ADDRESS5 #define MTX_ADDRESS5 0 #endif #ifndef MTX_ADDRESS6 #define MTX_ADDRESS6 0 #endif #ifndef MTX_ADDRESS7 #define MTX_ADDRESS7 0 #endif #ifndef MTX_ADDRESS8 #define MTX_ADDRESS8 0 #endif #ifndef HOME_ASSISTANT_DISCOVERY_ENABLE #define HOME_ASSISTANT_DISCOVERY_ENABLE 0 #endif #ifndef LATITUDE #define LATITUDE 48.858360 #endif #ifndef LONGITUDE #define LONGITUDE 2.294442 #endif #ifndef WORKING_PERIOD #define WORKING_PERIOD 5 #endif #ifndef COLOR_TEXT #define COLOR_TEXT "#000" #endif #ifndef COLOR_BACKGROUND #define COLOR_BACKGROUND "#fff" #endif #ifndef COLOR_FORM #define COLOR_FORM "#f2f2f2" #endif #ifndef COLOR_INPUT_TEXT #define COLOR_INPUT_TEXT "#000" #endif #ifndef COLOR_INPUT #define COLOR_INPUT "#fff" #endif #ifndef COLOR_CONSOLE_TEXT #define COLOR_CONSOLE_TEXT "#000" #endif #ifndef COLOR_CONSOLE #define COLOR_CONSOLE "#fff" #endif #ifndef COLOR_TEXT_WARNING #define COLOR_TEXT_WARNING "#f00" #endif #ifndef COLOR_TEXT_SUCCESS #define COLOR_TEXT_SUCCESS "#008000" #endif #ifndef COLOR_BUTTON_TEXT #define COLOR_BUTTON_TEXT "#fff" #endif #ifndef COLOR_BUTTON #define COLOR_BUTTON "#1fa3ec" #endif #ifndef COLOR_BUTTON_HOVER #define COLOR_BUTTON_HOVER "#0e70a4" #endif #ifndef COLOR_BUTTON_RESET #define COLOR_BUTTON_RESET "#d43535" #endif #ifndef COLOR_BUTTON_RESET_HOVER #define COLOR_BUTTON_RESET_HOVER "#931f1f" #endif #ifndef COLOR_BUTTON_SAVE #define COLOR_BUTTON_SAVE "#47c266" #endif #ifndef COLOR_BUTTON_SAVE_HOVER #define COLOR_BUTTON_SAVE_HOVER "#5aaf6f" #endif #ifndef COLOR_TIMER_TAB_TEXT #define COLOR_TIMER_TAB_TEXT "#fff" #endif #ifndef COLOR_TIMER_TAB_BACKGROUND #define COLOR_TIMER_TAB_BACKGROUND "#999" #endif #ifndef COLOR_TITLE_TEXT #define COLOR_TITLE_TEXT COLOR_TEXT #endif #ifndef IR_RCV_MIN_UNKNOWN_SIZE #define IR_RCV_MIN_UNKNOWN_SIZE 6 #endif #ifndef ENERGY_OVERTEMP #define ENERGY_OVERTEMP 90 #endif #ifndef DEFAULT_DIMMER_MAX #define DEFAULT_DIMMER_MAX 100 #endif #ifndef DEFAULT_DIMMER_MIN #define DEFAULT_DIMMER_MIN 0 #endif #ifndef DEFAULT_LIGHT_DIMMER #define DEFAULT_LIGHT_DIMMER 10 #endif #ifndef DEFAULT_LIGHT_COMPONENT #define DEFAULT_LIGHT_COMPONENT 255 #endif #ifndef CORS_ENABLED_ALL #define CORS_ENABLED_ALL "*" #endif enum WebColors { COL_TEXT, COL_BACKGROUND, COL_FORM, COL_INPUT_TEXT, COL_INPUT, COL_CONSOLE_TEXT, COL_CONSOLE, COL_TEXT_WARNING, COL_TEXT_SUCCESS, COL_BUTTON_TEXT, COL_BUTTON, COL_BUTTON_HOVER, COL_BUTTON_RESET, COL_BUTTON_RESET_HOVER, COL_BUTTON_SAVE, COL_BUTTON_SAVE_HOVER, COL_TIMER_TAB_TEXT, COL_TIMER_TAB_BACKGROUND, COL_TITLE, COL_LAST }; const char kWebColors[] PROGMEM = COLOR_TEXT "|" COLOR_BACKGROUND "|" COLOR_FORM "|" COLOR_INPUT_TEXT "|" COLOR_INPUT "|" COLOR_CONSOLE_TEXT "|" COLOR_CONSOLE "|" COLOR_TEXT_WARNING "|" COLOR_TEXT_SUCCESS "|" COLOR_BUTTON_TEXT "|" COLOR_BUTTON "|" COLOR_BUTTON_HOVER "|" COLOR_BUTTON_RESET "|" COLOR_BUTTON_RESET_HOVER "|" COLOR_BUTTON_SAVE "|" COLOR_BUTTON_SAVE_HOVER "|" COLOR_TIMER_TAB_TEXT "|" COLOR_TIMER_TAB_BACKGROUND "|" COLOR_TITLE_TEXT; enum TasmotaSerialConfig { TS_SERIAL_5N1, TS_SERIAL_6N1, TS_SERIAL_7N1, TS_SERIAL_8N1, TS_SERIAL_5N2, TS_SERIAL_6N2, TS_SERIAL_7N2, TS_SERIAL_8N2, TS_SERIAL_5E1, TS_SERIAL_6E1, TS_SERIAL_7E1, TS_SERIAL_8E1, TS_SERIAL_5E2, TS_SERIAL_6E2, TS_SERIAL_7E2, TS_SERIAL_8E2, TS_SERIAL_5O1, TS_SERIAL_6O1, TS_SERIAL_7O1, TS_SERIAL_8O1, TS_SERIAL_5O2, TS_SERIAL_6O2, TS_SERIAL_7O2, TS_SERIAL_8O2 }; const uint8_t kTasmotaSerialConfig[] PROGMEM = { SERIAL_5N1, SERIAL_6N1, SERIAL_7N1, SERIAL_8N1, SERIAL_5N2, SERIAL_6N2, SERIAL_7N2, SERIAL_8N2, SERIAL_5E1, SERIAL_6E1, SERIAL_7E1, SERIAL_8E1, SERIAL_5E2, SERIAL_6E2, SERIAL_7E2, SERIAL_8E2, SERIAL_5O1, SERIAL_6O1, SERIAL_7O1, SERIAL_8O1, SERIAL_5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2 }; const uint16_t RTC_MEM_VALID = 0xA55A; uint32_t rtc_settings_crc = 0; uint32_t GetRtcSettingsCrc(void) { uint32_t crc = 0; uint8_t *bytes = (uint8_t*)&RtcSettings; for (uint32_t i = 0; i < sizeof(RTCMEM); i++) { crc += bytes[i]*(i+1); } return crc; } void RtcSettingsSave(void) { if (GetRtcSettingsCrc() != rtc_settings_crc) { RtcSettings.valid = RTC_MEM_VALID; ESP.rtcUserMemoryWrite(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM)); rtc_settings_crc = GetRtcSettingsCrc(); } } void RtcSettingsLoad(void) { ESP.rtcUserMemoryRead(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM)); if (RtcSettings.valid != RTC_MEM_VALID) { memset(&RtcSettings, 0, sizeof(RTCMEM)); RtcSettings.valid = RTC_MEM_VALID; RtcSettings.energy_kWhtoday = Settings.energy_kWhtoday; RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal; RtcSettings.energy_usage = Settings.energy_usage; for (uint32_t i = 0; i < MAX_COUNTERS; i++) { RtcSettings.pulse_counter[i] = Settings.pulse_counter[i]; } RtcSettings.power = Settings.power; RtcSettingsSave(); } rtc_settings_crc = GetRtcSettingsCrc(); } bool RtcSettingsValid(void) { return (RTC_MEM_VALID == RtcSettings.valid); } uint32_t rtc_reboot_crc = 0; uint32_t GetRtcRebootCrc(void) { uint32_t crc = 0; uint8_t *bytes = (uint8_t*)&RtcReboot; for (uint32_t i = 0; i < sizeof(RTCRBT); i++) { crc += bytes[i]*(i+1); } return crc; } void RtcRebootSave(void) { if (GetRtcRebootCrc() != rtc_reboot_crc) { RtcReboot.valid = RTC_MEM_VALID; ESP.rtcUserMemoryWrite(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT)); rtc_reboot_crc = GetRtcRebootCrc(); } } void RtcRebootReset(void) { RtcReboot.fast_reboot_count = 0; RtcRebootSave(); } void RtcRebootLoad(void) { ESP.rtcUserMemoryRead(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT)); if (RtcReboot.valid != RTC_MEM_VALID) { memset(&RtcReboot, 0, sizeof(RTCRBT)); RtcReboot.valid = RTC_MEM_VALID; RtcRebootSave(); } rtc_reboot_crc = GetRtcRebootCrc(); } bool RtcRebootValid(void) { return (RTC_MEM_VALID == RtcReboot.valid); } extern "C" { #include "spi_flash.h" } #include "eboot_command.h" #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) extern "C" uint32_t _SPIFFS_end; const uint32_t SPIFFS_END = ((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE; #else #if AUTOFLASHSIZE #include "flash_hal.h" const uint32_t SPIFFS_END = (FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE; #else extern "C" uint32_t _FS_end; const uint32_t SPIFFS_END = ((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE; #endif #endif const uint32_t SETTINGS_LOCATION = SPIFFS_END; const uint8_t CFG_ROTATES = 8; uint32_t settings_location = SETTINGS_LOCATION; uint32_t settings_crc32 = 0; uint8_t *settings_buffer = nullptr; void SetFlashModeDout(void) { uint8_t *_buffer; uint32_t address; eboot_command ebcmd; eboot_command_read(&ebcmd); address = ebcmd.args[0]; _buffer = new uint8_t[FLASH_SECTOR_SIZE]; if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) { if (_buffer[2] != 3) { _buffer[2] = 3; if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) { ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE); } } } delete[] _buffer; } bool VersionCompatible(void) { if (Settings.flag3.compatibility_check) { return true; } eboot_command ebcmd; eboot_command_read(&ebcmd); uint32_t start_address = ebcmd.args[0]; uint32_t end_address = start_address + (ebcmd.args[2] & 0xFFFFF000) + FLASH_SECTOR_SIZE; uint32_t* buffer = new uint32_t[FLASH_SECTOR_SIZE / 4]; uint32_t version[3] = { 0 }; bool found = false; for (uint32_t address = start_address; address < end_address; address = address + FLASH_SECTOR_SIZE) { ESP.flashRead(address, (uint32_t*)buffer, FLASH_SECTOR_SIZE); if ((address == start_address) && (0x1F == (buffer[0] & 0xFF))) { version[1] = 0xFFFFFFFF; found = true; } else { for (uint32_t i = 0; i < (FLASH_SECTOR_SIZE / 4); i++) { version[0] = version[1]; version[1] = version[2]; version[2] = buffer[i]; if ((MARKER_START == version[0]) && (MARKER_END == version[2])) { found = true; break; } } } if (found) { break; } } delete[] buffer; if (!found) { version[1] = 0; } AddLog_P2(LOG_LEVEL_DEBUG, PSTR("OTA: Version 0x%08X, Compatible 0x%08X"), version[1], VERSION_COMPATIBLE); if (version[1] < VERSION_COMPATIBLE) { uint32_t eboot_magic = 0; ESP.rtcUserMemoryWrite(0, (uint32_t*)&eboot_magic, sizeof(eboot_magic)); return false; } return true; } void SettingsBufferFree(void) { if (settings_buffer != nullptr) { free(settings_buffer); settings_buffer = nullptr; } } bool SettingsBufferAlloc(void) { SettingsBufferFree(); if (!(settings_buffer = (uint8_t *)malloc(sizeof(Settings)))) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_UPLOAD_ERR_2)); return false; } return true; } uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size) { uint16_t crc = 0; for (uint32_t i = 0; i < size; i++) { if ((i < 14) || (i > 15)) { crc += bytes[i]*(i+1); } } return crc; } uint16_t GetSettingsCrc(void) { uint32_t size = ((Settings.version < 0x06060007) || (Settings.version > 0x0606000A)) ? 3584 : sizeof(SYSCFG); return GetCfgCrc16((uint8_t*)&Settings, size); } uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size) { uint32_t crc = 0; while (size--) { crc ^= *bytes++; for (uint32_t j = 0; j < 8; j++) { crc = (crc >> 1) ^ (-int(crc & 1) & 0xEDB88320); } } return ~crc; } uint32_t GetSettingsCrc32(void) { return GetCfgCrc32((uint8_t*)&Settings, sizeof(SYSCFG) -4); } void SettingsSaveAll(void) { if (Settings.flag.save_state) { Settings.power = power; } else { Settings.power = 0; } XsnsCall(FUNC_SAVE_BEFORE_RESTART); XdrvCall(FUNC_SAVE_BEFORE_RESTART); SettingsSave(0); } void UpdateQuickPowerCycle(bool update) { if (Settings.flag3.fast_power_cycle_disable) { return; } uint32_t pc_register; uint32_t pc_location = SETTINGS_LOCATION - CFG_ROTATES; ESP.flashRead(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register)); if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) { uint32_t counter = ((pc_register & 0xF) << 1) & 0xF; if (0 == counter) { SettingsErase(3); EspRestart(); } else { pc_register = 0xFFA55AB0 | counter; ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register)); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Flag %02X"), counter); } } else if (pc_register != 0xFFA55ABF) { pc_register = 0xFFA55ABF; if (ESP.flashEraseSector(pc_location)) { ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register)); } AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Reset")); } } uint32_t GetSettingsTextLen(void) { char* position = Settings.text_pool; for (uint32_t size = 0; size < SET_MAX; size++) { while (*position++ != '\0') { } } return position - Settings.text_pool; } bool SettingsUpdateText(uint32_t index, const char* replace_me) { if (index >= SET_MAX) { return false; } uint32_t replace_len = strlen(replace_me); char replace[replace_len +1]; memcpy(replace, replace_me, sizeof(replace)); uint32_t start_pos = 0; uint32_t end_pos = 0; char* position = Settings.text_pool; for (uint32_t size = 0; size < SET_MAX; size++) { while (*position++ != '\0') { } if (1 == index) { start_pos = position - Settings.text_pool; } else if (0 == index) { end_pos = position - Settings.text_pool -1; } index--; } uint32_t char_len = position - Settings.text_pool; uint32_t current_len = end_pos - start_pos; int diff = replace_len - current_len; int too_long = (char_len + diff) - settings_text_size; if (too_long > 0) { AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_CONFIG "Text overflow by %d char(s)"), too_long); return false; } if (diff != 0) { memmove_P(Settings.text_pool + start_pos + replace_len, Settings.text_pool + end_pos, char_len - end_pos); } memmove_P(Settings.text_pool + start_pos, replace, replace_len); memset(Settings.text_pool + char_len + diff, 0x00, settings_text_size - char_len - diff); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG "CR %d/%d"), GetSettingsTextLen(), settings_text_size); return true; } char* SettingsText(uint32_t index) { char* position = Settings.text_pool; if (index >= SET_MAX) { position += settings_text_size -1; } else { for (;index > 0; index--) { while (*position++ != '\0') { } } } return position; } uint32_t GetSettingsAddress(void) { return settings_location * SPI_FLASH_SEC_SIZE; } void SettingsSave(uint8_t rotate) { # 590 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" #ifndef FIRMWARE_MINIMAL if ((GetSettingsCrc32() != settings_crc32) || rotate) { if (1 == rotate) { stop_flash_rotate = 1; } if (2 == rotate) { settings_location = SETTINGS_LOCATION +1; } if (stop_flash_rotate) { settings_location = SETTINGS_LOCATION; } else { settings_location--; if (settings_location <= (SETTINGS_LOCATION - CFG_ROTATES)) { settings_location = SETTINGS_LOCATION; } } Settings.save_flag++; if (UtcTime() > START_VALID_TIME) { Settings.cfg_timestamp = UtcTime(); } else { Settings.cfg_timestamp++; } Settings.cfg_size = sizeof(SYSCFG); Settings.cfg_crc = GetSettingsCrc(); Settings.cfg_crc32 = GetSettingsCrc32(); if (ESP.flashEraseSector(settings_location)) { ESP.flashWrite(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG)); } if (!stop_flash_rotate && rotate) { for (uint32_t i = 1; i < CFG_ROTATES; i++) { ESP.flashEraseSector(settings_location -i); delay(1); } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG D_SAVED_TO_FLASH_AT " %X, " D_COUNT " %d, " D_BYTES " %d"), settings_location, Settings.save_flag, sizeof(SYSCFG)); settings_crc32 = Settings.cfg_crc32; } #endif RtcSettingsSave(); } void SettingsLoad(void) { struct SYSCFGH { uint16_t cfg_holder; uint16_t cfg_size; unsigned long save_flag; } _SettingsH; unsigned long save_flag = 0; settings_location = 0; uint32_t flash_location = SETTINGS_LOCATION +1; uint16_t cfg_holder = 0; for (uint32_t i = 0; i < CFG_ROTATES; i++) { flash_location--; ESP.flashRead(flash_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG)); bool valid = false; if (Settings.version > 0x06000000) { bool almost_valid = (Settings.cfg_crc32 == GetSettingsCrc32()); if (Settings.version < 0x0606000B) { almost_valid = (Settings.cfg_crc == GetSettingsCrc()); } if (almost_valid && (0 == cfg_holder)) { cfg_holder = Settings.cfg_holder; } valid = (cfg_holder == Settings.cfg_holder); } else { ESP.flashRead((flash_location -1) * SPI_FLASH_SEC_SIZE, (uint32*)&_SettingsH, sizeof(SYSCFGH)); valid = (Settings.cfg_holder == _SettingsH.cfg_holder); } if (valid) { if (Settings.save_flag > save_flag) { save_flag = Settings.save_flag; settings_location = flash_location; if (Settings.flag.stop_flash_rotate && (0 == i)) { break; } } } delay(1); } if (settings_location > 0) { ESP.flashRead(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG)); AddLog_P2(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_LOADED_FROM_FLASH_AT " %X, " D_COUNT " %lu"), settings_location, Settings.save_flag); } #ifndef FIRMWARE_MINIMAL if (!settings_location || (Settings.cfg_holder != (uint16_t)CFG_HOLDER)) { SettingsDefault(); } settings_crc32 = GetSettingsCrc32(); #endif RtcSettingsLoad(); } void EspErase(uint32_t start_sector, uint32_t end_sector) { bool serial_output = (LOG_LEVEL_DEBUG_MORE <= seriallog_level); for (uint32_t sector = start_sector; sector < end_sector; sector++) { bool result = ESP.flashEraseSector(sector); if (serial_output) { #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 Serial.printf(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n", sector, (result) ? D_OK : D_ERROR); #else Serial.printf_P(PSTR(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n"), sector, (result) ? D_OK : D_ERROR); #endif delay(10); } else { yield(); } OsWatchLoop(); } } void SettingsErase(uint8_t type) { # 733 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" #ifndef FIRMWARE_MINIMAL uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1; uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE; if (1 == type) { _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; } else if (2 == type) { _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; _sectorEnd = SETTINGS_LOCATION +1; } else if (3 == type) { _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart); EsptoolErase(_sectorStart, _sectorEnd); #endif } void SettingsSdkErase(void) { WiFi.disconnect(true); SettingsErase(1); delay(1000); } void SettingsDefault(void) { AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_USE_DEFAULTS)); SettingsDefaultSet1(); SettingsDefaultSet2(); SettingsSave(2); } void SettingsDefaultSet1(void) { memset(&Settings, 0x00, sizeof(SYSCFG)); Settings.cfg_holder = (uint16_t)CFG_HOLDER; Settings.cfg_size = sizeof(SYSCFG); Settings.version = VERSION; } void SettingsDefaultSet2(void) { memset((char*)&Settings +16, 0x00, sizeof(SYSCFG) -16); Settings.flag.stop_flash_rotate = APP_FLASH_CYCLE; Settings.flag.global_state = APP_ENABLE_LEDLINK; Settings.flag3.sleep_normal = APP_NORMAL_SLEEP; Settings.flag3.no_power_feedback = APP_NO_RELAY_SCAN; Settings.flag3.fast_power_cycle_disable = APP_DISABLE_POWERCYCLE; Settings.flag3.bootcount_update = DEEPSLEEP_BOOTCOUNT; Settings.flag3.compatibility_check = OTA_COMPATIBILITY; Settings.save_data = SAVE_DATA; Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY; Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET; Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW; Settings.sleep = APP_SLEEP; if (Settings.sleep < 50) { Settings.sleep = 50; } Settings.interlock[0] = 0xFF; Settings.module = MODULE; ModuleDefault(WEMOS); SettingsUpdateText(SET_FRIENDLYNAME1, FRIENDLY_NAME); SettingsUpdateText(SET_FRIENDLYNAME2, FRIENDLY_NAME"2"); SettingsUpdateText(SET_FRIENDLYNAME3, FRIENDLY_NAME"3"); SettingsUpdateText(SET_FRIENDLYNAME4, FRIENDLY_NAME"4"); SettingsUpdateText(SET_OTAURL, OTA_URL); Settings.flag.save_state = SAVE_STATE; Settings.power = APP_POWER; Settings.poweronstate = APP_POWERON_STATE; Settings.blinktime = APP_BLINKTIME; Settings.blinkcount = APP_BLINKCOUNT; Settings.ledstate = APP_LEDSTATE; Settings.ledmask = APP_LEDMASK; Settings.pulse_timer[0] = APP_PULSETIME; Settings.serial_config = TS_SERIAL_8N1; Settings.baudrate = APP_BAUDRATE / 300; Settings.sbaudrate = SOFT_BAUDRATE / 300; Settings.serial_delimiter = 0xff; Settings.seriallog_level = SERIAL_LOG_LEVEL; Settings.flag3.use_wifi_scan = WIFI_SCAN_AT_RESTART; Settings.flag3.use_wifi_rescan = WIFI_SCAN_REGULARLY; Settings.wifi_output_power = 170; ParseIp(&Settings.ip_address[0], WIFI_IP_ADDRESS); ParseIp(&Settings.ip_address[1], WIFI_GATEWAY); ParseIp(&Settings.ip_address[2], WIFI_SUBNETMASK); ParseIp(&Settings.ip_address[3], WIFI_DNS); Settings.sta_config = WIFI_CONFIG_TOOL; SettingsUpdateText(SET_STASSID1, STA_SSID1); SettingsUpdateText(SET_STASSID2, STA_SSID2); SettingsUpdateText(SET_STAPWD1, STA_PASS1); SettingsUpdateText(SET_STAPWD2, STA_PASS2); SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); SettingsUpdateText(SET_SYSLOG_HOST, SYS_LOG_HOST); Settings.syslog_port = SYS_LOG_PORT; Settings.syslog_level = SYS_LOG_LEVEL; Settings.flag2.emulation = EMULATION; Settings.flag3.gui_hostname_ip = GUI_SHOW_HOSTNAME; Settings.flag3.mdns_enabled = MDNS_ENABLED; Settings.webserver = WEB_SERVER; Settings.weblog_level = WEB_LOG_LEVEL; SettingsUpdateText(SET_WEBPWD, WEB_PASSWORD); SettingsUpdateText(SET_CORS, CORS_DOMAIN); Settings.flag.button_restrict = KEY_DISABLE_MULTIPRESS; Settings.flag.button_swap = KEY_SWAP_DOUBLE_PRESS; Settings.flag.button_single = KEY_ONLY_SINGLE_PRESS; Settings.param[P_HOLD_TIME] = KEY_HOLD_TIME; for (uint32_t i = 0; i < MAX_SWITCHES; i++) { Settings.switchmode[i] = SWITCH_MODE; } Settings.flag.mqtt_enabled = MQTT_USE; Settings.flag.mqtt_response = MQTT_RESULT_COMMAND; Settings.flag.mqtt_offline = MQTT_LWT_MESSAGE; Settings.flag.mqtt_power_retain = MQTT_POWER_RETAIN; Settings.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN; Settings.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN; Settings.flag.mqtt_sensor_retain = MQTT_SENSOR_RETAIN; Settings.flag.device_index_enable = MQTT_POWER_FORMAT; Settings.flag3.time_append_timezone = MQTT_APPEND_TIMEZONE; Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL; Settings.flag3.no_hold_retain = MQTT_NO_HOLD_RETAIN; Settings.flag3.use_underscore = MQTT_INDEX_SEPARATOR; Settings.flag3.grouptopic_mode = MQTT_GROUPTOPIC_FORMAT; SettingsUpdateText(SET_MQTT_HOST, MQTT_HOST); Settings.mqtt_port = MQTT_PORT; SettingsUpdateText(SET_MQTT_CLIENT, MQTT_CLIENT_ID); SettingsUpdateText(SET_MQTT_USER, MQTT_USER); SettingsUpdateText(SET_MQTT_PWD, MQTT_PASS); SettingsUpdateText(SET_MQTT_TOPIC, MQTT_TOPIC); SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC); SettingsUpdateText(SET_MQTT_GRP_TOPIC, MQTT_GRPTOPIC); SettingsUpdateText(SET_MQTT_FULLTOPIC, MQTT_FULLTOPIC); Settings.mqtt_retry = MQTT_RETRY_SECS; SettingsUpdateText(SET_MQTTPREFIX1, SUB_PREFIX); SettingsUpdateText(SET_MQTTPREFIX2, PUB_PREFIX); SettingsUpdateText(SET_MQTTPREFIX3, PUB_PREFIX2); SettingsUpdateText(SET_STATE_TXT1, MQTT_STATUS_OFF); SettingsUpdateText(SET_STATE_TXT2, MQTT_STATUS_ON); SettingsUpdateText(SET_STATE_TXT3, MQTT_CMND_TOGGLE); SettingsUpdateText(SET_STATE_TXT4, MQTT_CMND_HOLD); char fingerprint[60]; strlcpy(fingerprint, MQTT_FINGERPRINT1, sizeof(fingerprint)); char *p = fingerprint; for (uint32_t i = 0; i < 20; i++) { Settings.mqtt_fingerprint[0][i] = strtol(p, &p, 16); } strlcpy(fingerprint, MQTT_FINGERPRINT2, sizeof(fingerprint)); p = fingerprint; for (uint32_t i = 0; i < 20; i++) { Settings.mqtt_fingerprint[1][i] = strtol(p, &p, 16); } Settings.tele_period = TELE_PERIOD; Settings.mqttlog_level = MQTT_LOG_LEVEL; Settings.flag.no_power_on_check = ENERGY_VOLTAGE_ALWAYS; Settings.flag2.current_resolution = 3; Settings.flag2.energy_resolution = ENERGY_RESOLUTION; Settings.flag3.dds2382_model = ENERGY_DDS2382_MODE; Settings.flag3.hardware_energy_total = ENERGY_HARDWARE_TOTALS; Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY; Settings.energy_power_calibration = HLW_PREF_PULSE; Settings.energy_voltage_calibration = HLW_UREF_PULSE; Settings.energy_current_calibration = HLW_IREF_PULSE; # 944 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" Settings.energy_max_power_limit_hold = MAX_POWER_HOLD; Settings.energy_max_power_limit_window = MAX_POWER_WINDOW; Settings.energy_max_power_safe_limit_hold = SAFE_POWER_HOLD; Settings.energy_max_power_safe_limit_window = SAFE_POWER_WINDOW; RtcSettings.energy_kWhtotal = 0; memset((char*)&RtcSettings.energy_usage, 0x00, sizeof(RtcSettings.energy_usage)); Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP; Settings.flag.ir_receive_decimal = IR_DATA_RADIX; Settings.flag3.receive_raw = IR_ADD_RAW_DATA; Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE; Settings.flag.rf_receive_decimal = RF_DATA_RADIX; memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9); Settings.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER; # 979 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" Settings.flag.temperature_conversion = TEMP_CONVERSION; Settings.flag.pressure_conversion = PRESSURE_CONVERSION; Settings.flag2.pressure_resolution = PRESSURE_RESOLUTION; Settings.flag2.humidity_resolution = HUMIDITY_RESOLUTION; Settings.flag2.temperature_resolution = TEMP_RESOLUTION; Settings.flag3.ds18x20_internal_pullup = DS18X20_PULL_UP; Settings.flag3.counter_reset_on_tele = COUNTER_RESET; Settings.flag2.calc_resolution = CALC_RESOLUTION; Settings.flag3.timers_enable = TIMERS_ENABLED; Settings.flag.hass_light = HASS_AS_LIGHT; Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE; Settings.flag3.hass_tele_on_power = TELE_ON_POWER; Settings.flag.knx_enabled = KNX_ENABLED; Settings.flag.knx_enable_enhancement = KNX_ENHANCED; Settings.flag.pwm_control = LIGHT_MODE; Settings.flag.ws_clock_reverse = LIGHT_CLOCK_DIRECTION; Settings.flag.light_signal = LIGHT_PAIRS_CO2; Settings.flag.not_power_linked = LIGHT_POWER_CONTROL; Settings.flag.decimal_text = LIGHT_COLOR_RADIX; Settings.flag3.pwm_multi_channels = LIGHT_CHANNEL_MODE; Settings.flag3.slider_dimmer_stay_on = LIGHT_SLIDER_POWER; Settings.flag4.alexa_ct_range = LIGHT_ALEXA_CT_RANGE; Settings.pwm_frequency = PWM_FREQ; Settings.pwm_range = PWM_RANGE; for (uint32_t i = 0; i < MAX_PWMS; i++) { Settings.light_color[i] = DEFAULT_LIGHT_COMPONENT; } Settings.light_correction = 1; Settings.light_dimmer = DEFAULT_LIGHT_DIMMER; Settings.light_speed = 1; Settings.light_width = 1; Settings.light_pixels = WS2812_LEDS; Settings.ws_width[WS_SECOND] = 1; Settings.ws_color[WS_SECOND][WS_RED] = 255; Settings.ws_color[WS_SECOND][WS_BLUE] = 255; Settings.ws_width[WS_MINUTE] = 3; Settings.ws_color[WS_MINUTE][WS_GREEN] = 255; Settings.ws_width[WS_HOUR] = 5; Settings.ws_color[WS_HOUR][WS_RED] = 255; Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX; Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN; Settings.display_mode = 1; Settings.display_refresh = 2; Settings.display_rows = 2; Settings.display_cols[0] = 16; Settings.display_cols[1] = 8; Settings.display_dimmer = 1; Settings.display_size = 1; Settings.display_font = 1; Settings.display_address[0] = MTX_ADDRESS1; Settings.display_address[1] = MTX_ADDRESS2; Settings.display_address[2] = MTX_ADDRESS3; Settings.display_address[3] = MTX_ADDRESS4; Settings.display_address[4] = MTX_ADDRESS5; Settings.display_address[5] = MTX_ADDRESS6; Settings.display_address[6] = MTX_ADDRESS7; Settings.display_address[7] = MTX_ADDRESS8; if (((APP_TIMEZONE > -14) && (APP_TIMEZONE < 15)) || (99 == APP_TIMEZONE)) { Settings.timezone = APP_TIMEZONE; Settings.timezone_minutes = 0; } else { Settings.timezone = APP_TIMEZONE / 60; Settings.timezone_minutes = abs(APP_TIMEZONE % 60); } SettingsUpdateText(SET_NTPSERVER1, NTP_SERVER1); SettingsUpdateText(SET_NTPSERVER2, NTP_SERVER2); SettingsUpdateText(SET_NTPSERVER3, NTP_SERVER3); for (uint32_t i = 0; i < MAX_NTP_SERVERS; i++) { SettingsUpdateText(SET_NTPSERVER1 +i, ReplaceCommaWithDot(SettingsText(SET_NTPSERVER1 +i))); } Settings.latitude = (int)((double)LATITUDE * 1000000); Settings.longitude = (int)((double)LONGITUDE * 1000000); SettingsResetStd(); SettingsResetDst(); Settings.button_debounce = KEY_DEBOUNCE_TIME; Settings.switch_debounce = SWITCH_DEBOUNCE_TIME; for (uint32_t j = 0; j < 5; j++) { Settings.rgbwwTable[j] = 255; } Settings.novasds_startingoffset = STARTING_OFFSET; SettingsDefaultWebColor(); memset(&Settings.monitors, 0xFF, 20); SettingsEnableAllI2cDrivers(); Settings.flag3.tuya_apply_o20 = TUYA_SETOPTION_20; Settings.flag3.tuya_serial_mqtt_publish = MQTT_TUYA_RECEIVED; Settings.flag3.buzzer_enable = BUZZER_ENABLE; Settings.flag3.shutter_mode = SHUTTER_SUPPORT; Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS; Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES; } void SettingsResetStd(void) { Settings.tflag[0].hemis = TIME_STD_HEMISPHERE; Settings.tflag[0].week = TIME_STD_WEEK; Settings.tflag[0].dow = TIME_STD_DAY; Settings.tflag[0].month = TIME_STD_MONTH; Settings.tflag[0].hour = TIME_STD_HOUR; Settings.toffset[0] = TIME_STD_OFFSET; } void SettingsResetDst(void) { Settings.tflag[1].hemis = TIME_DST_HEMISPHERE; Settings.tflag[1].week = TIME_DST_WEEK; Settings.tflag[1].dow = TIME_DST_DAY; Settings.tflag[1].month = TIME_DST_MONTH; Settings.tflag[1].hour = TIME_DST_HOUR; Settings.toffset[1] = TIME_DST_OFFSET; } void SettingsDefaultWebColor(void) { char scolor[10]; for (uint32_t i = 0; i < COL_LAST; i++) { WebHexCode(i, GetTextIndexed(scolor, sizeof(scolor), i, kWebColors)); } } void SettingsEnableAllI2cDrivers(void) { Settings.i2c_drivers[0] = 0xFFFFFFFF; Settings.i2c_drivers[1] = 0xFFFFFFFF; Settings.i2c_drivers[2] = 0xFFFFFFFF; } void SettingsDelta(void) { if (Settings.version != VERSION) { if (Settings.version < 0x06000000) { Settings.cfg_size = sizeof(SYSCFG); Settings.cfg_crc = GetSettingsCrc(); } if (Settings.version < 0x06000002) { for (uint32_t i = 0; i < MAX_SWITCHES; i++) { if (i < 4) { Settings.switchmode[i] = Settings.interlock[i]; } else { Settings.switchmode[i] = SWITCH_MODE; } } for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { if (Settings.my_gp.io[i] >= GPIO_SWT5) { Settings.my_gp.io[i] += 4; } } } if (Settings.version < 0x06000003) { Settings.flag.mqtt_serial_raw = 0; Settings.flag.pressure_conversion = 0; Settings.flag3.data = 0; } if (Settings.version < 0x06010103) { Settings.flag3.timers_enable = 1; } if (Settings.version < 0x0601010C) { Settings.button_debounce = KEY_DEBOUNCE_TIME; Settings.switch_debounce = SWITCH_DEBOUNCE_TIME; } if (Settings.version < 0x0602010A) { for (uint32_t j = 0; j < 5; j++) { Settings.rgbwwTable[j] = 255; } } if (Settings.version < 0x06030002) { Settings.timezone_minutes = 0; } if (Settings.version < 0x06030004) { memset(&Settings.monitors, 0xFF, 20); } if (Settings.version < 0x0603000E) { Settings.flag2.calc_resolution = CALC_RESOLUTION; } if (Settings.version < 0x0603000F) { if (Settings.sleep < 50) { Settings.sleep = 50; } } if (Settings.version < 0x06040105) { Settings.flag3.mdns_enabled = MDNS_ENABLED; Settings.param[P_MDNS_DELAYED_START] = 0; } if (Settings.version < 0x0604010B) { Settings.interlock[0] = 0xFF; for (uint32_t i = 1; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; } } if (Settings.version < 0x0604010D) { Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET; } if (Settings.version < 0x06040110) { ModuleDefault(WEMOS); } if (Settings.version < 0x06040113) { Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW; } if (Settings.version < 0x06050003) { Settings.novasds_startingoffset = STARTING_OFFSET; } if (Settings.version < 0x06050006) { SettingsDefaultWebColor(); } if (Settings.version < 0x06050007) { Settings.ledmask = APP_LEDMASK; } if (Settings.version < 0x0605000A) { Settings.my_adc0 = ADC0_NONE; } if (Settings.version < 0x0605000D) { Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE; } if (Settings.version < 0x06060001) { Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP; } if (Settings.version < 0x06060007) { memset((char*)&Settings +0xE00, 0x00, sizeof(SYSCFG) -0xE00); } if (Settings.version < 0x06060008) { if (Settings.flag3.tuya_serial_mqtt_publish) { Settings.param[P_ex_DIMMER_MAX] = 100; } else { Settings.param[P_ex_DIMMER_MAX] = 255; } } if (Settings.version < 0x06060009) { Settings.baudrate = APP_BAUDRATE / 300; Settings.sbaudrate = SOFT_BAUDRATE / 300; } if (Settings.version < 0x0606000A) { uint8_t tuyaindex = 0; if (Settings.param[P_BACKLOG_DELAY] > 0) { Settings.tuya_fnid_map[tuyaindex].fnid = 21; Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_BACKLOG_DELAY]; tuyaindex++; } else if (Settings.flag3.fast_power_cycle_disable == 1) { Settings.tuya_fnid_map[tuyaindex].fnid = 11; Settings.tuya_fnid_map[tuyaindex].dpid = 1; tuyaindex++; } if (Settings.param[P_ex_TUYA_RELAYS] > 0) { for (uint8_t i = 0 ; i < Settings.param[P_ex_TUYA_RELAYS]; i++) { Settings.tuya_fnid_map[tuyaindex].fnid = 12 + i; Settings.tuya_fnid_map[tuyaindex].dpid = i + 2; tuyaindex++; } } if (Settings.param[P_ex_TUYA_POWER_ID] > 0) { Settings.tuya_fnid_map[tuyaindex].fnid = 31; Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_POWER_ID]; tuyaindex++; } if (Settings.param[P_ex_TUYA_VOLTAGE_ID] > 0) { Settings.tuya_fnid_map[tuyaindex].fnid = 33; Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_VOLTAGE_ID]; tuyaindex++; } if (Settings.param[P_ex_TUYA_CURRENT_ID] > 0) { Settings.tuya_fnid_map[tuyaindex].fnid = 32; Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_CURRENT_ID]; } } if (Settings.version < 0x0606000C) { memset((char*)&Settings +0x1D6, 0x00, 16); } if (Settings.version < 0x0606000F) { Settings.ex_shutter_accuracy = 0; Settings.ex_mqttlog_level = MQTT_LOG_LEVEL; } if (Settings.version < 0x06060011) { Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY; } if (Settings.version < 0x06060012) { Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN; Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX; if (TUYA_DIMMER == Settings.module) { if (Settings.flag3.ex_tuya_dimmer_min_limit) { Settings.dimmer_hw_min = 25; } else { Settings.dimmer_hw_min = 1; } Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX]; } else if (PS_16_DZ == Settings.module) { Settings.dimmer_hw_min = 10; Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX]; } } if (Settings.version < 0x06060014) { # 1323 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino" Settings.flag3.fast_power_cycle_disable = 0; Settings.energy_power_delta = Settings.ex_energy_power_delta; Settings.ex_energy_power_delta = 0; } if (Settings.version < 0x06060015) { if ((EX_WIFI_SMARTCONFIG == Settings.ex_sta_config) || (EX_WIFI_WPSCONFIG == Settings.ex_sta_config)) { Settings.ex_sta_config = WIFI_MANAGER; } } if (Settings.version < 0x07000002) { Settings.web_color2[0][0] = Settings.web_color[0][0]; Settings.web_color2[0][1] = Settings.web_color[0][1]; Settings.web_color2[0][2] = Settings.web_color[0][2]; } if (Settings.version < 0x07000003) { SettingsEnableAllI2cDrivers(); } if (Settings.version < 0x07000004) { Settings.ex_wifi_output_power = 170; } if (Settings.version < 0x07010202) { Settings.ex_serial_config = TS_SERIAL_8N1; } if (Settings.version < 0x07010204) { if (Settings.flag3.ex_cors_enabled == 1) { strlcpy(Settings.ex_cors_domain, CORS_ENABLED_ALL, sizeof(Settings.ex_cors_domain)); } else { Settings.ex_cors_domain[0] = 0; } } if (Settings.version < 0x07010205) { Settings.seriallog_level = Settings.ex_seriallog_level; Settings.sta_config = Settings.ex_sta_config; Settings.sta_active = Settings.ex_sta_active; memcpy((char*)&Settings.rule_stop, (char*)&Settings.ex_rule_stop, 47); } if (Settings.version < 0x07010206) { Settings.flag4 = Settings.ex_flag4; Settings.mqtt_port = Settings.ex_mqtt_port; memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); } if (Settings.version < 0x08000000) { char temp[strlen(Settings.text_pool) +1]; strncpy(temp, Settings.text_pool, sizeof(temp)); char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21)); char temp22[strlen(Settings.ex_mqtt_prefix[1]) +1]; strncpy(temp22, Settings.ex_mqtt_prefix[1], sizeof(temp22)); char temp23[strlen(Settings.ex_mqtt_prefix[2]) +1]; strncpy(temp23, Settings.ex_mqtt_prefix[2], sizeof(temp23)); char temp31[strlen(Settings.ex_sta_ssid[0]) +1]; strncpy(temp31, Settings.ex_sta_ssid[0], sizeof(temp31)); char temp32[strlen(Settings.ex_sta_ssid[1]) +1]; strncpy(temp32, Settings.ex_sta_ssid[1], sizeof(temp32)); char temp41[strlen(Settings.ex_sta_pwd[0]) +1]; strncpy(temp41, Settings.ex_sta_pwd[0], sizeof(temp41)); char temp42[strlen(Settings.ex_sta_pwd[1]) +1]; strncpy(temp42, Settings.ex_sta_pwd[1], sizeof(temp42)); char temp5[strlen(Settings.ex_hostname) +1]; strncpy(temp5, Settings.ex_hostname, sizeof(temp5)); char temp6[strlen(Settings.ex_syslog_host) +1]; strncpy(temp6, Settings.ex_syslog_host, sizeof(temp6)); char temp7[strlen(Settings.ex_mqtt_host) +1]; strncpy(temp7, Settings.ex_mqtt_host, sizeof(temp7)); char temp8[strlen(Settings.ex_mqtt_client) +1]; strncpy(temp8, Settings.ex_mqtt_client, sizeof(temp8)); char temp9[strlen(Settings.ex_mqtt_user) +1]; strncpy(temp9, Settings.ex_mqtt_user, sizeof(temp9)); char temp10[strlen(Settings.ex_mqtt_pwd) +1]; strncpy(temp10, Settings.ex_mqtt_pwd, sizeof(temp10)); char temp11[strlen(Settings.ex_mqtt_topic) +1]; strncpy(temp11, Settings.ex_mqtt_topic, sizeof(temp11)); char temp12[strlen(Settings.ex_button_topic) +1]; strncpy(temp12, Settings.ex_button_topic, sizeof(temp12)); char temp13[strlen(Settings.ex_mqtt_grptopic) +1]; strncpy(temp13, Settings.ex_mqtt_grptopic, sizeof(temp13)); memset(Settings.text_pool, 0x00, settings_text_size); SettingsUpdateText(SET_OTAURL, temp); SettingsUpdateText(SET_MQTTPREFIX1, temp21); SettingsUpdateText(SET_MQTTPREFIX2, temp22); SettingsUpdateText(SET_MQTTPREFIX3, temp23); SettingsUpdateText(SET_STASSID1, temp31); SettingsUpdateText(SET_STASSID2, temp32); SettingsUpdateText(SET_STAPWD1, temp41); SettingsUpdateText(SET_STAPWD2, temp42); SettingsUpdateText(SET_HOSTNAME, temp5); SettingsUpdateText(SET_SYSLOG_HOST, temp6); #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) if (!strlen(Settings.ex_mqtt_user)) { SettingsUpdateText(SET_MQTT_HOST, temp7); SettingsUpdateText(SET_MQTT_USER, temp9); } else { char aws_mqtt_host[66]; snprintf_P(aws_mqtt_host, sizeof(aws_mqtt_host), PSTR("%s%s"), temp9, temp7); SettingsUpdateText(SET_MQTT_HOST, aws_mqtt_host); SettingsUpdateText(SET_MQTT_USER, ""); } #else SettingsUpdateText(SET_MQTT_HOST, temp7); SettingsUpdateText(SET_MQTT_USER, temp9); #endif SettingsUpdateText(SET_MQTT_CLIENT, temp8); SettingsUpdateText(SET_MQTT_PWD, temp10); SettingsUpdateText(SET_MQTT_TOPIC, temp11); SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12); SettingsUpdateText(SET_MQTT_GRP_TOPIC, temp13); SettingsUpdateText(SET_WEBPWD, Settings.ex_web_password); SettingsUpdateText(SET_CORS, Settings.ex_cors_domain); SettingsUpdateText(SET_MQTT_FULLTOPIC, Settings.ex_mqtt_fulltopic); SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, Settings.ex_switch_topic); SettingsUpdateText(SET_STATE_TXT1, Settings.ex_state_text[0]); SettingsUpdateText(SET_STATE_TXT2, Settings.ex_state_text[1]); SettingsUpdateText(SET_STATE_TXT3, Settings.ex_state_text[2]); SettingsUpdateText(SET_STATE_TXT4, Settings.ex_state_text[3]); SettingsUpdateText(SET_NTPSERVER1, Settings.ex_ntp_server[0]); SettingsUpdateText(SET_NTPSERVER2, Settings.ex_ntp_server[1]); SettingsUpdateText(SET_NTPSERVER3, Settings.ex_ntp_server[2]); SettingsUpdateText(SET_MEM1, Settings.script_pram[0]); SettingsUpdateText(SET_MEM2, Settings.script_pram[1]); SettingsUpdateText(SET_MEM3, Settings.script_pram[2]); SettingsUpdateText(SET_MEM4, Settings.script_pram[3]); SettingsUpdateText(SET_MEM5, Settings.script_pram[4]); SettingsUpdateText(SET_FRIENDLYNAME1, Settings.ex_friendlyname[0]); SettingsUpdateText(SET_FRIENDLYNAME2, Settings.ex_friendlyname[1]); SettingsUpdateText(SET_FRIENDLYNAME3, Settings.ex_friendlyname[2]); SettingsUpdateText(SET_FRIENDLYNAME4, Settings.ex_friendlyname[3]); } Settings.version = VERSION; SettingsSave(1); } } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino" IPAddress syslog_host_addr; uint32_t syslog_host_hash = 0; extern "C" { extern struct rst_info resetInfo; } #include Ticker tickerOSWatch; const uint32_t OSWATCH_RESET_TIME = 120; static unsigned long oswatch_last_loop_time; uint8_t oswatch_blocked_loop = 0; #ifndef USE_WS2812_DMA #endif #ifdef USE_KNX bool knx_started = false; #endif void OsWatchTicker(void) { uint32_t t = millis(); uint32_t last_run = abs(t - oswatch_last_loop_time); #ifdef DEBUG_THEO AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d %% (%d dBm), last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI(), last_run); #endif if (last_run >= (OSWATCH_RESET_TIME * 1000)) { RtcSettings.oswatch_blocked_loop = 1; RtcSettingsSave(); volatile uint32_t dummy; dummy = *((uint32_t*) 0x00000000); } } void OsWatchInit(void) { oswatch_blocked_loop = RtcSettings.oswatch_blocked_loop; RtcSettings.oswatch_blocked_loop = 0; oswatch_last_loop_time = millis(); tickerOSWatch.attach_ms(((OSWATCH_RESET_TIME / 3) * 1000), OsWatchTicker); } void OsWatchLoop(void) { oswatch_last_loop_time = millis(); } bool OsWatchBlockedLoop(void) { return oswatch_blocked_loop; } uint32_t ResetReason(void) { # 101 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino" return resetInfo.reason; } String GetResetReason(void) { if (oswatch_blocked_loop) { char buff[32]; strncpy_P(buff, PSTR(D_JSON_BLOCKED_LOOP), sizeof(buff)); return String(buff); } else { return ESP.getResetReason(); } } size_t strchrspn(const char *str1, int character) { size_t ret = 0; char *start = (char*)str1; char *end = strchr(str1, character); if (end) ret = end - start; return ret; } char* subStr(char* dest, char* str, const char *delim, int index) { char *act; char *sub = nullptr; char *ptr; int i; strncpy(dest, str, strlen(str)+1); for (i = 1, act = dest; i <= index; i++, act = nullptr) { sub = strtok_r(act, delim, &ptr); if (sub == nullptr) break; } sub = Trim(sub); return sub; } float CharToFloat(const char *str) { char strbuf[24]; strlcpy(strbuf, str, sizeof(strbuf)); char *pt = strbuf; while ((*pt != '\0') && isblank(*pt)) { pt++; } signed char sign = 1; if (*pt == '-') { sign = -1; } if (*pt == '-' || *pt=='+') { pt++; } float left = 0; if (*pt != '.') { left = atoi(pt); while (isdigit(*pt)) { pt++; } } float right = 0; if (*pt == '.') { pt++; right = atoi(pt); while (isdigit(*pt)) { pt++; right /= 10.0f; } } float result = left + right; if (sign < 0) { return -result; } return result; } int TextToInt(char *str) { char *p; uint8_t radix = 10; if ('#' == str[0]) { radix = 16; str++; } return strtol(str, &p, radix); } char* ulltoa(unsigned long long value, char *str, int radix) { char digits[64]; char *dst = str; int i = 0; do { int n = value % radix; digits[i++] = (n < 10) ? (char)n+'0' : (char)n-10+'A'; value /= radix; } while (value != 0); while (i > 0) { *dst++ = digits[--i]; } *dst = 0; return str; } char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween) { static const char * hex = "0123456789ABCDEF"; int between = (inbetween) ? 3 : 2; const unsigned char * pin = in; char * pout = out; for (; pin < in+insz; pout += between, pin++) { pout[0] = hex[(pgm_read_byte(pin)>>4) & 0xF]; pout[1] = hex[ pgm_read_byte(pin) & 0xF]; if (inbetween) { pout[2] = inbetween; } if (pout + 3 - out > outsz) { break; } } pout[(inbetween && insz) ? -1 : 0] = 0; return out; } char* Uint64toHex(uint64_t value, char *str, uint16_t bits) { ulltoa(value, str, 16); int fill = 8; if ((bits > 3) && (bits < 65)) { fill = bits / 4; if (bits % 4) { fill++; } } int len = strlen(str); fill -= len; if (fill > 0) { memmove(str + fill, str, len +1); memset(str, '0', fill); } return str; } char* dtostrfd(double number, unsigned char prec, char *s) { if ((isnan(number)) || (isinf(number))) { strcpy(s, "null"); return s; } else { return dtostrf(number, 1, prec, s); } } char* Unescape(char* buffer, uint32_t* size) { uint8_t* read = (uint8_t*)buffer; uint8_t* write = (uint8_t*)buffer; int32_t start_size = *size; int32_t end_size = *size; uint8_t che = 0; while (start_size > 0) { uint8_t ch = *read++; start_size--; if (ch != '\\') { *write++ = ch; } else { if (start_size > 0) { uint8_t chi = *read++; start_size--; end_size--; switch (chi) { case '\\': che = '\\'; break; case 'a': che = '\a'; break; case 'b': che = '\b'; break; case 'e': che = '\e'; break; case 'f': che = '\f'; break; case 'n': che = '\n'; break; case 'r': che = '\r'; break; case 's': che = ' '; break; case 't': che = '\t'; break; case 'v': che = '\v'; break; case 'x': { uint8_t* start = read; che = (uint8_t)strtol((const char*)read, (char**)&read, 16); start_size -= (uint16_t)(read - start); end_size -= (uint16_t)(read - start); break; } case '"': che = '\"'; break; default : { che = chi; *write++ = ch; end_size++; } } *write++ = che; } } } *size = end_size; *write++ = 0; return buffer; } char* RemoveSpace(char* p) { char* write = p; char* read = p; char ch = '.'; while (ch != '\0') { ch = *read++; if (!isspace(ch)) { *write++ = ch; } } return p; } char* ReplaceCommaWithDot(char* p) { char* write = (char*)p; char* read = (char*)p; char ch = '.'; while (ch != '\0') { ch = *read++; if (ch == ',') { ch = '.'; } *write++ = ch; } return p; } char* LowerCase(char* dest, const char* source) { char* write = dest; const char* read = source; char ch = '.'; while (ch != '\0') { ch = *read++; *write++ = tolower(ch); } return dest; } char* UpperCase(char* dest, const char* source) { char* write = dest; const char* read = source; char ch = '.'; while (ch != '\0') { ch = *read++; *write++ = toupper(ch); } return dest; } char* UpperCase_P(char* dest, const char* source) { char* write = dest; const char* read = source; char ch = '.'; while (ch != '\0') { ch = pgm_read_byte(read++); *write++ = toupper(ch); } return dest; } char* Trim(char* p) { while ((*p != '\0') && isblank(*p)) { p++; } char* q = p + strlen(p) -1; while ((q >= p) && isblank(*q)) { q--; } q++; *q = '\0'; return p; } char* RemoveAllSpaces(char* p) { char *cursor = p; uint32_t offset = 0; while (1) { *cursor = *(cursor + offset); if ((' ' == *cursor) || ('\t' == *cursor) || ('\n' == *cursor)) { offset++; } else { if (0 == *cursor) { break; } cursor++; } } return p; } char* NoAlNumToUnderscore(char* dest, const char* source) { char* write = dest; const char* read = source; char ch = '.'; while (ch != '\0') { ch = *read++; *write++ = (isalnum(ch) || ('\0' == ch)) ? ch : '_'; } return dest; } char IndexSeparator(void) { if (Settings.flag3.use_underscore) { return '_'; } else { return '-'; } } void SetShortcutDefault(void) { if ('\0' != XdrvMailbox.data[0]) { XdrvMailbox.data[0] = '0' + SC_DEFAULT; XdrvMailbox.data[1] = '\0'; } } uint8_t Shortcut(void) { uint8_t result = 10; if ('\0' == XdrvMailbox.data[1]) { if (('"' == XdrvMailbox.data[0]) || ('0' == XdrvMailbox.data[0])) { result = SC_CLEAR; } else { result = atoi(XdrvMailbox.data); if (0 == result) { result = 10; } } } return result; } bool ValidIpAddress(const char* str) { const char* p = str; while (*p && ((*p == '.') || ((*p >= '0') && (*p <= '9')))) { p++; } return (*p == '\0'); } bool ParseIp(uint32_t* addr, const char* str) { uint8_t *part = (uint8_t*)addr; uint8_t i; *addr = 0; for (i = 0; i < 4; i++) { part[i] = strtoul(str, nullptr, 10); str = strchr(str, '.'); if (str == nullptr || *str == '\0') { break; } str++; } return (3 == i); } uint32_t ParseParameters(uint32_t count, uint32_t *params) { char *p; uint32_t i = 0; for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < count; str = strtok_r(nullptr, ", ", &p), i++) { params[i] = strtoul(str, nullptr, 0); } return i; } bool NewerVersion(char* version_str) { uint32_t version = 0; uint32_t i = 0; char *str_ptr; char version_dup[strlen(version_str) +1]; strncpy(version_dup, version_str, sizeof(version_dup)); for (char *str = strtok_r(version_dup, ".", &str_ptr); str && i < sizeof(VERSION); str = strtok_r(nullptr, ".", &str_ptr), i++) { int field = atoi(str); if ((field < 0) || (field > 255)) { return false; } version = (version << 8) + field; if ((2 == i) && isalpha(str[strlen(str)-1])) { field = str[strlen(str)-1] & 0x1f; version = (version << 8) + field; i++; } } if ((i < 2) || (i > sizeof(VERSION))) { return false; } while (i < sizeof(VERSION)) { version <<= 8; i++; } return (version > VERSION); } char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option) { strncpy_P(dest, S_RSLT_POWER, size); if ((devices_present + option) > 1) { char sidx[8]; snprintf_P(sidx, sizeof(sidx), PSTR("%d"), idx); strncat(dest, sidx, size - strlen(dest) -1); } return dest; } char* GetPowerDevice(char* dest, uint32_t idx, size_t size) { return GetPowerDevice(dest, idx, size, 0); } void GetEspHardwareType(void) { uint32_t efuse1 = *(uint32_t*)(0x3FF00050); uint32_t efuse2 = *(uint32_t*)(0x3FF00054); is_8285 = ( (efuse1 & (1 << 4)) || (efuse2 & (1 << 16)) ); if (is_8285 && (ESP.getFlashChipRealSize() > 1048576)) { is_8285 = false; } } String GetDeviceHardware(void) { char buff[10]; if (is_8285) { strcpy_P(buff, PSTR("ESP8285")); } else { strcpy_P(buff, PSTR("ESP8266EX")); } return String(buff); } float ConvertTemp(float c) { float result = c; global_update = uptime; global_temperature = c; if (!isnan(c) && Settings.flag.temperature_conversion) { result = c * 1.8 + 32; } result = result + (0.1 * Settings.temp_comp); return result; } float ConvertTempToCelsius(float c) { float result = c; if (!isnan(c) && Settings.flag.temperature_conversion) { result = (c - 32) / 1.8; } result = result + (0.1 * Settings.temp_comp); return result; } char TempUnit(void) { return (Settings.flag.temperature_conversion) ? 'F' : 'C'; } float ConvertHumidity(float h) { global_update = uptime; global_humidity = h; return h; } float ConvertPressure(float p) { float result = p; global_update = uptime; global_pressure = p; if (!isnan(p) && Settings.flag.pressure_conversion) { result = p * 0.75006375541921; } return result; } String PressureUnit(void) { return (Settings.flag.pressure_conversion) ? String(D_UNIT_MILLIMETER_MERCURY) : String(D_UNIT_PRESSURE); } void ResetGlobalValues(void) { if ((uptime - global_update) > GLOBAL_VALUES_VALID) { global_update = 0; global_temperature = 9999; global_humidity = 0; global_pressure = 0; } } uint32_t SqrtInt(uint32_t num) { if (num <= 1) { return num; } uint32_t x = num / 2; uint32_t y; do { y = (x + num / x) / 2; if (y >= x) { return x; } x = y; } while (true); } uint32_t RoundSqrtInt(uint32_t num) { uint32_t s = SqrtInt(4 * num); if (s & 1) { s++; } return s / 2; } char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack) { char* write = destination; const char* read = haystack; index++; while (index--) { size_t size = destination_size -1; write = destination; char ch = '.'; while ((ch != '\0') && (ch != '|')) { ch = pgm_read_byte(read++); if (size && (ch != '|')) { *write++ = ch; size--; } } if (0 == ch) { if (index) { write = destination; } break; } } *write = '\0'; return destination; } int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack) { int result = -1; const char* read = haystack; char* write = destination; while (true) { result++; size_t size = destination_size -1; write = destination; char ch = '.'; while ((ch != '\0') && (ch != '|')) { ch = pgm_read_byte(read++); if (size && (ch != '|')) { *write++ = ch; size--; } } *write = '\0'; if (!strcasecmp(needle, destination)) { break; } if (0 == ch) { result = -1; break; } } return result; } bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void)) { GetTextIndexed(XdrvMailbox.command, CMDSZ, 0, haystack); int prefix_length = strlen(XdrvMailbox.command); if (prefix_length) { char prefix[prefix_length +1]; snprintf_P(prefix, sizeof(prefix), XdrvMailbox.topic); if (strcasecmp(prefix, XdrvMailbox.command)) { return false; } } int command_code = GetCommandCode(XdrvMailbox.command + prefix_length, CMDSZ, XdrvMailbox.topic + prefix_length, haystack); if (command_code > 0) { XdrvMailbox.command_code = command_code -1; MyCommand[XdrvMailbox.command_code](); return true; } return false; } const char kOptions[] PROGMEM = "OFF|" D_OFF "|FALSE|" D_FALSE "|STOP|" D_STOP "|" D_CELSIUS "|" "ON|" D_ON "|TRUE|" D_TRUE "|START|" D_START "|" D_FAHRENHEIT "|" D_USER "|" "TOGGLE|" D_TOGGLE "|" D_ADMIN "|" "BLINK|" D_BLINK "|" "BLINKOFF|" D_BLINKOFF "|" "ALL" ; const uint8_t sNumbers[] PROGMEM = { 0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1, 2,2,2, 3,3, 4,4, 255 }; int GetStateNumber(char *state_text) { char command[CMDSZ]; int state_number = GetCommandCode(command, sizeof(command), state_text, kOptions); if (state_number >= 0) { state_number = pgm_read_byte(sNumbers + state_number); } return state_number; } String GetSerialConfig(void) { const char kParity[] = "NEOI"; char config[4]; config[0] = '5' + (Settings.serial_config & 0x3); config[1] = kParity[(Settings.serial_config >> 3) & 0x3]; config[2] = '1' + ((Settings.serial_config >> 2) & 0x1); config[3] = '\0'; return String(config); } void SetSerialBegin() { uint32_t baudrate = Settings.baudrate * 300; AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_SERIAL "Set to %s %d bit/s"), GetSerialConfig().c_str(), baudrate); Serial.flush(); Serial.begin(baudrate, (SerialConfig)pgm_read_byte(kTasmotaSerialConfig + Settings.serial_config)); } void SetSerialConfig(uint32_t serial_config) { if (serial_config > TS_SERIAL_8O2) { serial_config = TS_SERIAL_8N1; } if (serial_config != Settings.serial_config) { Settings.serial_config = serial_config; SetSerialBegin(); } } void SetSerialBaudrate(uint32_t baudrate) { Settings.baudrate = baudrate / 300; if (Serial.baudRate() != baudrate) { SetSerialBegin(); } } void SetSerial(uint32_t baudrate, uint32_t serial_config) { Settings.flag.mqtt_serial = 0; Settings.serial_config = serial_config; Settings.baudrate = baudrate / 300; SetSeriallog(LOG_LEVEL_NONE); SetSerialBegin(); } void ClaimSerial(void) { serial_local = true; AddLog_P(LOG_LEVEL_INFO, PSTR("SNS: Hardware Serial")); SetSeriallog(LOG_LEVEL_NONE); Settings.baudrate = Serial.baudRate() / 300; } void SerialSendRaw(char *codes) { char *p; char stemp[3]; uint8_t code; int size = strlen(codes); while (size > 1) { strlcpy(stemp, codes, sizeof(stemp)); code = strtol(stemp, &p, 16); Serial.write(code); size -= 2; codes += 2; } } uint32_t GetHash(const char *buffer, size_t size) { uint32_t hash = 0; for (uint32_t i = 0; i <= size; i++) { hash += (uint8_t)*buffer++ * (i +1); } return hash; } void ShowSource(uint32_t source) { if ((source > 0) && (source < SRC_MAX)) { char stemp1[20]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SRC: %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource)); } } void WebHexCode(uint32_t i, const char* code) { char scolor[10]; strlcpy(scolor, code, sizeof(scolor)); char* p = scolor; if ('#' == p[0]) { p++; } if (3 == strlen(p)) { p[6] = p[3]; p[5] = p[2]; p[4] = p[2]; p[3] = p[1]; p[2] = p[1]; p[1] = p[0]; } uint32_t color = strtol(p, nullptr, 16); uint32_t j = sizeof(Settings.web_color) / 3; # 916 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino" if (i >= j) { i += ((((uint8_t*)&Settings.web_color2 - (uint8_t*)&Settings.web_color) / 3) - j); } Settings.web_color[i][0] = (color >> 16) & 0xFF; Settings.web_color[i][1] = (color >> 8) & 0xFF; Settings.web_color[i][2] = color & 0xFF; } uint32_t WebColor(uint32_t i) { uint32_t j = sizeof(Settings.web_color) / 3; if (i >= j) { i += ((((uint8_t*)&Settings.web_color2 - (uint8_t*)&Settings.web_color) / 3) - j); } uint32_t tcolor = (Settings.web_color[i][0] << 16) | (Settings.web_color[i][1] << 8) | Settings.web_color[i][2]; return tcolor; } const uint16_t TIMESZ = 100; char* ResponseGetTime(uint32_t format, char* time_str) { switch (format) { case 1: snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%u"), GetDateAndTime(DT_LOCAL).c_str(), UtcTime()); break; case 2: snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":%u"), UtcTime()); break; default: snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str()); } return time_str; } int Response_P(const char* format, ...) { va_list args; va_start(args, format); int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), format, args); va_end(args); return len; } int ResponseTime_P(const char* format, ...) { va_list args; va_start(args, format); ResponseGetTime(Settings.flag2.time_format, mqtt_data); int mlen = strlen(mqtt_data); int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args); va_end(args); return len + mlen; } int ResponseAppend_P(const char* format, ...) { va_list args; va_start(args, format); int mlen = strlen(mqtt_data); int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args); va_end(args); return len + mlen; } int ResponseAppendTimeFormat(uint32_t format) { char time_str[TIMESZ]; return ResponseAppend_P(ResponseGetTime(format, time_str)); } int ResponseAppendTime(void) { return ResponseAppendTimeFormat(Settings.flag2.time_format); } int ResponseJsonEnd(void) { return ResponseAppend_P(PSTR("}")); } int ResponseJsonEndEnd(void) { return ResponseAppend_P(PSTR("}}")); } void DigitalWrite(uint32_t gpio_pin, uint32_t state) { if (pin[gpio_pin] < 99) { digitalWrite(pin[gpio_pin], state &1); } } uint8_t ModuleNr(void) { return (USER_MODULE == Settings.module) ? 0 : Settings.module +1; } bool ValidTemplateModule(uint32_t index) { for (uint32_t i = 0; i < sizeof(kModuleNiceList); i++) { if (index == pgm_read_byte(kModuleNiceList + i)) { return true; } } return false; } bool ValidModule(uint32_t index) { if (index == USER_MODULE) { return true; } return ValidTemplateModule(index); } String AnyModuleName(uint32_t index) { if (USER_MODULE == index) { return String(Settings.user_template.name); } else { return FPSTR(kModules[index].name); } } String ModuleName(void) { return AnyModuleName(Settings.module); } void ModuleGpios(myio *gp) { uint8_t *dest = (uint8_t *)gp; memset(dest, GPIO_NONE, sizeof(myio)); uint8_t src[sizeof(mycfgio)]; if (USER_MODULE == Settings.module) { memcpy(&src, &Settings.user_template.gp, sizeof(mycfgio)); } else { memcpy_P(&src, &kModules[Settings.module].gp, sizeof(mycfgio)); } uint32_t j = 0; for (uint32_t i = 0; i < sizeof(mycfgio); i++) { if (6 == i) { j = 9; } if (8 == i) { j = 12; } dest[j] = src[i]; j++; } } gpio_flag ModuleFlag(void) { gpio_flag flag; if (USER_MODULE == Settings.module) { flag = Settings.user_template.flag; } else { memcpy_P(&flag, &kModules[Settings.module].flag, sizeof(gpio_flag)); } return flag; } void ModuleDefault(uint32_t module) { if (USER_MODULE == module) { module = WEMOS; } Settings.user_template_base = module; memcpy_P(&Settings.user_template, &kModules[module], sizeof(mytmplt)); } void SetModuleType(void) { my_module_type = (USER_MODULE == Settings.module) ? Settings.user_template_base : Settings.module; } bool FlashPin(uint32_t pin) { return (((pin > 5) && (pin < 9)) || (11 == pin)); } uint8_t ValidPin(uint32_t pin, uint32_t gpio) { if (FlashPin(pin)) { return GPIO_NONE; } if ((WEMOS == Settings.module) && !Settings.flag3.user_esp8285_enable) { if ((pin == 9) || (pin == 10)) { return GPIO_NONE; } } return gpio; } bool ValidGPIO(uint32_t pin, uint32_t gpio) { return (GPIO_USER == ValidPin(pin, gpio)); } bool ValidAdc(void) { gpio_flag flag = ModuleFlag(); uint32_t template_adc0 = flag.data &15; return (ADC0_USER == template_adc0); } bool GetUsedInModule(uint32_t val, uint8_t *arr) { int offset = 0; if (!val) { return false; } if ((val >= GPIO_KEY1) && (val < GPIO_KEY1 + MAX_KEYS)) { offset = (GPIO_KEY1_NP - GPIO_KEY1); } if ((val >= GPIO_KEY1_NP) && (val < GPIO_KEY1_NP + MAX_KEYS)) { offset = -(GPIO_KEY1_NP - GPIO_KEY1); } if ((val >= GPIO_KEY1_INV) && (val < GPIO_KEY1_INV + MAX_KEYS)) { offset = -(GPIO_KEY1_INV - GPIO_KEY1); } if ((val >= GPIO_KEY1_INV_NP) && (val < GPIO_KEY1_INV_NP + MAX_KEYS)) { offset = -(GPIO_KEY1_INV_NP - GPIO_KEY1); } if ((val >= GPIO_SWT1) && (val < GPIO_SWT1 + MAX_SWITCHES)) { offset = (GPIO_SWT1_NP - GPIO_SWT1); } if ((val >= GPIO_SWT1_NP) && (val < GPIO_SWT1_NP + MAX_SWITCHES)) { offset = -(GPIO_SWT1_NP - GPIO_SWT1); } if ((val >= GPIO_REL1) && (val < GPIO_REL1 + MAX_RELAYS)) { offset = (GPIO_REL1_INV - GPIO_REL1); } if ((val >= GPIO_REL1_INV) && (val < GPIO_REL1_INV + MAX_RELAYS)) { offset = -(GPIO_REL1_INV - GPIO_REL1); } if ((val >= GPIO_LED1) && (val < GPIO_LED1 + MAX_LEDS)) { offset = (GPIO_LED1_INV - GPIO_LED1); } if ((val >= GPIO_LED1_INV) && (val < GPIO_LED1_INV + MAX_LEDS)) { offset = -(GPIO_LED1_INV - GPIO_LED1); } if ((val >= GPIO_PWM1) && (val < GPIO_PWM1 + MAX_PWMS)) { offset = (GPIO_PWM1_INV - GPIO_PWM1); } if ((val >= GPIO_PWM1_INV) && (val < GPIO_PWM1_INV + MAX_PWMS)) { offset = -(GPIO_PWM1_INV - GPIO_PWM1); } if ((val >= GPIO_CNTR1) && (val < GPIO_CNTR1 + MAX_COUNTERS)) { offset = (GPIO_CNTR1_NP - GPIO_CNTR1); } if ((val >= GPIO_CNTR1_NP) && (val < GPIO_CNTR1_NP + MAX_COUNTERS)) { offset = -(GPIO_CNTR1_NP - GPIO_CNTR1); } for (uint32_t i = 0; i < MAX_GPIO_PIN; i++) { if (arr[i] == val) { return true; } if (arr[i] == val + offset) { return true; } } return false; } bool JsonTemplate(const char* dataBuf) { if (strlen(dataBuf) < 9) { return false; } StaticJsonBuffer<350> jb; JsonObject& obj = jb.parseObject(dataBuf); if (!obj.success()) { return false; } const char* name = obj[D_JSON_NAME]; if (name != nullptr) { strlcpy(Settings.user_template.name, name, sizeof(Settings.user_template.name)); } if (obj[D_JSON_GPIO].success()) { for (uint32_t i = 0; i < sizeof(mycfgio); i++) { Settings.user_template.gp.io[i] = obj[D_JSON_GPIO][i] | 0; } } if (obj[D_JSON_FLAG].success()) { uint8_t flag = obj[D_JSON_FLAG] | 0; memcpy(&Settings.user_template.flag, &flag, sizeof(gpio_flag)); } if (obj[D_JSON_BASE].success()) { uint8_t base = obj[D_JSON_BASE]; if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; } Settings.user_template_base = base -1; } return true; } void TemplateJson(void) { Response_P(PSTR("{\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), Settings.user_template.name); for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) { ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Settings.user_template.gp.io[i]); } ResponseAppend_P(PSTR("],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), Settings.user_template.flag, Settings.user_template_base +1); } inline int32_t TimeDifference(uint32_t prev, uint32_t next) { return ((int32_t) (next - prev)); } int32_t TimePassedSince(uint32_t timestamp) { return TimeDifference(timestamp, millis()); } bool TimeReached(uint32_t timer) { const long passed = TimePassedSince(timer); return (passed >= 0); } void SetNextTimeInterval(unsigned long& timer, const unsigned long step) { timer += step; const long passed = TimePassedSince(timer); if (passed < 0) { return; } if (static_cast(passed) > step) { timer = millis() + step; return; } timer = millis() + (step - passed); } int32_t TimePassedSinceUsec(uint32_t timestamp) { return TimeDifference(timestamp, micros()); } bool TimeReachedUsec(uint32_t timer) { const long passed = TimePassedSinceUsec(timer); return (passed >= 0); } #ifdef USE_I2C const uint8_t I2C_RETRY_COUNTER = 3; uint32_t i2c_active[4] = { 0 }; uint32_t i2c_buffer = 0; bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size) { uint8_t retry = I2C_RETRY_COUNTER; bool status = false; i2c_buffer = 0; while (!status && retry) { Wire.beginTransmission(addr); Wire.write(reg); if (0 == Wire.endTransmission(false)) { Wire.requestFrom((int)addr, (int)size); if (Wire.available() == size) { for (uint32_t i = 0; i < size; i++) { i2c_buffer = i2c_buffer << 8 | Wire.read(); } status = true; } } retry--; } return status; } bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg) { bool status = I2cValidRead(addr, reg, 1); *data = (uint8_t)i2c_buffer; return status; } bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg) { bool status = I2cValidRead(addr, reg, 2); *data = (uint16_t)i2c_buffer; return status; } bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg) { bool status = I2cValidRead(addr, reg, 2); *data = (int16_t)i2c_buffer; return status; } bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg) { uint16_t ldata; bool status = I2cValidRead16(&ldata, addr, reg); *data = (ldata >> 8) | (ldata << 8); return status; } bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg) { uint16_t ldata; bool status = I2cValidRead16LE(&ldata, addr, reg); *data = (int16_t)ldata; return status; } bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg) { bool status = I2cValidRead(addr, reg, 3); *data = i2c_buffer; return status; } uint8_t I2cRead8(uint8_t addr, uint8_t reg) { I2cValidRead(addr, reg, 1); return (uint8_t)i2c_buffer; } uint16_t I2cRead16(uint8_t addr, uint8_t reg) { I2cValidRead(addr, reg, 2); return (uint16_t)i2c_buffer; } int16_t I2cReadS16(uint8_t addr, uint8_t reg) { I2cValidRead(addr, reg, 2); return (int16_t)i2c_buffer; } uint16_t I2cRead16LE(uint8_t addr, uint8_t reg) { I2cValidRead(addr, reg, 2); uint16_t temp = (uint16_t)i2c_buffer; return (temp >> 8) | (temp << 8); } int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg) { return (int16_t)I2cRead16LE(addr, reg); } int32_t I2cRead24(uint8_t addr, uint8_t reg) { I2cValidRead(addr, reg, 3); return i2c_buffer; } bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size) { uint8_t x = I2C_RETRY_COUNTER; do { Wire.beginTransmission((uint8_t)addr); Wire.write(reg); uint8_t bytes = size; while (bytes--) { Wire.write((val >> (8 * bytes)) & 0xFF); } x--; } while (Wire.endTransmission(true) != 0 && x != 0); return (x); } bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val) { return I2cWrite(addr, reg, val, 1); } bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val) { return I2cWrite(addr, reg, val, 2); } int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len) { Wire.beginTransmission((uint8_t)addr); Wire.write((uint8_t)reg); Wire.endTransmission(); if (len != Wire.requestFrom((uint8_t)addr, (uint8_t)len)) { return 1; } while (len--) { *reg_data = (uint8_t)Wire.read(); reg_data++; } return 0; } int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len) { Wire.beginTransmission((uint8_t)addr); Wire.write((uint8_t)reg); while (len--) { Wire.write(*reg_data); reg_data++; } Wire.endTransmission(); return 0; } void I2cScan(char *devs, unsigned int devs_len) { uint8_t error = 0; uint8_t address = 0; uint8_t any = 0; snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_DEVICES_FOUND_AT)); for (address = 1; address <= 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (0 == error) { any = 1; snprintf_P(devs, devs_len, PSTR("%s 0x%02x"), devs, address); } else if (error != 2) { any = 2; snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"Error %d at 0x%02x"), error, address); break; } } if (any) { strncat(devs, "\"}", devs_len - strlen(devs) -1); } else { snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_NO_DEVICES_FOUND "\"}")); } } void I2cResetActive(uint32_t addr, uint32_t count = 1) { addr &= 0x7F; count &= 0x7F; while (count-- && (addr < 128)) { i2c_active[addr / 32] &= ~(1 << (addr % 32)); addr++; } } void I2cSetActive(uint32_t addr, uint32_t count = 1) { addr &= 0x7F; count &= 0x7F; while (count-- && (addr < 128)) { i2c_active[addr / 32] |= (1 << (addr % 32)); addr++; } } void I2cSetActiveFound(uint32_t addr, const char *types) { I2cSetActive(addr); AddLog_P2(LOG_LEVEL_INFO, S_LOG_I2C_FOUND_AT, types, addr); } bool I2cActive(uint32_t addr) { addr &= 0x7F; if (i2c_active[addr / 32] & (1 << (addr % 32))) { return true; } return false; } bool I2cSetDevice(uint32_t addr) { addr &= 0x7F; if (I2cActive(addr)) { return false; } Wire.beginTransmission((uint8_t)addr); return (0 == Wire.endTransmission()); } #endif # 1559 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino" void SetSeriallog(uint32_t loglevel) { Settings.seriallog_level = loglevel; seriallog_level = loglevel; seriallog_timer = 0; } void SetSyslog(uint32_t loglevel) { Settings.syslog_level = loglevel; syslog_level = loglevel; syslog_timer = 0; } #ifdef USE_WEBSERVER void GetLog(uint32_t idx, char** entry_pp, size_t* len_p) { char* entry_p = nullptr; size_t len = 0; if (idx) { char* it = web_log; do { uint32_t cur_idx = *it; it++; size_t tmp = strchrspn(it, '\1'); tmp++; if (cur_idx == idx) { len = tmp; entry_p = it; break; } it += tmp; } while (it < web_log + WEB_LOG_SIZE && *it != '\0'); } *entry_pp = entry_p; *len_p = len; } #endif void Syslog(void) { uint32_t current_hash = GetHash(SettingsText(SET_SYSLOG_HOST), strlen(SettingsText(SET_SYSLOG_HOST))); if (syslog_host_hash != current_hash) { syslog_host_hash = current_hash; WiFi.hostByName(SettingsText(SET_SYSLOG_HOST), syslog_host_addr); } if (PortUdp.beginPacket(syslog_host_addr, Settings.syslog_port)) { char syslog_preamble[64]; snprintf_P(syslog_preamble, sizeof(syslog_preamble), PSTR("%s ESP-"), my_hostname); memmove(log_data + strlen(syslog_preamble), log_data, sizeof(log_data) - strlen(syslog_preamble)); log_data[sizeof(log_data) -1] = '\0'; memcpy(log_data, syslog_preamble, strlen(syslog_preamble)); PortUdp.write(log_data, strlen(log_data)); PortUdp.endPacket(); delay(1); } else { syslog_level = 0; syslog_timer = SYSLOG_TIMER; AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_HOST_NOT_FOUND ". " D_RETRY_IN " %d " D_UNIT_SECOND), SYSLOG_TIMER); } } void AddLog(uint32_t loglevel) { char mxtime[10]; snprintf_P(mxtime, sizeof(mxtime), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d "), RtcTime.hour, RtcTime.minute, RtcTime.second); if (loglevel <= seriallog_level) { Serial.printf("%s%s\r\n", mxtime, log_data); } #ifdef USE_WEBSERVER if (Settings.webserver && (loglevel <= Settings.weblog_level)) { web_log_index &= 0xFF; if (!web_log_index) web_log_index++; while (web_log_index == web_log[0] || strlen(web_log) + strlen(log_data) + 13 > WEB_LOG_SIZE) { char* it = web_log; it++; it += strchrspn(it, '\1'); it++; memmove(web_log, it, WEB_LOG_SIZE -(it-web_log)); } snprintf_P(web_log, sizeof(web_log), PSTR("%s%c%s%s\1"), web_log, web_log_index++, mxtime, log_data); web_log_index &= 0xFF; if (!web_log_index) web_log_index++; } #endif if (Settings.flag.mqtt_enabled && !global_state.mqtt_down && (loglevel <= Settings.mqttlog_level)) { MqttPublishLogging(mxtime); } if (!global_state.wifi_down && (loglevel <= syslog_level)) { Syslog(); } } void AddLog_P(uint32_t loglevel, const char *formatP) { snprintf_P(log_data, sizeof(log_data), formatP); AddLog(loglevel); } void AddLog_P(uint32_t loglevel, const char *formatP, const char *formatP2) { char message[sizeof(log_data)]; snprintf_P(log_data, sizeof(log_data), formatP); snprintf_P(message, sizeof(message), formatP2); strncat(log_data, message, sizeof(log_data) - strlen(log_data) -1); AddLog(loglevel); } void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...) { va_list arg; va_start(arg, formatP); vsnprintf_P(log_data, sizeof(log_data), formatP, arg); va_end(arg); prepped_loglevel = loglevel; } void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...) { va_list arg; va_start(arg, formatP); vsnprintf_P(log_data, sizeof(log_data), formatP, arg); va_end(arg); AddLog(loglevel); } void AddLog_Debug(PGM_P formatP, ...) { va_list arg; va_start(arg, formatP); vsnprintf_P(log_data, sizeof(log_data), formatP, arg); va_end(arg); AddLog(LOG_LEVEL_DEBUG); } void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count) { # 1721 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino" char hex_char[(count * 3) + 2]; AddLog_P2(loglevel, PSTR("DMP: %s"), ToHex_P(buffer, count, hex_char, sizeof(hex_char), ' ')); } void AddLogSerial(uint32_t loglevel) { AddLogBuffer(loglevel, (uint8_t*)serial_in_buffer, serial_in_byte_counter); } void AddLogMissed(char *sensor, uint32_t misses) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SNS: %s missed %d"), sensor, SENSOR_MAX_MISS - misses); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino" #define BUTTON_V1 #ifdef BUTTON_V1 #define MAX_BUTTON_COMMANDS 5 const char kCommands[] PROGMEM = D_CMND_WIFICONFIG " 2|" D_CMND_WIFICONFIG " 2|" D_CMND_WIFICONFIG " 2|" D_CMND_RESTART " 1|" D_CMND_UPGRADE " 1"; struct BUTTON { unsigned long debounce = 0; uint16_t hold_timer[MAX_KEYS] = { 0 }; uint16_t dual_code = 0; uint8_t last_state[MAX_KEYS] = { NOT_PRESSED, NOT_PRESSED, NOT_PRESSED, NOT_PRESSED }; uint8_t window_timer[MAX_KEYS] = { 0 }; uint8_t press_counter[MAX_KEYS] = { 0 }; uint8_t dual_receive_count = 0; uint8_t no_pullup_mask = 0; uint8_t inverted_mask = 0; uint8_t present = 0; uint8_t adc = 99; } Button; void ButtonPullupFlag(uint8 button_bit) { bitSet(Button.no_pullup_mask, button_bit); } void ButtonInvertFlag(uint8 button_bit) { bitSet(Button.inverted_mask, button_bit); } void ButtonInit(void) { Button.present = 0; for (uint32_t i = 0; i < MAX_KEYS; i++) { if (pin[GPIO_KEY1 +i] < 99) { Button.present++; pinMode(pin[GPIO_KEY1 +i], bitRead(Button.no_pullup_mask, i) ? INPUT : ((16 == pin[GPIO_KEY1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP)); } #ifndef USE_ADC_VCC else if ((99 == Button.adc) && ((ADC0_BUTTON == my_adc0) || (ADC0_BUTTON_INV == my_adc0))) { Button.present++; Button.adc = i; } #endif } } uint8_t ButtonSerial(uint8_t serial_in_byte) { if (Button.dual_receive_count) { Button.dual_receive_count--; if (Button.dual_receive_count) { Button.dual_code = (Button.dual_code << 8) | serial_in_byte; serial_in_byte = 0; } else { if (serial_in_byte != 0xA1) { Button.dual_code = 0; } } } if (0xA0 == serial_in_byte) { serial_in_byte = 0; Button.dual_code = 0; Button.dual_receive_count = 3; } return serial_in_byte; } # 108 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino" void ButtonHandler(void) { if (uptime < 4) { return; } uint8_t hold_time_extent = IMMINENT_RESET_FACTOR; uint16_t loops_per_second = 1000 / Settings.button_debounce; char scmnd[20]; for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) { uint8_t button = NOT_PRESSED; uint8_t button_present = 0; if (!button_index && ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type))) { button_present = 1; if (Button.dual_code) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON " " D_CODE " %04X"), Button.dual_code); button = PRESSED; if (0xF500 == Button.dual_code) { Button.hold_timer[button_index] = (loops_per_second * Settings.param[P_HOLD_TIME] / 10) -1; hold_time_extent = 1; } Button.dual_code = 0; } } else if (pin[GPIO_KEY1 +button_index] < 99) { button_present = 1; button = (digitalRead(pin[GPIO_KEY1 +button_index]) != bitRead(Button.inverted_mask, button_index)); } #ifndef USE_ADC_VCC if (Button.adc == button_index) { button_present = 1; if (ADC0_BUTTON_INV == my_adc0) { button = (AdcRead(1) < 128); } else if (ADC0_BUTTON == my_adc0) { button = (AdcRead(1) > 128); } } #endif if (button_present) { XdrvMailbox.index = button_index; XdrvMailbox.payload = button; if (XdrvCall(FUNC_BUTTON_PRESSED)) { } else if (SONOFF_4CHPRO == my_module_type) { if (Button.hold_timer[button_index]) { Button.hold_timer[button_index]--; } bool button_pressed = false; if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_LEVEL_10), button_index +1); Button.hold_timer[button_index] = loops_per_second; button_pressed = true; } if ((NOT_PRESSED == button) && (PRESSED == Button.last_state[button_index])) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_LEVEL_01), button_index +1); if (!Button.hold_timer[button_index]) { button_pressed = true; } } if (button_pressed) { if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) { ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON); } } } else { if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) { if (Settings.flag.button_single) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_IMMEDIATE), button_index +1); if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) { ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON); } } else { Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_MULTI_PRESS " %d"), button_index +1, Button.press_counter[button_index]); Button.window_timer[button_index] = loops_per_second / 2; } blinks = 201; } if (NOT_PRESSED == button) { Button.hold_timer[button_index] = 0; } else { Button.hold_timer[button_index]++; if (Settings.flag.button_single) { if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) { snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_SETOPTION "13 0")); ExecuteCommand(scmnd, SRC_BUTTON); } } else { if (Settings.flag.button_restrict) { if (Settings.param[P_HOLD_IGNORE] > 0) { if (Button.hold_timer[button_index] > loops_per_second * Settings.param[P_HOLD_IGNORE] / 10) { Button.hold_timer[button_index] = 0; Button.press_counter[button_index] = 0; DEBUG_CORE_LOG(PSTR("BTN: " D_BUTTON "%d cancel by " D_CMND_SETOPTION "40 %d"), button_index +1, Settings.param[P_HOLD_IGNORE]); } } if (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10) { Button.press_counter[button_index] = 0; SendKey(KEY_BUTTON, button_index +1, POWER_HOLD); } } else { if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) { Button.press_counter[button_index] = 0; snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); ExecuteCommand(scmnd, SRC_BUTTON); } } } } if (!Settings.flag.button_single) { if (Button.window_timer[button_index]) { Button.window_timer[button_index]--; } else { if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0) && (Button.press_counter[button_index] < MAX_BUTTON_COMMANDS +3)) { bool single_press = false; if (Button.press_counter[button_index] < 3) { if ((SONOFF_DUAL_R2 == my_module_type) || (SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { single_press = true; } else { single_press = (Settings.flag.button_swap +1 == Button.press_counter[button_index]); if ((1 == Button.present) && (2 == devices_present)) { if (Settings.flag.button_swap) { Button.press_counter[button_index] = (single_press) ? 1 : 2; } } else { Button.press_counter[button_index] = 1; } } } #if defined(USE_LIGHT) && defined(ROTARY_V1) if (!((0 == button_index) && RotaryButtonPressed())) { #endif if (single_press && SendKey(KEY_BUTTON, button_index + Button.press_counter[button_index], POWER_TOGGLE)) { } else { if (Button.press_counter[button_index] < 3) { if (WifiState() > WIFI_RESTART) { restart_flag = 1; } else { ExecuteCommandPower(button_index + Button.press_counter[button_index], POWER_TOGGLE, SRC_BUTTON); } } else { if (!Settings.flag.button_restrict) { GetTextIndexed(scmnd, sizeof(scmnd), Button.press_counter[button_index] -3, kCommands); ExecuteCommand(scmnd, SRC_BUTTON); } } } #if defined(USE_LIGHT) && defined(ROTARY_V1) } #endif Button.press_counter[button_index] = 0; } } } } } Button.last_state[button_index] = button; } } void ButtonLoop(void) { if (Button.present) { if (TimeReached(Button.debounce)) { SetNextTimeInterval(Button.debounce, Settings.button_debounce); ButtonHandler(); } } } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino" const char kTasmotaCommands[] PROGMEM = "|" D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_SLEEP "|" D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|" D_CMND_SERIALLOG "|" D_CMND_RESTART "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" D_CMND_BLINKTIME "|" D_CMND_BLINKCOUNT "|" D_CMND_SAVEDATA "|" D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|" D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|" D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|" D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|" D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|" #ifdef USE_I2C D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|" #endif D_CMND_SENSOR "|" D_CMND_DRIVER; void (* const TasmotaCommand[])(void) PROGMEM = { &CmndBacklog, &CmndDelay, &CmndPower, &CmndStatus, &CmndState, &CmndSleep, &CmndUpgrade, &CmndUpgrade, &CmndOtaUrl, &CmndSeriallog, &CmndRestart, &CmndPowerOnState, &CmndPulsetime, &CmndBlinktime, &CmndBlinkcount, &CmndSavedata, &CmndSetoption, &CmndTemperatureResolution, &CmndHumidityResolution, &CmndPressureResolution, &CmndPowerResolution, &CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution, &CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange, &CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig, &CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig, &CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd, &CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset, #ifdef USE_I2C &CmndI2cScan, CmndI2cDriver, #endif &CmndSensor, &CmndDriver }; const char kWifiConfig[] PROGMEM = D_WCFG_0_RESTART "||" D_WCFG_2_WIFIMANAGER "||" D_WCFG_4_RETRY "|" D_WCFG_5_WAIT "|" D_WCFG_6_SERIAL "|" D_WCFG_7_WIFIMANAGER_RESET_ONLY; void ResponseCmndNumber(int value) { Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, value); } void ResponseCmndFloat(float value, uint32_t decimals) { char stemp1[TOPSZ]; dtostrfd(value, decimals, stemp1); Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp1); } void ResponseCmndIdxNumber(int value) { Response_P(S_JSON_COMMAND_INDEX_NVALUE, XdrvMailbox.command, XdrvMailbox.index, value); } void ResponseCmndChar(const char* value) { Response_P(S_JSON_COMMAND_SVALUE, XdrvMailbox.command, value); } void ResponseCmndStateText(uint32_t value) { ResponseCmndChar(GetStateText(value)); } void ResponseCmndDone(void) { ResponseCmndChar(D_JSON_DONE); } void ResponseCmndIdxChar(const char* value) { Response_P(S_JSON_COMMAND_INDEX_SVALUE, XdrvMailbox.command, XdrvMailbox.index, value); } void ResponseCmndAll(uint32_t text_index, uint32_t count) { mqtt_data[0] = '\0'; for (uint32_t i = 0; i < count; i++) { ResponseAppend_P(PSTR("%c\"%s%d\":\"%s\""), (i) ? ',' : '{', XdrvMailbox.command, i +1, SettingsText(text_index +i)); } ResponseJsonEnd(); } void ExecuteCommand(const char *cmnd, uint32_t source) { #ifdef USE_DEBUG_DRIVER ShowFreeMem(PSTR("ExecuteCommand")); #endif ShowSource(source); const char *pos = cmnd; while (*pos && isspace(*pos)) { pos++; } const char *start = pos; while (*pos && (isalpha(*pos) || isdigit(*pos) || '_' == *pos || '/' == *pos)) { if ('/' == *pos) { start = pos + 1; } pos++; } if ('\0' == *start || pos <= start) { return; } uint32_t size = pos - start; char stopic[size + 2]; stopic[0] = '/'; memcpy(stopic+1, start, size); stopic[size+1] = '\0'; char svalue[strlen(pos) +1]; strlcpy(svalue, pos, sizeof(svalue)); CommandHandler(stopic, svalue, strlen(svalue)); } # 148 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino" void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len) { #ifdef USE_DEBUG_DRIVER ShowFreeMem(PSTR("CommandHandler")); #endif while (*dataBuf && isspace(*dataBuf)) { dataBuf++; data_len--; } bool grpflg = (strstr(topicBuf, SettingsText(SET_MQTT_GRP_TOPIC)) != nullptr); char stemp1[TOPSZ]; GetFallbackTopic_P(stemp1, ""); fallback_topic_flag = (!strncmp(topicBuf, stemp1, strlen(stemp1))); char *type = strrchr(topicBuf, '/'); uint32_t index = 1; bool user_index = false; if (type != nullptr) { type++; uint32_t i; for (i = 0; i < strlen(type); i++) { type[i] = toupper(type[i]); } while (isdigit(type[i-1])) { i--; } if (i < strlen(type)) { index = atoi(type +i); user_index = true; } type[i] = '\0'; } AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CMD: " D_GROUP " %d, " D_INDEX " %d, " D_COMMAND " \"%s\", " D_DATA " \"%s\""), grpflg, index, type, dataBuf); if (type != nullptr) { Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_ERROR "\"}")); if (Settings.ledstate &0x02) { blinks++; } if (!strcmp(dataBuf,"?")) { data_len = 0; } char *p; int32_t payload = strtol(dataBuf, &p, 0); if (p == dataBuf) { payload = -99; } int temp_payload = GetStateNumber(dataBuf); if (temp_payload > -1) { payload = temp_payload; } DEBUG_CORE_LOG(PSTR("CMD: Payload %d"), payload); backlog_delay = millis() + Settings.param[P_BACKLOG_DELAY]; char command[CMDSZ] = { 0 }; XdrvMailbox.command = command; XdrvMailbox.index = index; XdrvMailbox.data_len = data_len; XdrvMailbox.payload = payload; XdrvMailbox.grpflg = grpflg; XdrvMailbox.usridx = user_index; XdrvMailbox.topic = type; XdrvMailbox.data = dataBuf; #ifdef USE_SCRIPT_SUB_COMMAND if (!Script_SubCmd()) { if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) { if (!XdrvCall(FUNC_COMMAND)) { if (!XsnsCall(FUNC_COMMAND)) { type = nullptr; } } } } #else if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) { if (!XdrvCall(FUNC_COMMAND)) { if (!XsnsCall(FUNC_COMMAND)) { type = nullptr; } } } #endif } if (type == nullptr) { blinks = 201; snprintf_P(stemp1, sizeof(stemp1), PSTR(D_JSON_COMMAND)); Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_UNKNOWN "\"}")); type = (char*)stemp1; } if (mqtt_data[0] != '\0') { MqttPublishPrefixTopic_P(RESULT_OR_STAT, type); XdrvRulesProcess(); } fallback_topic_flag = false; } void CmndBacklog(void) { if (XdrvMailbox.data_len) { #ifdef SUPPORT_IF_STATEMENT char *blcommand = strtok(XdrvMailbox.data, ";"); while ((blcommand != nullptr) && (backlog.size() < MAX_BACKLOG)) #else uint32_t bl_pointer = (!backlog_pointer) ? MAX_BACKLOG -1 : backlog_pointer; bl_pointer--; char *blcommand = strtok(XdrvMailbox.data, ";"); while ((blcommand != nullptr) && (backlog_index != bl_pointer)) #endif { while(true) { blcommand = Trim(blcommand); if (!strncasecmp_P(blcommand, PSTR(D_CMND_BACKLOG), strlen(D_CMND_BACKLOG))) { blcommand += strlen(D_CMND_BACKLOG); } else { break; } } if (*blcommand != '\0') { #ifdef SUPPORT_IF_STATEMENT if (backlog.size() < MAX_BACKLOG) { backlog.add(blcommand); } #else backlog[backlog_index] = String(blcommand); backlog_index++; if (backlog_index >= MAX_BACKLOG) backlog_index = 0; #endif } blcommand = strtok(nullptr, ";"); } mqtt_data[0] = '\0'; } else { bool blflag = BACKLOG_EMPTY; #ifdef SUPPORT_IF_STATEMENT backlog.clear(); #else backlog_pointer = backlog_index; #endif ResponseCmndChar(blflag ? D_JSON_EMPTY : D_JSON_ABORTED); } } void CmndDelay(void) { if ((XdrvMailbox.payload >= (MIN_BACKLOG_DELAY / 100)) && (XdrvMailbox.payload <= 3600)) { backlog_delay = millis() + (100 * XdrvMailbox.payload); } uint32_t bl_delay = 0; long bl_delta = TimePassedSince(backlog_delay); if (bl_delta < 0) { bl_delay = (bl_delta *-1) / 100; } ResponseCmndNumber(bl_delay); } void CmndPower(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= devices_present)) { if ((XdrvMailbox.payload < POWER_OFF) || (XdrvMailbox.payload > POWER_BLINK_STOP)) { XdrvMailbox.payload = POWER_SHOW_STATE; } ExecuteCommandPower(XdrvMailbox.index, XdrvMailbox.payload, SRC_IGNORE); mqtt_data[0] = '\0'; } else if (0 == XdrvMailbox.index) { if ((XdrvMailbox.payload < POWER_OFF) || (XdrvMailbox.payload > POWER_TOGGLE)) { XdrvMailbox.payload = POWER_SHOW_STATE; } SetAllPower(XdrvMailbox.payload, SRC_IGNORE); mqtt_data[0] = '\0'; } } void CmndStatus(void) { uint32_t payload = ((XdrvMailbox.payload < 0) || (XdrvMailbox.payload > MAX_STATUS)) ? 99 : XdrvMailbox.payload; uint32_t option = STAT; char stemp[200]; char stemp2[TOPSZ]; if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; } if (!energy_flg && (9 == payload)) { payload = 99; } if (!CrashFlag() && (12 == payload)) { payload = 99; } if ((0 == payload) || (99 == payload)) { uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; #ifdef USE_SONOFF_IFAN if (IsModuleIfan()) { maxfn = 1; } #endif stemp[0] = '\0'; for (uint32_t i = 0; i < maxfn; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%s%s\"%s\"" ), stemp, (i > 0 ? "," : ""), SettingsText(SET_FRIENDLYNAME1 +i)); } stemp2[0] = '\0'; for (uint32_t i = 0; i < MAX_SWITCHES; i++) { snprintf_P(stemp2, sizeof(stemp2), PSTR("%s%s%d" ), stemp2, (i > 0 ? "," : ""), Settings.switchmode[i]); } Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\"" D_CMND_BUTTONTOPIC "\":\"%s\",\"" D_CMND_POWER "\":%d,\"" D_CMND_POWERONSTATE "\":%d,\"" D_CMND_LEDSTATE "\":%d,\"" D_CMND_LEDMASK "\":\"%04X\",\"" D_CMND_SAVEDATA "\":%d,\"" D_JSON_SAVESTATE "\":%d,\"" D_CMND_SWITCHTOPIC "\":\"%s\",\"" D_CMND_SWITCHMODE "\":[%s],\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_SWITCHRETAIN "\":%d,\"" D_CMND_SENSORRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"), ModuleNr(), stemp, mqtt_topic, SettingsText(SET_MQTT_BUTTON_TOPIC), power, Settings.poweronstate, Settings.ledstate, Settings.ledmask, Settings.save_data, Settings.flag.save_state, SettingsText(SET_MQTT_SWITCH_TOPIC), stemp2, Settings.flag.mqtt_button_retain, Settings.flag.mqtt_switch_retain, Settings.flag.mqtt_sensor_retain, Settings.flag.mqtt_power_retain); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS)); } if ((0 == payload) || (1 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS1_PARAMETER "\":{\"" D_JSON_BAUDRATE "\":%d,\"" D_CMND_SERIALCONFIG "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\",\"" D_CMND_OTAURL "\":\"%s\",\"" D_JSON_RESTARTREASON "\":\"%s\",\"" D_JSON_UPTIME "\":\"%s\",\"" D_JSON_STARTUPUTC "\":\"%s\",\"" D_CMND_SLEEP "\":%d,\"" D_JSON_CONFIG_HOLDER "\":%d,\"" D_JSON_BOOTCOUNT "\":%d,\"BCResetTime\":\"%s\",\"" D_JSON_SAVECOUNT "\":%d,\"" D_JSON_SAVEADDRESS "\":\"%X\"}}"), Settings.baudrate * 300, GetSerialConfig().c_str(), SettingsText(SET_MQTT_GRP_TOPIC), SettingsText(SET_OTAURL), GetResetReason().c_str(), GetUptime().c_str(), GetDateAndTime(DT_RESTART).c_str(), Settings.sleep, Settings.cfg_holder, Settings.bootcount, GetDateAndTime(DT_BOOTCOUNT).c_str(), Settings.save_flag, GetSettingsAddress()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "1")); } if ((0 == payload) || (2 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS2_FIRMWARE "\":{\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_BUILDDATETIME "\":\"%s\",\"" D_JSON_BOOTVERSION "\":%d,\"" D_JSON_COREVERSION "\":\"" ARDUINO_ESP8266_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\"," "\"Hardware\":\"%s\"" "%s}}"), my_version, my_image, GetBuildDateAndTime().c_str(), ESP.getBootVersion(), ESP.getSdkVersion(), GetDeviceHardware().c_str(), GetStatistics().c_str()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "2")); } if ((0 == payload) || (3 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS3_LOGGING "\":{\"" D_CMND_SERIALLOG "\":%d,\"" D_CMND_WEBLOG "\":%d,\"" D_CMND_MQTTLOG "\":%d,\"" D_CMND_SYSLOG "\":%d,\"" D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" D_CMND_SSID "\":[\"%s\",\"%s\"],\"" D_CMND_TELEPERIOD "\":%d,\"" D_JSON_RESOLUTION "\":\"%08X\",\"" D_CMND_SETOPTION "\":[\"%08X\",\"%s\",\"%08X\",\"%08X\"]}}"), Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), Settings.tele_period, Settings.flag2.data, Settings.flag.data, ToHex_P((unsigned char*)Settings.param, PARAM8_SIZE, stemp2, sizeof(stemp2)), Settings.flag3.data, Settings.flag4.data); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "3")); } if ((0 == payload) || (4 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS4_MEMORY "\":{\"" D_JSON_PROGRAMSIZE "\":%d,\"" D_JSON_FREEMEMORY "\":%d,\"" D_JSON_HEAPSIZE "\":%d,\"" D_JSON_PROGRAMFLASHSIZE "\":%d,\"" D_JSON_FLASHSIZE "\":%d,\"" D_JSON_FLASHCHIPID "\":\"%06X\",\"" D_JSON_FLASHMODE "\":%d,\"" D_JSON_FEATURES "\":[\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\"]"), ESP.getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP.getFreeHeap()/1024, ESP.getFlashChipSize()/1024, ESP.getFlashChipRealSize()/1024, ESP.getFlashChipId(), ESP.getFlashChipMode(), LANGUAGE_LCID, feature_drv1, feature_drv2, feature_sns1, feature_sns2, feature5, feature6); XsnsDriverState(); ResponseAppend_P(PSTR(",\"Sensors\":")); XsnsSensorState(); ResponseJsonEndEnd(); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "4")); } if ((0 == payload) || (5 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"" D_JSON_GATEWAY "\":\"%s\",\"" D_JSON_SUBNETMASK "\":\"%s\",\"" D_JSON_DNSSERVER "\":\"%s\",\"" D_JSON_MAC "\":\"%s\",\"" D_CMND_WEBSERVER "\":%d,\"" D_CMND_WIFICONFIG "\":%d,\"" D_CMND_WIFIPOWER "\":%s}}"), my_hostname, WiFi.localIP().toString().c_str(), IPAddress(Settings.ip_address[1]).toString().c_str(), IPAddress(Settings.ip_address[2]).toString().c_str(), IPAddress(Settings.ip_address[3]).toString().c_str(), WiFi.macAddress().c_str(), Settings.webserver, Settings.sta_config, WifiGetOutputPower().c_str()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "5")); } if (((0 == payload) || (6 == payload)) && Settings.flag.mqtt_enabled) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_JSON_MASK "\":\"%s\",\"" D_CMND_MQTTCLIENT "\":\"%s\",\"" D_CMND_MQTTUSER "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"), SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), mqtt_client, SettingsText(SET_MQTT_USER), MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "6")); } if ((0 == payload) || (7 == payload)) { if (99 == Settings.timezone) { snprintf_P(stemp, sizeof(stemp), PSTR("%d" ), Settings.timezone); } else { snprintf_P(stemp, sizeof(stemp), PSTR("\"%s\"" ), GetTimeZone().c_str()); } #if defined(USE_TIMERS) && defined(USE_SUNRISE) Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\"" D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%s,\"" D_JSON_SUNRISE "\":\"%s\",\"" D_JSON_SUNSET "\":\"%s\"}}"), GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(), GetTime(3).c_str(), stemp, GetSun(0).c_str(), GetSun(1).c_str()); #else Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\"" D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%s}}"), GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(), GetTime(3).c_str(), stemp); #endif MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "7")); } #if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION) if (energy_flg) { if ((0 == payload) || (9 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":%d,\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"), Settings.energy_power_delta, Settings.energy_min_power, Settings.energy_max_power, Settings.energy_min_voltage, Settings.energy_max_voltage, Settings.energy_min_current, Settings.energy_max_current); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9")); } } #endif if ((0 == payload) || (8 == payload) || (10 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS10_SENSOR "\":")); MqttShowSensor(); ResponseJsonEnd(); if (8 == payload) { MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "8")); } else { MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "10")); } } if ((0 == payload) || (11 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS11_STATUS "\":")); MqttShowState(); ResponseJsonEnd(); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "11")); } if (CrashFlag()) { if ((0 == payload) || (12 == payload)) { Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS12_STATUS "\":")); CrashDump(); ResponseJsonEnd(); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "12")); } } #ifdef USE_SCRIPT_STATUS if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data); #endif mqtt_data[0] = '\0'; } void CmndState(void) { mqtt_data[0] = '\0'; MqttShowState(); if (Settings.flag3.hass_tele_on_power) { MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN); } #ifdef USE_HOME_ASSISTANT if (Settings.flag.hass_discovery) { HAssPublishStatus(); } #endif } void CmndTempOffset(void) { if (XdrvMailbox.data_len > 0) { int value = (int)(CharToFloat(XdrvMailbox.data) * 10); if ((value > -127) && (value < 127)) { Settings.temp_comp = value; } } ResponseCmndFloat((float)(Settings.temp_comp) / 10, 1); } void CmndSleep(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 251)) { Settings.sleep = XdrvMailbox.payload; sleep = XdrvMailbox.payload; WiFiSetSleepMode(); } Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.sleep, sleep); } void CmndUpgrade(void) { if (((1 == XdrvMailbox.data_len) && (1 == XdrvMailbox.payload)) || ((XdrvMailbox.data_len >= 3) && NewerVersion(XdrvMailbox.data))) { ota_state_flag = 3; char stemp1[TOPSZ]; Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, my_version, GetOtaUrl(stemp1, sizeof(stemp1))); } else { Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, my_version); } } void CmndOtaUrl(void) { if (XdrvMailbox.data_len > 0) { SettingsUpdateText(SET_OTAURL, (SC_DEFAULT == Shortcut()) ? OTA_URL : XdrvMailbox.data); } ResponseCmndChar(SettingsText(SET_OTAURL)); } void CmndSeriallog(void) { if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { Settings.flag.mqtt_serial = 0; SetSeriallog(XdrvMailbox.payload); } Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.seriallog_level, seriallog_level); } void CmndRestart(void) { switch (XdrvMailbox.payload) { case 1: restart_flag = 2; ResponseCmndChar(D_JSON_RESTARTING); break; case -1: CmndCrash(); break; case -2: CmndWDT(); break; case -3: CmndBlockedLoop(); break; case 99: AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); EspRestart(); break; default: ResponseCmndChar(D_JSON_ONE_TO_RESTART); } } void CmndPowerOnState(void) { if (my_module_type != MOTOR) { if ((XdrvMailbox.payload >= POWER_ALL_OFF) && (XdrvMailbox.payload <= POWER_ALL_OFF_PULSETIME_ON)) { Settings.poweronstate = XdrvMailbox.payload; if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) { for (uint32_t i = 1; i <= devices_present; i++) { ExecuteCommandPower(i, POWER_ON, SRC_IGNORE); } } } ResponseCmndNumber(Settings.poweronstate); } } void CmndPulsetime(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PULSETIMERS)) { uint32_t items = 1; if (!XdrvMailbox.usridx && !XdrvMailbox.data_len) { items = MAX_PULSETIMERS; } else { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) { Settings.pulse_timer[XdrvMailbox.index -1] = XdrvMailbox.payload; SetPulseTimer(XdrvMailbox.index -1, XdrvMailbox.payload); } } mqtt_data[0] = '\0'; for (uint32_t i = 0; i < items; i++) { uint32_t index = (1 == items) ? XdrvMailbox.index : i +1; ResponseAppend_P(PSTR("%c\"%s%d\":{\"" D_JSON_SET "\":%d,\"" D_JSON_REMAINING "\":%d}"), (i) ? ',' : '{', XdrvMailbox.command, index, Settings.pulse_timer[index -1], GetPulseTimer(index -1)); } ResponseJsonEnd(); } } void CmndBlinktime(void) { if ((XdrvMailbox.payload > 1) && (XdrvMailbox.payload <= 3600)) { Settings.blinktime = XdrvMailbox.payload; if (blink_timer > 0) { blink_timer = millis() + (100 * XdrvMailbox.payload); } } ResponseCmndNumber(Settings.blinktime); } void CmndBlinkcount(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) { Settings.blinkcount = XdrvMailbox.payload; if (blink_counter) { blink_counter = Settings.blinkcount *2; } } ResponseCmndNumber(Settings.blinkcount); } void CmndSavedata(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3600)) { Settings.save_data = XdrvMailbox.payload; save_data_counter = Settings.save_data; } SettingsSaveAll(); char stemp1[TOPSZ]; if (Settings.save_data > 1) { snprintf_P(stemp1, sizeof(stemp1), PSTR(D_JSON_EVERY " %d " D_UNIT_SECOND), Settings.save_data); } ResponseCmndChar((Settings.save_data > 1) ? stemp1 : GetStateText(Settings.save_data)); } void CmndSetoption(void) { if (XdrvMailbox.index < 114) { uint32_t ptype; uint32_t pindex; if (XdrvMailbox.index <= 31) { ptype = 2; pindex = XdrvMailbox.index; } else if (XdrvMailbox.index <= 49) { ptype = 1; pindex = XdrvMailbox.index -32; } else if (XdrvMailbox.index <= 81) { ptype = 3; pindex = XdrvMailbox.index -50; } else { ptype = 4; pindex = XdrvMailbox.index -82; } if (XdrvMailbox.payload >= 0) { if (1 == ptype) { uint32_t param_low = 0; uint32_t param_high = 255; switch (pindex) { case P_HOLD_TIME: case P_MAX_POWER_RETRY: param_low = 1; param_high = 250; break; } if ((XdrvMailbox.payload >= param_low) && (XdrvMailbox.payload <= param_high)) { Settings.param[pindex] = XdrvMailbox.payload; #ifdef USE_LIGHT if (P_RGB_REMAP == pindex) { LightUpdateColorMapping(); restart_flag = 2; } #endif #if (defined(USE_IR_REMOTE) && defined(USE_IR_RECEIVE)) || defined(USE_IR_REMOTE_FULL) if (P_IR_UNKNOW_THRESHOLD == pindex) { IrReceiveUpdateThreshold(); } #endif } else { ptype = 99; } } else { if (XdrvMailbox.payload <= 1) { if (2 == ptype) { switch (pindex) { case 5: case 6: case 7: case 9: case 14: case 22: case 23: case 25: case 27: ptype = 99; break; case 3: case 15: restart_flag = 2; default: bitWrite(Settings.flag.data, pindex, XdrvMailbox.payload); } if (12 == pindex) { stop_flash_rotate = XdrvMailbox.payload; SettingsSave(2); } #ifdef USE_HOME_ASSISTANT if ((19 == pindex) || (30 == pindex)) { HAssDiscover(); } #endif } else if (3 == ptype) { bitWrite(Settings.flag3.data, pindex, XdrvMailbox.payload); switch (pindex) { case 5: if (0 == XdrvMailbox.payload) { restart_flag = 2; } break; case 10: WiFiSetSleepMode(); break; case 18: case 25: restart_flag = 2; break; } } else if (4 == ptype) { bitWrite(Settings.flag4.data, pindex, XdrvMailbox.payload); } } else { ptype = 99; } } } if (ptype < 99) { if (1 == ptype) { ResponseCmndIdxNumber(Settings.param[pindex]); } else { uint32_t flag = Settings.flag.data; if (3 == ptype) { flag = Settings.flag3.data; } else if (4 == ptype) { flag = Settings.flag4.data; } ResponseCmndIdxChar(GetStateText(bitRead(flag, pindex))); } } } } void CmndTemperatureResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.temperature_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.temperature_resolution); } void CmndHumidityResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.humidity_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.humidity_resolution); } void CmndPressureResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.pressure_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.pressure_resolution); } void CmndPowerResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.wattage_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.wattage_resolution); } void CmndVoltageResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.voltage_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.voltage_resolution); } void CmndFrequencyResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.frequency_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.frequency_resolution); } void CmndCurrentResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.current_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.current_resolution); } void CmndEnergyResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) { Settings.flag2.energy_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.energy_resolution); } void CmndWeightResolution(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { Settings.flag2.weight_resolution = XdrvMailbox.payload; } ResponseCmndNumber(Settings.flag2.weight_resolution); } void CmndModule(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= MAXMODULE)) { bool present = false; if (0 == XdrvMailbox.payload) { XdrvMailbox.payload = USER_MODULE; present = true; } else { XdrvMailbox.payload--; present = ValidTemplateModule(XdrvMailbox.payload); } if (present) { Settings.last_module = Settings.module; Settings.module = XdrvMailbox.payload; SetModuleType(); if (Settings.last_module != XdrvMailbox.payload) { for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { Settings.my_gp.io[i] = GPIO_NONE; } } restart_flag = 2; } } Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, ModuleNr(), ModuleName().c_str()); } void CmndModules(void) { uint32_t midx = USER_MODULE; uint32_t lines = 1; bool jsflg = false; for (uint32_t i = 0; i <= sizeof(kModuleNiceList); i++) { if (i > 0) { midx = pgm_read_byte(kModuleNiceList + i -1); } if (!jsflg) { Response_P(PSTR("{\"" D_CMND_MODULES "%d\":{"), lines); } else { ResponseAppend_P(PSTR(",")); } jsflg = true; uint32_t j = i ? midx +1 : 0; if ((ResponseAppend_P(PSTR("\"%d\":\"%s\""), j, AnyModuleName(midx).c_str()) > (LOGSZ - TOPSZ)) || (i == sizeof(kModuleNiceList))) { ResponseJsonEndEnd(); MqttPublishPrefixTopic_P(RESULT_OR_STAT, UpperCase(XdrvMailbox.command, XdrvMailbox.command)); jsflg = false; lines++; } } mqtt_data[0] = '\0'; } void CmndGpio(void) { if (XdrvMailbox.index < sizeof(Settings.my_gp)) { myio cmodule; ModuleGpios(&cmodule); if (ValidGPIO(XdrvMailbox.index, cmodule.io[XdrvMailbox.index]) && (XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < GPIO_SENSOR_END)) { bool present = false; for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) { uint32_t midx = pgm_read_byte(kGpioNiceList + i); if (midx == XdrvMailbox.payload) { present = true; } } if (present) { for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { if (ValidGPIO(i, cmodule.io[i]) && (Settings.my_gp.io[i] == XdrvMailbox.payload)) { Settings.my_gp.io[i] = GPIO_NONE; } } Settings.my_gp.io[XdrvMailbox.index] = XdrvMailbox.payload; restart_flag = 2; } } Response_P(PSTR("{")); bool jsflg = false; for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { if (ValidGPIO(i, cmodule.io[i]) || ((GPIO_USER == XdrvMailbox.payload) && !FlashPin(i))) { if (jsflg) { ResponseAppend_P(PSTR(",")); } jsflg = true; uint8_t sensor_type = Settings.my_gp.io[i]; if (!ValidGPIO(i, cmodule.io[i])) { sensor_type = cmodule.io[i]; if (GPIO_USER == sensor_type) { sensor_type = GPIO_NONE; } } uint8_t sensor_name_idx = sensor_type; const char *sensor_names = kSensorNames; if (sensor_type > GPIO_FIX_START) { sensor_name_idx = sensor_type - GPIO_FIX_START -1; sensor_names = kSensorNamesFixed; } char stemp1[TOPSZ]; ResponseAppend_P(PSTR("\"" D_CMND_GPIO "%d\":{\"%d\":\"%s\"}"), i, sensor_type, GetTextIndexed(stemp1, sizeof(stemp1), sensor_name_idx, sensor_names)); } } if (jsflg) { ResponseJsonEnd(); } else { ResponseCmndChar(D_JSON_NOT_SUPPORTED); } } } void CmndGpios(void) { myio cmodule; ModuleGpios(&cmodule); uint32_t lines = 1; bool jsflg = false; for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) { uint32_t midx = pgm_read_byte(kGpioNiceList + i); if ((XdrvMailbox.payload != 255) && GetUsedInModule(midx, cmodule.io)) { continue; } if (!jsflg) { Response_P(PSTR("{\"" D_CMND_GPIOS "%d\":{"), lines); } else { ResponseAppend_P(PSTR(",")); } jsflg = true; char stemp1[TOPSZ]; if ((ResponseAppend_P(PSTR("\"%d\":\"%s\""), midx, GetTextIndexed(stemp1, sizeof(stemp1), midx, kSensorNames)) > (LOGSZ - TOPSZ)) || (i == sizeof(kGpioNiceList) -1)) { ResponseJsonEndEnd(); MqttPublishPrefixTopic_P(RESULT_OR_STAT, UpperCase(XdrvMailbox.command, XdrvMailbox.command)); jsflg = false; lines++; } } mqtt_data[0] = '\0'; } void CmndTemplate(void) { bool error = false; if (strstr(XdrvMailbox.data, "{") == nullptr) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= MAXMODULE)) { XdrvMailbox.payload--; if (ValidTemplateModule(XdrvMailbox.payload)) { ModuleDefault(XdrvMailbox.payload); if (USER_MODULE == Settings.module) { restart_flag = 2; } } } else if (0 == XdrvMailbox.payload) { if (Settings.module != USER_MODULE) { ModuleDefault(Settings.module); } } else if (255 == XdrvMailbox.payload) { if (Settings.module != USER_MODULE) { ModuleDefault(Settings.module); } snprintf_P(Settings.user_template.name, sizeof(Settings.user_template.name), PSTR("Merged")); uint32_t j = 0; for (uint32_t i = 0; i < sizeof(mycfgio); i++) { if (6 == i) { j = 9; } if (8 == i) { j = 12; } if (my_module.io[j] > GPIO_NONE) { Settings.user_template.gp.io[i] = my_module.io[j]; } j++; } } } else { if (JsonTemplate(XdrvMailbox.data)) { if (USER_MODULE == Settings.module) { restart_flag = 2; } } else { ResponseCmndChar(D_JSON_INVALID_JSON); error = true; } } if (!error) { TemplateJson(); } } void CmndPwm(void) { if (pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings.pwm_range) && (pin[GPIO_PWM1 + XdrvMailbox.index -1] < 99)) { Settings.pwm_value[XdrvMailbox.index -1] = XdrvMailbox.payload; analogWrite(pin[GPIO_PWM1 + XdrvMailbox.index -1], bitRead(pwm_inverted, XdrvMailbox.index -1) ? Settings.pwm_range - XdrvMailbox.payload : XdrvMailbox.payload); } Response_P(PSTR("{")); MqttShowPWMState(); ResponseJsonEnd(); } } void CmndPwmfrequency(void) { if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) { Settings.pwm_frequency = (1 == XdrvMailbox.payload) ? PWM_FREQ : XdrvMailbox.payload; analogWriteFreq(Settings.pwm_frequency); } ResponseCmndNumber(Settings.pwm_frequency); } void CmndPwmrange(void) { if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 254) && (XdrvMailbox.payload < 1024))) { Settings.pwm_range = (1 == XdrvMailbox.payload) ? PWM_RANGE : XdrvMailbox.payload; for (uint32_t i = 0; i < MAX_PWMS; i++) { if (Settings.pwm_value[i] > Settings.pwm_range) { Settings.pwm_value[i] = Settings.pwm_range; } } analogWriteRange(Settings.pwm_range); } ResponseCmndNumber(Settings.pwm_range); } void CmndButtonDebounce(void) { if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) { Settings.button_debounce = XdrvMailbox.payload; } ResponseCmndNumber(Settings.button_debounce); } void CmndSwitchDebounce(void) { if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) { Settings.switch_debounce = XdrvMailbox.payload; } ResponseCmndNumber(Settings.switch_debounce); } void CmndBaudrate(void) { if (XdrvMailbox.payload >= 300) { XdrvMailbox.payload /= 300; uint32_t baudrate = (XdrvMailbox.payload & 0xFFFF) * 300; SetSerialBaudrate(baudrate); } ResponseCmndNumber(Settings.baudrate * 300); } void CmndSerialConfig(void) { if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len < 3) { if ((XdrvMailbox.payload >= TS_SERIAL_5N1) && (XdrvMailbox.payload <= TS_SERIAL_8O2)) { SetSerialConfig(XdrvMailbox.payload); } } else if ((XdrvMailbox.payload >= 5) && (XdrvMailbox.payload <= 8)) { uint8_t serial_config = XdrvMailbox.payload -5; bool valid = true; char parity = (XdrvMailbox.data[1] & 0xdf); if ('E' == parity) { serial_config += 0x08; } else if ('O' == parity) { serial_config += 0x10; } else if ('N' != parity) { valid = false; } if ('2' == XdrvMailbox.data[2]) { serial_config += 0x04; } else if ('1' != XdrvMailbox.data[2]) { valid = false; } if (valid) { SetSerialConfig(serial_config); } } } ResponseCmndChar(GetSerialConfig().c_str()); } void CmndSerialSend(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) { SetSeriallog(LOG_LEVEL_NONE); Settings.flag.mqtt_serial = 1; Settings.flag.mqtt_serial_raw = (XdrvMailbox.index > 3) ? 1 : 0; if (XdrvMailbox.data_len > 0) { if (1 == XdrvMailbox.index) { Serial.printf("%s\n", XdrvMailbox.data); } else if (2 == XdrvMailbox.index || 4 == XdrvMailbox.index) { for (uint32_t i = 0; i < XdrvMailbox.data_len; i++) { Serial.write(XdrvMailbox.data[i]); } } else if (3 == XdrvMailbox.index) { uint32_t dat_len = XdrvMailbox.data_len; Serial.printf("%s", Unescape(XdrvMailbox.data, &dat_len)); } else if (5 == XdrvMailbox.index) { SerialSendRaw(RemoveSpace(XdrvMailbox.data)); } ResponseCmndDone(); } } } void CmndSerialDelimiter(void) { if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.payload < 256)) { if (XdrvMailbox.payload > 0) { Settings.serial_delimiter = XdrvMailbox.payload; } else { uint32_t dat_len = XdrvMailbox.data_len; Unescape(XdrvMailbox.data, &dat_len); Settings.serial_delimiter = XdrvMailbox.data[0]; } } ResponseCmndNumber(Settings.serial_delimiter); } void CmndSyslog(void) { if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) { SetSyslog(XdrvMailbox.payload); } Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.syslog_level, syslog_level); } void CmndLoghost(void) { if (XdrvMailbox.data_len > 0) { SettingsUpdateText(SET_SYSLOG_HOST, (SC_DEFAULT == Shortcut()) ? SYS_LOG_HOST : XdrvMailbox.data); } ResponseCmndChar(SettingsText(SET_SYSLOG_HOST)); } void CmndLogport(void) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) { Settings.syslog_port = (1 == XdrvMailbox.payload) ? SYS_LOG_PORT : XdrvMailbox.payload; } ResponseCmndNumber(Settings.syslog_port); } void CmndIpAddress(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) { uint32_t address; if (ParseIp(&address, XdrvMailbox.data)) { Settings.ip_address[XdrvMailbox.index -1] = address; } char stemp1[TOPSZ]; snprintf_P(stemp1, sizeof(stemp1), PSTR(" (%s)"), WiFi.localIP().toString().c_str()); Response_P(S_JSON_COMMAND_INDEX_SVALUE_SVALUE, XdrvMailbox.command, XdrvMailbox.index, IPAddress(Settings.ip_address[XdrvMailbox.index -1]).toString().c_str(), (1 == XdrvMailbox.index) ? stemp1:""); } } void CmndNtpServer(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_NTP_SERVERS)) { if (!XdrvMailbox.usridx) { ResponseCmndAll(SET_NTPSERVER1, MAX_NTP_SERVERS); } else { uint32_t ntp_server = SET_NTPSERVER1 + XdrvMailbox.index -1; if (XdrvMailbox.data_len > 0) { SettingsUpdateText(ntp_server, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? NTP_SERVER1 : (2 == XdrvMailbox.index) ? NTP_SERVER2 : NTP_SERVER3 : XdrvMailbox.data); SettingsUpdateText(ntp_server, ReplaceCommaWithDot(SettingsText(ntp_server))); ntp_force_sync = true; } ResponseCmndIdxChar(SettingsText(ntp_server)); } } } void CmndAp(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { switch (XdrvMailbox.payload) { case 0: Settings.sta_active ^= 1; break; case 1: case 2: Settings.sta_active = XdrvMailbox.payload -1; } restart_flag = 2; } Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active)); } void CmndSsid(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SSIDS)) { if (!XdrvMailbox.usridx) { ResponseCmndAll(SET_STASSID1, MAX_SSIDS); } else { if (XdrvMailbox.data_len > 0) { SettingsUpdateText(SET_STASSID1 + XdrvMailbox.index -1, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_SSID1 : STA_SSID2 : XdrvMailbox.data); Settings.sta_active = XdrvMailbox.index -1; restart_flag = 2; } ResponseCmndIdxChar(SettingsText(SET_STASSID1 + XdrvMailbox.index -1)); } } } void CmndPassword(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) { if ((XdrvMailbox.data_len > 4) || (SC_CLEAR == Shortcut()) || (SC_DEFAULT == Shortcut())) { SettingsUpdateText(SET_STAPWD1 + XdrvMailbox.index -1, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_PASS1 : STA_PASS2 : XdrvMailbox.data); Settings.sta_active = XdrvMailbox.index -1; restart_flag = 2; ResponseCmndIdxChar(SettingsText(SET_STAPWD1 + XdrvMailbox.index -1)); } else { Response_P(S_JSON_COMMAND_INDEX_ASTERISK, XdrvMailbox.command, XdrvMailbox.index); } } } void CmndHostname(void) { if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) { SettingsUpdateText(SET_HOSTNAME, (SC_DEFAULT == Shortcut()) ? WIFI_HOSTNAME : XdrvMailbox.data); if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) { SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME); } restart_flag = 2; } ResponseCmndChar(SettingsText(SET_HOSTNAME)); } void CmndWifiConfig(void) { if ((XdrvMailbox.payload >= WIFI_RESTART) && (XdrvMailbox.payload < MAX_WIFI_OPTION)) { if ((EX_WIFI_SMARTCONFIG == XdrvMailbox.payload) || (EX_WIFI_WPSCONFIG == XdrvMailbox.payload)) { XdrvMailbox.payload = WIFI_MANAGER; } Settings.sta_config = XdrvMailbox.payload; wifi_state_flag = Settings.sta_config; if (WifiState() > WIFI_RESTART) { restart_flag = 2; } } char stemp1[TOPSZ]; Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_config, GetTextIndexed(stemp1, sizeof(stemp1), Settings.sta_config, kWifiConfig)); } void CmndFriendlyname(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) { if (!XdrvMailbox.usridx) { ResponseCmndAll(SET_FRIENDLYNAME1, MAX_FRIENDLYNAMES); } else { if (XdrvMailbox.data_len > 0) { char stemp1[TOPSZ]; if (1 == XdrvMailbox.index) { snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME)); } else { snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), XdrvMailbox.index); } SettingsUpdateText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data); } ResponseCmndIdxChar(SettingsText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1)); } } } void CmndSwitchMode(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SWITCHES)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_SWITCH_OPTION)) { Settings.switchmode[XdrvMailbox.index -1] = XdrvMailbox.payload; } ResponseCmndIdxNumber(Settings.switchmode[XdrvMailbox.index-1]); } } void CmndInterlock(void) { uint32_t max_relays = devices_present; if (light_type) { max_relays--; } if (max_relays > sizeof(Settings.interlock[0]) * 8) { max_relays = sizeof(Settings.interlock[0]) * 8; } if (max_relays > 1) { if (XdrvMailbox.data_len > 0) { if (strstr(XdrvMailbox.data, ",") != nullptr) { for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; } char *group; char *q; uint32_t group_index = 0; power_t relay_mask = 0; for (group = strtok_r(XdrvMailbox.data, " ", &q); group && group_index < MAX_INTERLOCKS; group = strtok_r(nullptr, " ", &q)) { char *str; char *p; for (str = strtok_r(group, ",", &p); str; str = strtok_r(nullptr, ",", &p)) { int pbit = atoi(str); if ((pbit > 0) && (pbit <= max_relays)) { pbit--; if (!bitRead(relay_mask, pbit)) { bitSet(relay_mask, pbit); bitSet(Settings.interlock[group_index], pbit); } } } group_index++; } for (uint32_t i = 0; i < group_index; i++) { uint32_t minimal_bits = 0; for (uint32_t j = 0; j < max_relays; j++) { if (bitRead(Settings.interlock[i], j)) { minimal_bits++; } } if (minimal_bits < 2) { Settings.interlock[i] = 0; } } } else { Settings.flag.interlock = XdrvMailbox.payload &1; if (Settings.flag.interlock) { SetDevicePower(power, SRC_IGNORE); } } #ifdef USE_SHUTTER if (Settings.flag3.shutter_mode) { ShutterInit(); } #endif } Response_P(PSTR("{\"" D_CMND_INTERLOCK "\":\"%s\",\"" D_JSON_GROUPS "\":\""), GetStateText(Settings.flag.interlock)); uint32_t anygroup = 0; for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { if (Settings.interlock[i]) { anygroup++; ResponseAppend_P(PSTR("%s"), (anygroup > 1) ? " " : ""); uint32_t anybit = 0; power_t mask = 1; for (uint32_t j = 0; j < max_relays; j++) { if (Settings.interlock[i] & mask) { anybit++; ResponseAppend_P(PSTR("%s%d"), (anybit > 1) ? "," : "", j +1); } mask <<= 1; } } } if (!anygroup) { for (uint32_t j = 1; j <= max_relays; j++) { ResponseAppend_P(PSTR("%s%d"), (j > 1) ? "," : "", j); } } ResponseAppend_P(PSTR("\"}")); } else { Settings.flag.interlock = 0; ResponseCmndStateText(Settings.flag.interlock); } } void CmndTeleperiod(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings.tele_period = (1 == XdrvMailbox.payload) ? TELE_PERIOD : XdrvMailbox.payload; if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) Settings.tele_period = 10; tele_period = Settings.tele_period; } ResponseCmndNumber(Settings.tele_period); } void CmndReset(void) { switch (XdrvMailbox.payload) { case 1: restart_flag = 211; ResponseCmndChar(D_JSON_RESET_AND_RESTARTING); break; case 2 ... 6: restart_flag = 210 + XdrvMailbox.payload; Response_P(PSTR("{\"" D_CMND_RESET "\":\"" D_JSON_ERASE ", " D_JSON_RESET_AND_RESTARTING "\"}")); break; case 99: Settings.bootcount = 0; Settings.bootcount_reset_time = 0; ResponseCmndDone(); break; default: ResponseCmndChar(D_JSON_ONE_TO_RESET); } } void CmndTime(void) { uint32_t format = Settings.flag2.time_format; if (XdrvMailbox.data_len > 0) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4)) { Settings.flag2.time_format = XdrvMailbox.payload -1; format = Settings.flag2.time_format; } else { format = 1; RtcSetTime(XdrvMailbox.payload); } } mqtt_data[0] = '\0'; ResponseAppendTimeFormat(format); ResponseJsonEnd(); } void CmndTimezone(void) { if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.payload >= -13)) { Settings.timezone = XdrvMailbox.payload; Settings.timezone_minutes = 0; if (XdrvMailbox.payload < 15) { char *p = strtok (XdrvMailbox.data, ":"); if (p) { p = strtok (nullptr, ":"); if (p) { Settings.timezone_minutes = strtol(p, nullptr, 10); if (Settings.timezone_minutes > 59) { Settings.timezone_minutes = 59; } } } } else { Settings.timezone = 99; } ntp_force_sync = true; } if (99 == Settings.timezone) { ResponseCmndNumber(Settings.timezone); } else { char stemp1[TOPSZ]; snprintf_P(stemp1, sizeof(stemp1), PSTR("%+03d:%02d"), Settings.timezone, Settings.timezone_minutes); ResponseCmndChar(stemp1); } } void CmndTimeStdDst(uint32_t ts) { if (XdrvMailbox.data_len > 0) { if (strstr(XdrvMailbox.data, ",") != nullptr) { uint32_t tpos = 0; int value = 0; char *p = XdrvMailbox.data; char *q = p; while (p && (tpos < 7)) { if (p > q) { if (1 == tpos) { Settings.tflag[ts].hemis = value &1; } if (2 == tpos) { Settings.tflag[ts].week = (value < 0) ? 0 : (value > 4) ? 4 : value; } if (3 == tpos) { Settings.tflag[ts].month = (value < 1) ? 1 : (value > 12) ? 12 : value; } if (4 == tpos) { Settings.tflag[ts].dow = (value < 1) ? 1 : (value > 7) ? 7 : value; } if (5 == tpos) { Settings.tflag[ts].hour = (value < 0) ? 0 : (value > 23) ? 23 : value; } if (6 == tpos) { Settings.toffset[ts] = (value < -900) ? -900 : (value > 900) ? 900 : value; } } p = Trim(p); if (tpos && (*p == ',')) { p++; } p = Trim(p); q = p; value = strtol(p, &p, 10); tpos++; } ntp_force_sync = true; } else { if (0 == XdrvMailbox.payload) { if (0 == ts) { SettingsResetStd(); } else { SettingsResetDst(); } } ntp_force_sync = true; } } Response_P(PSTR("{\"%s\":{\"Hemisphere\":%d,\"Week\":%d,\"Month\":%d,\"Day\":%d,\"Hour\":%d,\"Offset\":%d}}"), XdrvMailbox.command, Settings.tflag[ts].hemis, Settings.tflag[ts].week, Settings.tflag[ts].month, Settings.tflag[ts].dow, Settings.tflag[ts].hour, Settings.toffset[ts]); } void CmndTimeStd(void) { CmndTimeStdDst(0); } void CmndTimeDst(void) { CmndTimeStdDst(1); } void CmndAltitude(void) { if ((XdrvMailbox.data_len > 0) && ((XdrvMailbox.payload >= -30000) && (XdrvMailbox.payload <= 30000))) { Settings.altitude = XdrvMailbox.payload; } ResponseCmndNumber(Settings.altitude); } void CmndLedPower(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_LEDS)) { if (99 == pin[GPIO_LEDLNK]) { XdrvMailbox.index = 1; } if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { Settings.ledstate &= 8; uint32_t mask = 1 << (XdrvMailbox.index -1); switch (XdrvMailbox.payload) { case 0: led_power &= (0xFF ^ mask); Settings.ledstate = 0; break; case 1: led_power |= mask; Settings.ledstate = 8; break; case 2: led_power ^= mask; Settings.ledstate ^= 8; break; } blinks = 0; if (99 == pin[GPIO_LEDLNK]) { SetLedPower(Settings.ledstate &8); } else { SetLedPowerIdx(XdrvMailbox.index -1, (led_power & mask)); } } bool state = bitRead(led_power, XdrvMailbox.index -1); if (99 == pin[GPIO_LEDLNK]) { state = bitRead(Settings.ledstate, 3); } ResponseCmndIdxChar(GetStateText(state)); } } void CmndLedState(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_LED_OPTION)) { Settings.ledstate = XdrvMailbox.payload; if (!Settings.ledstate) { SetLedPowerAll(0); SetLedLink(0); } } ResponseCmndNumber(Settings.ledstate); } void CmndLedMask(void) { if (XdrvMailbox.data_len > 0) { Settings.ledmask = XdrvMailbox.payload; } char stemp1[TOPSZ]; snprintf_P(stemp1, sizeof(stemp1), PSTR("%d (0x%04X)"), Settings.ledmask, Settings.ledmask); ResponseCmndChar(stemp1); } void CmndWifiPower(void) { if (XdrvMailbox.data_len > 0) { Settings.wifi_output_power = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10); if (Settings.wifi_output_power > 205) { Settings.wifi_output_power = 205; } WifiSetOutputPower(); } ResponseCmndChar(WifiGetOutputPower().c_str()); } #ifdef USE_I2C void CmndI2cScan(void) { if (i2c_flg) { I2cScan(mqtt_data, sizeof(mqtt_data)); } } void CmndI2cDriver(void) { if (XdrvMailbox.index < MAX_I2C_DRIVERS) { if (XdrvMailbox.payload >= 0) { bitWrite(Settings.i2c_drivers[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1); restart_flag = 2; } } Response_P(PSTR("{\"" D_CMND_I2CDRIVER "\":")); I2cDriverState(); ResponseJsonEnd(); } #endif void CmndSensor(void) { XsnsCall(FUNC_COMMAND_SENSOR); } void CmndDriver(void) { XdrvCall(FUNC_COMMAND_DRIVER); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_crash_recorder.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_crash_recorder.ino" const uint32_t crash_magic = 0x53415400; const uint32_t crash_rtc_offset = 32; const uint32_t crash_dump_max_len = 31; extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end ) { uint32_t addr_written = 0; uint32_t value; for (uint32_t i = stack; i < stack_end; i += 4) { value = *((uint32_t*) i); if ((value >= 0x40000000) && (value < 0x40300000)) { ESP.rtcUserMemoryWrite(crash_rtc_offset + addr_written, (uint32_t*)&value, sizeof(value)); addr_written++; if (addr_written >= crash_dump_max_len) { break; } } } value = crash_magic + addr_written; ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); } void CmndCrash(void) { volatile uint32_t dummy; dummy = *((uint32_t*) 0x00000000); } void CmndWDT(void) { volatile uint32_t dummy = 0; while (1) { dummy++; } } void CmndBlockedLoop(void) { while (1) { delay(1000); } } void CrashDumpClear(void) { uint32_t value = 0; ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); } bool CrashFlag(void) { return ((ResetReason() == REASON_EXCEPTION_RST) || (ResetReason() == REASON_SOFT_WDT_RST) || oswatch_blocked_loop); } void CrashDump(void) { ResponseAppend_P(PSTR("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":[\"%08x\",\"%08x\",\"%08x\"],\"EXCVADDR\":\"%08x\",\"DEPC\":\"%08x\""), resetInfo.exccause, GetResetReason().c_str(), resetInfo.epc1, resetInfo.epc2, resetInfo.epc3, resetInfo.excvaddr, resetInfo.depc); uint32_t value; ESP.rtcUserMemoryRead(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value)); if (crash_magic == (value & 0xFFFFFF00)) { ResponseAppend_P(PSTR(",\"CallChain\":[")); uint32_t count = value & 0x3F; for (uint32_t i = 0; i < count; i++) { ESP.rtcUserMemoryRead(crash_rtc_offset +i, (uint32_t*)&value, sizeof(value)); if (i > 0) { ResponseAppend_P(PSTR(",")); } ResponseAppend_P(PSTR("\"%08x\""), value); } ResponseAppend_P(PSTR("]")); } ResponseJsonEnd(); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_esptool.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_esptool.ino" #define USE_ESPTOOL #ifdef USE_ESPTOOL #define READ_REG(REG) (*((volatile uint32_t *)(REG))) #define WRITE_REG(REG,VAL) *((volatile uint32_t *)(REG)) = (VAL) #define REG_SET_MASK(reg,mask) WRITE_REG((reg), (READ_REG(reg)|(mask))) #define SPI_BASE_REG 0x60000200 #define SPI_CMD_REG (SPI_BASE_REG + 0x00) #define SPI_FLASH_RDSR (1<<27) #define SPI_FLASH_SE (1<<24) #define SPI_FLASH_BE (1<<23) #define SPI_FLASH_WREN (1<<30) #define SPI_ADDR_REG (SPI_BASE_REG + 0x04) #define SPI_CTRL_REG (SPI_BASE_REG + 0x08) #define SPI_RD_STATUS_REG (SPI_BASE_REG + 0x10) #define SPI_W0_REG (SPI_BASE_REG + 0x40) #define SPI_EXT2_REG (SPI_BASE_REG + 0xF8) #define SPI_ST 0x7 #define SECTORS_PER_BLOCK (FLASH_BLOCK_SIZE / SPI_FLASH_SEC_SIZE) static const uint32_t STATUS_WIP_BIT = (1 << 0); inline static void spi_wait_ready(void) { while((READ_REG(SPI_EXT2_REG) & SPI_ST)) { } } static bool spiflash_is_ready(void) { spi_wait_ready(); WRITE_REG(SPI_RD_STATUS_REG, 0); WRITE_REG(SPI_CMD_REG, SPI_FLASH_RDSR); while(READ_REG(SPI_CMD_REG) != 0) { } uint32_t status_value = READ_REG(SPI_RD_STATUS_REG); return (status_value & STATUS_WIP_BIT) == 0; } static void spi_write_enable(void) { while(!spiflash_is_ready()) { } WRITE_REG(SPI_CMD_REG, SPI_FLASH_WREN); while(READ_REG(SPI_CMD_REG) != 0) { } } bool EsptoolEraseSector(uint32_t sector) { spi_write_enable(); spi_wait_ready(); WRITE_REG(SPI_ADDR_REG, (sector * SPI_FLASH_SEC_SIZE) & 0xffffff); WRITE_REG(SPI_CMD_REG, SPI_FLASH_SE); while(READ_REG(SPI_CMD_REG) != 0) { } while(!spiflash_is_ready()) { } return true; } void EsptoolErase(uint32_t start_sector, uint32_t end_sector) { int next_erase_sector = start_sector; int remaining_erase_sector = end_sector - start_sector; while (remaining_erase_sector > 0) { spi_write_enable(); uint32_t command = SPI_FLASH_SE; uint32_t sectors_to_erase = 1; if (remaining_erase_sector >= SECTORS_PER_BLOCK && next_erase_sector % SECTORS_PER_BLOCK == 0) { command = SPI_FLASH_BE; sectors_to_erase = SECTORS_PER_BLOCK; } uint32_t addr = next_erase_sector * SPI_FLASH_SEC_SIZE; spi_wait_ready(); WRITE_REG(SPI_ADDR_REG, addr & 0xffffff); WRITE_REG(SPI_CMD_REG, command); while(READ_REG(SPI_CMD_REG) != 0) { } remaining_erase_sector -= sectors_to_erase; next_erase_sector += sectors_to_erase; while (!spiflash_is_ready()) { } yield(); OsWatchLoop(); } } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino" # 24 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino" void GetFeatures(void) { feature_drv1 = 0x00000000; #ifdef USE_ENERGY_MARGIN_DETECTION feature_drv1 |= 0x00000001; #endif #ifdef USE_LIGHT feature_drv1 |= 0x00000002; #endif #ifdef USE_I2C feature_drv1 |= 0x00000004; #endif #ifdef USE_SPI feature_drv1 |= 0x00000008; #endif #ifdef USE_DISCOVERY feature_drv1 |= 0x00000010; #endif #ifdef USE_ARDUINO_OTA feature_drv1 |= 0x00000020; #endif #ifdef USE_MQTT_TLS feature_drv1 |= 0x00000040; #endif #ifdef USE_WEBSERVER feature_drv1 |= 0x00000080; #endif #ifdef WEBSERVER_ADVERTISE feature_drv1 |= 0x00000100; #endif #ifdef USE_EMULATION_HUE feature_drv1 |= 0x00000200; #endif #if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT) feature_drv1 |= 0x00000400; #endif #if (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT) #endif #if (MQTT_LIBRARY_TYPE == MQTT_ESPMQTTARDUINO) #endif #ifdef MQTT_HOST_DISCOVERY feature_drv1 |= 0x00002000; #endif #ifdef USE_ARILUX_RF feature_drv1 |= 0x00004000; #endif #if defined(USE_LIGHT) && defined(USE_WS2812) feature_drv1 |= 0x00008000; #endif #ifdef USE_WS2812_DMA feature_drv1 |= 0x00010000; #endif #if defined(USE_IR_REMOTE) || defined(USE_IR_REMOTE_FULL) feature_drv1 |= 0x00020000; #endif #ifdef USE_IR_HVAC feature_drv1 |= 0x00040000; #endif #ifdef USE_IR_RECEIVE feature_drv1 |= 0x00080000; #endif #ifdef USE_DOMOTICZ feature_drv1 |= 0x00100000; #endif #ifdef USE_DISPLAY feature_drv1 |= 0x00200000; #endif #ifdef USE_HOME_ASSISTANT feature_drv1 |= 0x00400000; #endif #ifdef USE_SERIAL_BRIDGE feature_drv1 |= 0x00800000; #endif #ifdef USE_TIMERS feature_drv1 |= 0x01000000; #endif #ifdef USE_SUNRISE feature_drv1 |= 0x02000000; #endif #ifdef USE_TIMERS_WEB feature_drv1 |= 0x04000000; #endif #ifdef USE_RULES feature_drv1 |= 0x08000000; #endif #ifdef USE_KNX feature_drv1 |= 0x10000000; #endif #ifdef USE_WPS feature_drv1 |= 0x20000000; #endif #ifdef USE_SMARTCONFIG feature_drv1 |= 0x40000000; #endif #ifdef USE_ENERGY_POWER_LIMIT feature_drv1 |= 0x80000000; #endif feature_drv2 = 0x00000000; #ifdef USE_CONFIG_OVERRIDE feature_drv2 |= 0x00000001; #endif #ifdef FIRMWARE_MINIMAL feature_drv2 |= 0x00000002; #endif #ifdef FIRMWARE_SENSORS feature_drv2 |= 0x00000004; #endif #ifdef FIRMWARE_CLASSIC feature_drv2 |= 0x00000008; #endif #ifdef FIRMWARE_KNX_NO_EMULATION feature_drv2 |= 0x00000010; #endif #ifdef USE_DISPLAY_MODES1TO5 feature_drv2 |= 0x00000020; #endif #ifdef USE_DISPLAY_GRAPH feature_drv2 |= 0x00000040; #endif #ifdef USE_DISPLAY_LCD feature_drv2 |= 0x00000080; #endif #ifdef USE_DISPLAY_SSD1306 feature_drv2 |= 0x00000100; #endif #ifdef USE_DISPLAY_MATRIX feature_drv2 |= 0x00000200; #endif #ifdef USE_DISPLAY_ILI9341 feature_drv2 |= 0x00000400; #endif #ifdef USE_DISPLAY_EPAPER_29 feature_drv2 |= 0x00000800; #endif #ifdef USE_DISPLAY_SH1106 feature_drv2 |= 0x00001000; #endif #ifdef USE_MP3_PLAYER feature_drv2 |= 0x00002000; #endif #ifdef USE_PCA9685 feature_drv2 |= 0x00004000; #endif #if defined(USE_LIGHT) && defined(USE_TUYA_MCU) feature_drv2 |= 0x00008000; #endif #ifdef USE_RC_SWITCH feature_drv2 |= 0x00010000; #endif #if defined(USE_LIGHT) && defined(USE_ARMTRONIX_DIMMERS) feature_drv2 |= 0x00020000; #endif #if defined(USE_LIGHT) && defined(USE_SM16716) feature_drv2 |= 0x00040000; #endif #ifdef USE_SCRIPT feature_drv2 |= 0x00080000; #endif #ifdef USE_EMULATION_WEMO feature_drv2 |= 0x00100000; #endif #ifdef USE_SONOFF_IFAN feature_drv2 |= 0x00200000; #endif #ifdef USE_ZIGBEE feature_drv2 |= 0x00400000; #endif #ifdef NO_EXTRA_4K_HEAP feature_drv2 |= 0x00800000; #endif #ifdef VTABLES_IN_IRAM feature_drv2 |= 0x01000000; #endif #ifdef VTABLES_IN_DRAM feature_drv2 |= 0x02000000; #endif #ifdef VTABLES_IN_FLASH feature_drv2 |= 0x04000000; #endif #ifdef PIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH feature_drv2 |= 0x08000000; #endif #ifdef PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY feature_drv2 |= 0x10000000; #endif #ifdef PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH feature_drv2 |= 0x20000000; #endif #ifdef DEBUG_THEO feature_drv2 |= 0x40000000; #endif #ifdef USE_DEBUG_DRIVER feature_drv2 |= 0x80000000; #endif feature_sns1 = 0x00000000; #ifdef USE_COUNTER feature_sns1 |= 0x00000001; #endif #ifdef USE_ADC_VCC feature_sns1 |= 0x00000002; #endif #ifdef USE_ENERGY_SENSOR feature_sns1 |= 0x00000004; #endif #ifdef USE_PZEM004T feature_sns1 |= 0x00000008; #endif #ifdef USE_DS18B20 feature_sns1 |= 0x00000010; #endif #ifdef USE_DS18x20_LEGACY feature_sns1 |= 0x00000020; #endif #ifdef USE_DS18x20 feature_sns1 |= 0x00000040; #endif #ifdef USE_DHT feature_sns1 |= 0x00000080; #endif #ifdef USE_SHT feature_sns1 |= 0x00000100; #endif #ifdef USE_HTU feature_sns1 |= 0x00000200; #endif #ifdef USE_BMP feature_sns1 |= 0x00000400; #endif #ifdef USE_BME680 feature_sns1 |= 0x00000800; #endif #ifdef USE_BH1750 feature_sns1 |= 0x00001000; #endif #ifdef USE_VEML6070 feature_sns1 |= 0x00002000; #endif #ifdef USE_ADS1115_I2CDEV feature_sns1 |= 0x00004000; #endif #ifdef USE_ADS1115 feature_sns1 |= 0x00008000; #endif #ifdef USE_INA219 feature_sns1 |= 0x00010000; #endif #ifdef USE_SHT3X feature_sns1 |= 0x00020000; #endif #ifdef USE_MHZ19 feature_sns1 |= 0x00040000; #endif #ifdef USE_TSL2561 feature_sns1 |= 0x00080000; #endif #ifdef USE_SENSEAIR feature_sns1 |= 0x00100000; #endif #ifdef USE_PMS5003 feature_sns1 |= 0x00200000; #endif #ifdef USE_MGS feature_sns1 |= 0x00400000; #endif #ifdef USE_NOVA_SDS feature_sns1 |= 0x00800000; #endif #ifdef USE_SGP30 feature_sns1 |= 0x01000000; #endif #ifdef USE_SR04 feature_sns1 |= 0x02000000; #endif #ifdef USE_SDM120 feature_sns1 |= 0x04000000; #endif #ifdef USE_SI1145 feature_sns1 |= 0x08000000; #endif #ifdef USE_SDM630 feature_sns1 |= 0x10000000; #endif #ifdef USE_LM75AD feature_sns1 |= 0x20000000; #endif #ifdef USE_APDS9960 feature_sns1 |= 0x40000000; #endif #ifdef USE_TM1638 feature_sns1 |= 0x80000000; #endif feature_sns2 = 0x00000000; #ifdef USE_MCP230xx feature_sns2 |= 0x00000001; #endif #ifdef USE_MPR121 feature_sns2 |= 0x00000002; #endif #ifdef USE_CCS811 feature_sns2 |= 0x00000004; #endif #ifdef USE_MPU6050 feature_sns2 |= 0x00000008; #endif #ifdef USE_MCP230xx_OUTPUT feature_sns2 |= 0x00000010; #endif #ifdef USE_MCP230xx_DISPLAYOUTPUT feature_sns2 |= 0x00000020; #endif #ifdef USE_HLW8012 feature_sns2 |= 0x00000040; #endif #ifdef USE_CSE7766 feature_sns2 |= 0x00000080; #endif #ifdef USE_MCP39F501 feature_sns2 |= 0x00000100; #endif #ifdef USE_PZEM_AC feature_sns2 |= 0x00000200; #endif #ifdef USE_DS3231 feature_sns2 |= 0x00000400; #endif #ifdef USE_HX711 feature_sns2 |= 0x00000800; #endif #ifdef USE_PZEM_DC feature_sns2 |= 0x00001000; #endif #ifdef USE_TX20_WIND_SENSOR feature_sns2 |= 0x00002000; #endif #ifdef USE_MGC3130 feature_sns2 |= 0x00004000; #endif #ifdef USE_RF_SENSOR feature_sns2 |= 0x00008000; #endif #ifdef USE_THEO_V2 feature_sns2 |= 0x00010000; #endif #ifdef USE_ALECTO_V2 feature_sns2 |= 0x00020000; #endif #ifdef USE_AZ7798 feature_sns2 |= 0x00040000; #endif #ifdef USE_MAX31855 feature_sns2 |= 0x00080000; #endif #ifdef USE_PN532_HSU feature_sns2 |= 0x00100000; #endif #ifdef USE_MAX44009 feature_sns2 |= 0x00200000; #endif #ifdef USE_SCD30 feature_sns2 |= 0x00400000; #endif #ifdef USE_HRE feature_sns2 |= 0x00800000; #endif #ifdef USE_ADE7953 feature_sns2 |= 0x01000000; #endif #ifdef USE_SPS30 feature_sns2 |= 0x02000000; #endif #ifdef USE_VL53L0X feature_sns2 |= 0x04000000; #endif #ifdef USE_MLX90614 feature_sns2 |= 0x08000000; #endif #ifdef USE_MAX31865 feature_sns2 |= 0x10000000; #endif #ifdef USE_CHIRP feature_sns2 |= 0x20000000; #endif #ifdef USE_SOLAX_X1 feature_sns2 |= 0x40000000; #endif #ifdef USE_PAJ7620 feature_sns2 |= 0x80000000; #endif feature5 = 0x00000000; #ifdef USE_BUZZER feature5 |= 0x00000001; #endif #ifdef USE_RDM6300 feature5 |= 0x00000002; #endif #ifdef USE_IBEACON feature5 |= 0x00000004; #endif #ifdef USE_SML_M feature5 |= 0x00000008; #endif #ifdef USE_INA226 feature5 |= 0x00000010; #endif #ifdef USE_A4988_STEPPER feature5 |= 0x00000020; #endif #ifdef USE_DDS2382 feature5 |= 0x00000040; #endif #ifdef USE_SM2135 feature5 |= 0x00000080; #endif #ifdef USE_SHUTTER feature5 |= 0x00000100; #endif #ifdef USE_PCF8574 feature5 |= 0x00000200; #endif #ifdef USE_DDSU666 feature5 |= 0x00000400; #endif #ifdef USE_DEEPSLEEP feature5 |= 0x00000800; #endif #ifdef USE_SONOFF_SC feature5 |= 0x00001000; #endif #ifdef USE_SONOFF_RF feature5 |= 0x00002000; #endif #ifdef USE_SONOFF_L1 feature5 |= 0x00004000; #endif #ifdef USE_EXS_DIMMER feature5 |= 0x00008000; #endif #ifdef USE_ARDUINO_SLAVE feature5 |= 0x00010000; #endif #ifdef USE_HIH6 feature5 |= 0x00020000; #endif #ifdef USE_HPMA feature5 |= 0x00040000; #endif #ifdef USE_TSL2591 feature5 |= 0x00080000; #endif #ifdef USE_DHT12 feature5 |= 0x00100000; #endif #ifdef USE_DS1624 feature5 |= 0x00200000; #endif #ifdef USE_GPS feature5 |= 0x00400000; #endif #ifdef USE_HOTPLUG feature5 |= 0x00800000; #endif #ifdef USE_NRF24 feature5 |= 0x01000000; #endif #ifdef USE_MIBLE feature5 |= 0x02000000; #endif #ifdef USE_HM10 feature5 |= 0x04000000; #endif #ifdef USE_LE01MR feature5 |= 0x08000000; #endif #ifdef USE_AHT10 feature5 |= 0x10000000; #endif feature6 = 0x00000000; # 568 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino" } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino" # 39 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino" #ifdef USE_FLOG class FLOG #define MAGIC_WORD_FL 0x464c { struct header_t{ uint16_t magic_word; uint16_t padding; uint32_t physical_start_sector:10; uint32_t number:10; uint32_t buf_pointer:12; }; private: void _readSector(uint8_t one_sector); void _eraseSector(uint8_t one_sector); void _writeSector(uint8_t one_sector); void _clearBuffer(void); void _searchSaves(void); void _findFirstErasedSector(void); void _showBuffer(void); void _initBuffer(void); void _saveBufferToSector(void); header_t _saved_header; public: uint32_t size; uint32_t start; uint32_t end; uint16_t num_sectors; uint16_t first_erased_sector; uint16_t current_sector; uint16_t bytes_left; uint16_t sectors_left; uint8_t mode = 0; bool found_saved_data = false; bool ready = false; bool running_download = false; bool recording = false; union sector_t{ uint32_t dword_buffer[FLASH_SECTOR_SIZE/4]; uint8_t byte_buffer[FLASH_SECTOR_SIZE]; header_t header; } sector; void init(void); void addToBuffer(uint8_t src[], uint32_t size); void startRecording(bool append); void stopRecording(void); typedef void (*CallbackNoArgs) (); typedef void (*CallbackWithArgs) (uint8_t *_record); void startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter); }; extern "C" uint32_t _SPIFFS_start; extern "C" uint32_t _FS_start; void FLOG::init(void) { DEBUG_SENSOR_LOG(PSTR("FLOG: init ...")); size = ESP.getSketchSize(); start = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) end = (uint32_t)&_SPIFFS_start - 0x40200000; #else end = (uint32_t)&_FS_start - 0x40200000; #endif num_sectors = (end - start)/FLASH_SECTOR_SIZE; DEBUG_SENSOR_LOG(PSTR("FLOG: size: 0x%lx, start: 0x%lx, end: 0x%lx, num_sectors(dec): %lu"), size, start, end, num_sectors ); _findFirstErasedSector(); if(first_erased_sector == 0xffff){ _eraseSector(0); first_erased_sector = 0; } _searchSaves(); _initBuffer(); ready = true; } # 142 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino" void FLOG::_readSector(uint8_t one_sector){ DEBUG_SENSOR_LOG(PSTR("FLOG: read sector number: %u" ), one_sector); ESP.flashRead(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§or.dword_buffer, FLASH_SECTOR_SIZE); } void FLOG::_eraseSector(uint8_t one_sector){ DEBUG_SENSOR_LOG(PSTR("FLOG: erasing sector number: %u" ), one_sector); ESP.flashEraseSector((start/FLASH_SECTOR_SIZE)+one_sector); } void FLOG::_writeSector(uint8_t one_sector){ DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to sector number: %u" ), one_sector); ESP.flashWrite(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§or.dword_buffer, FLASH_SECTOR_SIZE); } void FLOG::_clearBuffer(){ for (uint32_t i = sizeof(sector.header)/4; i<(sizeof(sector.dword_buffer)/4); i++){ sector.dword_buffer[i] = 0; } sector.header.buf_pointer = sizeof(sector.header); } void FLOG::_saveBufferToSector(){ DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to current sector: %u" ),current_sector); _writeSector(current_sector); if(current_sector == num_sectors){ current_sector = 0; } else{ current_sector++; } _eraseSector(current_sector); _clearBuffer(); sector.header.number++; DEBUG_SENSOR_LOG(PSTR("FLOG: new sector header number: %u" ),sector.header.number); } void FLOG::_findFirstErasedSector(){ for (uint32_t i = 0; i3){ break; } } } void FLOG::addToBuffer(uint8_t src[], uint32_t size){ if(mode == 0){ if(sector.header.number == num_sectors && !ready){ return; } } if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){ memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size); sector.header.buf_pointer+=size; } else{ DEBUG_SENSOR_LOG(PSTR("FLOG: save buffer to flash sector: %u"), current_sector); DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer); _saveBufferToSector(); sectors_left++; if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){ memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size); sector.header.buf_pointer+=size; } } } void FLOG::startRecording(bool append){ if(recording){ DEBUG_SENSOR_LOG(PSTR("FLOG: already recording")); return; } recording = true; DEBUG_SENSOR_LOG(PSTR("FLOG: start recording")); _initBuffer(); if(!found_saved_data) { append = false; } if(append){ sector.header.number = _saved_header.number+1; sector.header.physical_start_sector = _saved_header.physical_start_sector; } else{ sector.header.physical_start_sector = (uint16_t)first_erased_sector; found_saved_data = false; sectors_left = 0; } } void FLOG::stopRecording(void){ _saveBufferToSector(); _findFirstErasedSector(); _searchSaves(); _initBuffer(); recording = false; found_saved_data = true; } # 381 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino" void FLOG::startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter){ _readSector(sector.header.physical_start_sector); uint32_t next_sector = sector.header.physical_start_sector; bytes_left = sector.header.buf_pointer - sizeof(sector.header); DEBUG_SENSOR_LOG(PSTR("FLOG: create file for download, will process %u bytes"), bytes_left); running_download = true; sendHeader(); while(sectors_left){ DEBUG_SENSOR_LOG(PSTR("FLOG: next sector: %u"), next_sector); uint32_t k = sizeof(sector.header); while(bytes_left){ uint8_t *_record_start = (uint8_t*)§or.byte_buffer[k]; sendRecord(_record_start); if(k%128 == 0){ OsWatchLoop(); delay(sleep); } k+=size; if(bytes_left>7){ bytes_left-=size; } else{ bytes_left = 0; DEBUG_SENSOR_LOG(PSTR("FLOG: Flog->bytes_left not dividable by 8 ??????")); } } next_sector++; if(next_sector>num_sectors){ next_sector = 0; } sectors_left--; _readSector(next_sector); bytes_left = sector.header.buf_pointer - sizeof(sector.header); OsWatchLoop(); delay(sleep); } running_download = false; sendFooter(); _searchSaves(); _initBuffer(); } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" # 23 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" float fmodf(float x, float y) { union {float f; uint32_t i;} ux = {x}, uy = {y}; int ex = ux.i>>23 & 0xff; int ey = uy.i>>23 & 0xff; uint32_t sx = ux.i & 0x80000000; uint32_t i; uint32_t uxi = ux.i; if (uy.i<<1 == 0 || isnan(y) || ex == 0xff) return (x*y)/(x*y); if (uxi<<1 <= uy.i<<1) { if (uxi<<1 == uy.i<<1) return 0*x; return x; } if (!ex) { for (i = uxi<<9; i>>31 == 0; ex--, i <<= 1); uxi <<= -ex + 1; } else { uxi &= -1U >> 9; uxi |= 1U << 23; } if (!ey) { for (i = uy.i<<9; i>>31 == 0; ey--, i <<= 1); uy.i <<= -ey + 1; } else { uy.i &= -1U >> 9; uy.i |= 1U << 23; } for (; ex > ey; ex--) { i = uxi - uy.i; if (i >> 31 == 0) { if (i == 0) return 0*x; uxi = i; } uxi <<= 1; } i = uxi - uy.i; if (i >> 31 == 0) { if (i == 0) return 0*x; uxi = i; } for (; uxi>>23 == 0; uxi <<= 1, ex--); if (ex > 0) { uxi -= 1U << 23; uxi |= (uint32_t)ex << 23; } else { uxi >>= -ex + 1; } uxi |= sx; ux.i = uxi; return ux.f; } double FastPrecisePow(double a, double b) { int e = abs((int)b); union { double d; int x[2]; } u = { a }; u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447); u.x[0] = 0; double r = 1.0; while (e) { if (e & 1) { r *= a; } a *= a; e >>= 1; } return r * u.d; } float FastPrecisePowf(const float x, const float y) { return (float)FastPrecisePow(x, y); } double TaylorLog(double x) { if (x <= 0.0) { return NAN; } double z = (x + 1) / (x - 1); double step = ((x - 1) * (x - 1)) / ((x + 1) * (x + 1)); double totalValue = 0; double powe = 1; for (uint32_t count = 0; count < 10; count++) { z *= step; double y = (1 / powe) * z; totalValue = totalValue + y; powe = powe + 2; } totalValue *= 2; # 144 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" return totalValue; } # 154 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" inline float sinf(float x) { return sin_52(x); } inline float cosf(float x) { return cos_52(x); } inline float tanf(float x) { return tan_56(x); } inline float atanf(float x) { return atan_66(x); } inline float asinf(float x) { return asinf1(x); } inline float acosf(float x) { return acosf1(x); } inline float sqrtf(float x) { return sqrt1(x); } inline float powf(float x, float y) { return FastPrecisePow(x, y); } double const f_pi = 3.1415926535897932384626433; double const f_twopi = 2.0 * f_pi; double const f_two_over_pi = 2.0 / f_pi; double const f_halfpi = f_pi / 2.0; double const f_threehalfpi = 3.0 * f_pi / 2.0; double const f_four_over_pi = 4.0 / f_pi; double const f_qtrpi = f_pi / 4.0; double const f_sixthpi = f_pi / 6.0; double const f_tansixthpi = tan(f_sixthpi); double const f_twelfthpi = f_pi / 12.0; double const f_tantwelfthpi = tan(f_twelfthpi); float const f_180pi = 180 / f_pi; # 194 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" float cos_52s(float x) { const float c1 = 0.9999932946; const float c2 = -0.4999124376; const float c3 = 0.0414877472; const float c4 = -0.0012712095; float x2 = x * x; return (c1 + x2 * (c2 + x2 * (c3 + c4 * x2))); } float cos_52(float x) { x = fmodf(x, f_twopi); if (x < 0) { x = -x; } int quad = int(x * (float)f_two_over_pi); switch (quad) { case 0: return cos_52s(x); case 1: return -cos_52s((float)f_pi - x); case 2: return -cos_52s(x-(float)f_pi); case 3: return cos_52s((float)f_twopi - x); } } float sin_52(float x) { return cos_52((float)f_halfpi - x); } # 247 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" float tan_56s(float x) { const float c1 = -3.16783027; const float c2 = 0.134516124; const float c3 = -4.033321984; float x2 = x * x; return (x * (c1 + c2 * x2) / (c3 + x2)); } # 267 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" float tan_56(float x) { x = fmodf(x, (float)f_twopi); int octant = int(x * (float)f_four_over_pi); switch (octant){ case 0: return tan_56s(x * (float)f_four_over_pi); case 1: return 1.0f / tan_56s(((float)f_halfpi - x) * (float)f_four_over_pi); case 2: return -1.0f / tan_56s((x-(float)f_halfpi) * (float)f_four_over_pi); case 3: return - tan_56s(((float)f_pi - x) * (float)f_four_over_pi); case 4: return tan_56s((x-(float)f_pi) * (float)f_four_over_pi); case 5: return 1.0f / tan_56s(((float)f_threehalfpi - x) * (float)f_four_over_pi); case 6: return -1.0f / tan_56s((x-(float)f_threehalfpi) * (float)f_four_over_pi); case 7: return - tan_56s(((float)f_twopi - x) * (float)f_four_over_pi); } } # 296 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" float atan_66s(float x) { const float c1 = 1.6867629106; const float c2 = 0.4378497304; const float c3 = 1.6867633134; float x2 = x * x; return (x * (c1 + x2 * c2) / (c3 + x2)); } float atan_66(float x) { float y; bool complement= false; bool region= false; bool sign= false; if (x < 0) { x = -x; sign = true; } if (x > 1.0) { x = 1.0 / x; complement = true; } if (x > (float)f_tantwelfthpi) { x = (x - (float)f_tansixthpi) / (1 + (float)f_tansixthpi * x); region = true; } y = atan_66s(x); if (region) { y += (float)f_sixthpi; } if (complement) { y = (float)f_halfpi-y; } if (sign) { y = -y; } return (y); } float asinf1(float x) { float d = 1.0f - x * x; if (d < 0.0f) { return NAN; } return 2 * atan_66(x / (1 + sqrt1(d))); } float acosf1(float x) { float d = 1.0f - x * x; if (d < 0.0f) { return NAN; } float y = asinf1(sqrt1(d)); if (x >= 0.0f) { return y; } else { return (float)f_pi - y; } } float sqrt1(const float x) { union { int i; float x; } u; u.x = x; u.i = (1 << 29) + (u.i >> 1) - (1 << 22); u.x = u.x + x / u.x; u.x = 0.25f * u.x + x / u.x; return u.x; } # 387 "C:/shared/sonoff/Git/Tasmota/tasmota/support_float.ino" uint16_t changeUIntScale(uint16_t inum, uint16_t ifrom_min, uint16_t ifrom_max, uint16_t ito_min, uint16_t ito_max) { if (ifrom_min >= ifrom_max) { if (ito_min > ito_max) { return ito_max; } else { return ito_min; } } uint32_t num = inum; uint32_t from_min = ifrom_min; uint32_t from_max = ifrom_max; uint32_t to_min = ito_min; uint32_t to_max = ito_max; num = (num > from_max ? from_max : (num < from_min ? from_min : num)); if (to_min > to_max) { num = (from_max - num) + from_min; to_min = ito_max; to_max = ito_min; } uint32_t numerator = (num - from_min) * (to_max - to_min); uint32_t result; if (numerator >= 0x80000000L) { result = numerator / (from_max - from_min) + to_min; } else { result = (((numerator * 2) / (from_max - from_min)) + 1) / 2 + to_min; } return (uint32_t) (result > to_max ? to_max : (result < to_min ? to_min : result)); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_legacy_cores.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_legacy_cores.ino" #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 void* memchr(const void* ptr, int value, size_t num) { unsigned char *p = (unsigned char*)ptr; while (num--) { if (*p != (unsigned char)value) { p++; } else { return p; } } return 0; } size_t strcspn(const char *str1, const char *str2) { size_t ret = 0; while (*str1) { if (strchr(str2, *str1)) { return ret; } else { str1++; ret++; } } return ret; } char* strpbrk(const char *s1, const char *s2) { while(*s1) { if (strchr(s2, *s1++)) { return (char*)--s1; } } return 0; } #ifndef __LONG_LONG_MAX__ #define __LONG_LONG_MAX__ 9223372036854775807LL #endif #ifndef ULLONG_MAX #define ULLONG_MAX (__LONG_LONG_MAX__ * 2ULL + 1) #endif unsigned long long strtoull(const char *__restrict nptr, char **__restrict endptr, int base) { const char *s = nptr; char c; do { c = *s++; } while (isspace((unsigned char)c)); int neg = 0; if (c == '-') { neg = 1; c = *s++; } else { if (c == '+') { c = *s++; } } if ((base == 0 || base == 16) && (c == '0') && (*s == 'x' || *s == 'X')) { c = s[1]; s += 2; base = 16; } if (base == 0) { base = (c == '0') ? 8 : 10; } unsigned long long acc = 0; int any = 0; if (base > 1 && base < 37) { unsigned long long cutoff = ULLONG_MAX / base; int cutlim = ULLONG_MAX % base; for ( ; ; c = *s++) { if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'A' && c <= 'Z') c -= 'A' - 10; else if (c >= 'a' && c <= 'z') c -= 'a' - 10; else break; if (c >= base) break; if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) any = -1; else { any = 1; acc *= base; acc += c; } } if (any < 0) { acc = ULLONG_MAX; } else if (any && neg) { acc = -acc; } } if (endptr != nullptr) { *endptr = (char *)(any ? s - 1 : nptr); } return acc; } #endif #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) void* memmove_P(void *dest, const void *src, size_t n) { if (src > (void*)0x40000000) { return memcpy_P(dest, src, n); } else { return memmove(dest, src, n); } } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rotary.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rotary.ino" #ifdef USE_LIGHT #ifdef ROTARY_V1 struct ROTARY { unsigned long debounce = 0; uint8_t present = 0; uint8_t state = 0; uint8_t position = 128; uint8_t last_position = 128; uint8_t interrupts_in_use_count = 0; uint8_t changed = 0; } Rotary; #ifndef ARDUINO_ESP8266_RELEASE_2_3_0 void update_rotary(void) ICACHE_RAM_ATTR; #endif void update_rotary(void) { if (MI_DESK_LAMP == my_module_type) { if (LightPowerIRAM()) { uint8_t s = Rotary.state & 3; if (digitalRead(pin[GPIO_ROT1A])) { s |= 4; } if (digitalRead(pin[GPIO_ROT1B])) { s |= 8; } switch (s) { case 0: case 5: case 10: case 15: break; case 1: case 7: case 8: case 14: Rotary.position++; break; case 2: case 4: case 11: case 13: Rotary.position--; break; case 3: case 12: Rotary.position = Rotary.position + 2; break; default: Rotary.position = Rotary.position - 2; break; } Rotary.state = (s >> 2); } } } bool RotaryButtonPressed(void) { if ((MI_DESK_LAMP == my_module_type) && (Rotary.changed) && LightPower()) { Rotary.changed = 0; return true; } return false; } void RotaryInit(void) { Rotary.present = 0; if ((pin[GPIO_ROT1A] < 99) && (pin[GPIO_ROT1B] < 99)) { Rotary.present++; pinMode(pin[GPIO_ROT1A], INPUT_PULLUP); pinMode(pin[GPIO_ROT1B], INPUT_PULLUP); if ((pin[GPIO_ROT1A] < 6) || (pin[GPIO_ROT1A] > 11)) { attachInterrupt(digitalPinToInterrupt(pin[GPIO_ROT1A]), update_rotary, CHANGE); Rotary.interrupts_in_use_count++; } if ((pin[GPIO_ROT1B] < 6) || (pin[GPIO_ROT1B] > 11)) { attachInterrupt(digitalPinToInterrupt(pin[GPIO_ROT1B]), update_rotary, CHANGE); Rotary.interrupts_in_use_count++; } } } void RotaryHandler(void) { if (Rotary.interrupts_in_use_count < 2) { noInterrupts(); update_rotary(); } else { noInterrupts(); } if (Rotary.last_position != Rotary.position) { if (MI_DESK_LAMP == my_module_type) { if (Button.hold_timer[0]) { Rotary.changed = 1; int16_t t = LightGetColorTemp(); t = t + (Rotary.position - Rotary.last_position); if (t < 153) { t = 153; } if (t > 500) { t = 500; } DEBUG_CORE_LOG(PSTR("ROT: " D_CMND_COLORTEMPERATURE " %d"), Rotary.position - Rotary.last_position); LightSetColorTemp((uint16_t)t); } else { int8_t d = Settings.light_dimmer; d = d + (Rotary.position - Rotary.last_position); if (d < 1) { d = 1; } if (d > 100) { d = 100; } DEBUG_CORE_LOG(PSTR("ROT: " D_CMND_DIMMER " %d"), Rotary.position - Rotary.last_position); LightSetDimmer((uint8_t)d); Settings.light_dimmer = d; } } Rotary.last_position = 128; Rotary.position = 128; } interrupts(); } void RotaryLoop(void) { if (Rotary.present) { if (TimeReached(Rotary.debounce)) { SetNextTimeInterval(Rotary.debounce, Settings.button_debounce); RotaryHandler(); } } } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rtc.ino" # 25 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rtc.ino" const uint32_t SECS_PER_MIN = 60UL; const uint32_t SECS_PER_HOUR = 3600UL; const uint32_t SECS_PER_DAY = SECS_PER_HOUR * 24UL; const uint32_t MINS_PER_HOUR = 60UL; #define LEAP_YEAR(Y) (((1970+Y)>0) && !((1970+Y)%4) && (((1970+Y)%100) || !((1970+Y)%400))) extern "C" { #include "sntp.h" } #include Ticker TickerRtc; static const uint8_t kDaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static const char kMonthNamesEnglish[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; struct RTC { uint32_t utc_time = 0; uint32_t local_time = 0; uint32_t daylight_saving_time = 0; uint32_t standard_time = 0; uint32_t ntp_time = 0; uint32_t midnight = 0; uint32_t restart_time = 0; int32_t drift_time = 0; int32_t time_timezone = 0; uint8_t ntp_sync_minute = 0; bool midnight_now = false; bool user_time_entry = false; } Rtc; uint32_t UtcTime(void) { return Rtc.utc_time; } uint32_t LocalTime(void) { return Rtc.local_time; } int32_t DriftTime(void) { return Rtc.drift_time; } uint32_t Midnight(void) { return Rtc.midnight; } bool MidnightNow(void) { if (Rtc.midnight_now) { Rtc.midnight_now = false; return true; } return false; } bool IsDst(void) { if (Rtc.time_timezone == Settings.toffset[1]) { return true; } return false; } String GetBuildDateAndTime(void) { char bdt[21]; char *p; char mdate[] = __DATE__; char *smonth = mdate; int day = 0; int year = 0; uint8_t i = 0; for (char *str = strtok_r(mdate, " ", &p); str && i < 3; str = strtok_r(nullptr, " ", &p)) { switch (i++) { case 0: smonth = str; break; case 1: day = atoi(str); break; case 2: year = atoi(str); } } int month = (strstr(kMonthNamesEnglish, smonth) -kMonthNamesEnglish) /3 +1; snprintf_P(bdt, sizeof(bdt), PSTR("%d" D_YEAR_MONTH_SEPARATOR "%02d" D_MONTH_DAY_SEPARATOR "%02d" D_DATE_TIME_SEPARATOR "%s"), year, month, day, __TIME__); return String(bdt); } String GetMinuteTime(uint32_t minutes) { char tm[6]; snprintf_P(tm, sizeof(tm), PSTR("%02d:%02d"), minutes / 60, minutes % 60); return String(tm); } String GetTimeZone(void) { char tz[7]; snprintf_P(tz, sizeof(tz), PSTR("%+03d:%02d"), Rtc.time_timezone / 60, abs(Rtc.time_timezone % 60)); return String(tz); } String GetDuration(uint32_t time) { char dt[16]; TIME_T ut; BreakTime(time, ut); snprintf_P(dt, sizeof(dt), PSTR("%dT%02d:%02d:%02d"), ut.days, ut.hour, ut.minute, ut.second); return String(dt); } String GetDT(uint32_t time) { char dt[20]; TIME_T tmpTime; BreakTime(time, tmpTime); snprintf_P(dt, sizeof(dt), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), tmpTime.year +1970, tmpTime.month, tmpTime.day_of_month, tmpTime.hour, tmpTime.minute, tmpTime.second); return String(dt); } # 181 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rtc.ino" String GetDateAndTime(uint8_t time_type) { uint32_t time = Rtc.local_time; switch (time_type) { case DT_BOOTCOUNT: time = Settings.bootcount_reset_time; break; case DT_ENERGY: time = Settings.energy_kWhtotal_time; break; case DT_UTC: time = Rtc.utc_time; break; case DT_RESTART: if (Rtc.restart_time == 0) { return ""; } time = Rtc.restart_time; break; } String dt = GetDT(time); if (Settings.flag3.time_append_timezone && (DT_LOCAL == time_type)) { dt += GetTimeZone(); } return dt; } String GetTime(int type) { char stime[25]; uint32_t time = Rtc.utc_time; if (1 == type) time = Rtc.local_time; if (2 == type) time = Rtc.daylight_saving_time; if (3 == type) time = Rtc.standard_time; snprintf_P(stime, sizeof(stime), sntp_get_real_time(time)); return String(stime); } uint32_t UpTime(void) { if (Rtc.restart_time) { return Rtc.utc_time - Rtc.restart_time; } else { return uptime; } } uint32_t MinutesUptime(void) { return (UpTime() / 60); } String GetUptime(void) { return GetDuration(UpTime()); } uint32_t MinutesPastMidnight(void) { uint32_t minutes = 0; if (RtcTime.valid) { minutes = (RtcTime.hour *60) + RtcTime.minute; } return minutes; } void BreakTime(uint32_t time_input, TIME_T &tm) { uint8_t year; uint8_t month; uint8_t month_length; uint32_t time; unsigned long days; time = time_input; tm.second = time % 60; time /= 60; tm.minute = time % 60; time /= 60; tm.hour = time % 24; time /= 24; tm.days = time; tm.day_of_week = ((time + 4) % 7) + 1; year = 0; days = 0; while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { year++; } tm.year = year; days -= LEAP_YEAR(year) ? 366 : 365; time -= days; tm.day_of_year = time; for (month = 0; month < 12; month++) { if (1 == month) { if (LEAP_YEAR(year)) { month_length = 29; } else { month_length = 28; } } else { month_length = kDaysInMonth[month]; } if (time >= month_length) { time -= month_length; } else { break; } } strlcpy(tm.name_of_month, kMonthNames + (month *3), 4); tm.month = month + 1; tm.day_of_month = time + 1; tm.valid = (time_input > START_VALID_TIME); } uint32_t MakeTime(TIME_T &tm) { int i; uint32_t seconds; seconds = tm.year * (SECS_PER_DAY * 365); for (i = 0; i < tm.year; i++) { if (LEAP_YEAR(i)) { seconds += SECS_PER_DAY; } } for (i = 1; i < tm.month; i++) { if ((2 == i) && LEAP_YEAR(tm.year)) { seconds += SECS_PER_DAY * 29; } else { seconds += SECS_PER_DAY * kDaysInMonth[i-1]; } } seconds+= (tm.day_of_month - 1) * SECS_PER_DAY; seconds+= tm.hour * SECS_PER_HOUR; seconds+= tm.minute * SECS_PER_MIN; seconds+= tm.second; return seconds; } uint32_t RuleToTime(TimeRule r, int yr) { TIME_T tm; uint32_t t; uint8_t m; uint8_t w; m = r.month; w = r.week; if (0 == w) { if (++m > 12) { m = 1; yr++; } w = 1; } tm.hour = r.hour; tm.minute = 0; tm.second = 0; tm.day_of_month = 1; tm.month = m; tm.year = yr - 1970; t = MakeTime(tm); BreakTime(t, tm); t += (7 * (w - 1) + (r.dow - tm.day_of_week + 7) % 7) * SECS_PER_DAY; if (0 == r.week) { t -= 7 * SECS_PER_DAY; } return t; } void RtcSecond(void) { TIME_T tmpTime; if (!Rtc.user_time_entry && !global_state.wifi_down) { uint8_t uptime_minute = (uptime / 60) % 60; if ((Rtc.ntp_sync_minute > 59) && (uptime_minute > 2)) { Rtc.ntp_sync_minute = 1; } uint8_t offset = (uptime < 30) ? RtcTime.second : (((ESP.getChipId() & 0xF) * 3) + 3) ; if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) || (Rtc.ntp_sync_minute == uptime_minute))) || ntp_force_sync ) ) { Rtc.ntp_time = sntp_get_current_timestamp(); if (Rtc.ntp_time > START_VALID_TIME) { ntp_force_sync = false; if (Rtc.utc_time > START_VALID_TIME) { Rtc.drift_time = Rtc.ntp_time - Rtc.utc_time; } Rtc.utc_time = Rtc.ntp_time; Rtc.ntp_sync_minute = 60; if (Rtc.restart_time == 0) { Rtc.restart_time = Rtc.utc_time - uptime; } BreakTime(Rtc.utc_time, tmpTime); RtcTime.year = tmpTime.year + 1970; Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year); Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year); PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: Drift %d, (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), DriftTime(), GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str()); if (Rtc.local_time < START_VALID_TIME) { rules_flag.time_init = 1; } else { rules_flag.time_set = 1; } } else { Rtc.ntp_sync_minute++; } } } Rtc.utc_time++; Rtc.local_time = Rtc.utc_time; if (Rtc.local_time > START_VALID_TIME) { int16_t timezone_minutes = Settings.timezone_minutes; if (Settings.timezone < 0) { timezone_minutes *= -1; } Rtc.time_timezone = (Settings.timezone * SECS_PER_HOUR) + (timezone_minutes * SECS_PER_MIN); if (99 == Settings.timezone) { int32_t dstoffset = Settings.toffset[1] * SECS_PER_MIN; int32_t stdoffset = Settings.toffset[0] * SECS_PER_MIN; if (Settings.tflag[1].hemis) { if ((Rtc.utc_time >= (Rtc.standard_time - dstoffset)) && (Rtc.utc_time < (Rtc.daylight_saving_time - stdoffset))) { Rtc.time_timezone = stdoffset; } else { Rtc.time_timezone = dstoffset; } } else { if ((Rtc.utc_time >= (Rtc.daylight_saving_time - stdoffset)) && (Rtc.utc_time < (Rtc.standard_time - dstoffset))) { Rtc.time_timezone = dstoffset; } else { Rtc.time_timezone = stdoffset; } } } Rtc.local_time += Rtc.time_timezone; Rtc.time_timezone /= 60; if (!Settings.energy_kWhtotal_time) { Settings.energy_kWhtotal_time = Rtc.local_time; } if (Settings.bootcount_reset_time < START_VALID_TIME) { Settings.bootcount_reset_time = Rtc.local_time; } } BreakTime(Rtc.local_time, RtcTime); if (RtcTime.valid) { if (!Rtc.midnight) { Rtc.midnight = Rtc.local_time - (RtcTime.hour * 3600) - (RtcTime.minute * 60) - RtcTime.second; } if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) { Rtc.midnight = Rtc.local_time; Rtc.midnight_now = true; } } RtcTime.year += 1970; } void RtcSetTime(uint32_t epoch) { if (epoch < START_VALID_TIME) { Rtc.user_time_entry = false; ntp_force_sync = true; sntp_init(); } else { sntp_stop(); Rtc.user_time_entry = true; Rtc.utc_time = epoch -1; } RtcSecond(); } void RtcInit(void) { sntp_setservername(0, SettingsText(SET_NTPSERVER1)); sntp_setservername(1, SettingsText(SET_NTPSERVER2)); sntp_setservername(2, SettingsText(SET_NTPSERVER3)); sntp_stop(); sntp_set_timezone(0); sntp_init(); Rtc.utc_time = 0; BreakTime(Rtc.utc_time, RtcTime); TickerRtc.attach(1, RtcSecond); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_static_buffer.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_static_buffer.ino" typedef struct SBuffer_impl { uint16_t size; uint16_t len; uint8_t buf[]; } SBuffer_impl; typedef class SBuffer { protected: SBuffer(void) { } public: SBuffer(const size_t size) { _buf = (SBuffer_impl*) new char[size+4]; _buf->size = size; _buf->len = 0; } inline size_t getSize(void) const { return _buf->size; } inline size_t size(void) const { return _buf->size; } inline size_t getLen(void) const { return _buf->len; } inline size_t len(void) const { return _buf->len; } inline uint8_t *getBuffer(void) const { return _buf->buf; } inline uint8_t *buf(size_t i = 0) const { return &_buf->buf[i]; } inline char *charptr(size_t i = 0) const { return (char*) &_buf->buf[i]; } virtual ~SBuffer(void) { delete[] _buf; } inline void setLen(const size_t len) { uint16_t old_len = _buf->len; _buf->len = (len <= _buf->size) ? len : _buf->size; if (old_len < _buf->len) { memset((void*) &_buf->buf[old_len], 0, _buf->len - old_len); } } void set8(const size_t offset, const uint8_t data) { if (offset < _buf->len) { _buf->buf[offset] = data; } } size_t add8(const uint8_t data) { if (_buf->len < _buf->size) { _buf->buf[_buf->len++] = data; } return _buf->len; } size_t add16(const uint16_t data) { if (_buf->len < _buf->size - 1) { _buf->buf[_buf->len++] = data; _buf->buf[_buf->len++] = data >> 8; } return _buf->len; } size_t add32(const uint32_t data) { if (_buf->len < _buf->size - 3) { _buf->buf[_buf->len++] = data; _buf->buf[_buf->len++] = data >> 8; _buf->buf[_buf->len++] = data >> 16; _buf->buf[_buf->len++] = data >> 24; } return _buf->len; } size_t add64(const uint64_t data) { if (_buf->len < _buf->size - 7) { _buf->buf[_buf->len++] = data; _buf->buf[_buf->len++] = data >> 8; _buf->buf[_buf->len++] = data >> 16; _buf->buf[_buf->len++] = data >> 24; _buf->buf[_buf->len++] = data >> 32; _buf->buf[_buf->len++] = data >> 40; _buf->buf[_buf->len++] = data >> 48; _buf->buf[_buf->len++] = data >> 56; } return _buf->len; } size_t addBuffer(const SBuffer &buf2) { if (len() + buf2.len() <= size()) { for (uint32_t i = 0; i < buf2.len(); i++) { _buf->buf[_buf->len++] = buf2.buf()[i]; } } return _buf->len; } size_t addBuffer(const uint8_t *buf2, size_t len2) { if (len() + len2 <= size()) { for (uint32_t i = 0; i < len2; i++) { _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); } } return _buf->len; } size_t addBuffer(const char *buf2, size_t len2) { if (len() + len2 <= size()) { for (uint32_t i = 0; i < len2; i++) { _buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]); } } return _buf->len; } uint8_t get8(size_t offset) const { if (offset < _buf->len) { return _buf->buf[offset]; } else { return 0; } } uint8_t read8(const size_t offset) const { if (offset < len()) { return _buf->buf[offset]; } return 0; } uint16_t get16(const size_t offset) const { if (offset < len() - 1) { return _buf->buf[offset] | (_buf->buf[offset+1] << 8); } return 0; } uint32_t get32(const size_t offset) const { if (offset < len() - 3) { return _buf->buf[offset] | (_buf->buf[offset+1] << 8) | (_buf->buf[offset+2] << 16) | (_buf->buf[offset+3] << 24); } return 0; } uint64_t get64(const size_t offset) const { if (offset < len() - 7) { return (uint64_t)_buf->buf[offset] | ((uint64_t)_buf->buf[offset+1] << 8) | ((uint64_t)_buf->buf[offset+2] << 16) | ((uint64_t)_buf->buf[offset+3] << 24) | ((uint64_t)_buf->buf[offset+4] << 32) | ((uint64_t)_buf->buf[offset+5] << 40) | ((uint64_t)_buf->buf[offset+6] << 48) | ((uint64_t)_buf->buf[offset+7] << 56); } return 0; } inline size_t strlen(const size_t offset) const { return strnlen((const char*) &_buf->buf[offset], len() - offset); } size_t strlen_s(const size_t offset) const { size_t slen = this->strlen(offset); if (slen == len() - offset) { return 0; } else { return slen; } } SBuffer subBuffer(const size_t start, size_t len) const { if (start >= _buf->len) { len = 0; } else if (start + len > _buf->len) { len = _buf->len - start; } SBuffer buf2(len); memcpy(buf2.buf(), buf()+start, len); buf2._buf->len = len; return buf2; } static SBuffer SBufferFromHex(const char *hex, size_t len) { size_t buf_len = (len + 3) / 2; SBuffer buf2(buf_len); uint8_t val; for (; len > 1; len -= 2) { val = asc2byte(*hex++) << 4; val |= asc2byte(*hex++); buf2.add8(val); } return buf2; } protected: static uint8_t asc2byte(char chr) { uint8_t rVal = 0; if (isdigit(chr)) { rVal = chr - '0'; } else if (chr >= 'A' && chr <= 'F') { rVal = chr + 10 - 'A'; } else if (chr >= 'a' && chr <= 'f') { rVal = chr + 10 - 'a'; } return rVal; } static void unHex(const char* in, uint8_t *out, size_t len) { } protected: SBuffer_impl * _buf; } SBuffer; typedef class PreAllocatedSBuffer : public SBuffer { public: PreAllocatedSBuffer(const size_t size, void * buffer) { _buf = (SBuffer_impl*) buffer; _buf->size = size - 4; _buf->len = 0; } ~PreAllocatedSBuffer(void) { _buf = nullptr; } } PreAllocatedSBuffer; # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_statistics.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_statistics.ino" #define USE_STATS_CODE #ifdef USE_STATS_CODE String GetStatistics(void) { char data[40]; snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); return String(data); } #else String GetStatistics(void) { return String(""); } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_switch.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_switch.ino" #define SWITCH_V2 #ifdef SWITCH_V2 const uint8_t SWITCH_PROBE_INTERVAL = 10; #include Ticker TickerSwitch; struct SWITCH { unsigned long debounce = 0; uint16_t no_pullup_mask = 0; uint8_t state[MAX_SWITCHES] = { 0 }; uint8_t last_state[MAX_SWITCHES]; uint8_t hold_timer[MAX_SWITCHES] = { 0 }; uint8_t virtual_state[MAX_SWITCHES]; uint8_t present = 0; } Switch; void SwitchPullupFlag(uint16 switch_bit) { bitSet(Switch.no_pullup_mask, switch_bit); } void SwitchSetVirtual(uint32_t index, uint8_t state) { Switch.virtual_state[index] = state; } uint8_t SwitchGetVirtual(uint32_t index) { return Switch.virtual_state[index]; } uint8_t SwitchLastState(uint32_t index) { return Switch.last_state[index]; } bool SwitchState(uint32_t index) { uint32_t switchmode = Settings.switchmode[index]; return ((FOLLOW_INV == switchmode) || (PUSHBUTTON_INV == switchmode) || (PUSHBUTTONHOLD_INV == switchmode) || (FOLLOWMULTI_INV == switchmode) || (PUSHHOLDMULTI_INV == switchmode) ) ^ Switch.last_state[index]; } void SwitchProbe(void) { if (uptime < 4) { return; } uint8_t state_filter = Settings.switch_debounce / SWITCH_PROBE_INTERVAL; uint8_t force_high = (Settings.switch_debounce % 50) &1; uint8_t force_low = (Settings.switch_debounce % 50) &2; for (uint32_t i = 0; i < MAX_SWITCHES; i++) { if (pin[GPIO_SWT1 +i] < 99) { if (1 == digitalRead(pin[GPIO_SWT1 +i])) { if (force_high) { if (1 == Switch.virtual_state[i]) { Switch.state[i] = state_filter; } } if (Switch.state[i] < state_filter) { Switch.state[i]++; if (state_filter == Switch.state[i]) { Switch.virtual_state[i] = 1; } } } else { if (force_low) { if (0 == Switch.virtual_state[i]) { Switch.state[i] = 0; } } if (Switch.state[i] > 0) { Switch.state[i]--; if (0 == Switch.state[i]) { Switch.virtual_state[i] = 0; } } } } } TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); } void SwitchInit(void) { Switch.present = 0; for (uint32_t i = 0; i < MAX_SWITCHES; i++) { Switch.last_state[i] = 1; if (pin[GPIO_SWT1 +i] < 99) { Switch.present++; pinMode(pin[GPIO_SWT1 +i], bitRead(Switch.no_pullup_mask, i) ? INPUT : ((16 == pin[GPIO_SWT1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP)); Switch.last_state[i] = digitalRead(pin[GPIO_SWT1 +i]); } Switch.virtual_state[i] = Switch.last_state[i]; } if (Switch.present) { TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); } } void SwitchHandler(uint8_t mode) { if (uptime < 4) { return; } uint16_t loops_per_second = 1000 / Settings.switch_debounce; for (uint32_t i = 0; i < MAX_SWITCHES; i++) { if ((pin[GPIO_SWT1 +i] < 99) || (mode)) { uint8_t button = Switch.virtual_state[i]; uint8_t switchflag = POWER_TOGGLE +1; if (Switch.hold_timer[i]) { Switch.hold_timer[i]--; if (0 == Switch.hold_timer[i]) { switch (Settings.switchmode[i]) { case TOGGLEMULTI: switchflag = POWER_TOGGLE; break; case FOLLOWMULTI: switchflag = button &1; break; case FOLLOWMULTI_INV: switchflag = ~button &1; break; case PUSHHOLDMULTI: if (NOT_PRESSED == button){ Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 25; SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); } else SendKey(KEY_SWITCH, i +1, POWER_CLEAR); break; case PUSHHOLDMULTI_INV: if (PRESSED == button){ Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 25; SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); } else SendKey(KEY_SWITCH, i +1, POWER_CLEAR); break; default: SendKey(KEY_SWITCH, i +1, POWER_HOLD); break; } } } if (button != Switch.last_state[i]) { switch (Settings.switchmode[i]) { case TOGGLE: case PUSHBUTTON_TOGGLE: switchflag = POWER_TOGGLE; break; case FOLLOW: switchflag = button &1; break; case FOLLOW_INV: switchflag = ~button &1; break; case PUSHBUTTON: if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { switchflag = POWER_TOGGLE; } break; case PUSHBUTTON_INV: if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { switchflag = POWER_TOGGLE; } break; case PUSHBUTTONHOLD: if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; } if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i]) && (Switch.hold_timer[i])) { Switch.hold_timer[i] = 0; switchflag = POWER_TOGGLE; } break; case PUSHBUTTONHOLD_INV: if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; } if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i]) && (Switch.hold_timer[i])) { Switch.hold_timer[i] = 0; switchflag = POWER_TOGGLE; } break; case TOGGLEMULTI: case FOLLOWMULTI: case FOLLOWMULTI_INV: if (Switch.hold_timer[i]) { Switch.hold_timer[i] = 0; SendKey(KEY_SWITCH, i +1, POWER_HOLD); } else { Switch.hold_timer[i] = loops_per_second / 2; } break; case PUSHHOLDMULTI: if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { if(Switch.hold_timer[i]!=0) SendKey(KEY_SWITCH, i +1, POWER_INV); Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; } if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { if(Switch.hold_timer[i] > loops_per_second * Settings.param[P_HOLD_TIME] / 25) switchflag = POWER_TOGGLE; Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; } break; case PUSHHOLDMULTI_INV: if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) { if(Switch.hold_timer[i]!=0) SendKey(KEY_SWITCH, i +1, POWER_INV); Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; } if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) { if(Switch.hold_timer[i] > loops_per_second * Settings.param[P_HOLD_TIME] / 25) switchflag = POWER_TOGGLE; Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10; } break; } Switch.last_state[i] = button; } if (switchflag <= POWER_TOGGLE) { if (!SendKey(KEY_SWITCH, i +1, switchflag)) { ExecuteCommandPower(i +1, switchflag, SRC_SWITCH); } } } } } void SwitchLoop(void) { if (Switch.present) { if (TimeReached(Switch.debounce)) { SetNextTimeInterval(Switch.debounce, Settings.switch_debounce); SwitchHandler(0); } } } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" const char kSleepMode[] PROGMEM = "Dynamic|Normal"; const char kPrefixes[] PROGMEM = D_CMND "|" D_STAT "|" D_TELE; char* Format(char* output, const char* input, int size) { char *token; uint32_t digits = 0; if (strstr(input, "%") != nullptr) { strlcpy(output, input, size); token = strtok(output, "%"); if (strstr(input, "%") == input) { output[0] = '\0'; } else { token = strtok(nullptr, ""); } if (token != nullptr) { digits = atoi(token); if (digits) { char tmp[size]; if (strchr(token, 'd')) { snprintf_P(tmp, size, PSTR("%s%c0%dd"), output, '%', digits); snprintf_P(output, size, tmp, ESP.getChipId() & 0x1fff); } else { snprintf_P(tmp, size, PSTR("%s%c0%dX"), output, '%', digits); snprintf_P(output, size, tmp, ESP.getChipId()); } } else { if (strchr(token, 'd')) { snprintf_P(output, size, PSTR("%s%d"), output, ESP.getChipId()); digits = 8; } } } } if (!digits) { strlcpy(output, input, size); } return output; } char* GetOtaUrl(char *otaurl, size_t otaurl_size) { if (strstr(SettingsText(SET_OTAURL), "%04d") != nullptr) { snprintf(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId() & 0x1fff); } else if (strstr(SettingsText(SET_OTAURL), "%d") != nullptr) { snprintf_P(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId()); } else { strlcpy(otaurl, SettingsText(SET_OTAURL), otaurl_size); } return otaurl; } char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic) { # 88 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" char romram[CMDSZ]; String fulltopic; snprintf_P(romram, sizeof(romram), subtopic); if (fallback_topic_flag || (prefix > 3)) { bool fallback = (prefix < 8); prefix &= 3; char stemp[11]; fulltopic = GetTextIndexed(stemp, sizeof(stemp), prefix, kPrefixes); fulltopic += F("/"); if (fallback) { fulltopic += mqtt_client; fulltopic += F("_fb"); } else { fulltopic += topic; } } else { fulltopic = SettingsText(SET_MQTT_FULLTOPIC); if ((0 == prefix) && (-1 == fulltopic.indexOf(FPSTR(MQTT_TOKEN_PREFIX)))) { fulltopic += F("/"); fulltopic += FPSTR(MQTT_TOKEN_PREFIX); } for (uint32_t i = 0; i < MAX_MQTT_PREFIXES; i++) { if (!strlen(SettingsText(SET_MQTTPREFIX1 + i))) { char temp[TOPSZ]; SettingsUpdateText(SET_MQTTPREFIX1 + i, GetTextIndexed(temp, sizeof(temp), i, kPrefixes)); } } fulltopic.replace(FPSTR(MQTT_TOKEN_PREFIX), SettingsText(SET_MQTTPREFIX1 + prefix)); fulltopic.replace(FPSTR(MQTT_TOKEN_TOPIC), topic); fulltopic.replace(F("%hostname%"), my_hostname); String token_id = WiFi.macAddress(); token_id.replace(":", ""); fulltopic.replace(F("%id%"), token_id); } fulltopic.replace(F("#"), ""); fulltopic.replace(F("//"), "/"); if (!fulltopic.endsWith("/")) { fulltopic += "/"; } snprintf_P(stopic, TOPSZ, PSTR("%s%s"), fulltopic.c_str(), romram); return stopic; } char* GetGroupTopic_P(char *stopic, const char* subtopic) { return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, SettingsText(SET_MQTT_GRP_TOPIC), subtopic); } char* GetFallbackTopic_P(char *stopic, const char* subtopic) { return GetTopic_P(stopic, CMND +4, nullptr, subtopic); } char* GetStateText(uint32_t state) { if (state >= MAX_STATE_TEXT) { state = 1; } return SettingsText(SET_STATE_TXT1 + state); } void SetLatchingRelay(power_t lpower, uint32_t state) { if (state && !latching_relay_pulse) { latching_power = lpower; latching_relay_pulse = 2; } for (uint32_t i = 0; i < devices_present; i++) { uint32_t port = (i << 1) + ((latching_power >> i) &1); DigitalWrite(GPIO_REL1 +port, bitRead(rel_inverted, port) ? !state : state); } } void SetDevicePower(power_t rpower, uint32_t source) { ShowSource(source); last_source = source; if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) { power = (1 << devices_present) -1; rpower = power; } if (Settings.flag.interlock) { for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { power_t mask = 1; uint32_t count = 0; for (uint32_t j = 0; j < devices_present; j++) { if ((Settings.interlock[i] & mask) && (rpower & mask)) { count++; } mask <<= 1; } if (count > 1) { mask = ~Settings.interlock[i]; power &= mask; rpower &= mask; } } } if (rpower) { last_power = rpower; } XdrvMailbox.index = rpower; XdrvCall(FUNC_SET_POWER); XdrvMailbox.index = rpower; XdrvMailbox.payload = source; if (XdrvCall(FUNC_SET_DEVICE_POWER)) { } else if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { Serial.write(0xA0); Serial.write(0x04); Serial.write(rpower &0xFF); Serial.write(0xA1); Serial.write('\n'); Serial.flush(); } else if (EXS_RELAY == my_module_type) { SetLatchingRelay(rpower, 1); } else { for (uint32_t i = 0; i < devices_present; i++) { power_t state = rpower &1; if (i < MAX_RELAYS) { DigitalWrite(GPIO_REL1 +i, bitRead(rel_inverted, i) ? !state : state); } rpower >>= 1; } } } void RestorePower(bool publish_power, uint32_t source) { if (power != last_power) { power = last_power; SetDevicePower(power, source); if (publish_power) { MqttPublishAllPowerState(); } } } void SetAllPower(uint32_t state, uint32_t source) { # 256 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" bool publish_power = true; if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) { state &= 3; publish_power = false; } if ((state >= POWER_OFF) && (state <= POWER_TOGGLE)) { power_t all_on = (1 << devices_present) -1; switch (state) { case POWER_OFF: power = 0; break; case POWER_ON: power = all_on; break; case POWER_TOGGLE: power ^= all_on; } SetDevicePower(power, source); } if (publish_power) { MqttPublishAllPowerState(); } } void SetPowerOnState(void) { if (MOTOR == my_module_type) { Settings.poweronstate = POWER_ALL_ON; } if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) { SetDevicePower(1, SRC_RESTART); } else { if ((ResetReason() == REASON_DEFAULT_RST) || (ResetReason() == REASON_EXT_SYS_RST)) { switch (Settings.poweronstate) { case POWER_ALL_OFF: case POWER_ALL_OFF_PULSETIME_ON: power = 0; SetDevicePower(power, SRC_RESTART); break; case POWER_ALL_ON: power = (1 << devices_present) -1; SetDevicePower(power, SRC_RESTART); break; case POWER_ALL_SAVED_TOGGLE: power = (Settings.power & ((1 << devices_present) -1)) ^ POWER_MASK; if (Settings.flag.save_state) { SetDevicePower(power, SRC_RESTART); } break; case POWER_ALL_SAVED: power = Settings.power & ((1 << devices_present) -1); if (Settings.flag.save_state) { SetDevicePower(power, SRC_RESTART); } break; } } else { power = Settings.power & ((1 << devices_present) -1); if (Settings.flag.save_state) { SetDevicePower(power, SRC_RESTART); } } } for (uint32_t i = 0; i < devices_present; i++) { if (!Settings.flag3.no_power_feedback) { if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) { bitWrite(power, i, digitalRead(pin[GPIO_REL1 +i]) ^ bitRead(rel_inverted, i)); } } if ((i < MAX_PULSETIMERS) && (bitRead(power, i) || (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate))) { SetPulseTimer(i, Settings.pulse_timer[i]); } } blink_powersave = power; } void SetLedPowerIdx(uint32_t led, uint32_t state) { if ((99 == pin[GPIO_LEDLNK]) && (0 == led)) { if (pin[GPIO_LED2] < 99) { led = 1; } } if (pin[GPIO_LED1 + led] < 99) { uint32_t mask = 1 << led; if (state) { state = 1; led_power |= mask; } else { led_power &= (0xFF ^ mask); } DigitalWrite(GPIO_LED1 + led, bitRead(led_inverted, led) ? !state : state); } #ifdef USE_BUZZER if (led == 0) { BuzzerSetStateToLed(state); } #endif } void SetLedPower(uint32_t state) { if (99 == pin[GPIO_LEDLNK]) { SetLedPowerIdx(0, state); } else { power_t mask = 1; for (uint32_t i = 0; i < leds_present; i++) { bool tstate = (power & mask); SetLedPowerIdx(i, tstate); mask <<= 1; } } } void SetLedPowerAll(uint32_t state) { for (uint32_t i = 0; i < leds_present; i++) { SetLedPowerIdx(i, state); } } void SetLedLink(uint32_t state) { uint32_t led_pin = pin[GPIO_LEDLNK]; uint32_t led_inv = ledlnk_inverted; if (99 == led_pin) { led_pin = pin[GPIO_LED1]; led_inv = bitRead(led_inverted, 0); } if (led_pin < 99) { if (state) { state = 1; } digitalWrite(led_pin, (led_inv) ? !state : state); } #ifdef USE_BUZZER BuzzerSetStateToLed(state); #endif } void SetPulseTimer(uint32_t index, uint32_t time) { pulse_timer[index] = (time > 111) ? millis() + (1000 * (time - 100)) : (time > 0) ? millis() + (100 * time) : 0L; } uint32_t GetPulseTimer(uint32_t index) { long time = TimePassedSince(pulse_timer[index]); if (time < 0) { time *= -1; return (time > 11100) ? (time / 1000) + 100 : (time > 0) ? time / 100 : 0; } return 0; } bool SendKey(uint32_t key, uint32_t device, uint32_t state) { # 423 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" char stopic[TOPSZ]; char scommand[CMDSZ]; char key_topic[TOPSZ]; bool result = false; char *tmp = (key) ? SettingsText(SET_MQTT_SWITCH_TOPIC) : SettingsText(SET_MQTT_BUTTON_TOPIC); Format(key_topic, tmp, sizeof(key_topic)); if (Settings.flag.mqtt_enabled && MqttIsConnected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) { if (!key && (device > devices_present)) { device = 1; } GetTopic_P(stopic, CMND, key_topic, GetPowerDevice(scommand, device, sizeof(scommand), (key + Settings.flag.device_index_enable))); if (CLEAR_RETAIN == state) { mqtt_data[0] = '\0'; } else { if ((Settings.flag3.button_switch_force_local || !strcmp(mqtt_topic, key_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic)) && (POWER_TOGGLE == state)) { state = ~(power >> (device -1)) &1; } snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(state)); } #ifdef USE_DOMOTICZ if (!(DomoticzSendKey(key, device, state, strlen(mqtt_data)))) { #endif MqttPublish(stopic, ((key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain) && (state != POWER_HOLD || !Settings.flag3.no_hold_retain)); #ifdef USE_DOMOTICZ } #endif result = !Settings.flag3.button_switch_force_local; } else { Response_P(PSTR("{\"%s%d\":{\"State\":%d}}"), (key) ? "Switch" : "Button", device, state); result = XdrvRulesProcess(); } int32_t payload_save = XdrvMailbox.payload; XdrvMailbox.payload = key << 16 | state << 8 | device; XdrvCall(FUNC_ANY_KEY); XdrvMailbox.payload = payload_save; return result; } void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source) { # 483 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" #ifdef USE_SONOFF_IFAN if (IsModuleIfan()) { blink_mask &= 1; Settings.flag.interlock = 0; Settings.pulse_timer[1] = 0; Settings.pulse_timer[2] = 0; Settings.pulse_timer[3] = 0; } #endif bool publish_power = true; if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) { state &= 3; publish_power = false; } if ((device < 1) || (device > devices_present)) { device = 1; } active_device = device; if (device <= MAX_PULSETIMERS) { SetPulseTimer(device -1, 0); } power_t mask = 1 << (device -1); if (state <= POWER_TOGGLE) { if ((blink_mask & mask)) { blink_mask &= (POWER_MASK ^ mask); MqttPublishPowerBlinkState(device); } if (Settings.flag.interlock && !interlock_mutex && ((POWER_ON == state) || ((POWER_TOGGLE == state) && !(power & mask))) ) { interlock_mutex = true; for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { if (Settings.interlock[i] & mask) { for (uint32_t j = 0; j < devices_present; j++) { power_t imask = 1 << j; if ((Settings.interlock[i] & imask) && (power & imask) && (mask != imask)) { ExecuteCommandPower(j +1, POWER_OFF, SRC_IGNORE); delay(50); } } break; } } interlock_mutex = false; } switch (state) { case POWER_OFF: { power &= (POWER_MASK ^ mask); break; } case POWER_ON: power |= mask; break; case POWER_TOGGLE: power ^= mask; } SetDevicePower(power, source); #ifdef USE_DOMOTICZ DomoticzUpdatePowerState(device); #endif #ifdef USE_KNX KnxUpdatePowerState(device, power); #endif if (publish_power && Settings.flag3.hass_tele_on_power) { MqttPublishTeleState(); } if (device <= MAX_PULSETIMERS) { SetPulseTimer(device -1, (((POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? ~power : power) & mask) ? Settings.pulse_timer[device -1] : 0); } } else if (POWER_BLINK == state) { if (!(blink_mask & mask)) { blink_powersave = (blink_powersave & (POWER_MASK ^ mask)) | (power & mask); blink_power = (power >> (device -1))&1; } blink_timer = millis() + 100; blink_counter = ((!Settings.blinkcount) ? 64000 : (Settings.blinkcount *2)) +1; blink_mask |= mask; MqttPublishPowerBlinkState(device); return; } else if (POWER_BLINK_STOP == state) { bool flag = (blink_mask & mask); blink_mask &= (POWER_MASK ^ mask); MqttPublishPowerBlinkState(device); if (flag) { ExecuteCommandPower(device, (blink_powersave >> (device -1))&1, SRC_IGNORE); } return; } if (publish_power) { MqttPublishPowerState(device); } } void StopAllPowerBlink(void) { power_t mask; for (uint32_t i = 1; i <= devices_present; i++) { mask = 1 << (i -1); if (blink_mask & mask) { blink_mask &= (POWER_MASK ^ mask); MqttPublishPowerBlinkState(i); ExecuteCommandPower(i, (blink_powersave >> (i -1))&1, SRC_IGNORE); } } } void MqttShowPWMState(void) { ResponseAppend_P(PSTR("\"" D_CMND_PWM "\":{")); bool first = true; for (uint32_t i = 0; i < MAX_PWMS; i++) { if (pin[GPIO_PWM1 + i] < 99) { ResponseAppend_P(PSTR("%s\"" D_CMND_PWM "%d\":%d"), first ? "" : ",", i+1, Settings.pwm_value[i]); first = false; } } ResponseJsonEnd(); } void MqttShowState(void) { char stemp1[TOPSZ]; ResponseAppendTime(); ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime()); #ifdef USE_ADC_VCC dtostrfd((double)ESP.getVcc()/1000, 3, stemp1); ResponseAppend_P(PSTR(",\"" D_JSON_VCC "\":%s"), stemp1); #endif ResponseAppend_P(PSTR(",\"" D_JSON_HEAPSIZE "\":%d,\"SleepMode\":\"%s\",\"Sleep\":%u,\"LoadAvg\":%u,\"MqttCount\":%u"), ESP.getFreeHeap()/1024, GetTextIndexed(stemp1, sizeof(stemp1), Settings.flag3.sleep_normal, kSleepMode), sleep, loop_load_avg, MqttConnectCount()); for (uint32_t i = 1; i <= devices_present; i++) { #ifdef USE_LIGHT if ((LightDevice()) && (i >= LightDevice())) { if (i == LightDevice()) { LightState(1); } } else { #endif ResponseAppend_P(PSTR(",\"%s\":\"%s\""), GetPowerDevice(stemp1, i, sizeof(stemp1), Settings.flag.device_index_enable), GetStateText(bitRead(power, i-1))); #ifdef USE_SONOFF_IFAN if (IsModuleIfan()) { ResponseAppend_P(PSTR(",\"" D_CMND_FANSPEED "\":%d"), GetFanspeed()); break; } #endif #ifdef USE_LIGHT } #endif } if (pwm_present) { ResponseAppend_P(PSTR(",")); MqttShowPWMState(); } ResponseAppend_P(PSTR(",\"" D_JSON_WIFI "\":{\"" D_JSON_AP "\":%d,\"" D_JSON_SSID "\":\"%s\",\"" D_JSON_BSSID "\":\"%s\",\"" D_JSON_CHANNEL "\":%d,\"" D_JSON_RSSI "\":%d,\"" D_JSON_SIGNAL "\":%d,\"" D_JSON_LINK_COUNT "\":%d,\"" D_JSON_DOWNTIME "\":\"%s\"}}"), Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WiFi.BSSIDstr().c_str(), WiFi.channel(), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI(), WifiLinkCount(), WifiDowntime().c_str()); } void MqttPublishTeleState(void) { mqtt_data[0] = '\0'; MqttShowState(); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN); #if defined(USE_RULES) || defined(USE_SCRIPT) RulesTeleperiod(); #endif } bool MqttShowSensor(void) { ResponseAppendTime(); int json_data_start = strlen(mqtt_data); for (uint32_t i = 0; i < MAX_SWITCHES; i++) { #ifdef USE_TM1638 if ((pin[GPIO_SWT1 +i] < 99) || ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99))) { #else if (pin[GPIO_SWT1 +i] < 99) { #endif ResponseAppend_P(PSTR(",\"" D_JSON_SWITCH "%d\":\"%s\""), i +1, GetStateText(SwitchState(i))); } } XsnsCall(FUNC_JSON_APPEND); XdrvCall(FUNC_JSON_APPEND); bool json_data_available = (strlen(mqtt_data) - json_data_start); if (strstr_P(mqtt_data, PSTR(D_JSON_PRESSURE)) != nullptr) { ResponseAppend_P(PSTR(",\"" D_JSON_PRESSURE_UNIT "\":\"%s\""), PressureUnit().c_str()); } if (strstr_P(mqtt_data, PSTR(D_JSON_TEMPERATURE)) != nullptr) { ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE_UNIT "\":\"%c\""), TempUnit()); } ResponseJsonEnd(); if (json_data_available) { XdrvCall(FUNC_SHOW_SENSOR); } return json_data_available; } void MqttPublishSensor(void) { mqtt_data[0] = '\0'; if (MqttShowSensor()) { MqttPublishTeleSensor(); } } # 710 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" void PerformEverySecond(void) { uptime++; if (POWER_CYCLE_TIME == uptime) { UpdateQuickPowerCycle(false); } if (BOOT_LOOP_TIME == uptime) { RtcRebootReset(); #ifdef USE_DEEPSLEEP if (!(DeepSleepEnabled() && !Settings.flag3.bootcount_update)) { #endif Settings.bootcount++; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BOOT_COUNT " %d"), Settings.bootcount); #ifdef USE_DEEPSLEEP } #endif } if (mqtt_cmnd_blocked_reset) { mqtt_cmnd_blocked_reset--; if (!mqtt_cmnd_blocked_reset) { mqtt_cmnd_blocked = 0; } } if (seriallog_timer) { seriallog_timer--; if (!seriallog_timer) { if (seriallog_level) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SERIAL_LOGGING_DISABLED)); } seriallog_level = 0; } } if (syslog_timer) { syslog_timer--; if (!syslog_timer) { syslog_level = Settings.syslog_level; if (Settings.syslog_level) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_LOGGING_REENABLED)); } } } ResetGlobalValues(); if (Settings.tele_period) { if (tele_period >= 9999) { if (!global_state.wifi_down) { tele_period = 0; } } else { tele_period++; if (tele_period >= Settings.tele_period) { tele_period = 0; MqttPublishTeleState(); mqtt_data[0] = '\0'; if (MqttShowSensor()) { MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); #if defined(USE_RULES) || defined(USE_SCRIPT) RulesTeleperiod(); #endif } XdrvCall(FUNC_AFTER_TELEPERIOD); } } } } void Every100mSeconds(void) { power_t power_now; if (prepped_loglevel) { AddLog(prepped_loglevel); prepped_loglevel = 0; } if (latching_relay_pulse) { latching_relay_pulse--; if (!latching_relay_pulse) SetLatchingRelay(0, 0); } for (uint32_t i = 0; i < MAX_PULSETIMERS; i++) { if (pulse_timer[i] != 0L) { if (TimeReached(pulse_timer[i])) { pulse_timer[i] = 0L; ExecuteCommandPower(i +1, (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? POWER_ON : POWER_OFF, SRC_PULSETIMER); } } } if (blink_mask) { if (TimeReached(blink_timer)) { SetNextTimeInterval(blink_timer, 100 * Settings.blinktime); blink_counter--; if (!blink_counter) { StopAllPowerBlink(); } else { blink_power ^= 1; power_now = (power & (POWER_MASK ^ blink_mask)) | ((blink_power) ? blink_mask : 0); SetDevicePower(power_now, SRC_IGNORE); } } } } void Every250mSeconds(void) { uint32_t blinkinterval = 1; state_250mS++; state_250mS &= 0x3; if (!Settings.flag.global_state) { if (global_state.data) { if (global_state.mqtt_down) { blinkinterval = 7; } if (global_state.wifi_down) { blinkinterval = 3; } blinks = 201; } } if (blinks || restart_flag || ota_state_flag) { if (restart_flag || ota_state_flag) { blinkstate = true; } else { blinkspeed--; if (!blinkspeed) { blinkspeed = blinkinterval; blinkstate ^= 1; } } if ((!(Settings.ledstate &0x08)) && ((Settings.ledstate &0x06) || (blinks > 200) || (blinkstate))) { SetLedLink(blinkstate); } if (!blinkstate) { blinks--; if (200 == blinks) blinks = 0; } } if (Settings.ledstate &1 && (pin[GPIO_LEDLNK] < 99 || !(blinks || restart_flag || ota_state_flag)) ) { bool tstate = power & Settings.ledmask; if ((SONOFF_TOUCH == my_module_type) || (SONOFF_T11 == my_module_type) || (SONOFF_T12 == my_module_type) || (SONOFF_T13 == my_module_type)) { tstate = (!power) ? 1 : 0; } SetLedPower(tstate); } switch (state_250mS) { case 0: if (ota_state_flag && BACKLOG_EMPTY) { ota_state_flag--; if (2 == ota_state_flag) { RtcSettings.ota_loader = 0; ota_retry_counter = OTA_ATTEMPTS; ESPhttpUpdate.rebootOnUpdate(false); SettingsSave(1); } if (ota_state_flag <= 0) { #ifdef USE_WEBSERVER if (Settings.webserver) StopWebserver(); #endif #ifdef USE_ARILUX_RF AriluxRfDisable(); #endif ota_state_flag = 92; ota_result = 0; ota_retry_counter--; if (ota_retry_counter) { strlcpy(mqtt_data, GetOtaUrl(log_data, sizeof(log_data)), sizeof(mqtt_data)); #ifndef FIRMWARE_MINIMAL if (RtcSettings.ota_loader) { # 915 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino" char *bch = strrchr(mqtt_data, '/'); if (bch == nullptr) { bch = mqtt_data; } char *ech = strrchr(bch, '.'); if ((ech != nullptr) && (0 == strncasecmp_P(ech, PSTR(".GZ"), 3))) { char *fch = ech; *fch = '\0'; ech = strrchr(bch, '.'); *fch = '.'; } if (ech == nullptr) { ech = mqtt_data + strlen(mqtt_data); } char ota_url_type[strlen(ech) +1]; strncpy(ota_url_type, ech, sizeof(ota_url_type)); char *pch = strrchr(bch, '-'); if (pch == nullptr) { pch = ech; } *pch = '\0'; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s-" D_JSON_MINIMAL "%s"), mqtt_data, ota_url_type); } #endif AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "%s"), mqtt_data); #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(mqtt_data)); #else WiFiClient OTAclient; ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(OTAclient, mqtt_data)); #endif if (!ota_result) { #ifndef FIRMWARE_MINIMAL int ota_error = ESPhttpUpdate.getLastError(); DEBUG_CORE_LOG(PSTR("OTA: Error %d"), ota_error); if ((HTTP_UE_TOO_LESS_SPACE == ota_error) || (HTTP_UE_BIN_FOR_WRONG_FLASH == ota_error)) { RtcSettings.ota_loader = 1; } #endif ota_state_flag = 2; } } } if (90 == ota_state_flag) { ota_state_flag = 0; Response_P(PSTR("{\"" D_CMND_UPGRADE "\":\"")); if (ota_result) { if (!VersionCompatible()) { ResponseAppend_P(PSTR(D_JSON_FAILED " " D_UPLOAD_ERR_14)); } else { ResponseAppend_P(PSTR(D_JSON_SUCCESSFUL ". " D_JSON_RESTARTING)); restart_flag = 2; } } else { ResponseAppend_P(PSTR(D_JSON_FAILED " %s"), ESPhttpUpdate.getLastErrorString().c_str()); } ResponseAppend_P(PSTR("\"}")); MqttPublishPrefixTopic_P(STAT, PSTR(D_CMND_UPGRADE)); } } break; case 1: if (MidnightNow()) { XsnsCall(FUNC_SAVE_AT_MIDNIGHT); } if (save_data_counter && BACKLOG_EMPTY) { save_data_counter--; if (save_data_counter <= 0) { if (Settings.flag.save_state) { power_t mask = POWER_MASK; for (uint32_t i = 0; i < MAX_PULSETIMERS; i++) { if ((Settings.pulse_timer[i] > 0) && (Settings.pulse_timer[i] < 30)) { mask &= ~(1 << i); } } if (!((Settings.power &mask) == (power &mask))) { Settings.power = power; } } else { Settings.power = 0; } SettingsSave(0); save_data_counter = Settings.save_data; } } if (restart_flag && BACKLOG_EMPTY) { if ((214 == restart_flag) || (215 == restart_flag) || (216 == restart_flag)) { char storage_ssid1[strlen(SettingsText(SET_STASSID1)) +1]; strncpy(storage_ssid1, SettingsText(SET_STASSID1), sizeof(storage_ssid1)); char storage_ssid2[strlen(SettingsText(SET_STASSID2)) +1]; strncpy(storage_ssid2, SettingsText(SET_STASSID2), sizeof(storage_ssid2)); char storage_pass1[strlen(SettingsText(SET_STAPWD1)) +1]; strncpy(storage_pass1, SettingsText(SET_STAPWD1), sizeof(storage_pass1)); char storage_pass2[strlen(SettingsText(SET_STAPWD2)) +1]; strncpy(storage_pass2, SettingsText(SET_STAPWD2), sizeof(storage_pass2)); char storage_mqtthost[strlen(SettingsText(SET_MQTT_HOST)) +1]; strncpy(storage_mqtthost, SettingsText(SET_MQTT_HOST), sizeof(storage_mqtthost)); char storage_mqttuser[strlen(SettingsText(SET_MQTT_USER)) +1]; strncpy(storage_mqttuser, SettingsText(SET_MQTT_USER), sizeof(storage_mqttuser)); char storage_mqttpwd[strlen(SettingsText(SET_MQTT_PWD)) +1]; strncpy(storage_mqttpwd, SettingsText(SET_MQTT_PWD), sizeof(storage_mqttpwd)); char storage_mqtttopic[strlen(SettingsText(SET_MQTT_TOPIC)) +1]; strncpy(storage_mqtttopic, SettingsText(SET_MQTT_TOPIC), sizeof(storage_mqtttopic)); uint16_t mqtt_port = Settings.mqtt_port; if ((215 == restart_flag) || (216 == restart_flag)) { SettingsErase(0); } SettingsDefault(); SettingsUpdateText(SET_STASSID1, storage_ssid1); SettingsUpdateText(SET_STASSID2, storage_ssid2); SettingsUpdateText(SET_STAPWD1, storage_pass1); SettingsUpdateText(SET_STAPWD2, storage_pass2); if (216 == restart_flag) { SettingsUpdateText(SET_MQTT_HOST, storage_mqtthost); SettingsUpdateText(SET_MQTT_USER, storage_mqttuser); SettingsUpdateText(SET_MQTT_PWD, storage_mqttpwd); SettingsUpdateText(SET_MQTT_TOPIC, storage_mqtttopic); Settings.mqtt_port = mqtt_port; } restart_flag = 2; } else if (213 == restart_flag) { SettingsSdkErase(); restart_flag = 2; } else if (212 == restart_flag) { SettingsErase(0); restart_flag = 211; } if (211 == restart_flag) { SettingsDefault(); restart_flag = 2; } if (2 == restart_flag) { SettingsSaveAll(); } restart_flag--; if (restart_flag <= 0) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); EspRestart(); } } break; case 2: WifiCheck(wifi_state_flag); wifi_state_flag = WIFI_RESTART; break; case 3: if (!global_state.wifi_down) { MqttCheck(); } break; } } #ifdef USE_ARDUINO_OTA bool arduino_ota_triggered = false; uint16_t arduino_ota_progress_dot_count = 0; void ArduinoOTAInit(void) { ArduinoOTA.setPort(8266); ArduinoOTA.setHostname(my_hostname); if (strlen(SettingsText(SET_WEBPWD))) { ArduinoOTA.setPassword(SettingsText(SET_WEBPWD)); } ArduinoOTA.onStart([]() { SettingsSave(1); #ifdef USE_WEBSERVER if (Settings.webserver) { StopWebserver(); } #endif #ifdef USE_ARILUX_RF AriluxRfDisable(); #endif if (Settings.flag.mqtt_enabled) { MqttDisconnect(); } AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_UPLOAD_STARTED)); arduino_ota_triggered = true; arduino_ota_progress_dot_count = 0; delay(100); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { if ((LOG_LEVEL_DEBUG <= seriallog_level)) { arduino_ota_progress_dot_count++; Serial.printf("."); if (!(arduino_ota_progress_dot_count % 80)) { Serial.println(); } } }); ArduinoOTA.onError([](ota_error_t error) { char error_str[100]; if ((LOG_LEVEL_DEBUG <= seriallog_level) && arduino_ota_progress_dot_count) { Serial.println(); } switch (error) { case OTA_BEGIN_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_2), sizeof(error_str)); break; case OTA_RECEIVE_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_5), sizeof(error_str)); break; case OTA_END_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_7), sizeof(error_str)); break; default: snprintf_P(error_str, sizeof(error_str), PSTR(D_UPLOAD_ERROR_CODE " %d"), error); } AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA %s. " D_RESTARTING), error_str); EspRestart(); }); ArduinoOTA.onEnd([]() { if ((LOG_LEVEL_DEBUG <= seriallog_level)) { Serial.println(); } AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_SUCCESSFUL ". " D_RESTARTING)); EspRestart(); }); ArduinoOTA.begin(); AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_ENABLED " " D_PORT " 8266")); } void ArduinoOtaLoop(void) { MDNS.update(); ArduinoOTA.handle(); while (arduino_ota_triggered) { ArduinoOTA.handle(); } } #endif void SerialInput(void) { while (Serial.available()) { delay(0); serial_in_byte = Serial.read(); if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { serial_in_byte = ButtonSerial(serial_in_byte); } if (XdrvCall(FUNC_SERIAL)) { serial_in_byte_counter = 0; Serial.flush(); return; } if (serial_in_byte > 127 && !Settings.flag.mqtt_serial_raw) { serial_in_byte_counter = 0; Serial.flush(); return; } if (!Settings.flag.mqtt_serial) { if (isprint(serial_in_byte)) { if (serial_in_byte_counter < INPUT_BUFFER_SIZE -1) { serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; } else { serial_in_byte_counter = 0; } } } else { if (serial_in_byte || Settings.flag.mqtt_serial_raw) { if ((serial_in_byte_counter < INPUT_BUFFER_SIZE -1) && ((isprint(serial_in_byte) && (128 == Settings.serial_delimiter)) || ((serial_in_byte != Settings.serial_delimiter) && (128 != Settings.serial_delimiter)) || Settings.flag.mqtt_serial_raw)) { serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; serial_polling_window = millis(); } else { serial_polling_window = 0; break; } } } #ifdef USE_SONOFF_SC if (SONOFF_SC == my_module_type) { if (serial_in_byte == '\x1B') { serial_in_buffer[serial_in_byte_counter] = 0; SonoffScSerialInput(serial_in_buffer); serial_in_byte_counter = 0; Serial.flush(); return; } } else #endif if (!Settings.flag.mqtt_serial && (serial_in_byte == '\n')) { serial_in_buffer[serial_in_byte_counter] = 0; seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? (uint8_t)LOG_LEVEL_INFO : Settings.seriallog_level; AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer); ExecuteCommand(serial_in_buffer, SRC_SERIAL); serial_in_byte_counter = 0; serial_polling_window = 0; Serial.flush(); return; } } if (Settings.flag.mqtt_serial && serial_in_byte_counter && (millis() > (serial_polling_window + SERIAL_POLLING))) { serial_in_buffer[serial_in_byte_counter] = 0; char hex_char[(serial_in_byte_counter * 2) + 2]; bool assume_json = (!Settings.flag.mqtt_serial_raw && (serial_in_buffer[0] == '{')); Response_P(PSTR("{\"" D_JSON_SERIALRECEIVED "\":%s%s%s}"), (assume_json) ? "" : """", (Settings.flag.mqtt_serial_raw) ? ToHex_P((unsigned char*)serial_in_buffer, serial_in_byte_counter, hex_char, sizeof(hex_char)) : serial_in_buffer, (assume_json) ? "" : """"); MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SERIALRECEIVED)); XdrvRulesProcess(); serial_in_byte_counter = 0; } } void GpioInit(void) { uint32_t mpin; if (!ValidModule(Settings.module)) { uint32_t module = MODULE; if (!ValidModule(MODULE)) { module = SONOFF_BASIC; } Settings.module = module; Settings.last_module = module; } SetModuleType(); if (Settings.module != Settings.last_module) { Settings.baudrate = APP_BAUDRATE / 300; Settings.serial_config = TS_SERIAL_8N1; } for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) { if ((Settings.user_template.gp.io[i] >= GPIO_SENSOR_END) && (Settings.user_template.gp.io[i] < GPIO_USER)) { Settings.user_template.gp.io[i] = GPIO_USER; } } myio def_gp; ModuleGpios(&def_gp); for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { if ((Settings.my_gp.io[i] >= GPIO_SENSOR_END) && (Settings.my_gp.io[i] < GPIO_USER)) { Settings.my_gp.io[i] = GPIO_NONE; } else if (Settings.my_gp.io[i] > GPIO_NONE) { my_module.io[i] = Settings.my_gp.io[i]; } if ((def_gp.io[i] > GPIO_NONE) && (def_gp.io[i] < GPIO_USER)) { my_module.io[i] = def_gp.io[i]; } } if ((Settings.my_adc0 >= ADC0_END) && (Settings.my_adc0 < ADC0_USER)) { Settings.my_adc0 = ADC0_NONE; } else if (Settings.my_adc0 > ADC0_NONE) { my_adc0 = Settings.my_adc0; } my_module_flag = ModuleFlag(); uint32_t template_adc0 = my_module_flag.data &15; if ((template_adc0 > ADC0_NONE) && (template_adc0 < ADC0_USER)) { my_adc0 = template_adc0; } for (uint32_t i = 0; i < GPIO_MAX; i++) { pin[i] = 99; } for (uint32_t i = 0; i < sizeof(my_module.io); i++) { mpin = ValidPin(i, my_module.io[i]); DEBUG_CORE_LOG(PSTR("INI: gpio pin %d, mpin %d"), i, mpin); if (mpin) { XdrvMailbox.index = mpin; XdrvMailbox.payload = i; if ((mpin >= GPIO_SWT1_NP) && (mpin < (GPIO_SWT1_NP + MAX_SWITCHES))) { SwitchPullupFlag(mpin - GPIO_SWT1_NP); mpin -= (GPIO_SWT1_NP - GPIO_SWT1); } else if ((mpin >= GPIO_KEY1_NP) && (mpin < (GPIO_KEY1_NP + MAX_KEYS))) { ButtonPullupFlag(mpin - GPIO_KEY1_NP); mpin -= (GPIO_KEY1_NP - GPIO_KEY1); } else if ((mpin >= GPIO_KEY1_INV) && (mpin < (GPIO_KEY1_INV + MAX_KEYS))) { ButtonInvertFlag(mpin - GPIO_KEY1_INV); mpin -= (GPIO_KEY1_INV - GPIO_KEY1); } else if ((mpin >= GPIO_KEY1_INV_NP) && (mpin < (GPIO_KEY1_INV_NP + MAX_KEYS))) { ButtonPullupFlag(mpin - GPIO_KEY1_INV_NP); ButtonInvertFlag(mpin - GPIO_KEY1_INV_NP); mpin -= (GPIO_KEY1_INV_NP - GPIO_KEY1); } else if ((mpin >= GPIO_REL1_INV) && (mpin < (GPIO_REL1_INV + MAX_RELAYS))) { bitSet(rel_inverted, mpin - GPIO_REL1_INV); mpin -= (GPIO_REL1_INV - GPIO_REL1); } else if ((mpin >= GPIO_LED1_INV) && (mpin < (GPIO_LED1_INV + MAX_LEDS))) { bitSet(led_inverted, mpin - GPIO_LED1_INV); mpin -= (GPIO_LED1_INV - GPIO_LED1); } else if (mpin == GPIO_LEDLNK_INV) { ledlnk_inverted = 1; mpin -= (GPIO_LEDLNK_INV - GPIO_LEDLNK); } else if ((mpin >= GPIO_PWM1_INV) && (mpin < (GPIO_PWM1_INV + MAX_PWMS))) { bitSet(pwm_inverted, mpin - GPIO_PWM1_INV); mpin -= (GPIO_PWM1_INV - GPIO_PWM1); } else if (XdrvCall(FUNC_PIN_STATE)) { mpin = XdrvMailbox.index; } else if (XsnsCall(FUNC_PIN_STATE)) { mpin = XdrvMailbox.index; }; } if (mpin) pin[mpin] = i; } if ((2 == pin[GPIO_TXD]) || (H801 == my_module_type)) { Serial.set_tx(2); } analogWriteRange(Settings.pwm_range); analogWriteFreq(Settings.pwm_frequency); #ifdef USE_SPI spi_flg = ((((pin[GPIO_SPI_CS] < 99) && (pin[GPIO_SPI_CS] > 14)) || (pin[GPIO_SPI_CS] < 12)) || (((pin[GPIO_SPI_DC] < 99) && (pin[GPIO_SPI_DC] > 14)) || (pin[GPIO_SPI_DC] < 12))); if (spi_flg) { for (uint32_t i = 0; i < GPIO_MAX; i++) { if ((pin[i] >= 12) && (pin[i] <=14)) pin[i] = 99; } my_module.io[12] = GPIO_SPI_MISO; pin[GPIO_SPI_MISO] = 12; my_module.io[13] = GPIO_SPI_MOSI; pin[GPIO_SPI_MOSI] = 13; my_module.io[14] = GPIO_SPI_CLK; pin[GPIO_SPI_CLK] = 14; } soft_spi_flg = ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && ((pin[GPIO_SSPI_MOSI] < 99) || (pin[GPIO_SSPI_MOSI] < 99))); #endif #ifdef USE_I2C i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99)); if (i2c_flg) { Wire.begin(pin[GPIO_I2C_SDA], pin[GPIO_I2C_SCL]); } #endif devices_present = 0; light_type = LT_BASIC; if (XdrvCall(FUNC_MODULE_INIT)) { } else if (YTF_IR_BRIDGE == my_module_type) { ClaimSerial(); } else if (SONOFF_DUAL == my_module_type) { devices_present = 2; SetSerial(19200, TS_SERIAL_8N1); } else if (CH4 == my_module_type) { devices_present = 4; SetSerial(19200, TS_SERIAL_8N1); } #ifdef USE_SONOFF_SC else if (SONOFF_SC == my_module_type) { SetSerial(19200, TS_SERIAL_8N1); } #endif for (uint32_t i = 0; i < MAX_PWMS; i++) { if (pin[GPIO_PWM1 +i] < 99) { pinMode(pin[GPIO_PWM1 +i], OUTPUT); if (light_type) { analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range : 0); } else { pwm_present = true; analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]); } } } for (uint32_t i = 0; i < MAX_RELAYS; i++) { if (pin[GPIO_REL1 +i] < 99) { pinMode(pin[GPIO_REL1 +i], OUTPUT); devices_present++; if (EXS_RELAY == my_module_type) { digitalWrite(pin[GPIO_REL1 +i], bitRead(rel_inverted, i) ? 1 : 0); if (i &1) { devices_present--; } } } } for (uint32_t i = 0; i < MAX_LEDS; i++) { if (pin[GPIO_LED1 +i] < 99) { #ifdef USE_ARILUX_RF if ((3 == i) && (leds_present < 2) && (99 == pin[GPIO_ARIRFSEL])) { pin[GPIO_ARIRFSEL] = pin[GPIO_LED4]; pin[GPIO_LED4] = 99; } else { #endif pinMode(pin[GPIO_LED1 +i], OUTPUT); leds_present++; digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i)); #ifdef USE_ARILUX_RF } #endif } } if (pin[GPIO_LEDLNK] < 99) { pinMode(pin[GPIO_LEDLNK], OUTPUT); digitalWrite(pin[GPIO_LEDLNK], ledlnk_inverted); } ButtonInit(); SwitchInit(); #ifdef ROTARY_V1 RotaryInit(); #endif SetLedPower(Settings.ledstate &8); SetLedLink(Settings.ledstate &8); XdrvCall(FUNC_PRE_INIT); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_udp.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_udp.ino" #ifdef USE_EMULATION #define UDP_BUFFER_SIZE 200 #define UDP_MSEARCH_SEND_DELAY 1500 #include Ticker TickerMSearch; IPAddress udp_remote_ip; uint16_t udp_remote_port; bool udp_connected = false; bool udp_response_mutex = false; const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**"; const char URN_BELKIN_DEVICE_CAP[] PROGMEM = "urn:Belkin:device:**"; const char UPNP_ROOTDEVICE[] PROGMEM = "upnp:rootdevice"; const char SSDPSEARCH_ALL[] PROGMEM = "ssdpsearch:all"; const char SSDP_ALL[] PROGMEM = "ssdp:all"; bool UdpDisconnect(void) { if (udp_connected) { PortUdp.flush(); WiFiUDP::stopAll(); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); udp_connected = false; } return udp_connected; } bool UdpConnect(void) { if (!udp_connected) { if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); udp_response_mutex = false; udp_connected = true; } else { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); udp_connected = false; } } return udp_connected; } void PollUdp(void) { if (udp_connected) { while (PortUdp.parsePacket()) { char packet_buffer[UDP_BUFFER_SIZE]; int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); packet_buffer[len] = 0; AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); #ifdef USE_SCRIPT_HUE if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { #else if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { #endif udp_response_mutex = true; udp_remote_ip = PortUdp.remoteIP(); udp_remote_port = PortUdp.remotePort(); uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); LowerCase(packet_buffer, packet_buffer); RemoveSpace(packet_buffer); #ifdef USE_EMULATION_WEMO if (EMUL_WEMO == Settings.flag2.emulation) { if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1); return; } else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2); return; } } #endif #ifdef USE_EMULATION_HUE if (EMUL_HUE == Settings.flag2.emulation) { if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) || (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { TickerMSearch.attach_ms(response_delay, HueRespondToMSearch); return; } } #endif udp_response_mutex = false; } } optimistic_yield(100); } } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino" # 24 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino" #ifndef WIFI_RSSI_THRESHOLD #define WIFI_RSSI_THRESHOLD 5 #endif #ifndef WIFI_RESCAN_MINUTES #define WIFI_RESCAN_MINUTES 5 #endif const uint8_t WIFI_CONFIG_SEC = 180; const uint8_t WIFI_CHECK_SEC = 5; const uint8_t WIFI_RETRY_OFFSET_SEC = 20; #include #if LWIP_IPV6 #include #endif struct WIFI { uint32_t last_event = 0; uint32_t downtime = 0; uint16_t link_count = 0; uint8_t counter; uint8_t retry_init; uint8_t retry; uint8_t status; uint8_t config_type = 0; uint8_t config_counter = 0; uint8_t mdns_begun = 0; uint8_t scan_state; uint8_t bssid[6] = {0}; uint8_t bssid_last[6] = {0}; int8_t best_network_db; } Wifi; int WifiGetRssiAsQuality(int rssi) { int quality = 0; if (rssi <= -100) { quality = 0; } else if (rssi >= -50) { quality = 100; } else { quality = 2 * (rssi + 100); } return quality; } bool WifiConfigCounter(void) { if (Wifi.config_counter) { Wifi.config_counter = WIFI_CONFIG_SEC; } return (Wifi.config_counter); } void WifiConfig(uint8_t type) { if (!Wifi.config_type) { if ((WIFI_RETRY == type) || (WIFI_WAIT == type)) { return; } #ifdef USE_EMULATION UdpDisconnect(); #endif WiFi.disconnect(); Wifi.config_type = type; #ifndef USE_WEBSERVER if (WIFI_MANAGER == Wifi.config_type) { Wifi.config_type = WIFI_SERIAL; } #endif Wifi.config_counter = WIFI_CONFIG_SEC; Wifi.counter = Wifi.config_counter +5; blinks = 1999; if (WIFI_RESTART == Wifi.config_type) { restart_flag = 2; } else if (WIFI_SERIAL == Wifi.config_type) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_6_SERIAL " " D_ACTIVE_FOR_3_MINUTES)); } #ifdef USE_WEBSERVER else if (WIFI_MANAGER == Wifi.config_type || WIFI_MANAGER_RESET_ONLY == Wifi.config_type) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_2_WIFIMANAGER " " D_ACTIVE_FOR_3_MINUTES)); WifiManagerBegin(WIFI_MANAGER_RESET_ONLY == Wifi.config_type); } #endif } } void WifiSetMode(WiFiMode_t wifi_mode) { if (WiFi.getMode() == wifi_mode) { return; } if (wifi_mode != WIFI_OFF) { WiFi.forceSleepWake(); delay(100); } uint32_t retry = 2; while (!WiFi.mode(wifi_mode) && retry--) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR("Retry set Mode...")); delay(100); } if (wifi_mode == WIFI_OFF) { delay(1000); WiFi.forceSleepBegin(); delay(1); } else { delay(30); } } void WiFiSetSleepMode(void) { # 156 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino" #if defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) #else if (sleep && Settings.flag3.sleep_normal) { WiFi.setSleepMode(WIFI_LIGHT_SLEEP); } else { WiFi.setSleepMode(WIFI_MODEM_SLEEP); } #endif WifiSetOutputPower(); } void WifiBegin(uint8_t flag, uint8_t channel) { const char kWifiPhyMode[] = " BGN"; #ifdef USE_EMULATION UdpDisconnect(); #endif #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_PATCH_ISSUE_2186)); WifiSetMode(WIFI_OFF); #endif WiFi.persistent(false); WiFi.disconnect(true); delay(200); WifiSetMode(WIFI_STA); WiFiSetSleepMode(); if (!WiFi.getAutoConnect()) { WiFi.setAutoConnect(true); } switch (flag) { case 0: case 1: Settings.sta_active = flag; break; case 2: Settings.sta_active ^= 1; } if (!strlen(SettingsText(SET_STASSID1 + Settings.sta_active))) { Settings.sta_active ^= 1; } if (Settings.ip_address[0]) { WiFi.config(Settings.ip_address[0], Settings.ip_address[1], Settings.ip_address[2], Settings.ip_address[3]); } WiFi.hostname(my_hostname); char stemp[40] = { 0 }; if (channel) { WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active), channel, Wifi.bssid); char hex_char[18]; snprintf_P(stemp, sizeof(stemp), PSTR(" Channel %d BSSId %s"), channel, ToHex_P((unsigned char*)Wifi.bssid, 6, hex_char, sizeof(hex_char), ':')); } else { WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active)); } AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECTING_TO_AP "%d %s%s " D_IN_MODE " 11%c " D_AS " %s..."), Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), stemp, kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname); #if LWIP_IPV6 for (bool configured = false; !configured;) { uint16_t cfgcnt = 0; for (auto addr : addrList) { if ((configured = !addr.isLocal() && addr.isV6()) || cfgcnt==30) { AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI "Got IPv6 global address %s"), addr.toString().c_str()); break; } delay(500); cfgcnt++; } } #endif } void WifiBeginAfterScan(void) { if (0 == Wifi.scan_state) { return; } if (1 == Wifi.scan_state) { memset((void*) &Wifi.bssid, 0, sizeof(Wifi.bssid)); Wifi.best_network_db = -127; Wifi.scan_state = 3; } if (2 == Wifi.scan_state) { uint8_t* bssid = WiFi.BSSID(); memcpy((void*) &Wifi.bssid, (void*) bssid, sizeof(Wifi.bssid)); Wifi.best_network_db = WiFi.RSSI(); if (Wifi.best_network_db < -WIFI_RSSI_THRESHOLD) { Wifi.best_network_db += WIFI_RSSI_THRESHOLD; } Wifi.scan_state = 3; } if (3 == Wifi.scan_state) { if (WiFi.scanComplete() != WIFI_SCAN_RUNNING) { WiFi.scanNetworks(true); Wifi.scan_state++; AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR("Network (re)scan started...")); return; } } int8_t wifi_scan_result = WiFi.scanComplete(); if (4 == Wifi.scan_state) { if (wifi_scan_result != WIFI_SCAN_RUNNING) { Wifi.scan_state++; } } if (5 == Wifi.scan_state) { uint32_t number_known = 0; int32_t channel_max = 0; int8_t ap_max = 3; uint8_t bssid_max[6]; memcpy((void*) &bssid_max, (void*) &Wifi.bssid, sizeof(bssid_max)); int32_t channel = 0; int8_t ap = 3; uint8_t last_bssid[6]; memcpy((void*) &last_bssid, (void*) &Wifi.bssid, sizeof(last_bssid)); if (wifi_scan_result > 0) { for (uint32_t i = 0; i < wifi_scan_result; ++i) { String ssid_scan; int32_t rssi_scan; uint8_t sec_scan; uint8_t* bssid_scan; int32_t chan_scan; bool hidden_scan; WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, bssid_scan, chan_scan, hidden_scan); bool known = false; uint32_t j; for (j = 0; j < MAX_SSIDS; j++) { if (ssid_scan == SettingsText(SET_STASSID1 + j)) { known = true; number_known++; if (rssi_scan > Wifi.best_network_db) { if (sec_scan == ENC_TYPE_NONE || SettingsText(SET_STAPWD1 + j)) { memcpy((void*) &bssid_max, (void*) bssid_scan, sizeof(bssid_max)); channel_max = chan_scan; ap_max = j; for (uint32_t i = 0; i < sizeof(Wifi.bssid_last); i++) { if (bssid_scan[i] != Wifi.bssid_last[i]) { Wifi.best_network_db = (int8_t)rssi_scan; channel = chan_scan; ap = j; memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid)); memcpy((void*) &Wifi.bssid_last, (void*) bssid_scan, sizeof(Wifi.bssid_last)); break; } } } } break; } } char hex_char[18]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "Network %d, AP%c, SSId %s, Channel %d, BSSId %s, RSSI %d, Encryption %d"), i, (known) ? (j) ? '2' : '1' : '-', ssid_scan.c_str(), chan_scan, ToHex_P((unsigned char*)bssid_scan, 6, hex_char, sizeof(hex_char), ':'), rssi_scan, (sec_scan == ENC_TYPE_NONE) ? 0 : 1); delay(0); } WiFi.scanDelete(); delay(0); } if (number_known == 1) { memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last)); memcpy((void*) &Wifi.bssid, (void*) bssid_max, sizeof(Wifi.bssid)); channel = channel_max; ap = ap_max; } Wifi.scan_state = 0; for (uint32_t i = 0; i < sizeof(Wifi.bssid); i++) { if (last_bssid[i] != Wifi.bssid[i]) { WifiBegin(ap, channel); break; } } } } uint16_t WifiLinkCount(void) { return Wifi.link_count; } String WifiDowntime(void) { return GetDuration(Wifi.downtime); } void WifiSetState(uint8_t state) { if (state == global_state.wifi_down) { if (state) { rules_flag.wifi_connected = 1; Wifi.link_count++; Wifi.downtime += UpTime() - Wifi.last_event; } else { rules_flag.wifi_disconnected = 1; Wifi.last_event = UpTime(); } } global_state.wifi_down = state ^1; } #if LWIP_IPV6 bool WifiCheckIPv6(void) { bool ipv6_global=false; for (auto a : addrList) { if(!a.isLocal() && a.isV6()) ipv6_global=true; } return ipv6_global; } String WifiGetIPv6(void) { for (auto a : addrList) { if(!a.isLocal() && a.isV6()) return a.toString(); } return ""; } bool WifiCheckIPAddrStatus(void) { bool ip_global=false; for (auto a : addrList) { if(!a.isLocal()) ip_global=true; } return ip_global; } #endif void WifiCheckIp(void) { #if LWIP_IPV6 if(WifiCheckIPAddrStatus()) { Wifi.status = WL_CONNECTED; #else if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0)) { #endif memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last)); WifiSetState(1); Wifi.counter = WIFI_CHECK_SEC; Wifi.retry = Wifi.retry_init; if (Wifi.status != WL_CONNECTED) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECTED)); } Wifi.status = WL_CONNECTED; #ifdef USE_DISCOVERY #ifdef WEBSERVER_ADVERTISE if (2 == Wifi.mdns_begun) { MDNS.update(); AddLog_P(LOG_LEVEL_DEBUG_MORE, D_LOG_MDNS, "MDNS.update"); } #endif #endif } else { WifiSetState(0); uint8_t wifi_config_tool = Settings.sta_config; Wifi.status = WiFi.status(); switch (Wifi.status) { case WL_CONNECTED: AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_NO_IP_ADDRESS)); wifi_station_dhcpc_start(); break; case WL_NO_SSID_AVAIL: AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_NOT_REACHED)); if (WIFI_WAIT == Settings.sta_config) { Wifi.retry = Wifi.retry_init; } else { if (Wifi.retry > (Wifi.retry_init / 2)) { Wifi.retry = Wifi.retry_init / 2; } else if (Wifi.retry) { Wifi.retry = 0; } } break; case WL_CONNECT_FAILED: AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_WRONG_PASSWORD)); if (Wifi.retry > (Wifi.retry_init / 2)) { Wifi.retry = Wifi.retry_init / 2; } else if (Wifi.retry) { Wifi.retry = 0; } break; default: if (!Wifi.retry || ((Wifi.retry_init / 2) == Wifi.retry)) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_TIMEOUT)); } else { if (!strlen(SettingsText(SET_STASSID1)) && !strlen(SettingsText(SET_STASSID2))) { wifi_config_tool = WIFI_MANAGER; Wifi.retry = 0; } else { AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_ATTEMPTING_CONNECTION)); } } } if (Wifi.retry) { if (Settings.flag3.use_wifi_scan) { if ((Wifi.retry_init == Wifi.retry) || ((Wifi.retry_init / 2) == Wifi.retry)){ Wifi.scan_state = 1; } } else { if (Wifi.retry_init == Wifi.retry) { WifiBegin(3, 0); } if ((Settings.sta_config != WIFI_WAIT) && ((Wifi.retry_init / 2) == Wifi.retry)) { WifiBegin(2, 0); } } Wifi.counter = 1; Wifi.retry--; } else { WifiConfig(wifi_config_tool); Wifi.counter = 1; Wifi.retry = Wifi.retry_init; } } } void WifiCheck(uint8_t param) { Wifi.counter--; switch (param) { case WIFI_SERIAL: case WIFI_MANAGER: WifiConfig(param); break; default: if (Wifi.config_counter) { Wifi.config_counter--; Wifi.counter = Wifi.config_counter +5; if (Wifi.config_counter) { if (!Wifi.config_counter) { if (strlen(WiFi.SSID().c_str())) { SettingsUpdateText(SET_STASSID1, WiFi.SSID().c_str()); } if (strlen(WiFi.psk().c_str())) { SettingsUpdateText(SET_STAPWD1, WiFi.psk().c_str()); } Settings.sta_active = 0; AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_WCFG_2_WIFIMANAGER D_CMND_SSID "1 %s"), SettingsText(SET_STASSID1)); } } if (!Wifi.config_counter) { restart_flag = 2; } } else { if (Wifi.scan_state) { WifiBeginAfterScan(); } if (Wifi.counter <= 0) { AddLog_P(LOG_LEVEL_DEBUG_MORE, S_LOG_WIFI, PSTR(D_CHECKING_CONNECTION)); Wifi.counter = WIFI_CHECK_SEC; WifiCheckIp(); } #if LWIP_IPV6 if (WifiCheckIPAddrStatus()) { #else if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0) && !Wifi.config_type) { #endif WifiSetState(1); if (Settings.flag3.use_wifi_rescan) { if (!(uptime % (60 * WIFI_RESCAN_MINUTES))) { Wifi.scan_state = 2; } } #ifdef FIRMWARE_MINIMAL if (1 == RtcSettings.ota_loader) { RtcSettings.ota_loader = 0; ota_state_flag = 3; } #endif #ifdef USE_DISCOVERY if (Settings.flag3.mdns_enabled) { if (!Wifi.mdns_begun) { Wifi.mdns_begun = (uint8_t)MDNS.begin(my_hostname); AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS "%s"), (Wifi.mdns_begun) ? D_INITIALIZED : D_FAILED); } } #endif #ifdef USE_WEBSERVER if (Settings.webserver) { StartWebserver(Settings.webserver, WiFi.localIP()); #ifdef USE_DISCOVERY #ifdef WEBSERVER_ADVERTISE if (1 == Wifi.mdns_begun) { Wifi.mdns_begun = 2; MDNS.addService("http", "tcp", WEB_PORT); } #endif #endif } else { StopWebserver(); } #ifdef USE_EMULATION if (Settings.flag2.emulation) { UdpConnect(); } #endif #endif #ifdef USE_KNX if (!knx_started && Settings.flag.knx_enabled) { KNXStart(); knx_started = true; } #endif } else { WifiSetState(0); #ifdef USE_EMULATION UdpDisconnect(); #endif Wifi.mdns_begun = 0; #ifdef USE_KNX knx_started = false; #endif } } } } int WifiState(void) { int state = -1; if (!global_state.wifi_down) { state = WIFI_RESTART; } if (Wifi.config_type) { state = Wifi.config_type; } return state; } String WifiGetOutputPower(void) { char stemp1[TOPSZ]; dtostrfd((float)(Settings.wifi_output_power) / 10, 1, stemp1); return String(stemp1); } void WifiSetOutputPower(void) { WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10); } void WifiConnect(void) { WifiSetState(0); WifiSetOutputPower(); WiFi.persistent(false); Wifi.status = 0; Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + (ESP.getChipId() & 0xF); Wifi.retry = Wifi.retry_init; Wifi.counter = 1; } void WifiDisconnect(void) { WiFi.persistent(true); ETS_UART_INTR_DISABLE(); wifi_station_disconnect(); ETS_UART_INTR_ENABLE(); WiFi.persistent(false); } void WifiShutdown(void) { delay(100); if (Settings.flag.mqtt_enabled) { MqttDisconnect(); } WifiDisconnect(); } void EspRestart(void) { WifiShutdown(); CrashDumpClear(); ESP.reset(); } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino" # 24 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino" #ifdef USE_MQTT_TLS_CA_CERT #ifndef USE_MQTT_AWS_IOT # 38 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino" static const unsigned char PROGMEM TA0_DN[] = { 0x30, 0x4A, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x0D, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x1A, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x20, 0x58, 0x33 }; static const unsigned char PROGMEM TA0_RSA_N[] = { 0x9C, 0xD3, 0x0C, 0xF0, 0x5A, 0xE5, 0x2E, 0x47, 0xB7, 0x72, 0x5D, 0x37, 0x83, 0xB3, 0x68, 0x63, 0x30, 0xEA, 0xD7, 0x35, 0x26, 0x19, 0x25, 0xE1, 0xBD, 0xBE, 0x35, 0xF1, 0x70, 0x92, 0x2F, 0xB7, 0xB8, 0x4B, 0x41, 0x05, 0xAB, 0xA9, 0x9E, 0x35, 0x08, 0x58, 0xEC, 0xB1, 0x2A, 0xC4, 0x68, 0x87, 0x0B, 0xA3, 0xE3, 0x75, 0xE4, 0xE6, 0xF3, 0xA7, 0x62, 0x71, 0xBA, 0x79, 0x81, 0x60, 0x1F, 0xD7, 0x91, 0x9A, 0x9F, 0xF3, 0xD0, 0x78, 0x67, 0x71, 0xC8, 0x69, 0x0E, 0x95, 0x91, 0xCF, 0xFE, 0xE6, 0x99, 0xE9, 0x60, 0x3C, 0x48, 0xCC, 0x7E, 0xCA, 0x4D, 0x77, 0x12, 0x24, 0x9D, 0x47, 0x1B, 0x5A, 0xEB, 0xB9, 0xEC, 0x1E, 0x37, 0x00, 0x1C, 0x9C, 0xAC, 0x7B, 0xA7, 0x05, 0xEA, 0xCE, 0x4A, 0xEB, 0xBD, 0x41, 0xE5, 0x36, 0x98, 0xB9, 0xCB, 0xFD, 0x6D, 0x3C, 0x96, 0x68, 0xDF, 0x23, 0x2A, 0x42, 0x90, 0x0C, 0x86, 0x74, 0x67, 0xC8, 0x7F, 0xA5, 0x9A, 0xB8, 0x52, 0x61, 0x14, 0x13, 0x3F, 0x65, 0xE9, 0x82, 0x87, 0xCB, 0xDB, 0xFA, 0x0E, 0x56, 0xF6, 0x86, 0x89, 0xF3, 0x85, 0x3F, 0x97, 0x86, 0xAF, 0xB0, 0xDC, 0x1A, 0xEF, 0x6B, 0x0D, 0x95, 0x16, 0x7D, 0xC4, 0x2B, 0xA0, 0x65, 0xB2, 0x99, 0x04, 0x36, 0x75, 0x80, 0x6B, 0xAC, 0x4A, 0xF3, 0x1B, 0x90, 0x49, 0x78, 0x2F, 0xA2, 0x96, 0x4F, 0x2A, 0x20, 0x25, 0x29, 0x04, 0xC6, 0x74, 0xC0, 0xD0, 0x31, 0xCD, 0x8F, 0x31, 0x38, 0x95, 0x16, 0xBA, 0xA8, 0x33, 0xB8, 0x43, 0xF1, 0xB1, 0x1F, 0xC3, 0x30, 0x7F, 0xA2, 0x79, 0x31, 0x13, 0x3D, 0x2D, 0x36, 0xF8, 0xE3, 0xFC, 0xF2, 0x33, 0x6A, 0xB9, 0x39, 0x31, 0xC5, 0xAF, 0xC4, 0x8D, 0x0D, 0x1D, 0x64, 0x16, 0x33, 0xAA, 0xFA, 0x84, 0x29, 0xB6, 0xD4, 0x0B, 0xC0, 0xD8, 0x7D, 0xC3, 0x93 }; static const unsigned char TA0_RSA_E[] = { 0x01, 0x00, 0x01 }; static const br_x509_trust_anchor PROGMEM LetsEncryptX3CrossSigned_TA = { { (unsigned char *)TA0_DN, sizeof TA0_DN }, BR_X509_TA_CA, { BR_KEYTYPE_RSA, { .rsa = { (unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N, (unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E, } } } }; #define TAs_NUM 1 #endif #ifdef USE_MQTT_AWS_IOT # 106 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino" const unsigned char PROGMEM TA0_DN[] = { 0x30, 0x39, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x06, 0x41, 0x6D, 0x61, 0x7A, 0x6F, 0x6E, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6D, 0x61, 0x7A, 0x6F, 0x6E, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31 }; const unsigned char PROGMEM TA0_RSA_N[] = { 0xB2, 0x78, 0x80, 0x71, 0xCA, 0x78, 0xD5, 0xE3, 0x71, 0xAF, 0x47, 0x80, 0x50, 0x74, 0x7D, 0x6E, 0xD8, 0xD7, 0x88, 0x76, 0xF4, 0x99, 0x68, 0xF7, 0x58, 0x21, 0x60, 0xF9, 0x74, 0x84, 0x01, 0x2F, 0xAC, 0x02, 0x2D, 0x86, 0xD3, 0xA0, 0x43, 0x7A, 0x4E, 0xB2, 0xA4, 0xD0, 0x36, 0xBA, 0x01, 0xBE, 0x8D, 0xDB, 0x48, 0xC8, 0x07, 0x17, 0x36, 0x4C, 0xF4, 0xEE, 0x88, 0x23, 0xC7, 0x3E, 0xEB, 0x37, 0xF5, 0xB5, 0x19, 0xF8, 0x49, 0x68, 0xB0, 0xDE, 0xD7, 0xB9, 0x76, 0x38, 0x1D, 0x61, 0x9E, 0xA4, 0xFE, 0x82, 0x36, 0xA5, 0xE5, 0x4A, 0x56, 0xE4, 0x45, 0xE1, 0xF9, 0xFD, 0xB4, 0x16, 0xFA, 0x74, 0xDA, 0x9C, 0x9B, 0x35, 0x39, 0x2F, 0xFA, 0xB0, 0x20, 0x50, 0x06, 0x6C, 0x7A, 0xD0, 0x80, 0xB2, 0xA6, 0xF9, 0xAF, 0xEC, 0x47, 0x19, 0x8F, 0x50, 0x38, 0x07, 0xDC, 0xA2, 0x87, 0x39, 0x58, 0xF8, 0xBA, 0xD5, 0xA9, 0xF9, 0x48, 0x67, 0x30, 0x96, 0xEE, 0x94, 0x78, 0x5E, 0x6F, 0x89, 0xA3, 0x51, 0xC0, 0x30, 0x86, 0x66, 0xA1, 0x45, 0x66, 0xBA, 0x54, 0xEB, 0xA3, 0xC3, 0x91, 0xF9, 0x48, 0xDC, 0xFF, 0xD1, 0xE8, 0x30, 0x2D, 0x7D, 0x2D, 0x74, 0x70, 0x35, 0xD7, 0x88, 0x24, 0xF7, 0x9E, 0xC4, 0x59, 0x6E, 0xBB, 0x73, 0x87, 0x17, 0xF2, 0x32, 0x46, 0x28, 0xB8, 0x43, 0xFA, 0xB7, 0x1D, 0xAA, 0xCA, 0xB4, 0xF2, 0x9F, 0x24, 0x0E, 0x2D, 0x4B, 0xF7, 0x71, 0x5C, 0x5E, 0x69, 0xFF, 0xEA, 0x95, 0x02, 0xCB, 0x38, 0x8A, 0xAE, 0x50, 0x38, 0x6F, 0xDB, 0xFB, 0x2D, 0x62, 0x1B, 0xC5, 0xC7, 0x1E, 0x54, 0xE1, 0x77, 0xE0, 0x67, 0xC8, 0x0F, 0x9C, 0x87, 0x23, 0xD6, 0x3F, 0x40, 0x20, 0x7F, 0x20, 0x80, 0xC4, 0x80, 0x4C, 0x3E, 0x3B, 0x24, 0x26, 0x8E, 0x04, 0xAE, 0x6C, 0x9A, 0xC8, 0xAA, 0x0D }; static const unsigned char PROGMEM TA0_RSA_E[] = { 0x01, 0x00, 0x01 }; const br_x509_trust_anchor PROGMEM AmazonRootCA1_TA = { { (unsigned char *)TA0_DN, sizeof TA0_DN }, BR_X509_TA_CA, { BR_KEYTYPE_RSA, { .rsa = { (unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N, (unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E, } } } }; #define TAs_NUM 1 #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_01_webserver.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_01_webserver.ino" #ifdef USE_WEBSERVER #define XDRV_01 1 #ifndef WIFI_SOFT_AP_CHANNEL #define WIFI_SOFT_AP_CHANNEL 1 #endif const uint16_t CHUNKED_BUFFER_SIZE = 400; const uint16_t HTTP_REFRESH_TIME = 2345; #define HTTP_RESTART_RECONNECT_TIME 9000 #define HTTP_OTA_RESTART_RECONNECT_TIME 20000 #include #include #ifdef USE_RF_FLASH uint8_t *efm8bb1_update = nullptr; #endif enum UploadTypes { UPL_TASMOTA, UPL_SETTINGS, UPL_EFM8BB1, UPL_TASMOTASLAVE }; static const char * HEADER_KEYS[] = { "User-Agent", }; const char HTTP_HEADER[] PROGMEM = "" "" "" "" "%s - %s" ""; const char HTTP_HEAD_STYLE1[] PROGMEM = "" "" "" "
" #ifdef FIRMWARE_MINIMAL "

" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "

" #endif "
" #ifdef LANGUAGE_MODULE_NAME "

" D_MODULE " %s

" #else "

%s " D_MODULE "

" #endif "

%s

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

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

" D_USER "

" "

" D_PASSWORD "

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

" "
 " D_TEMPLATE_FLAGS " 

" "

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

" D_MODULE_TYPE " (%s)

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

" D_AP1_SSID " (" STA_SSID1 ")

" "

" D_AP1_PASSWORD "

" "

" D_AP2_SSID " (" STA_SSID2 ")

" "

" D_AP2_PASSWORD "

" "

" D_HOSTNAME " (%s)

" "

" D_CORS_DOMAIN "

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

" D_SYSLOG_HOST " (" SYS_LOG_HOST ")

" "

" D_SYSLOG_PORT " (" STR(SYS_LOG_PORT) ")

" "

" D_TELEMETRY_PERIOD " (" STR(TELE_PERIOD) ")

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

" "
 " D_TEMPLATE " " "

" "

" D_ACTIVATE "

" "
" "
" "" D_WEB_ADMIN_PASSWORD "

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

" "
" "


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

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


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

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

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

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

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

%s (%s)

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

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

 " D_EMULATION " 

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

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

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

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

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

" D_HOST " (" MQTT_HOST ")

" "

" D_PORT " (" STR(MQTT_PORT) ")

" "

" D_CLIENT " (%s)

"; const char HTTP_FORM_MQTT2[] PROGMEM = "

" D_USER " (" MQTT_USER ")

" "

" D_PASSWORD "

" "

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

" "

" D_FULL_TOPIC " (%s)

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

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

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


" "



" "

" "
" "" D_TIMER_ARM " " "" D_TIMER_REPEAT "" "

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

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

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

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

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

")); WSContentSend_P(PSTR("

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

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

" D_KNX_PHYSICAL_ADDRESS_NOTE "

" "

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

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

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

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

" D_INVERT_PORTS "


"; const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM = "" D_DEVICE " %d " D_PORT " %d"; void HandlePcf8574(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_PCF8574)); if (WebServer->hasArg("save")) { Pcf8574SaveSettings(); WebRestart(1); return; } WSContentStart_P(D_CONFIGURE_PCF8574); WSContentSendStyle(); WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? " checked" : ""); WSContentSend_P(HTTP_TABLE100); for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { for (uint32_t idx2 = 0; idx2 < 8; idx2++) { uint8_t helper = 1 << idx2; WSContentSend_P(HTTP_FORM_I2C_PCF8574_2, idx +1, idx2, idx2 + 8*idx, idx2 + 8*idx, ((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? " selected " : " ", ((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? " selected " : " " ); } } WSContentSend_P(PSTR("")); WSContentSend_P(HTTP_FORM_END); WSContentSpaceButton(BUTTON_CONFIGURATION); WSContentStop(); } void Pcf8574SaveSettings(void) { char stemp[7]; char tmp[100]; Settings.flag3.pcf8574_ports_inverted = WebServer->hasArg("b1"); for (byte idx = 0; idx < Pcf8574.max_devices; idx++) { byte count=0; byte n = Settings.pcf8574_config[idx]; while(n!=0) { n = n&(n-1); count++; } if (count <= devices_present) { devices_present = devices_present - count; } for (byte i = 0; i < 8; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("i2cs%d"), i+8*idx); WebGetArg(stemp, tmp, sizeof(tmp)); byte _value = (!strlen(tmp)) ? 0 : atoi(tmp); if (_value) { Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] | 1 << i; devices_present++; Pcf8574.max_connected_ports++; } else { Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] & ~(1 << i ); } } } } #endif bool Xdrv28(uint8_t function) { if (!I2cEnabled(XI2C_02)) { return false; } bool result = false; if (FUNC_PRE_INIT == function) { Pcf8574Init(); } else if (Pcf8574.type) { switch (function) { case FUNC_SET_POWER: Pcf8574SwitchRelay(); break; #ifdef USE_WEBSERVER case FUNC_WEB_ADD_BUTTON: WSContentSend_P(HTTP_BTN_MENU_PCF8574); break; case FUNC_WEB_ADD_HANDLER: WebServer->on("/" WEB_HANDLE_PCF8574, HandlePcf8574); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino" #ifdef USE_DEEPSLEEP # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino" #define XDRV_29 29 #define D_PRFX_DEEPSLEEP "DeepSleep" #define D_CMND_DEEPSLEEP_TIME "Time" const uint32_t DEEPSLEEP_MAX = 10 * 366 * 24 * 60 * 60; const uint32_t DEEPSLEEP_MAX_CYCLE = 60 * 60; const uint32_t DEEPSLEEP_MIN_TIME = 5; const uint32_t DEEPSLEEP_START_COUNTDOWN = 4; const char kDeepsleepCommands[] PROGMEM = D_PRFX_DEEPSLEEP "|" D_CMND_DEEPSLEEP_TIME ; void (* const DeepsleepCommand[])(void) PROGMEM = { &CmndDeepsleepTime }; uint32_t deepsleep_sleeptime = 0; uint8_t deepsleep_flag = 0; bool DeepSleepEnabled(void) { if ((Settings.deepsleep < 10) || (Settings.deepsleep > DEEPSLEEP_MAX)) { Settings.deepsleep = 0; return false; } if (pin[GPIO_DEEPSLEEP] < 99) { pinMode(pin[GPIO_DEEPSLEEP], INPUT_PULLUP); return (digitalRead(pin[GPIO_DEEPSLEEP])); } return true; } void DeepSleepReInit(void) { if ((ResetReason() == REASON_DEEP_SLEEP_AWAKE) && DeepSleepEnabled()) { if ((RtcSettings.ultradeepsleep > DEEPSLEEP_MAX_CYCLE) && (RtcSettings.ultradeepsleep < 1700000000)) { RtcSettings.ultradeepsleep = RtcSettings.ultradeepsleep - DEEPSLEEP_MAX_CYCLE; AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Remain DeepSleep %d"), RtcSettings.ultradeepsleep); RtcSettingsSave(); RtcRebootReset(); ESP.deepSleep(100 * RtcSettings.deepsleep_slip * (DEEPSLEEP_MAX_CYCLE < RtcSettings.ultradeepsleep ? DEEPSLEEP_MAX_CYCLE : RtcSettings.ultradeepsleep), WAKE_RF_DEFAULT); yield(); } } RtcSettings.ultradeepsleep = 0; } void DeepSleepPrepare(void) { if ((RtcSettings.nextwakeup == 0) || (RtcSettings.deepsleep_slip < 9000) || (RtcSettings.deepsleep_slip > 11000) || (RtcSettings.nextwakeup > (UtcTime() + Settings.deepsleep))) { AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Reset wrong settings wakeup: %ld, slip %ld"), RtcSettings.nextwakeup, RtcSettings.deepsleep_slip ); RtcSettings.nextwakeup = 0; RtcSettings.deepsleep_slip = 10000; } int16_t timeslip = (int16_t)(RtcSettings.nextwakeup + millis() / 1000 - UtcTime()) * 10; timeslip = (timeslip < -(int32_t)Settings.deepsleep) ? 0 : (timeslip > (int32_t)Settings.deepsleep) ? 0 : 1; if (timeslip) { RtcSettings.deepsleep_slip = (Settings.deepsleep + RtcSettings.nextwakeup - UtcTime()) * RtcSettings.deepsleep_slip / tmax((Settings.deepsleep - (millis() / 1000)),5); RtcSettings.deepsleep_slip = tmin(tmax(RtcSettings.deepsleep_slip, 9000), 11000); RtcSettings.nextwakeup += Settings.deepsleep; } if (RtcSettings.nextwakeup <= (UtcTime() - DEEPSLEEP_MIN_TIME)) { RtcSettings.nextwakeup += (((UtcTime() + DEEPSLEEP_MIN_TIME - RtcSettings.nextwakeup) / Settings.deepsleep) + 1) * Settings.deepsleep; } String dt = GetDT(RtcSettings.nextwakeup + LocalTime() - UtcTime()); deepsleep_sleeptime = tmin((uint32_t)DEEPSLEEP_MAX_CYCLE ,RtcSettings.nextwakeup - UtcTime()); Response_P(PSTR("{\"" D_PRFX_DEEPSLEEP "\":{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%d}}"), (char*)dt.c_str(), RtcSettings.nextwakeup); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_STATUS)); } void DeepSleepStart(void) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "Sleeping")); WifiShutdown(); RtcSettings.ultradeepsleep = RtcSettings.nextwakeup - UtcTime(); RtcSettingsSave(); ESP.deepSleep(100 * RtcSettings.deepsleep_slip * deepsleep_sleeptime); yield(); } void DeepSleepEverySecond(void) { if (!deepsleep_flag) { return; } if (DeepSleepEnabled()) { if (DEEPSLEEP_START_COUNTDOWN == deepsleep_flag) { SettingsSaveAll(); DeepSleepPrepare(); } deepsleep_flag--; if (deepsleep_flag <= 0) { DeepSleepStart(); } } else { deepsleep_flag = 0; } } void CmndDeepsleepTime(void) { if ((0 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 10) && (XdrvMailbox.payload < DEEPSLEEP_MAX))) { Settings.deepsleep = XdrvMailbox.payload; RtcSettings.nextwakeup = 0; deepsleep_flag = (0 == XdrvMailbox.payload) ? 0 : DEEPSLEEP_START_COUNTDOWN; if (deepsleep_flag) { if (!Settings.tele_period) { Settings.tele_period = TELE_PERIOD; } } } Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, Settings.deepsleep); } bool Xdrv29(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_SECOND: DeepSleepEverySecond(); break; case FUNC_AFTER_TELEPERIOD: if (DeepSleepEnabled() && !deepsleep_flag && (Settings.tele_period == 10 || Settings.tele_period == 300 || UpTime() > Settings.tele_period)) { deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; } break; case FUNC_COMMAND: result = DecodeCommand(kDeepsleepCommands, DeepsleepCommand); break; case FUNC_PRE_INIT: DeepSleepReInit(); break; } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" #ifdef USE_LIGHT #ifdef USE_EXS_DIMMER #define XDRV_30 30 #define EXS_GATE_1_ON 0x20 #define EXS_GATE_1_OFF 0x21 #define EXS_DIMM_1_ON 0x22 #define EXS_DIMM_1_OFF 0x23 #define EXS_DIMM_1_TBL 0x24 #define EXS_DIMM_1_VAL 0x25 #define EXS_GATE_2_ON 0x30 #define EXS_GATE_2_OFF 0x31 #define EXS_DIMM_2_ON 0x32 #define EXS_DIMM_2_OFF 0x33 #define EXS_DIMM_2_TBL 0x34 #define EXS_DIMM_2_VAL 0x35 #define EXS_GATES_ON 0x40 #define EXS_GATES_OFF 0x41 #define EXS_DIMMS_ON 0x50 #define EXS_DIMMS_OFF 0x51 #define EXS_CH_LOCK 0x60 #define EXS_GET_VALUES 0xFA #define EXS_WRITE_EE 0xFC #define EXS_READ_EE 0xFD #define EXS_GET_VERSION 0xFE #define EXS_RESET 0xFF #define EXS_BUFFER_SIZE 256 #define EXS_ACK_TIMEOUT 200 #include TasmotaSerial *ExsSerial = nullptr; typedef struct { uint8_t on = 0; uint8_t bright_tbl = 0; uint8_t dimm = 0; uint8_t impuls_start = 0; uint32_t impuls_len = 0; } CHANNEL; typedef struct { uint8_t version_major = 0; uint8_t version_minor = 0; CHANNEL channel[2]; uint8_t gate_lock = 0; } DIMMER; struct EXS { uint8_t *buffer = nullptr; int byte_counter = 0; int cmd_status = 0; uint8_t power = 0; uint8_t dimm[2] = {0, 0}; DIMMER dimmer; } Exs; uint8_t crc8(const uint8_t *p, uint8_t len) { const uint8_t table[] = { 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D}; const uint8_t table_rev[] = { 0x00, 0x70, 0xE0, 0x90, 0xC1, 0xB1, 0x21, 0x51, 0x83, 0xF3, 0x63, 0x13, 0x42, 0x32, 0xA2, 0xD2}; uint8_t offset; uint8_t temp, crc8_temp; uint8_t crc8 = 0; for (int i = 0; i < len; i++) { temp = *(p + i); offset = temp ^ crc8; offset >>= 4; crc8_temp = crc8 & 0x0f; crc8 = crc8_temp ^ table_rev[offset]; offset = crc8 ^ temp; offset &= 0x0f; crc8_temp = crc8 & 0xf0; crc8 = crc8_temp ^ table[offset]; } return crc8 ^ 0x55; } void ExsSerialSend(const uint8_t data[] = nullptr, uint16_t len = 0) { int retries = 3; char rc; #ifdef EXS_DEBUG snprintf_P(log_data, sizeof(log_data), PSTR("EXS: Tx Packet: \"")); for (uint32_t i = 0; i < len; i++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, data[i]); } snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data); AddLog(LOG_LEVEL_DEBUG_MORE); #endif while (retries) { retries--; ExsSerial->write(data, len); ExsSerial->flush(); uint32_t snd_time = millis(); while ((TimePassedSince(snd_time) < EXS_ACK_TIMEOUT) && (!ExsSerial->available())) ; if (!ExsSerial->available()) { #ifdef EXS_DEBUG AddLog_P(LOG_LEVEL_DEBUG, PSTR("ESX: serial send timeout")); #endif continue; } rc = ExsSerial->read(); if (rc == 0xFF) break; } } void ExsSendCmd(uint8_t cmd, uint8_t value) { uint8_t buffer[8]; uint16_t len; buffer[0] = 0x7b; buffer[3] = cmd; switch (cmd) { case EXS_GATE_1_ON: case EXS_GATE_1_OFF: case EXS_DIMM_1_ON: case EXS_DIMM_1_OFF: case EXS_GATE_2_ON: case EXS_GATE_2_OFF: case EXS_DIMM_2_ON: case EXS_DIMM_2_OFF: case EXS_GATES_ON: case EXS_GATES_OFF: case EXS_DIMMS_ON: case EXS_DIMMS_OFF: case EXS_GET_VALUES: case EXS_GET_VERSION: case EXS_RESET: buffer[2] = 1; len = 4; break; case EXS_CH_LOCK: case EXS_DIMM_1_TBL: case EXS_DIMM_1_VAL: case EXS_DIMM_2_TBL: case EXS_DIMM_2_VAL: buffer[2] = 2; buffer[4] = value; len = 5; break; } buffer[1] = crc8(&buffer[3], buffer[2]); ExsSerialSend(buffer, len); } uint8_t ExsSetPower(uint8_t device, uint8_t power) { Exs.dimmer.channel[device].dimm = power; ExsSendCmd(EXS_DIMM_1_ON + 0x10 * device + power ^ 1, 0); } uint8_t ExsSetBri(uint8_t device, uint8_t bri) { Exs.dimmer.channel[device].bright_tbl = bri; ExsSendCmd(EXS_DIMM_1_TBL + 0x10 * device, bri); } uint8_t ExsSyncState(uint8_t device) { #ifdef EXS_DEBUG AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Channel %d Power Want %d, Is %d"), device, bitRead(Exs.power, device), Exs.dimmer.channel[device].dimm); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Set Channel %d Brightness Want %d, Is %d"), device, Exs.dimm[device], Exs.dimmer.channel[device].bright_tbl); #endif if (bitRead(Exs.power, device) && Exs.dimm[device] != Exs.dimmer.channel[device].bright_tbl) { ExsSetBri(device, Exs.dimm[device]); } if (!Exs.dimm[device]) { Exs.dimmer.channel[device].dimm = 0; } else if (Exs.dimmer.channel[device].dimm != bitRead(Exs.power, device)) { ExsSetPower(device, bitRead(Exs.power, device)); } } bool ExsSyncState() { #ifdef EXS_DEBUG AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Serial %p, Cmd %d"), ExsSerial, Exs.cmd_status); #endif if (!ExsSerial || Exs.cmd_status != 0) return false; ExsSyncState(0); ExsSyncState(1); } void ExsDebugState() { #ifdef EXS_DEBUG AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: MCU v%d.%d, c0: On:%d,Dim:%d,Tbl:%d(%d%%), c1: On:%d,Dim:%d,Tbl:%d(%d%%), ChLock: %d"), Exs.dimmer.version_major, Exs.dimmer.version_minor, Exs.dimmer.channel[0].on, Exs.dimmer.channel[0].dimm, Exs.dimmer.channel[0].bright_tbl, changeUIntScale(Exs.dimmer.channel[0].bright_tbl, 0, 255, 0, 100), Exs.dimmer.channel[1].on, Exs.dimmer.channel[1].dimm, Exs.dimmer.channel[1].bright_tbl, changeUIntScale(Exs.dimmer.channel[1].bright_tbl, 0, 255, 0, 100), Exs.dimmer.gate_lock); #endif } void ExsPacketProcess(void) { uint8_t len = Exs.buffer[1]; uint8_t cmd = Exs.buffer[2]; switch (cmd) { case EXS_GET_VALUES: # 294 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" if (len > 9) { Exs.dimmer.version_major = Exs.buffer[3]; Exs.dimmer.version_minor = Exs.buffer[4]; Exs.dimmer.channel[0].on = Exs.buffer[6]; Exs.dimmer.channel[0].dimm = Exs.buffer[6]; Exs.dimmer.channel[0].bright_tbl = Exs.buffer[7]; Exs.dimmer.channel[1].on = Exs.buffer[9]; Exs.dimmer.channel[1].dimm = Exs.buffer[9]; Exs.dimmer.channel[1].bright_tbl = Exs.buffer[10]; Exs.dimmer.gate_lock = Exs.buffer[11]; } else # 327 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino" { Exs.dimmer.version_major = 1; Exs.dimmer.version_minor = 0; Exs.dimmer.channel[0].on = Exs.buffer[4] - 48; Exs.dimmer.channel[0].dimm = Exs.buffer[4] - 48; Exs.dimmer.channel[0].bright_tbl = Exs.buffer[5] - 48; Exs.dimmer.channel[1].on = Exs.buffer[7] - 48; Exs.dimmer.channel[1].dimm = Exs.buffer[7] - 48; Exs.dimmer.channel[1].bright_tbl = Exs.buffer[8] - 48; Exs.dimmer.gate_lock = Exs.buffer[9] - 48; } ExsDebugState(); ExsSyncState(); ExsDebugState(); break; default: break; } } bool ExsModuleSelected(void) { Settings.light_correction = 0; Settings.flag.mqtt_serial = 0; Settings.flag3.pwm_multi_channels = 1; SetSeriallog(LOG_LEVEL_NONE); devices_present = +2; light_type = LT_SERIAL2; return true; } bool ExsSetChannels(void) { #ifdef EXS_DEBUG snprintf_P(log_data, sizeof(log_data), PSTR("EXS: SetChannels: \"")); for (int i = 0; i < XdrvMailbox.data_len; i++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, ((uint8_t *)XdrvMailbox.data)[i]); } snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data); AddLog(LOG_LEVEL_DEBUG_MORE); #endif Exs.dimm[0] = ((uint8_t *)XdrvMailbox.data)[0]; Exs.dimm[1] = ((uint8_t *)XdrvMailbox.data)[1]; return ExsSyncState(); } bool ExsSetPower(void) { AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Set Power, Device %d, Power 0x%02x"), active_device, XdrvMailbox.index); Exs.power = XdrvMailbox.index; return ExsSyncState(); } void EsxMcuStart(void) { int retries = 3; #ifdef EXS_DEBUG AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Request MCU configuration, PIN %d to Low"), pin[GPIO_EXS_ENABLE]); #endif pinMode(pin[GPIO_EXS_ENABLE], OUTPUT); digitalWrite(pin[GPIO_EXS_ENABLE], LOW); delay(1); while (ExsSerial->available()) { ExsSerial->read(); } } void ExsInit(void) { #ifdef EXS_DEBUG AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Starting Tx %d Rx %d"), pin[GPIO_TXD], pin[GPIO_RXD]); #endif Exs.buffer = (uint8_t *)malloc(EXS_BUFFER_SIZE); if (Exs.buffer != nullptr) { ExsSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2); if (ExsSerial->begin(9600)) { if (ExsSerial->hardwareSerial()) { ClaimSerial(); } ExsSerial->flush(); EsxMcuStart(); ExsSendCmd(EXS_CH_LOCK, 0); ExsSendCmd(EXS_GET_VALUES, 0); } } } void ExsSerialInput(void) { while (ExsSerial->available()) { yield(); uint8_t serial_in_byte = ExsSerial->read(); AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Serial In Byte 0x%02x"), serial_in_byte); if (Exs.cmd_status == 0 && serial_in_byte == 0x7B) { Exs.cmd_status = 1; Exs.byte_counter = 0; } else if (Exs.byte_counter >= EXS_BUFFER_SIZE) { Exs.cmd_status = 0; } else if (Exs.cmd_status == 1) { Exs.buffer[Exs.byte_counter++] = serial_in_byte; if (Exs.byte_counter > 2 && Exs.byte_counter == Exs.buffer[1] + 2) { uint8_t crc = crc8(&Exs.buffer[2], Exs.buffer[1]); Exs.cmd_status = 0; #ifdef EXS_DEBUG snprintf_P(log_data, sizeof(log_data), PSTR("EXS: RX Packet: \"")); for (uint32_t i = 0; i < Exs.byte_counter; i++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, Exs.buffer[i]); } snprintf_P(log_data, sizeof(log_data), PSTR("%s\", CRC: 0x%02x"), log_data, crc); AddLog(LOG_LEVEL_DEBUG_MORE); #endif if (Exs.buffer[0] == crc) { ExsSerial->write(0xFF); ExsPacketProcess(); } else { ExsSerial->write(0x00); } } } } } #ifdef EXS_MCU_CMNDS #define D_PRFX_EXS "Exs" #define D_CMND_EXS_DIMM "Dimm" #define D_CMND_EXS_DIMM_TBL "DimmTbl" #define D_CMND_EXS_DIMM_VAL "DimmVal" #define D_CMND_EXS_DIMMS "Dimms" #define D_CMND_EXS_CH_LOCK "ChLock" #define D_CMND_EXS_STATE "State" const char kExsCommands[] PROGMEM = D_PRFX_EXS "|" D_CMND_EXS_DIMM "|" D_CMND_EXS_DIMM_TBL "|" D_CMND_EXS_DIMM_VAL "|" D_CMND_EXS_DIMMS "|" D_CMND_EXS_CH_LOCK "|" D_CMND_EXS_STATE; void (* const ExsCommand[])(void) PROGMEM = { &CmndExsDimm, &CmndExsDimmTbl, &CmndExsDimmVal, &CmndExsDimms, &CmndExsChLock, &CmndExsState }; void CmndExsDimm(void) { if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) && (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1)) { ExsSendCmd(EXS_DIMM_1_ON + 0x10 * (XdrvMailbox.index - 1) + XdrvMailbox.payload ^ 1, 0); } CmndExsState(); } void CmndExsDimmTbl(void) { if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) && (XdrvMailbox.payload > 0 || XdrvMailbox.payload <= 255)) { ExsSendCmd(EXS_DIMM_1_TBL + 0x10 * (XdrvMailbox.index - 1), XdrvMailbox.payload); } CmndExsState(); } void CmndExsDimmVal(void) { if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) && (XdrvMailbox.payload > 0 || XdrvMailbox.payload <= 255)) { ExsSendCmd(EXS_DIMM_1_VAL + 0x10 * (XdrvMailbox.index - 1), XdrvMailbox.payload); } CmndExsState(); } void CmndExsDimms(void) { if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1) { ExsSendCmd(EXS_DIMMS_ON + XdrvMailbox.payload ^ 1, 0); } CmndExsState(); } void CmndExsChLock(void) { if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1) { ExsSendCmd(EXS_CH_LOCK, XdrvMailbox.payload); } CmndExsState(); } void CmndExsState(void) { ExsSendCmd(EXS_GET_VALUES, 0); uint32_t snd_time = millis(); while ((TimePassedSince(snd_time) < EXS_ACK_TIMEOUT) && (!ExsSerial->available())) ; ExsSerialInput(); Response_P(PSTR("{\"" D_CMND_EXS_STATE "\":{")); ResponseAppend_P(PSTR("\"McuVersion\":\"%d.%d\"," "\"Channels\":["), Exs.dimmer.version_major, Exs.dimmer.version_minor); for (uint32_t i = 0; i < 2; i++) { if (i != 0) { ResponseAppend_P(PSTR(",")); } ResponseAppend_P(PSTR("{\"On\":\"%d\"," "\"BrightProz\":\"%d\"," "\"BrightTab\":\"%d\"," "\"Dimm\":\"%d\"}"), Exs.dimmer.channel[i].on, changeUIntScale(Exs.dimmer.channel[i].bright_tbl, 0, 255, 0, 100), Exs.dimmer.channel[i].bright_tbl, Exs.dimmer.channel[i].dimm); } ResponseAppend_P(PSTR("],")); ResponseAppend_P(PSTR("\"GateLock\":\"%d\""), Exs.dimmer.gate_lock); ResponseJsonEndEnd(); } #endif bool Xdrv30(uint8_t function) { bool result = false; if (EXS_DIMMER == my_module_type) { switch (function) { case FUNC_LOOP: if (ExsSerial) ExsSerialInput(); break; case FUNC_MODULE_INIT: result = ExsModuleSelected(); break; case FUNC_INIT: ExsInit(); break; case FUNC_SET_DEVICE_POWER: result = ExsSetPower(); break; case FUNC_SET_CHANNELS: result = ExsSetChannels(); break; #ifdef EXS_MCU_CMNDS case FUNC_COMMAND: result = DecodeCommand(kExsCommands, ExsCommand); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_31_tasmota_slave.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_31_tasmota_slave.ino" #ifdef USE_TASMOTA_SLAVE #define XDRV_31 31 #define CONST_STK_CRC_EOP 0x20 #define CMND_STK_GET_SYNC 0x30 #define CMND_STK_SET_DEVICE 0x42 #define CMND_STK_SET_DEVICE_EXT 0x45 #define CMND_STK_ENTER_PROGMODE 0x50 #define CMND_STK_LEAVE_PROGMODE 0x51 #define CMND_STK_LOAD_ADDRESS 0x55 #define CMND_STK_PROG_PAGE 0x64 #define CMND_START 0xFC #define CMND_END 0xFD #define CMND_FEATURES 0x01 #define CMND_JSON 0x02 #define CMND_FUNC_EVERY_SECOND 0x03 #define CMND_FUNC_EVERY_100_MSECOND 0x04 #define CMND_SLAVE_SEND 0x05 #define CMND_PUBLISH_TELE 0x06 #define CMND_EXECUTE_CMND 0x07 #define PARAM_DATA_START 0xFE #define PARAM_DATA_END 0xFF #include class SimpleHexParse { public: SimpleHexParse(void); uint8_t parseLine(char *hexline); uint8_t ptr_l = 0; uint8_t ptr_h = 0; bool PageIsReady = false; bool firstrun = true; bool EndOfFile = false; uint8_t FlashPage[128]; uint8_t FlashPageIdx = 0; uint8_t layoverBuffer[16]; uint8_t layoverIdx = 0; uint8_t getByte(char *hexline, uint8_t idx); }; SimpleHexParse::SimpleHexParse(void) { } uint8_t SimpleHexParse::parseLine(char *hexline) { if (layoverIdx) { memcpy(&FlashPage[0], &layoverBuffer[0], layoverIdx); FlashPageIdx = layoverIdx; layoverIdx = 0; } uint8_t len = getByte(hexline, 1); uint8_t addr_h = getByte(hexline, 2); uint8_t addr_l = getByte(hexline, 3); uint8_t rectype = getByte(hexline, 4); for (uint8_t idx = 0; idx < len; idx++) { if (FlashPageIdx < 128) { FlashPage[FlashPageIdx] = getByte(hexline, idx+5); FlashPageIdx++; } else { layoverBuffer[layoverIdx] = getByte(hexline, idx+5); layoverIdx++; } } if (1 == rectype) { EndOfFile = true; while (FlashPageIdx < 128) { FlashPage[FlashPageIdx] = 0xFF; FlashPageIdx++; } } if (FlashPageIdx == 128) { if (firstrun) { firstrun = false; } else { ptr_l += 0x40; if (ptr_l == 0) { ptr_l = 0; ptr_h++; } } firstrun = false; PageIsReady = true; } return 0; } uint8_t SimpleHexParse::getByte(char* hexline, uint8_t idx) { char buff[3]; buff[3] = '\0'; memcpy(&buff, &hexline[(idx*2)-1], 2); return strtol(buff, 0, 16); } struct TSLAVE { uint32_t spi_hex_size = 0; uint32_t spi_sector_counter = 0; uint8_t spi_sector_cursor = 0; uint8_t inverted = LOW; bool type = false; bool flashing = false; bool SerialEnabled = false; uint8_t waitstate = 0; bool unsupported = false; } TSlave; typedef union { uint32_t data; struct { uint32_t func_json_append : 1; uint32_t func_every_second : 1; uint32_t func_every_100_msecond : 1; uint32_t func_slave_send : 1; uint32_t spare4 : 1; uint32_t spare5 : 1; uint32_t spare6 : 1; uint32_t spare7 : 1; uint32_t spare8 : 1; uint32_t spare9 : 1; uint32_t spare10 : 1; uint32_t spare11 : 1; uint32_t spare12 : 1; uint32_t spare13 : 1; uint32_t spare14 : 1; uint32_t spare15 : 1; uint32_t spare16 : 1; uint32_t spare17 : 1; uint32_t spare18 : 1; uint32_t spare19 : 1; uint32_t spare20 : 1; uint32_t spare21 : 1; uint32_t spare22 : 1; uint32_t spare23 : 1; uint32_t spare24 : 1; uint32_t spare25 : 1; uint32_t spare26 : 1; uint32_t spare27 : 1; uint32_t spare28 : 1; uint32_t spare29 : 1; uint32_t spare30 : 1; uint32_t spare31 : 1; }; } TSlaveFeatureCfg; struct TSLAVE_FEATURES { uint32_t features_version; TSlaveFeatureCfg features; } TSlaveSettings; struct TSLAVE_COMMAND { uint8_t command; uint8_t parameter; uint8_t unused2; uint8_t unused3; } TSlaveCommand; TasmotaSerial *TasmotaSlave_Serial; uint32_t TasmotaSlave_FlashStart(void) { return (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 2; } uint8_t TasmotaSlave_UpdateInit(void) { TSlave.spi_hex_size = 0; TSlave.spi_sector_counter = TasmotaSlave_FlashStart(); TSlave.spi_sector_cursor = 0; return 0; } void TasmotaSlave_Reset(void) { if (TSlave.SerialEnabled) { digitalWrite(pin[GPIO_TASMOTASLAVE_RST], !TSlave.inverted); delay(1); digitalWrite(pin[GPIO_TASMOTASLAVE_RST], TSlave.inverted); delay(1); digitalWrite(pin[GPIO_TASMOTASLAVE_RST], !TSlave.inverted); delay(5); } } uint8_t TasmotaSlave_waitForSerialData(int dataCount, int timeout) { int timer = 0; while (timer < timeout) { if (TasmotaSlave_Serial->available() >= dataCount) { return 1; } delay(1); timer++; } return 0; } uint8_t TasmotaSlave_sendBytes(uint8_t* bytes, int count) { TasmotaSlave_Serial->write(bytes, count); TasmotaSlave_waitForSerialData(2, 250); uint8_t sync = TasmotaSlave_Serial->read(); uint8_t ok = TasmotaSlave_Serial->read(); if ((sync == 0x14) && (ok == 0x10)) { return 1; } return 0; } uint8_t TasmotaSlave_execCmd(uint8_t cmd) { uint8_t bytes[] = { cmd, CONST_STK_CRC_EOP }; return TasmotaSlave_sendBytes(bytes, 2); } uint8_t TasmotaSlave_execParam(uint8_t cmd, uint8_t* params, int count) { uint8_t bytes[32]; bytes[0] = cmd; int i = 0; while (i < count) { bytes[i + 1] = params[i]; i++; } bytes[i + 1] = CONST_STK_CRC_EOP; return TasmotaSlave_sendBytes(bytes, i + 2); } uint8_t TasmotaSlave_exitProgMode(void) { return TasmotaSlave_execCmd(CMND_STK_LEAVE_PROGMODE); } uint8_t TasmotaSlave_SetupFlash(void) { uint8_t ProgParams[] = {0x86, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00}; uint8_t ExtProgParams[] = {0x05, 0x04, 0xd7, 0xc2, 0x00}; TasmotaSlave_Serial->begin(USE_TASMOTA_SLAVE_FLASH_SPEED); if (TasmotaSlave_Serial->hardwareSerial()) { ClaimSerial(); } TasmotaSlave_Reset(); uint8_t timeout = 0; uint8_t no_error = 0; while (50 > timeout) { if (TasmotaSlave_execCmd(CMND_STK_GET_SYNC)) { timeout = 200; no_error = 1; } timeout++; delay(1); } if (no_error) { AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Found bootloader")); } else { no_error = 0; AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Bootloader could not be found")); } if (no_error) { if (TasmotaSlave_execParam(CMND_STK_SET_DEVICE, ProgParams, sizeof(ProgParams))) { } else { no_error = 0; AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Could not configure device for programming (1)")); } } if (no_error) { if (TasmotaSlave_execParam(CMND_STK_SET_DEVICE_EXT, ExtProgParams, sizeof(ExtProgParams))) { } else { no_error = 0; AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Could not configure device for programming (2)")); } } if (no_error) { if (TasmotaSlave_execCmd(CMND_STK_ENTER_PROGMODE)) { } else { no_error = 0; AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Failed to put bootloader into programming mode")); } } return no_error; } uint8_t TasmotaSlave_loadAddress(uint8_t adrHi, uint8_t adrLo) { uint8_t params[] = { adrLo, adrHi }; return TasmotaSlave_execParam(CMND_STK_LOAD_ADDRESS, params, sizeof(params)); } void TasmotaSlave_FlashPage(uint8_t addr_h, uint8_t addr_l, uint8_t* data) { uint8_t Header[] = {CMND_STK_PROG_PAGE, 0x00, 0x80, 0x46}; TasmotaSlave_loadAddress(addr_h, addr_l); TasmotaSlave_Serial->write(Header, 4); for (int i = 0; i < 128; i++) { TasmotaSlave_Serial->write(data[i]); } TasmotaSlave_Serial->write(CONST_STK_CRC_EOP); TasmotaSlave_waitForSerialData(2, 250); TasmotaSlave_Serial->read(); TasmotaSlave_Serial->read(); } void TasmotaSlave_Flash(void) { bool reading = true; uint32_t read = 0; uint32_t processed = 0; char thishexline[50]; uint8_t position = 0; char* flash_buffer; SimpleHexParse hexParse = SimpleHexParse(); if (!TasmotaSlave_SetupFlash()) { AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Flashing aborted!")); TSlave.flashing = false; restart_flag = 2; return; } flash_buffer = new char[SPI_FLASH_SEC_SIZE]; uint32_t flash_start = TasmotaSlave_FlashStart() * SPI_FLASH_SEC_SIZE; while (reading) { ESP.flashRead(flash_start + read, (uint32_t*)flash_buffer, SPI_FLASH_SEC_SIZE); read = read + SPI_FLASH_SEC_SIZE; if (read >= TSlave.spi_hex_size) { reading = false; } for (uint32_t ca = 0; ca < SPI_FLASH_SEC_SIZE; ca++) { processed++; if ((processed <= TSlave.spi_hex_size) && (!hexParse.EndOfFile)) { if (':' == flash_buffer[ca]) { position = 0; } if (0x0D == flash_buffer[ca]) { thishexline[position] = 0; hexParse.parseLine(thishexline); if (hexParse.PageIsReady) { TasmotaSlave_FlashPage(hexParse.ptr_h, hexParse.ptr_l, hexParse.FlashPage); hexParse.PageIsReady = false; hexParse.FlashPageIdx = 0; } } else { if (0x0A != flash_buffer[ca]) { thishexline[position] = flash_buffer[ca]; position++; } } } } } TasmotaSlave_exitProgMode(); AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Flash done!")); TSlave.flashing = false; restart_flag = 2; } void TasmotaSlave_SetFlagFlashing(bool value) { TSlave.flashing = value; } bool TasmotaSlave_GetFlagFlashing(void) { return TSlave.flashing; } void TasmotaSlave_WriteBuffer(uint8_t *buf, size_t size) { if (0 == TSlave.spi_sector_cursor) { ESP.flashEraseSector(TSlave.spi_sector_counter); } TSlave.spi_sector_cursor++; ESP.flashWrite((TSlave.spi_sector_counter * SPI_FLASH_SEC_SIZE) + ((TSlave.spi_sector_cursor-1)*2048), (uint32_t*)buf, size); TSlave.spi_hex_size = TSlave.spi_hex_size + size; if (2 == TSlave.spi_sector_cursor) { TSlave.spi_sector_cursor = 0; TSlave.spi_sector_counter++; } } void TasmotaSlave_Init(void) { if (TSlave.type) { return; } if (10 > TSlave.waitstate) { TSlave.waitstate++; return; } if (!TSlave.SerialEnabled) { if ((pin[GPIO_TASMOTASLAVE_RXD] < 99) && (pin[GPIO_TASMOTASLAVE_TXD] < 99) && ((pin[GPIO_TASMOTASLAVE_RST] < 99) || (pin[GPIO_TASMOTASLAVE_RST_INV] < 99))) { TasmotaSlave_Serial = new TasmotaSerial(pin[GPIO_TASMOTASLAVE_RXD], pin[GPIO_TASMOTASLAVE_TXD], 1, 0, 200); if (TasmotaSlave_Serial->begin(USE_TASMOTA_SLAVE_SERIAL_SPEED)) { if (TasmotaSlave_Serial->hardwareSerial()) { ClaimSerial(); } TasmotaSlave_Serial->setTimeout(50); if (pin[GPIO_TASMOTASLAVE_RST_INV] < 99) { pin[GPIO_TASMOTASLAVE_RST] = pin[GPIO_TASMOTASLAVE_RST_INV]; pin[GPIO_TASMOTASLAVE_RST_INV] = 99; TSlave.inverted = HIGH; } pinMode(pin[GPIO_TASMOTASLAVE_RST], OUTPUT); TSlave.SerialEnabled = true; TasmotaSlave_Reset(); AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Enabled")); } } } if (TSlave.SerialEnabled) { TasmotaSlave_sendCmnd(CMND_FEATURES, 0); char buffer[32]; TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_START), buffer, sizeof(buffer)); uint8_t len = TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_END), buffer, sizeof(buffer)); memcpy(&TSlaveSettings, &buffer, sizeof(TSlaveSettings)); if (20191129 == TSlaveSettings.features_version) { TSlave.type = true; AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Version %u"), TSlaveSettings.features_version); } else { if ((!TSlave.unsupported) && (TSlaveSettings.features_version > 0)) { AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Version %u not supported!"), TSlaveSettings.features_version); TSlave.unsupported = true; } } } } void TasmotaSlave_Show(void) { if ((TSlave.type) && (TSlaveSettings.features.func_json_append)) { char buffer[100]; TasmotaSlave_sendCmnd(CMND_JSON, 0); TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_START), buffer, sizeof(buffer)-1); uint8_t len = TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_END), buffer, sizeof(buffer)-1); buffer[len] = '\0'; ResponseAppend_P(PSTR(",\"TasmotaSlave\":%s"), buffer); } } void TasmotaSlave_sendCmnd(uint8_t cmnd, uint8_t param) { TSlaveCommand.command = cmnd; TSlaveCommand.parameter = param; char buffer[sizeof(TSlaveCommand)+2]; buffer[0] = CMND_START; memcpy(&buffer[1], &TSlaveCommand, sizeof(TSlaveCommand)); buffer[sizeof(TSlaveCommand)+1] = CMND_END; for (uint8_t ca = 0; ca < sizeof(buffer); ca++) { TasmotaSlave_Serial->write(buffer[ca]); } } #define D_PRFX_SLAVE "Slave" #define D_CMND_SLAVE_RESET "Reset" #define D_CMND_SLAVE_SEND "Send" const char kTasmotaSlaveCommands[] PROGMEM = D_PRFX_SLAVE "|" D_CMND_SLAVE_RESET "|" D_CMND_SLAVE_SEND; void (* const TasmotaSlaveCommand[])(void) PROGMEM = { &CmndTasmotaSlaveReset, &CmndTasmotaSlaveSend }; void CmndTasmotaSlaveReset(void) { TasmotaSlave_Reset(); TSlave.type = false; TSlave.waitstate = 7; TSlave.unsupported = false; ResponseCmndDone(); } void CmndTasmotaSlaveSend(void) { if (0 < XdrvMailbox.data_len) { TasmotaSlave_sendCmnd(CMND_SLAVE_SEND, XdrvMailbox.data_len); TasmotaSlave_Serial->write(char(PARAM_DATA_START)); for (uint8_t idx = 0; idx < XdrvMailbox.data_len; idx++) { TasmotaSlave_Serial->write(XdrvMailbox.data[idx]); } TasmotaSlave_Serial->write(char(PARAM_DATA_END)); } ResponseCmndDone(); } void TasmotaSlave_ProcessIn(void) { uint8_t cmnd = TasmotaSlave_Serial->read(); switch (cmnd) { case CMND_START: TasmotaSlave_waitForSerialData(sizeof(TSlaveCommand),50); uint8_t buffer[sizeof(TSlaveCommand)]; for (uint8_t idx = 0; idx < sizeof(TSlaveCommand); idx++) { buffer[idx] = TasmotaSlave_Serial->read(); } TasmotaSlave_Serial->read(); memcpy(&TSlaveCommand, &buffer, sizeof(TSlaveCommand)); char inbuf[TSlaveCommand.parameter+1]; TasmotaSlave_waitForSerialData(TSlaveCommand.parameter, 50); TasmotaSlave_Serial->read(); for (uint8_t idx = 0; idx < TSlaveCommand.parameter; idx++) { inbuf[idx] = TasmotaSlave_Serial->read(); } TasmotaSlave_Serial->read(); inbuf[TSlaveCommand.parameter] = '\0'; if (CMND_PUBLISH_TELE == TSlaveCommand.command) { Response_P(PSTR("{\"TasmotaSlave\":")); ResponseAppend_P("%s", inbuf); ResponseJsonEnd(); MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); XdrvRulesProcess(); } if (CMND_EXECUTE_CMND == TSlaveCommand.command) { ExecuteCommand(inbuf, SRC_IGNORE); } break; default: break; } } bool Xdrv31(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_100_MSECOND: if (TSlave.type) { if (TasmotaSlave_Serial->available()) { TasmotaSlave_ProcessIn(); } if (TSlaveSettings.features.func_every_100_msecond) { TasmotaSlave_sendCmnd(CMND_FUNC_EVERY_100_MSECOND, 0); } } break; case FUNC_EVERY_SECOND: if ((TSlave.type) && (TSlaveSettings.features.func_every_second)) { TasmotaSlave_sendCmnd(CMND_FUNC_EVERY_SECOND, 0); } TasmotaSlave_Init(); break; case FUNC_JSON_APPEND: if ((TSlave.type) && (TSlaveSettings.features.func_json_append)) { TasmotaSlave_Show(); } break; case FUNC_COMMAND: result = DecodeCommand(kTasmotaSlaveCommands, TasmotaSlaveCommand); break; } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_32_hotplug.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_32_hotplug.ino" #ifdef USE_HOTPLUG #define XDRV_32 32 const uint32_t HOTPLUG_MAX = 254; const char kHotPlugCommands[] PROGMEM = "|" D_CMND_HOTPLUG; void (* const HotPlugCommand[])(void) PROGMEM = { &CmndHotPlugTime }; struct { bool enabled = false; uint8_t timeout = 0; } Hotplug; void HotPlugInit(void) { if (Settings.hotplug_scan == 0xFF) { Settings.hotplug_scan = 0; } if (Settings.hotplug_scan != 0) { Hotplug.enabled = true; Hotplug.timeout = 1; } else Hotplug.enabled = false; } void HotPlugEverySecond(void) { if (Hotplug.enabled) { if (Hotplug.timeout == 0) { XsnsCall(FUNC_HOTPLUG_SCAN); Hotplug.timeout = Settings.hotplug_scan; } Hotplug.timeout--; } } void CmndHotPlugTime(void) { if (XdrvMailbox.payload <= HOTPLUG_MAX) { Settings.hotplug_scan = XdrvMailbox.payload; HotPlugInit(); } ResponseCmndNumber(Settings.hotplug_scan); } bool Xdrv32(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_SECOND: HotPlugEverySecond(); break; case FUNC_COMMAND: result = DecodeCommand(kHotPlugCommands, HotPlugCommand); break; case FUNC_PRE_INIT: HotPlugInit(); break; } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_33_nrf24l01.ino" # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_33_nrf24l01.ino" #ifdef USE_SPI #ifdef USE_NRF24 #define XDRV_33 33 #define MOSI 13 #define MISO 12 #define SCK 14 #include #include const char NRF24type[] PROGMEM = "NRF24"; const char HTTP_NRF24[] PROGMEM = "{s}%sL01%c: " "{m}started{e}"; struct { uint8_t chipType = 0; } NRF24; RF24 NRF24radio; bool NRF24initRadio() { NRF24radio.begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_DC]); NRF24radio.powerUp(); if(NRF24radio.isChipConnected()){ DEBUG_DRIVER_LOG(PSTR("NRF24 chip connected")); return true; } DEBUG_DRIVER_LOG(PSTR("NRF24 chip NOT !!!! connected")); return false; } bool NRF24Detect(void) { if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_DC]<99)){ SPI.pins(SCK,MOSI,MISO,-1); if(NRF24initRadio()){ NRF24.chipType = 32; AddLog_P2(LOG_LEVEL_INFO,PSTR("NRF24L01 initialized")); if(NRF24radio.isPVariant()){ NRF24.chipType = 43; AddLog_P2(LOG_LEVEL_INFO,PSTR("NRF24L01+ detected")); } return true; } } return false; } bool Xdrv33(uint8_t function) { bool result = false; if (FUNC_INIT == function) { result = NRF24Detect(); } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino" # 22 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino" #ifdef DEBUG_THEO #ifndef USE_DEBUG_DRIVER #define USE_DEBUG_DRIVER #endif #endif #ifdef USE_DEBUG_DRIVER #define XDRV_99 99 #ifndef CPU_LOAD_CHECK #define CPU_LOAD_CHECK 1 #endif #define D_CMND_CFGDUMP "CfgDump" #define D_CMND_CFGPEEK "CfgPeek" #define D_CMND_CFGPOKE "CfgPoke" #define D_CMND_CFGSHOW "CfgShow" #define D_CMND_CFGXOR "CfgXor" #define D_CMND_CPUCHECK "CpuChk" #define D_CMND_EXCEPTION "Exception" #define D_CMND_FLASHDUMP "FlashDump" #define D_CMND_FLASHMODE "FlashMode" #define D_CMND_FREEMEM "FreeMem" #define D_CMND_HELP "Help" #define D_CMND_RTCDUMP "RtcDump" #define D_CMND_SETSENSOR "SetSensor" #define D_CMND_I2CWRITE "I2CWrite" #define D_CMND_I2CREAD "I2CRead" #define D_CMND_I2CSTRETCH "I2CStretch" #define D_CMND_I2CCLOCK "I2CClock" const char kDebugCommands[] PROGMEM = "|" D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|" #ifdef USE_WEBSERVER D_CMND_CFGXOR "|" #endif D_CMND_CPUCHECK "|" #ifdef DEBUG_THEO D_CMND_EXCEPTION "|" #endif D_CMND_FLASHDUMP "|" D_CMND_FLASHMODE "|" D_CMND_FREEMEM"|" D_CMND_HELP "|" D_CMND_RTCDUMP "|" D_CMND_SETSENSOR "|" #ifdef USE_I2C D_CMND_I2CWRITE "|" D_CMND_I2CREAD "|" D_CMND_I2CSTRETCH "|" D_CMND_I2CCLOCK #endif ; void (* const DebugCommand[])(void) PROGMEM = { &CmndCfgDump, &CmndCfgPeek, &CmndCfgPoke, #ifdef USE_WEBSERVER &CmndCfgXor, #endif &CmndCpuCheck, #ifdef DEBUG_THEO &CmndException, #endif &CmndFlashDump, &CmndFlashMode, &CmndFreemem, &CmndHelp, &CmndRtcDump, &CmndSetSensor, #ifdef USE_I2C &CmndI2cWrite, &CmndI2cRead, &CmndI2cStretch, &CmndI2cClock #endif }; uint32_t CPU_loops = 0; uint32_t CPU_last_millis = 0; uint32_t CPU_last_loop_time = 0; uint8_t CPU_load_check = 0; uint8_t CPU_show_freemem = 0; #ifdef DEBUG_THEO void ExceptionTest(uint8_t type) { # 145 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino" if (1 == type) { char svalue[10]; snprintf_P(svalue, sizeof(svalue), PSTR("%s"), 7); } # 159 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino" if (2 == type) { while(1) delay(1000); } } #endif void CpuLoadLoop(void) { CPU_last_loop_time = millis(); if (CPU_load_check && CPU_last_millis) { CPU_loops ++; if ((CPU_last_millis + (CPU_load_check *1000)) <= CPU_last_loop_time) { #if defined(F_CPU) && (F_CPU == 160000000L) int CPU_load = 100 - ( (CPU_loops*(1 + 30*sleep)) / (CPU_load_check *800) ); CPU_loops = CPU_loops / CPU_load_check; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, CPU %d%%(160MHz), Loops/sec %d"), ESP.getFreeHeap(), CPU_load, CPU_loops); #else int CPU_load = 100 - ( (CPU_loops*(1 + 30*sleep)) / (CPU_load_check *400) ); CPU_loops = CPU_loops / CPU_load_check; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, CPU %d%%(80MHz), Loops/sec %d"), ESP.getFreeHeap(), CPU_load, CPU_loops); #endif CPU_last_millis = CPU_last_loop_time; CPU_loops = 0; } } } #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) extern "C" { #include extern cont_t g_cont; } void DebugFreeMem(void) { register uint32_t *sp asm("a1"); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, FreeStack %d (%s)"), ESP.getFreeHeap(), 4 * (sp - g_cont.stack), XdrvMailbox.data); } #else extern "C" { #include extern cont_t* g_pcont; } void DebugFreeMem(void) { register uint32_t *sp asm("a1"); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, FreeStack %d (%s)"), ESP.getFreeHeap(), 4 * (sp - g_pcont->stack), XdrvMailbox.data); } #endif void DebugRtcDump(char* parms) { #define CFG_COLS 16 uint16_t idx; uint16_t maxrow; uint16_t row; uint16_t col; char *p; # 246 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino" uint8_t buffer[768]; system_rtc_mem_read(0, (uint32_t*)&buffer, sizeof(buffer)); maxrow = ((sizeof(buffer)+CFG_COLS)/CFG_COLS); uint16_t srow = strtol(parms, &p, 16) / CFG_COLS; uint16_t mrow = strtol(p, &p, 10); if (0 == mrow) { mrow = 8; } if (srow > maxrow) { srow = maxrow - mrow; } if (mrow < (maxrow - srow)) { maxrow = srow + mrow; } for (row = srow; row < maxrow; row++) { idx = row * CFG_COLS; snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), idx); for (col = 0; col < CFG_COLS; col++) { if (!(col%4)) { snprintf_P(log_data, sizeof(log_data), PSTR("%s "), log_data); } snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[idx + col]); } snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data); for (col = 0; col < CFG_COLS; col++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); } snprintf_P(log_data, sizeof(log_data), PSTR("%s|"), log_data); AddLog(LOG_LEVEL_INFO); } } void DebugCfgDump(char* parms) { #define CFG_COLS 16 uint16_t idx; uint16_t maxrow; uint16_t row; uint16_t col; char *p; uint8_t *buffer = (uint8_t *) &Settings; maxrow = ((sizeof(SYSCFG)+CFG_COLS)/CFG_COLS); uint16_t srow = strtol(parms, &p, 16) / CFG_COLS; uint16_t mrow = strtol(p, &p, 10); if (0 == mrow) { mrow = 8; } if (srow > maxrow) { srow = maxrow - mrow; } if (mrow < (maxrow - srow)) { maxrow = srow + mrow; } for (row = srow; row < maxrow; row++) { idx = row * CFG_COLS; snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), idx); for (col = 0; col < CFG_COLS; col++) { if (!(col%4)) { snprintf_P(log_data, sizeof(log_data), PSTR("%s "), log_data); } snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[idx + col]); } snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data); for (col = 0; col < CFG_COLS; col++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); } snprintf_P(log_data, sizeof(log_data), PSTR("%s|"), log_data); AddLog(LOG_LEVEL_INFO); delay(1); } } void DebugCfgPeek(char* parms) { char *p; uint16_t address = strtol(parms, &p, 16); if (address > sizeof(SYSCFG)) address = sizeof(SYSCFG) -4; address = (address >> 2) << 2; uint8_t *buffer = (uint8_t *) &Settings; uint8_t data8 = buffer[address]; uint16_t data16 = (buffer[address +1] << 8) + buffer[address]; uint32_t data32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + data16; snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), address); for (uint32_t i = 0; i < 4; i++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[address +i]); } snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data); for (uint32_t i = 0; i < 4; i++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[address +i] > 0x20) && (buffer[address +i] < 0x7F)) ? (char)buffer[address +i] : ' '); } snprintf_P(log_data, sizeof(log_data), PSTR("%s| 0x%02X (%d), 0x%04X (%d), 0x%0LX (%lu)"), log_data, data8, data8, data16, data16, data32, data32); AddLog(LOG_LEVEL_INFO); } void DebugCfgPoke(char* parms) { char *p; uint16_t address = strtol(parms, &p, 16); if (address > sizeof(SYSCFG)) address = sizeof(SYSCFG) -4; address = (address >> 2) << 2; uint32_t data = strtol(p, &p, 16); uint8_t *buffer = (uint8_t *) &Settings; uint32_t data32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + (buffer[address +1] << 8) + buffer[address]; uint8_t *nbuffer = (uint8_t *) &data; for (uint32_t i = 0; i < 4; i++) { buffer[address +i] = nbuffer[+i]; } uint32_t ndata32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + (buffer[address +1] << 8) + buffer[address]; AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: 0x%0LX (%lu) poked to 0x%0LX (%lu)"), address, data32, data32, ndata32, ndata32); } void SetFlashMode(uint8_t mode) { uint8_t *_buffer; uint32_t address; address = 0; _buffer = new uint8_t[FLASH_SECTOR_SIZE]; if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) { if (_buffer[2] != mode) { _buffer[2] = mode; if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) { ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE); } } } delete[] _buffer; } void CmndHelp(void) { AddLog_P(LOG_LEVEL_INFO, PSTR("HLP: "), kDebugCommands); ResponseCmndDone(); } void CmndRtcDump(void) { DebugRtcDump(XdrvMailbox.data); ResponseCmndDone(); } void CmndCfgDump(void) { DebugCfgDump(XdrvMailbox.data); ResponseCmndDone(); } void CmndCfgPeek(void) { DebugCfgPeek(XdrvMailbox.data); ResponseCmndDone(); } void CmndCfgPoke(void) { DebugCfgPoke(XdrvMailbox.data); ResponseCmndDone(); } #ifdef USE_WEBSERVER void CmndCfgXor(void) { if (XdrvMailbox.data_len > 0) { Web.config_xor_on_set = XdrvMailbox.payload; } ResponseCmndNumber(Web.config_xor_on_set); } #endif #ifdef DEBUG_THEO void CmndException(void) { if (XdrvMailbox.data_len > 0) { ExceptionTest(XdrvMailbox.payload); } ResponseCmndDone(); } #endif void CmndCpuCheck(void) { if (XdrvMailbox.data_len > 0) { CPU_load_check = XdrvMailbox.payload; CPU_last_millis = CPU_last_loop_time; } ResponseCmndNumber(CPU_load_check); } void CmndFreemem(void) { if (XdrvMailbox.data_len > 0) { CPU_show_freemem = XdrvMailbox.payload; } ResponseCmndNumber(CPU_show_freemem); } void CmndSetSensor(void) { if (XdrvMailbox.index < MAX_XSNS_DRIVERS) { if (XdrvMailbox.payload >= 0) { bitWrite(Settings.sensors[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1); if (1 == XdrvMailbox.payload) { restart_flag = 2; } } Response_P(PSTR("{\"" D_CMND_SETSENSOR "\":")); XsnsSensorState(); ResponseJsonEnd(); } } void CmndFlashMode(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) { SetFlashMode(XdrvMailbox.payload); } ResponseCmndNumber(ESP.getFlashChipMode()); } uint32_t DebugSwap32(uint32_t x) { return ((x << 24) & 0xff000000 ) | ((x << 8) & 0x00ff0000 ) | ((x >> 8) & 0x0000ff00 ) | ((x >> 24) & 0x000000ff ); } void CmndFlashDump(void) { const uint32_t flash_start = 0x40200000; const uint8_t bytes_per_cols = 0x20; const uint32_t max = (SPIFFS_END + 5) * SPI_FLASH_SEC_SIZE; uint32_t start = flash_start; uint32_t rows = 8; if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= (max - bytes_per_cols))) { start += (XdrvMailbox.payload &0x7FFFFFFC); char *p; uint32_t is_payload = strtol(XdrvMailbox.data, &p, 16); rows = strtol(p, &p, 10); if (0 == rows) { rows = 8; } } uint32_t end = start + (rows * bytes_per_cols); if ((end - flash_start) > max) { end = flash_start + max; } for (uint32_t pos = start; pos < end; pos += bytes_per_cols) { uint32_t* values = (uint32_t*)(pos); AddLog_P2(LOG_LEVEL_INFO, PSTR("%06X: %08X %08X %08X %08X %08X %08X %08X %08X"), pos - flash_start, DebugSwap32(values[0]), DebugSwap32(values[1]), DebugSwap32(values[2]), DebugSwap32(values[3]), DebugSwap32(values[4]), DebugSwap32(values[5]), DebugSwap32(values[6]), DebugSwap32(values[7])); } ResponseCmndDone(); } #ifdef USE_I2C void CmndI2cWrite(void) { if (i2c_flg) { char* parms = XdrvMailbox.data; uint8_t buffer[100]; uint32_t index = 0; char *p; char *data = strtok_r(parms, " ,", &p); while (data != NULL && index < sizeof(buffer)) { buffer[index++] = strtol(data, nullptr, 16); data = strtok_r(nullptr, " ,", &p); } if (index > 1) { AddLogBuffer(LOG_LEVEL_INFO, buffer, index); Wire.beginTransmission(buffer[0]); for (uint32_t i = 1; i < index; i++) { Wire.write(buffer[i]); } int result = Wire.endTransmission(); AddLog_P2(LOG_LEVEL_INFO, PSTR("I2C: Result %d"), result); } } ResponseCmndDone(); } void CmndI2cRead(void) { if (i2c_flg) { char* parms = XdrvMailbox.data; uint8_t buffer[100]; uint32_t index = 0; char *p; char *data = strtok_r(parms, " ,", &p); while (data != NULL && index < sizeof(buffer)) { buffer[index++] = strtol(data, nullptr, 16); data = strtok_r(nullptr, " ,", &p); } if (index > 0) { uint8_t size = 1; if (index > 1) { size = buffer[1]; } Wire.requestFrom(buffer[0], size); index = 0; while (Wire.available() && index < sizeof(buffer)) { buffer[index++] = Wire.read(); } if (index > 0) { AddLogBuffer(LOG_LEVEL_INFO, buffer, index); } } } ResponseCmndDone(); } void CmndI2cStretch(void) { if (i2c_flg && (XdrvMailbox.payload > 0)) { Wire.setClockStretchLimit(XdrvMailbox.payload); } ResponseCmndDone(); } void CmndI2cClock(void) { if (i2c_flg && (XdrvMailbox.payload > 0)) { Wire.setClock(XdrvMailbox.payload); } ResponseCmndDone(); } #endif bool Xdrv99(uint8_t function) { bool result = false; switch (function) { case FUNC_LOOP: CpuLoadLoop(); break; case FUNC_FREE_MEM: if (CPU_show_freemem) { DebugFreeMem(); } break; case FUNC_PRE_INIT: CPU_last_millis = millis(); break; case FUNC_COMMAND: result = DecodeCommand(kDebugCommands, DebugCommand); break; } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_interface.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_interface.ino" #ifdef XFUNC_PTR_IN_ROM bool (* const xdrv_func_ptr[])(uint8_t) PROGMEM = { #else bool (* const xdrv_func_ptr[])(uint8_t) = { #endif #ifdef XDRV_01 &Xdrv01, #endif #ifdef XDRV_02 &Xdrv02, #endif #ifdef XDRV_03 &Xdrv03, #endif #ifdef XDRV_04 &Xdrv04, #endif #ifdef XDRV_05 &Xdrv05, #endif #ifdef XDRV_06 &Xdrv06, #endif #ifdef XDRV_07 &Xdrv07, #endif #ifdef XDRV_08 &Xdrv08, #endif #ifdef XDRV_09 &Xdrv09, #endif #ifdef XDRV_10 &Xdrv10, #endif #ifdef XDRV_11 &Xdrv11, #endif #ifdef XDRV_12 &Xdrv12, #endif #ifdef XDRV_13 &Xdrv13, #endif #ifdef XDRV_14 &Xdrv14, #endif #ifdef XDRV_15 &Xdrv15, #endif #ifdef XDRV_16 &Xdrv16, #endif #ifdef XDRV_17 &Xdrv17, #endif #ifdef XDRV_18 &Xdrv18, #endif #ifdef XDRV_19 &Xdrv19, #endif #ifdef XDRV_20 &Xdrv20, #endif #ifdef XDRV_21 &Xdrv21, #endif #ifdef XDRV_22 &Xdrv22, #endif #ifdef XDRV_23 &Xdrv23, #endif #ifdef XDRV_24 &Xdrv24, #endif #ifdef XDRV_25 &Xdrv25, #endif #ifdef XDRV_26 &Xdrv26, #endif #ifdef XDRV_27 &Xdrv27, #endif #ifdef XDRV_28 &Xdrv28, #endif #ifdef XDRV_29 &Xdrv29, #endif #ifdef XDRV_30 &Xdrv30, #endif #ifdef XDRV_31 &Xdrv31, #endif #ifdef XDRV_32 &Xdrv32, #endif #ifdef XDRV_33 &Xdrv33, #endif #ifdef XDRV_34 &Xdrv34, #endif #ifdef XDRV_35 &Xdrv35, #endif #ifdef XDRV_36 &Xdrv36, #endif #ifdef XDRV_37 &Xdrv37, #endif #ifdef XDRV_38 &Xdrv38, #endif #ifdef XDRV_39 &Xdrv39, #endif #ifdef XDRV_40 &Xdrv40, #endif #ifdef XDRV_41 &Xdrv41, #endif #ifdef XDRV_42 &Xdrv42, #endif #ifdef XDRV_43 &Xdrv43, #endif #ifdef XDRV_44 &Xdrv44, #endif #ifdef XDRV_45 &Xdrv45, #endif #ifdef XDRV_46 &Xdrv46, #endif #ifdef XDRV_47 &Xdrv47, #endif #ifdef XDRV_48 &Xdrv48, #endif #ifdef XDRV_49 &Xdrv49, #endif #ifdef XDRV_50 &Xdrv50, #endif #ifdef XDRV_51 &Xdrv51, #endif #ifdef XDRV_52 &Xdrv52, #endif #ifdef XDRV_53 &Xdrv53, #endif #ifdef XDRV_54 &Xdrv54, #endif #ifdef XDRV_55 &Xdrv55, #endif #ifdef XDRV_56 &Xdrv56, #endif #ifdef XDRV_57 &Xdrv57, #endif #ifdef XDRV_58 &Xdrv58, #endif #ifdef XDRV_59 &Xdrv59, #endif #ifdef XDRV_60 &Xdrv60, #endif #ifdef XDRV_61 &Xdrv61, #endif #ifdef XDRV_62 &Xdrv62, #endif #ifdef XDRV_63 &Xdrv63, #endif #ifdef XDRV_64 &Xdrv64, #endif #ifdef XDRV_65 &Xdrv65, #endif #ifdef XDRV_66 &Xdrv66, #endif #ifdef XDRV_67 &Xdrv67, #endif #ifdef XDRV_68 &Xdrv68, #endif #ifdef XDRV_69 &Xdrv69, #endif #ifdef XDRV_70 &Xdrv70, #endif #ifdef XDRV_71 &Xdrv71, #endif #ifdef XDRV_72 &Xdrv72, #endif #ifdef XDRV_73 &Xdrv73, #endif #ifdef XDRV_74 &Xdrv74, #endif #ifdef XDRV_75 &Xdrv75, #endif #ifdef XDRV_76 &Xdrv76, #endif #ifdef XDRV_77 &Xdrv77, #endif #ifdef XDRV_78 &Xdrv78, #endif #ifdef XDRV_79 &Xdrv79, #endif #ifdef XDRV_80 &Xdrv80, #endif #ifdef XDRV_81 &Xdrv81, #endif #ifdef XDRV_82 &Xdrv82, #endif #ifdef XDRV_83 &Xdrv83, #endif #ifdef XDRV_84 &Xdrv84, #endif #ifdef XDRV_85 &Xdrv85, #endif #ifdef XDRV_86 &Xdrv86, #endif #ifdef XDRV_87 &Xdrv87, #endif #ifdef XDRV_88 &Xdrv88, #endif #ifdef XDRV_89 &Xdrv89, #endif #ifdef XDRV_90 &Xdrv90, #endif #ifdef XDRV_91 &Xdrv91, #endif #ifdef XDRV_92 &Xdrv92, #endif #ifdef XDRV_93 &Xdrv93, #endif #ifdef XDRV_94 &Xdrv94, #endif #ifdef XDRV_95 &Xdrv95, #endif #ifdef XDRV_96 &Xdrv96, #endif #ifdef XDRV_97 &Xdrv97, #endif #ifdef XDRV_98 &Xdrv98, #endif #ifdef XDRV_99 &Xdrv99 #endif }; const uint8_t xdrv_present = sizeof(xdrv_func_ptr) / sizeof(xdrv_func_ptr[0]); #ifdef XFUNC_PTR_IN_ROM const uint8_t kXdrvList[] PROGMEM = { #else const uint8_t kXdrvList[] = { #endif #ifdef XDRV_01 XDRV_01, #endif #ifdef XDRV_02 XDRV_02, #endif #ifdef XDRV_03 XDRV_03, #endif #ifdef XDRV_04 XDRV_04, #endif #ifdef XDRV_05 XDRV_05, #endif #ifdef XDRV_06 XDRV_06, #endif #ifdef XDRV_07 XDRV_07, #endif #ifdef XDRV_08 XDRV_08, #endif #ifdef XDRV_09 XDRV_09, #endif #ifdef XDRV_10 XDRV_10, #endif #ifdef XDRV_11 XDRV_11, #endif #ifdef XDRV_12 XDRV_12, #endif #ifdef XDRV_13 XDRV_13, #endif #ifdef XDRV_14 XDRV_14, #endif #ifdef XDRV_15 XDRV_15, #endif #ifdef XDRV_16 XDRV_16, #endif #ifdef XDRV_17 XDRV_17, #endif #ifdef XDRV_18 XDRV_18, #endif #ifdef XDRV_19 XDRV_19, #endif #ifdef XDRV_20 XDRV_20, #endif #ifdef XDRV_21 XDRV_21, #endif #ifdef XDRV_22 XDRV_22, #endif #ifdef XDRV_23 XDRV_23, #endif #ifdef XDRV_24 XDRV_24, #endif #ifdef XDRV_25 XDRV_25, #endif #ifdef XDRV_26 XDRV_26, #endif #ifdef XDRV_27 XDRV_27, #endif #ifdef XDRV_28 XDRV_28, #endif #ifdef XDRV_29 XDRV_29, #endif #ifdef XDRV_30 XDRV_30, #endif #ifdef XDRV_31 XDRV_31, #endif #ifdef XDRV_32 XDRV_32, #endif #ifdef XDRV_33 XDRV_33, #endif #ifdef XDRV_34 XDRV_34, #endif #ifdef XDRV_35 XDRV_35, #endif #ifdef XDRV_36 XDRV_36, #endif #ifdef XDRV_37 XDRV_37, #endif #ifdef XDRV_38 XDRV_38, #endif #ifdef XDRV_39 XDRV_39, #endif #ifdef XDRV_40 XDRV_40, #endif #ifdef XDRV_41 XDRV_41, #endif #ifdef XDRV_42 XDRV_42, #endif #ifdef XDRV_43 XDRV_43, #endif #ifdef XDRV_44 XDRV_44, #endif #ifdef XDRV_45 XDRV_45, #endif #ifdef XDRV_46 XDRV_46, #endif #ifdef XDRV_47 XDRV_47, #endif #ifdef XDRV_48 XDRV_48, #endif #ifdef XDRV_49 XDRV_49, #endif #ifdef XDRV_50 XDRV_50, #endif #ifdef XDRV_51 XDRV_51, #endif #ifdef XDRV_52 XDRV_52, #endif #ifdef XDRV_53 XDRV_53, #endif #ifdef XDRV_54 XDRV_54, #endif #ifdef XDRV_55 XDRV_55, #endif #ifdef XDRV_56 XDRV_56, #endif #ifdef XDRV_57 XDRV_57, #endif #ifdef XDRV_58 XDRV_58, #endif #ifdef XDRV_59 XDRV_59, #endif #ifdef XDRV_60 XDRV_60, #endif #ifdef XDRV_61 XDRV_61, #endif #ifdef XDRV_62 XDRV_62, #endif #ifdef XDRV_63 XDRV_63, #endif #ifdef XDRV_64 XDRV_64, #endif #ifdef XDRV_65 XDRV_65, #endif #ifdef XDRV_66 XDRV_66, #endif #ifdef XDRV_67 XDRV_67, #endif #ifdef XDRV_68 XDRV_68, #endif #ifdef XDRV_69 XDRV_69, #endif #ifdef XDRV_70 XDRV_70, #endif #ifdef XDRV_71 XDRV_71, #endif #ifdef XDRV_72 XDRV_72, #endif #ifdef XDRV_73 XDRV_73, #endif #ifdef XDRV_74 XDRV_74, #endif #ifdef XDRV_75 XDRV_75, #endif #ifdef XDRV_76 XDRV_76, #endif #ifdef XDRV_77 XDRV_77, #endif #ifdef XDRV_78 XDRV_78, #endif #ifdef XDRV_79 XDRV_79, #endif #ifdef XDRV_80 XDRV_80, #endif #ifdef XDRV_81 XDRV_81, #endif #ifdef XDRV_82 XDRV_82, #endif #ifdef XDRV_83 XDRV_83, #endif #ifdef XDRV_84 XDRV_84, #endif #ifdef XDRV_85 XDRV_85, #endif #ifdef XDRV_86 XDRV_86, #endif #ifdef XDRV_87 XDRV_87, #endif #ifdef XDRV_88 XDRV_88, #endif #ifdef XDRV_89 XDRV_89, #endif #ifdef XDRV_90 XDRV_90, #endif #ifdef XDRV_91 XDRV_91, #endif #ifdef XDRV_92 XDRV_92, #endif #ifdef XDRV_93 XDRV_93, #endif #ifdef XDRV_94 XDRV_94, #endif #ifdef XDRV_95 XDRV_95, #endif #ifdef XDRV_96 XDRV_96, #endif #ifdef XDRV_97 XDRV_97, #endif #ifdef XDRV_98 XDRV_98, #endif #ifdef XDRV_99 XDRV_99 #endif }; void XsnsDriverState(void) { ResponseAppend_P(PSTR(",\"Drivers\":\"")); for (uint32_t i = 0; i < sizeof(kXdrvList); i++) { #ifdef XFUNC_PTR_IN_ROM uint32_t driverid = pgm_read_byte(kXdrvList + i); #else uint32_t driverid = kXdrvList[i]; #endif ResponseAppend_P(PSTR("%s%d"), (i) ? "," : "", driverid); } ResponseAppend_P(PSTR("\"")); } bool XdrvRulesProcess(void) { return XdrvCallDriver(10, FUNC_RULES_PROCESS); } #ifdef USE_DEBUG_DRIVER void ShowFreeMem(const char *where) { char stemp[30]; snprintf_P(stemp, sizeof(stemp), where); XdrvMailbox.data = stemp; XdrvCall(FUNC_FREE_MEM); } #endif bool XdrvCallDriver(uint32_t driver, uint8_t Function) { for (uint32_t x = 0; x < xdrv_present; x++) { #ifdef XFUNC_PTR_IN_ROM uint32_t listed = pgm_read_byte(kXdrvList + x); #else uint32_t listed = kXdrvList[x]; #endif if (driver == listed) { return xdrv_func_ptr[x](Function); } } return false; } bool XdrvCall(uint8_t Function) { bool result = false; DEBUG_TRACE_LOG(PSTR("DRV: %d"), Function); for (uint32_t x = 0; x < xdrv_present; x++) { result = xdrv_func_ptr[x](Function); if (result && ((FUNC_COMMAND == Function) || (FUNC_COMMAND_DRIVER == Function) || (FUNC_MQTT_DATA == Function) || (FUNC_RULES_PROCESS == Function) || (FUNC_BUTTON_PRESSED == Function) || (FUNC_SERIAL == Function) || (FUNC_MODULE_INIT == Function) || (FUNC_SET_CHANNELS == Function) || (FUNC_PIN_STATE == Function) || (FUNC_SET_DEVICE_POWER == Function) )) { break; } } return result; } # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino" #ifdef USE_I2C #ifdef USE_DISPLAY #ifdef USE_DISPLAY_LCD #define XDSP_01 1 #define XI2C_03 3 #define LCD_ADDRESS1 0x27 #define LCD_ADDRESS2 0x3F #include #include LiquidCrystal_I2C *lcd; void LcdInitMode(void) { lcd->init(); lcd->clear(); } void LcdInit(uint8_t mode) { switch(mode) { case DISPLAY_INIT_MODE: LcdInitMode(); #ifdef USE_DISPLAY_MODES1TO5 DisplayClearScreenBuffer(); #endif break; case DISPLAY_INIT_PARTIAL: case DISPLAY_INIT_FULL: break; } } void LcdInitDriver(void) { if (!Settings.display_model) { if (I2cSetDevice(LCD_ADDRESS1)) { Settings.display_address[0] = LCD_ADDRESS1; Settings.display_model = XDSP_01; } else if (I2cSetDevice(LCD_ADDRESS2)) { Settings.display_address[0] = LCD_ADDRESS2; Settings.display_model = XDSP_01; } } if (XDSP_01 == Settings.display_model) { I2cSetActiveFound(Settings.display_address[0], "LCD"); Settings.display_width = Settings.display_cols[0]; Settings.display_height = Settings.display_rows; lcd = new LiquidCrystal_I2C(Settings.display_address[0], Settings.display_cols[0], Settings.display_rows); #ifdef USE_DISPLAY_MODES1TO5 DisplayAllocScreenBuffer(); #endif LcdInitMode(); } } void LcdDrawStringAt(void) { if (dsp_flag) { dsp_x--; dsp_y--; } lcd->setCursor(dsp_x, dsp_y); lcd->print(dsp_str); } void LcdDisplayOnOff(uint8_t on) { if (on) { lcd->backlight(); } else { lcd->noBacklight(); } } #ifdef USE_DISPLAY_MODES1TO5 void LcdCenter(uint8_t row, char* txt) { char line[Settings.display_cols[0] +2]; int len = strlen(txt); int offset = 0; if (len >= Settings.display_cols[0]) { len = Settings.display_cols[0]; } else { offset = (Settings.display_cols[0] - len) / 2; } memset(line, 0x20, Settings.display_cols[0]); line[Settings.display_cols[0]] = 0; for (uint32_t i = 0; i < len; i++) { line[offset +i] = txt[i]; } lcd->setCursor(0, row); lcd->print(line); } bool LcdPrintLog(void) { bool result = false; disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } char* txt = DisplayLogBuffer('\337'); if (txt != nullptr) { uint8_t last_row = Settings.display_rows -1; for (uint32_t i = 0; i < last_row; i++) { strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); lcd->setCursor(0, i); lcd->print(disp_screen_buffer[i +1]); } strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); lcd->setCursor(0, last_row); lcd->print(disp_screen_buffer[last_row]); result = true; } } return result; } void LcdTime(void) { char line[Settings.display_cols[0] +1]; snprintf_P(line, sizeof(line), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); LcdCenter(0, line); snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); LcdCenter(1, line); } void LcdRefresh(void) { if (Settings.display_mode) { switch (Settings.display_mode) { case 1: LcdTime(); break; case 2: case 4: LcdPrintLog(); break; case 3: case 5: { if (!LcdPrintLog()) { LcdTime(); } break; } } } } #endif bool Xdsp01(uint8_t function) { if (!I2cEnabled(XI2C_03)) { return false; } bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { LcdInitDriver(); } else if (XDSP_01 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; case FUNC_DISPLAY_INIT: LcdInit(dsp_init); break; case FUNC_DISPLAY_POWER: LcdDisplayOnOff(disp_power); break; case FUNC_DISPLAY_CLEAR: lcd->clear(); break; # 238 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino" case FUNC_DISPLAY_DRAW_STRING: LcdDrawStringAt(); break; case FUNC_DISPLAY_ONOFF: LcdDisplayOnOff(dsp_on); break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: LcdRefresh(); break; #endif } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_02_ssd1306.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_02_ssd1306.ino" #ifdef USE_I2C #ifdef USE_DISPLAY #ifdef USE_DISPLAY_SSD1306 #define XDSP_02 2 #define XI2C_04 4 #define OLED_RESET 4 #define SPRINT(A) char str[32];sprintf(str,"val: %d ",A);Serial.println((char*)str); #define OLED_ADDRESS1 0x3C #define OLED_ADDRESS2 0x3D #define OLED_BUFFER_COLS 40 #define OLED_BUFFER_ROWS 16 #define OLED_FONT_WIDTH 6 #define OLED_FONT_HEIGTH 8 #include #include #include Adafruit_SSD1306 *oled1306; extern uint8_t *buffer; void SSD1306InitDriver(void) { if (!Settings.display_model) { if (I2cSetDevice(OLED_ADDRESS1)) { Settings.display_address[0] = OLED_ADDRESS1; Settings.display_model = XDSP_02; } else if (I2cSetDevice(OLED_ADDRESS2)) { Settings.display_address[0] = OLED_ADDRESS2; Settings.display_model = XDSP_02; } } if (XDSP_02 == Settings.display_model) { I2cSetActiveFound(Settings.display_address[0], "SSD1306"); if ((Settings.display_width != 64) && (Settings.display_width != 96) && (Settings.display_width != 128)) { Settings.display_width = 128; } if ((Settings.display_height != 16) && (Settings.display_height != 32) && (Settings.display_height != 48) && (Settings.display_height != 64)) { Settings.display_height = 64; } uint8_t reset_pin = -1; if (pin[GPIO_OLED_RESET] < 99) { reset_pin = pin[GPIO_OLED_RESET]; } if (buffer) { free(buffer); } buffer = (unsigned char*)calloc((Settings.display_width * Settings.display_height) / 8,1); if (!buffer) { return; } oled1306 = new Adafruit_SSD1306(Settings.display_width, Settings.display_height, &Wire, reset_pin); oled1306->begin(SSD1306_SWITCHCAPVCC, Settings.display_address[0], reset_pin >= 0); renderer = oled1306; renderer->DisplayInit(DISPLAY_INIT_MODE, Settings.display_size, Settings.display_rotate, Settings.display_font); renderer->setTextColor(1,0); #ifdef SHOW_SPLASH renderer->setTextFont(0); renderer->setTextSize(2); renderer->setCursor(20,20); renderer->println(F("SSD1306")); renderer->Updateframe(); renderer->DisplayOnff(1); #endif } } #ifdef USE_DISPLAY_MODES1TO5 void Ssd1306PrintLog(void) { disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } char* txt = DisplayLogBuffer('\370'); if (txt != NULL) { uint8_t last_row = Settings.display_rows -1; renderer->clearDisplay(); renderer->setTextSize(Settings.display_size); renderer->setCursor(0,0); for (byte i = 0; i < last_row; i++) { strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); renderer->println(disp_screen_buffer[i]); } strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]); renderer->Updateframe(); } } } void Ssd1306Time(void) { char line[12]; renderer->clearDisplay(); renderer->setTextSize(Settings.display_size); renderer->setTextFont(Settings.display_font); renderer->setCursor(0, 0); snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); renderer->println(line); snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); renderer->println(line); renderer->Updateframe(); } void Ssd1306Refresh(void) { if (!renderer) return; if (Settings.display_mode) { switch (Settings.display_mode) { case 1: Ssd1306Time(); break; case 2: case 3: case 4: case 5: Ssd1306PrintLog(); break; } } } #endif bool Xdsp02(byte function) { if (!I2cEnabled(XI2C_04)) { return false; } bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { SSD1306InitDriver(); } else if (XDSP_02 == Settings.display_model) { switch (function) { #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: Ssd1306Refresh(); break; #endif case FUNC_DISPLAY_MODEL: result = true; break; } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_03_matrix.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_03_matrix.ino" #ifdef USE_I2C #ifdef USE_DISPLAY #ifdef USE_DISPLAY_MATRIX #define XDSP_03 3 #define XI2C_05 5 #define MTX_MAX_SCREEN_BUFFER 80 #include #include #include Adafruit_8x8matrix *matrix[8]; uint8_t mtx_matrices = 0; uint8_t mtx_state = 0; uint8_t mtx_counter = 0; int16_t mtx_x = 0; int16_t mtx_y = 0; char *mtx_buffer = nullptr; uint8_t mtx_mode = 0; uint8_t mtx_loop = 0; uint8_t mtx_done = 0; void MatrixWrite(void) { for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->writeDisplay(); } } void MatrixClear(void) { for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->clear(); } MatrixWrite(); } void MatrixFixed(char* txt) { for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->clear(); matrix[i]->setCursor(-i *8, 0); matrix[i]->print(txt); matrix[i]->setBrightness(Settings.display_dimmer); } MatrixWrite(); } void MatrixCenter(char* txt) { int offset; int len = strlen(txt); offset = (len < 8) ? offset = ((mtx_matrices *8) - (len *6)) / 2 : 0; for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->clear(); matrix[i]->setCursor(-(i *8)+offset, 0); matrix[i]->print(txt); matrix[i]->setBrightness(Settings.display_dimmer); } MatrixWrite(); } void MatrixScrollLeft(char* txt, int loop) { switch (mtx_state) { case 1: mtx_state = 2; mtx_x = 8 * mtx_matrices; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), txt); disp_refresh = Settings.display_refresh; case 2: disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->clear(); matrix[i]->setCursor(mtx_x - i *8, 0); matrix[i]->print(txt); matrix[i]->setBrightness(Settings.display_dimmer); } MatrixWrite(); mtx_x--; int16_t len = strlen(txt); if (mtx_x < -(len *6)) { mtx_state = loop; } } break; } } void MatrixScrollUp(char* txt, int loop) { int wordcounter = 0; char tmpbuf[200]; char *words[100]; char separators[] = " /"; switch (mtx_state) { case 1: mtx_state = 2; mtx_y = 8; mtx_counter = 0; disp_refresh = Settings.display_refresh; case 2: disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; strlcpy(tmpbuf, txt, sizeof(tmpbuf)); char *p = strtok(tmpbuf, separators); while (p != nullptr && wordcounter < 40) { words[wordcounter++] = p; p = strtok(nullptr, separators); } for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->clear(); for (uint32_t j = 0; j < wordcounter; j++) { matrix[i]->setCursor(-i *8, mtx_y + (j *8)); matrix[i]->println(words[j]); } matrix[i]->setBrightness(Settings.display_dimmer); } MatrixWrite(); if (((mtx_y %8) == 0) && mtx_counter) { mtx_counter--; } else { mtx_y--; mtx_counter = STATES * 1; } if (mtx_y < -(wordcounter *8)) { mtx_state = loop; } } break; } } void MatrixInitMode(void) { for (uint32_t i = 0; i < mtx_matrices; i++) { matrix[i]->setRotation(Settings.display_rotate); matrix[i]->setBrightness(Settings.display_dimmer); matrix[i]->blinkRate(0); matrix[i]->setTextWrap(false); matrix[i]->cp437(true); } MatrixClear(); } void MatrixInit(uint8_t mode) { switch(mode) { case DISPLAY_INIT_MODE: MatrixInitMode(); break; case DISPLAY_INIT_PARTIAL: case DISPLAY_INIT_FULL: break; } } void MatrixInitDriver(void) { mtx_buffer = (char*)(malloc(MTX_MAX_SCREEN_BUFFER)); if (mtx_buffer != nullptr) { if (!Settings.display_model) { if (I2cSetDevice(Settings.display_address[1])) { Settings.display_model = XDSP_03; } } if (XDSP_03 == Settings.display_model) { mtx_state = 1; for (mtx_matrices = 0; mtx_matrices < 8; mtx_matrices++) { if (Settings.display_address[mtx_matrices]) { I2cSetActiveFound(Settings.display_address[mtx_matrices], "8x8Matrix"); matrix[mtx_matrices] = new Adafruit_8x8matrix(); matrix[mtx_matrices]->begin(Settings.display_address[mtx_matrices]); } else { break; } } Settings.display_width = mtx_matrices * 8; Settings.display_height = 8; MatrixInitMode(); } } } void MatrixOnOff(void) { if (!disp_power) { MatrixClear(); } } void MatrixDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag) { strlcpy(mtx_buffer, str, MTX_MAX_SCREEN_BUFFER); mtx_mode = x &1; mtx_loop = y &1; if (!mtx_state) { mtx_state = 1; } } #ifdef USE_DISPLAY_MODES1TO5 void MatrixPrintLog(uint8_t direction) { char* txt = (!mtx_done) ? DisplayLogBuffer('\370') : mtx_buffer; if (txt != nullptr) { if (!mtx_state) { mtx_state = 1; } if (!mtx_done) { uint8_t space = 0; uint8_t max_cols = (disp_log_buffer_cols < MTX_MAX_SCREEN_BUFFER) ? disp_log_buffer_cols : MTX_MAX_SCREEN_BUFFER; mtx_buffer[0] = '\0'; uint8_t i = 0; while ((txt[i] != '\0') && (i < max_cols)) { if (txt[i] == ' ') { space++; } else { space = 0; } if (space < 2) { strncat(mtx_buffer, (const char*)txt +i, (strlen(mtx_buffer) < MTX_MAX_SCREEN_BUFFER -1) ? 1 : 0); } i++; } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), mtx_buffer); mtx_done = 1; } if (direction) { MatrixScrollUp(mtx_buffer, 0); } else { MatrixScrollLeft(mtx_buffer, 0); } if (!mtx_state) { mtx_done = 0; } } else { char disp_time[9]; snprintf_P(disp_time, sizeof(disp_time), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); MatrixFixed(disp_time); } } #endif void MatrixRefresh(void) { if (disp_power) { switch (Settings.display_mode) { case 0: { switch (mtx_mode) { case 0: MatrixScrollLeft(mtx_buffer, mtx_loop); break; case 1: MatrixScrollUp(mtx_buffer, mtx_loop); break; } break; } #ifdef USE_DISPLAY_MODES1TO5 case 2: { char disp_date[9]; snprintf_P(disp_date, sizeof(disp_date), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year -2000); MatrixFixed(disp_date); break; } case 3: { char disp_day[10]; snprintf_P(disp_day, sizeof(disp_day), PSTR("%d %s"), RtcTime.day_of_month, RtcTime.name_of_month); MatrixCenter(disp_day); break; } case 4: MatrixPrintLog(0); break; case 1: case 5: MatrixPrintLog(1); break; #endif } } } bool Xdsp03(uint8_t function) { if (!I2cEnabled(XI2C_05)) { return false; } bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { MatrixInitDriver(); } else if (XDSP_03 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; case FUNC_DISPLAY_INIT: MatrixInit(dsp_init); break; case FUNC_DISPLAY_EVERY_50_MSECOND: MatrixRefresh(); break; case FUNC_DISPLAY_POWER: MatrixOnOff(); break; case FUNC_DISPLAY_DRAW_STRING: MatrixDrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag); break; } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_04_ili9341.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_04_ili9341.ino" #ifdef USE_SPI #ifdef USE_DISPLAY #ifdef USE_DISPLAY_ILI9341 #define XDSP_04 4 #define TFT_TOP 16 #define TFT_BOTTOM 16 #define TFT_FONT_WIDTH 6 #define TFT_FONT_HEIGTH 8 #include #include #include Adafruit_ILI9341 *tft; uint16_t tft_scroll; void Ili9341InitMode(void) { tft->setRotation(Settings.display_rotate); tft->invertDisplay(0); tft->fillScreen(ILI9341_BLACK); tft->setTextWrap(false); tft->cp437(true); if (!Settings.display_mode) { tft->setCursor(0, 0); tft->setTextColor(ILI9341_WHITE, ILI9341_BLACK); tft->setTextSize(1); } else { tft->setScrollMargins(TFT_TOP, TFT_BOTTOM); tft->setCursor(0, 0); tft->setTextColor(ILI9341_YELLOW, ILI9341_BLACK); tft->setTextSize(2); tft_scroll = TFT_TOP; } } void Ili9341Init(uint8_t mode) { switch(mode) { case DISPLAY_INIT_MODE: Ili9341InitMode(); #ifdef USE_DISPLAY_MODES1TO5 if (Settings.display_rotate) { DisplayClearScreenBuffer(); } #endif break; case DISPLAY_INIT_PARTIAL: case DISPLAY_INIT_FULL: break; } } void Ili9341InitDriver(void) { if (!Settings.display_model) { Settings.display_model = XDSP_04; } if (XDSP_04 == Settings.display_model) { if (Settings.display_width != ILI9341_TFTWIDTH) { Settings.display_width = ILI9341_TFTWIDTH; } if (Settings.display_height != ILI9341_TFTHEIGHT) { Settings.display_height = ILI9341_TFTHEIGHT; } tft = new Adafruit_ILI9341(pin[GPIO_SPI_CS], pin[GPIO_SPI_DC]); tft->begin(); #ifdef USE_DISPLAY_MODES1TO5 if (Settings.display_rotate) { DisplayAllocScreenBuffer(); } #endif Ili9341InitMode(); } } void Ili9341Clear(void) { tft->fillScreen(ILI9341_BLACK); tft->setCursor(0, 0); } void Ili9341DrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag) { uint16_t active_color = ILI9341_WHITE; tft->setTextSize(Settings.display_size); if (!flag) { tft->setCursor(x, y); } else { tft->setCursor((x-1) * TFT_FONT_WIDTH * Settings.display_size, (y-1) * TFT_FONT_HEIGTH * Settings.display_size); } if (color) { active_color = color; } tft->setTextColor(active_color, ILI9341_BLACK); tft->println(str); } void Ili9341DisplayOnOff(uint8_t on) { if (pin[GPIO_BACKLIGHT] < 99) { pinMode(pin[GPIO_BACKLIGHT], OUTPUT); digitalWrite(pin[GPIO_BACKLIGHT], on); } } void Ili9341OnOff(void) { Ili9341DisplayOnOff(disp_power); } #ifdef USE_DISPLAY_MODES1TO5 void Ili9341PrintLog(void) { disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; if (Settings.display_rotate) { if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } } char* txt = DisplayLogBuffer('\370'); if (txt != nullptr) { uint8_t size = Settings.display_size; uint16_t theight = size * TFT_FONT_HEIGTH; tft->setTextSize(size); tft->setTextColor(ILI9341_CYAN, ILI9341_BLACK); if (!Settings.display_rotate) { tft->setCursor(0, tft_scroll); tft->fillRect(0, tft_scroll, tft->width(), theight, ILI9341_BLACK); tft->print(txt); tft_scroll += theight; if (tft_scroll >= (tft->height() - TFT_BOTTOM)) { tft_scroll = TFT_TOP; } tft->scrollTo(tft_scroll); } else { uint8_t last_row = Settings.display_rows -1; tft_scroll = theight; tft->setCursor(0, tft_scroll); for (uint32_t i = 0; i < last_row; i++) { strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); tft->print(disp_screen_buffer[i]); tft_scroll += theight; tft->setCursor(0, tft_scroll); delay(1); } strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); tft->print(disp_screen_buffer[last_row]); } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), txt); } } } void Ili9341Refresh(void) { if (Settings.display_mode) { char tftdt[Settings.display_cols[0] +1]; char date4[11]; char space[Settings.display_cols[0] - 17]; char time[9]; tft->setTextSize(2); tft->setTextColor(ILI9341_YELLOW, ILI9341_RED); tft->setCursor(0, 0); snprintf_P(date4, sizeof(date4), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); memset(space, 0x20, sizeof(space)); space[sizeof(space) -1] = '\0'; snprintf_P(time, sizeof(time), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); snprintf_P(tftdt, sizeof(tftdt), PSTR("%s%s%s"), date4, space, time); tft->print(tftdt); switch (Settings.display_mode) { case 1: case 2: case 3: case 4: case 5: Ili9341PrintLog(); break; } } } #endif bool Xdsp04(uint8_t function) { bool result = false; if (spi_flg) { if (FUNC_DISPLAY_INIT_DRIVER == function) { Ili9341InitDriver(); } else if (XDSP_04 == Settings.display_model) { if (!dsp_color) { dsp_color = ILI9341_WHITE; } switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; case FUNC_DISPLAY_INIT: Ili9341Init(dsp_init); break; case FUNC_DISPLAY_POWER: Ili9341OnOff(); break; case FUNC_DISPLAY_CLEAR: Ili9341Clear(); break; case FUNC_DISPLAY_DRAW_HLINE: tft->writeFastHLine(dsp_x, dsp_y, dsp_len, dsp_color); break; case FUNC_DISPLAY_DRAW_VLINE: tft->writeFastVLine(dsp_x, dsp_y, dsp_len, dsp_color); break; case FUNC_DISPLAY_DRAW_LINE: tft->writeLine(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color); break; case FUNC_DISPLAY_DRAW_CIRCLE: tft->drawCircle(dsp_x, dsp_y, dsp_rad, dsp_color); break; case FUNC_DISPLAY_FILL_CIRCLE: tft->fillCircle(dsp_x, dsp_y, dsp_rad, dsp_color); break; case FUNC_DISPLAY_DRAW_RECTANGLE: tft->drawRect(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color); break; case FUNC_DISPLAY_FILL_RECTANGLE: tft->fillRect(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color); break; case FUNC_DISPLAY_TEXT_SIZE: tft->setTextSize(Settings.display_size); break; case FUNC_DISPLAY_FONT_SIZE: break; case FUNC_DISPLAY_DRAW_STRING: Ili9341DrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag); break; case FUNC_DISPLAY_ONOFF: Ili9341DisplayOnOff(dsp_on); break; case FUNC_DISPLAY_ROTATION: tft->setRotation(Settings.display_rotate); break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: Ili9341Refresh(); break; #endif } } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino" #ifdef USE_SPI #ifdef USE_DISPLAY #ifdef USE_DISPLAY_EPAPER_29 #define XDSP_05 5 #define EPD_TOP 12 #define EPD_FONT_HEIGTH 12 #define COLORED 1 #define UNCOLORED 0 #define USE_TINY_FONT #include #include extern uint8_t *buffer; uint16_t epd_scroll; Epd *epd; void EpdInitDriver29() { if (!Settings.display_model) { Settings.display_model = XDSP_05; } if (XDSP_05 == Settings.display_model) { if (Settings.display_width != EPD_WIDTH) { Settings.display_width = EPD_WIDTH; } if (Settings.display_height != EPD_HEIGHT) { Settings.display_height = EPD_HEIGHT; } if (buffer) free(buffer); buffer=(unsigned char*)calloc((EPD_WIDTH * EPD_HEIGHT) / 8,1); if (!buffer) return; epd = new Epd(EPD_WIDTH,EPD_HEIGHT); if ((pin[GPIO_SPI_CS] < 99) && (pin[GPIO_SPI_CLK] < 99) && (pin[GPIO_SPI_MOSI] < 99)) { epd->Begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EPD: HardSPI CS %d, CLK %d, MOSI %d"),pin[GPIO_SPI_CS], pin[GPIO_SPI_CLK], pin[GPIO_SPI_MOSI]); } else if ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && (pin[GPIO_SSPI_MOSI] < 99)) { epd->Begin(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]); AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EPD: SoftSPI CS %d, CLK %d, MOSI %d"),pin[GPIO_SSPI_CS], pin[GPIO_SSPI_SCLK], pin[GPIO_SSPI_MOSI]); } else { free(buffer); return; } renderer = epd; epd->Init(DISPLAY_INIT_FULL); epd->Init(DISPLAY_INIT_PARTIAL); renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); renderer->setTextColor(1,0); #ifdef SHOW_SPLASH renderer->setTextFont(1); renderer->DrawStringAt(50, 50, "Waveshare E-Paper Display!", COLORED,0); renderer->Updateframe(); delay(1000); renderer->fillScreen(0); #endif } } #ifdef USE_DISPLAY_MODES1TO5 #define EPD_FONT_HEIGTH 12 void EpdPrintLog29(void) { disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } char* txt = DisplayLogBuffer('\040'); if (txt != nullptr) { uint8_t size = Settings.display_size; uint16_t theight = size * EPD_FONT_HEIGTH; renderer->setTextFont(size); uint8_t last_row = Settings.display_rows -1; epd_scroll = 0; for (uint32_t i = 0; i < last_row; i++) { strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); renderer->DrawStringAt(0, epd_scroll, disp_screen_buffer[i], COLORED, 0); epd_scroll += theight; } strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); renderer->DrawStringAt(0, epd_scroll, disp_screen_buffer[last_row], COLORED, 0); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), txt); } } } void EpdRefresh29(void) { if (Settings.display_mode) { if (!renderer) return; # 165 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino" switch (Settings.display_mode) { case 1: case 2: case 3: case 4: case 5: EpdPrintLog29(); renderer->Updateframe(); break; } } } #endif bool Xdsp05(uint8_t function) { bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { EpdInitDriver29(); } else if (XDSP_05 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: EpdRefresh29(); break; #endif } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_06_epaper_42.ino" # 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_06_epaper_42.ino" #ifdef USE_SPI #ifdef USE_DISPLAY #ifdef USE_DISPLAY_EPAPER_42 #define XDSP_06 6 #define COLORED42 1 #define UNCOLORED42 0 #define USE_TINY_FONT #include #include extern uint8_t *buffer; Epd42 *epd42; void EpdInitDriver42() { if (!Settings.display_model) { Settings.display_model = XDSP_06; } if (XDSP_06 == Settings.display_model) { if (Settings.display_width != EPD_WIDTH42) { Settings.display_width = EPD_WIDTH42; } if (Settings.display_height != EPD_HEIGHT42) { Settings.display_height = EPD_HEIGHT42; } if (buffer) free(buffer); buffer=(unsigned char*)calloc((EPD_WIDTH42 * EPD_HEIGHT42) / 8,1); if (!buffer) return; epd42 = new Epd42(EPD_WIDTH42,EPD_HEIGHT42); #ifdef USE_SPI if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)) { epd42->Begin(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]); } else { free(buffer); return; } #else if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)) { epd42->Begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]); } else { free(buffer); return; } #endif renderer = epd42; epd42->Init(); renderer->fillScreen(0); epd42->Init(DISPLAY_INIT_FULL); renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); epd42->ClearFrame(); renderer->Updateframe(); delay(3000); renderer->setTextColor(1,0); #ifdef SHOW_SPLASH renderer->setTextFont(2); renderer->DrawStringAt(50, 140, "Waveshare E-Paper!", COLORED42,0); renderer->Updateframe(); delay(350); renderer->fillScreen(0); #endif } } #ifdef USE_DISPLAY_MODES1TO5 void EpdRefresh42() { if (Settings.display_mode) { } } #endif bool Xdsp06(uint8_t function) { bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { EpdInitDriver42(); } else if (XDSP_06 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: EpdRefresh42(); break; #endif } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_07_sh1106.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_07_sh1106.ino" #ifdef USE_I2C #ifdef USE_DISPLAY #ifdef USE_DISPLAY_SH1106 #define OLED_RESET 4 #define SPRINT(A) char str[32];sprintf(str,"val: %d ",A);Serial.println((char*)str); extern uint8_t *buffer; #define XDSP_07 7 #define XI2C_06 6 #define OLED_ADDRESS1 0x3C #define OLED_ADDRESS2 0x3D #define OLED_BUFFER_COLS 40 #define OLED_BUFFER_ROWS 16 #define OLED_FONT_WIDTH 6 #define OLED_FONT_HEIGTH 8 #include #include #include Adafruit_SH1106 *oled1106; void SH1106InitDriver() { if (!Settings.display_model) { if (I2cSetDevice(OLED_ADDRESS1)) { Settings.display_address[0] = OLED_ADDRESS1; Settings.display_model = XDSP_07; } else if (I2cSetDevice(OLED_ADDRESS2)) { Settings.display_address[0] = OLED_ADDRESS2; Settings.display_model = XDSP_07; } } if (XDSP_07 == Settings.display_model) { I2cSetActiveFound(Settings.display_address[0], "SH1106"); if (Settings.display_width != SH1106_LCDWIDTH) { Settings.display_width = SH1106_LCDWIDTH; } if (Settings.display_height != SH1106_LCDHEIGHT) { Settings.display_height = SH1106_LCDHEIGHT; } if (buffer) free(buffer); buffer=(unsigned char*)calloc((SH1106_LCDWIDTH * SH1106_LCDHEIGHT) / 8,1); if (!buffer) return; oled1106 = new Adafruit_SH1106(SH1106_LCDWIDTH,SH1106_LCDHEIGHT); renderer=oled1106; renderer->Begin(SH1106_SWITCHCAPVCC, Settings.display_address[0],0); renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); renderer->setTextColor(1,0); #ifdef SHOW_SPLASH renderer->setTextFont(0); renderer->setTextSize(2); renderer->setCursor(20,20); renderer->println(F("SH1106")); renderer->Updateframe(); renderer->DisplayOnff(1); #endif } } #ifdef USE_DISPLAY_MODES1TO5 void SH1106PrintLog(void) { disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } char* txt = DisplayLogBuffer('\370'); if (txt != NULL) { uint8_t last_row = Settings.display_rows -1; renderer->clearDisplay(); renderer->setTextSize(Settings.display_size); renderer->setCursor(0,0); for (byte i = 0; i < last_row; i++) { strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); renderer->println(disp_screen_buffer[i]); } strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]); renderer->Updateframe(); } } } void SH1106Time(void) { char line[12]; renderer->clearDisplay(); renderer->setTextSize(Settings.display_size); renderer->setTextFont(Settings.display_font); renderer->setCursor(0, 0); snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); renderer->println(line); snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); renderer->println(line); renderer->Updateframe(); } void SH1106Refresh(void) { if (!renderer) return; if (Settings.display_mode) { switch (Settings.display_mode) { case 1: SH1106Time(); break; case 2: case 3: case 4: case 5: SH1106PrintLog(); break; } } } #endif bool Xdsp07(uint8_t function) { if (!I2cEnabled(XI2C_06)) { return false; } bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { SH1106InitDriver(); } else if (XDSP_07 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: SH1106Refresh(); break; #endif } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_08_ILI9488.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_08_ILI9488.ino" #ifdef USE_SPI #ifdef USE_DISPLAY #ifdef USE_DISPLAY_ILI9488 #define XDSP_08 8 #define XI2C_38 38 #define COLORED 1 #define UNCOLORED 0 #define FT6236_address 0x38 #define USE_TINY_FONT #include #include TouchLocation ili9488_pLoc; uint8_t ili9488_ctouch_counter = 0; #define BACKPLANE_PIN 2 extern uint8_t *buffer; extern uint8_t color_type; ILI9488 *ili9488; #ifdef USE_TOUCH_BUTTONS extern VButton *buttons[]; #endif extern const uint16_t picture[]; uint8_t FT6236_found; void ILI9488_InitDriver() { if (!Settings.display_model) { Settings.display_model = XDSP_08; } if (XDSP_08 == Settings.display_model) { if (Settings.display_width != ILI9488_TFTWIDTH) { Settings.display_width = ILI9488_TFTWIDTH; } if (Settings.display_height != ILI9488_TFTHEIGHT) { Settings.display_height = ILI9488_TFTHEIGHT; } buffer=NULL; fg_color = ILI9488_WHITE; bg_color = ILI9488_BLACK; uint8_t bppin=BACKPLANE_PIN; if (pin[GPIO_BACKLIGHT]<99) { bppin=pin[GPIO_BACKLIGHT]; } if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)){ ili9488 = new ILI9488(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK],bppin); } else { if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)) { ili9488 = new ILI9488(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK],bppin); } else { return; } } SPI.begin(); ili9488->begin(); renderer = ili9488; renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); #ifdef SHOW_SPLASH renderer->setTextFont(2); renderer->setTextColor(ILI9488_WHITE,ILI9488_BLACK); renderer->DrawStringAt(50, 50, "ILI9488 TFT Display!", ILI9488_WHITE,0); delay(1000); #endif color_type = COLOR_COLOR; if (I2cEnabled(XI2C_38) && I2cSetDevice(FT6236_address)) { FT6236begin(FT6236_address); FT6236_found=1; I2cSetActiveFound(FT6236_address, "FT6236"); } else { FT6236_found=0; } } } #ifdef USE_TOUCH_BUTTONS void ILI9488_MQTT(uint8_t count,const char *cp) { ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7); MqttPublishTeleSensor(); } void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr) { buttons[count]->xdrawButton(pwr); if (pwr) buttons[count]->vpower|=0x80; else buttons[count]->vpower&=0x7f; } void FT6236Check() { uint16_t temp; uint8_t rbutt=0,vbutt=0; ili9488_ctouch_counter++; if (2 == ili9488_ctouch_counter) { ili9488_ctouch_counter=0; if (FT6236readTouchLocation(&ili9488_pLoc,1)) { if (renderer) { uint8_t rot=renderer->getRotation(); switch (rot) { case 0: temp=ili9488_pLoc.y; ili9488_pLoc.y=renderer->height()-ili9488_pLoc.x; ili9488_pLoc.x=temp; break; case 1: break; case 2: break; case 3: temp=ili9488_pLoc.y; ili9488_pLoc.y=ili9488_pLoc.x; ili9488_pLoc.x=renderer->width()-temp; break; } for (uint8_t count=0; countvpower&0x7f; if (buttons[count]->contains(ili9488_pLoc.x,ili9488_pLoc.y)) { buttons[count]->press(true); if (buttons[count]->justPressed()) { if (!bflags) { uint8_t pwr=bitRead(power,rbutt); if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) { ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON); ILI9488_RDW_BUTT(count,!pwr); } } else { const char *cp; if (bflags==1) { buttons[count]->vpower^=0x80; cp="TBT"; } else { buttons[count]->vpower|=0x80; cp="PBT"; } buttons[count]->xdrawButton(buttons[count]->vpower&0x80); ILI9488_MQTT(count,cp); } } } if (!bflags) { rbutt++; } else { vbutt++; } } } } } else { for (uint8_t count=0; countvpower&0x7f; buttons[count]->press(false); if (buttons[count]->justReleased()) { uint8_t bflags=buttons[count]->vpower&0x7f; if (bflags>0) { if (bflags>1) { buttons[count]->vpower&=0x7f; ILI9488_MQTT(count,"PBT"); } buttons[count]->xdrawButton(buttons[count]->vpower&0x80); } } if (!bflags) { uint8_t pwr=bitRead(power,rbutt); uint8_t vpwr=(buttons[count]->vpower&0x80)>>7; if (pwr!=vpwr) { ILI9488_RDW_BUTT(count,pwr); } rbutt++; } } } ili9488_pLoc.x=0; ili9488_pLoc.y=0; } } } #endif bool Xdsp08(uint8_t function) { bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { ILI9488_InitDriver(); } else if (XDSP_08 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; case FUNC_DISPLAY_EVERY_50_MSECOND: #ifdef USE_TOUCH_BUTTONS if (FT6236_found) FT6236Check(); #endif break; } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_09_SSD1351.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_09_SSD1351.ino" #ifdef USE_SPI #ifdef USE_DISPLAY #ifdef USE_DISPLAY_SSD1351 #define XDSP_09 9 #define COLORED 1 #define UNCOLORED 0 #define USE_TINY_FONT #include extern uint8_t *buffer; extern uint8_t color_type; SSD1351 *ssd1351; void SSD1351_InitDriver() { if (!Settings.display_model) { Settings.display_model = XDSP_09; } if (XDSP_09 == Settings.display_model) { if (Settings.display_width != SSD1351_WIDTH) { Settings.display_width = SSD1351_WIDTH; } if (Settings.display_height != SSD1351_HEIGHT) { Settings.display_height = SSD1351_HEIGHT; } buffer=0; fg_color = SSD1351_WHITE; bg_color = SSD1351_BLACK; if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)){ ssd1351 = new SSD1351(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]); } else { if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)){ ssd1351 = new SSD1351(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]); } else { return; } } delay(100); SPI.begin(); ssd1351->begin(); renderer = ssd1351; renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); renderer->dim(Settings.display_dimmer); #ifdef SHOW_SPLASH renderer->setTextFont(2); renderer->setTextColor(SSD1351_WHITE,SSD1351_BLACK); renderer->DrawStringAt(10, 60, "SSD1351", SSD1351_RED,0); delay(1000); #endif color_type = COLOR_COLOR; } } #ifdef USE_DISPLAY_MODES1TO5 void SSD1351PrintLog(void) { disp_refresh--; if (!disp_refresh) { disp_refresh = Settings.display_refresh; if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); } char* txt = DisplayLogBuffer('\370'); if (txt != NULL) { uint8_t last_row = Settings.display_rows -1; renderer->clearDisplay(); renderer->setTextSize(Settings.display_size); renderer->setCursor(0,0); for (byte i = 0; i < last_row; i++) { strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols); renderer->println(disp_screen_buffer[i]); } strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]); renderer->Updateframe(); } } } void SSD1351Time(void) { char line[12]; renderer->clearDisplay(); renderer->setTextSize(2); renderer->setCursor(0, 0); snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second); renderer->println(line); snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year); renderer->println(line); renderer->Updateframe(); } void SSD1351Refresh(void) { if (Settings.display_mode) { switch (Settings.display_mode) { case 1: SSD1351Time(); break; case 2: case 3: case 4: case 5: SSD1351PrintLog(); break; } } } #endif bool Xdsp09(uint8_t function) { bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { SSD1351_InitDriver(); } else if (XDSP_09 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; #ifdef USE_DISPLAY_MODES1TO5 case FUNC_DISPLAY_EVERY_SECOND: SSD1351Refresh(); break; #endif } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino" #ifdef USE_SPI #ifdef USE_DISPLAY #ifdef USE_DISPLAY_RA8876 #define XDSP_10 10 #define XI2C_39 39 #define COLORED 1 #define UNCOLORED 0 #define FT5316_address 0x38 #define USE_TINY_FONT #include #include TouchLocation ra8876_pLoc; uint8_t ra8876_ctouch_counter = 0; #ifdef USE_TOUCH_BUTTONS extern VButton *buttons[]; #endif extern uint8_t *buffer; extern uint8_t color_type; RA8876 *ra8876; uint8_t FT5316_found; void RA8876_InitDriver() { if (!Settings.display_model) { Settings.display_model = XDSP_10; } if (XDSP_10 == Settings.display_model) { if (Settings.display_width != RA8876_TFTWIDTH) { Settings.display_width = RA8876_TFTWIDTH; } if (Settings.display_height != RA8876_TFTHEIGHT) { Settings.display_height = RA8876_TFTHEIGHT; } buffer=0; fg_color = RA8876_WHITE; bg_color = RA8876_BLACK; if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]==13) && (pin[GPIO_SSPI_MISO]==12) && (pin[GPIO_SSPI_SCLK]==14)) { ra8876 = new RA8876(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_MISO],pin[GPIO_SSPI_SCLK],pin[GPIO_BACKLIGHT]); } else { if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]==13) && (pin[GPIO_SPI_MISO]==12) && (pin[GPIO_SPI_CLK]==14)) { ra8876 = new RA8876(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_MISO],pin[GPIO_SPI_CLK],pin[GPIO_BACKLIGHT]); } else { return; } } ra8876->begin(); renderer = ra8876; renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); renderer->dim(Settings.display_dimmer); #ifdef SHOW_SPLASH renderer->setTextFont(2); renderer->setTextColor(RA8876_WHITE,RA8876_BLACK); renderer->DrawStringAt(600, 300, "RA8876", RA8876_RED,0); delay(1000); #endif color_type = COLOR_COLOR; if (I2cEnabled(XI2C_39) && I2cSetDevice(FT5316_address)) { FT6236begin(FT5316_address); FT5316_found=1; I2cSetActiveFound(FT5316_address, "FT5316"); } else { FT5316_found=0; } } } #ifdef USE_TOUCH_BUTTONS void RA8876_MQTT(uint8_t count,const char *cp) { ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7); MqttPublishTeleSensor(); } void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr) { buttons[count]->xdrawButton(pwr); if (pwr) buttons[count]->vpower|=0x80; else buttons[count]->vpower&=0x7f; } void FT5316Check() { uint16_t temp; uint8_t rbutt=0,vbutt=0; ra8876_ctouch_counter++; if (2 == ra8876_ctouch_counter) { ra8876_ctouch_counter=0; if (FT6236readTouchLocation(&ra8876_pLoc,1)) { ra8876_pLoc.x=ra8876_pLoc.x*RA8876_TFTWIDTH/800; ra8876_pLoc.y=ra8876_pLoc.y*RA8876_TFTHEIGHT/480; if (renderer) { ra8876_pLoc.x=RA8876_TFTWIDTH-ra8876_pLoc.x; ra8876_pLoc.y=RA8876_TFTHEIGHT-ra8876_pLoc.y; # 170 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino" for (uint8_t count=0; countvpower&0x7f; if (buttons[count]->contains(ra8876_pLoc.x,ra8876_pLoc.y)) { buttons[count]->press(true); if (buttons[count]->justPressed()) { if (!bflags) { uint8_t pwr=bitRead(power,rbutt); if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) { ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON); RA8876_RDW_BUTT(count,!pwr); } } else { const char *cp; if (bflags==1) { buttons[count]->vpower^=0x80; cp="TBT"; } else { buttons[count]->vpower|=0x80; cp="PBT"; } buttons[count]->xdrawButton(buttons[count]->vpower&0x80); RA8876_MQTT(count,cp); } } } if (!bflags) { rbutt++; } else { vbutt++; } } } } } else { for (uint8_t count=0; countvpower&0x7f; buttons[count]->press(false); if (buttons[count]->justReleased()) { if (bflags>0) { if (bflags>1) { buttons[count]->vpower&=0x7f; RA8876_MQTT(count,"PBT"); } buttons[count]->xdrawButton(buttons[count]->vpower&0x80); } } if (!bflags) { uint8_t pwr=bitRead(power,rbutt); uint8_t vpwr=(buttons[count]->vpower&0x80)>>7; if (pwr!=vpwr) { RA8876_RDW_BUTT(count,pwr); } rbutt++; } } } ra8876_pLoc.x=0; ra8876_pLoc.y=0; } } } #endif # 426 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino" bool Xdsp10(uint8_t function) { bool result = false; if (FUNC_DISPLAY_INIT_DRIVER == function) { RA8876_InitDriver(); } else if (XDSP_10 == Settings.display_model) { switch (function) { case FUNC_DISPLAY_MODEL: result = true; break; case FUNC_DISPLAY_EVERY_50_MSECOND: #ifdef USE_TOUCH_BUTTONS if (FT5316_found) FT5316Check(); #endif break; } } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino" #ifdef USE_DISPLAY #ifdef XFUNC_PTR_IN_ROM bool (* const xdsp_func_ptr[])(uint8_t) PROGMEM = { #else bool (* const xdsp_func_ptr[])(uint8_t) = { #endif #ifdef XDSP_01 &Xdsp01, #endif #ifdef XDSP_02 &Xdsp02, #endif #ifdef XDSP_03 &Xdsp03, #endif #ifdef XDSP_04 &Xdsp04, #endif #ifdef XDSP_05 &Xdsp05, #endif #ifdef XDSP_06 &Xdsp06, #endif #ifdef XDSP_07 &Xdsp07, #endif #ifdef XDSP_08 &Xdsp08, #endif #ifdef XDSP_09 &Xdsp09, #endif #ifdef XDSP_10 &Xdsp10, #endif #ifdef XDSP_11 &Xdsp11, #endif #ifdef XDSP_12 &Xdsp12, #endif #ifdef XDSP_13 &Xdsp13, #endif #ifdef XDSP_14 &Xdsp14, #endif #ifdef XDSP_15 &Xdsp15, #endif #ifdef XDSP_16 &Xdsp16 #endif }; const uint8_t xdsp_present = sizeof(xdsp_func_ptr) / sizeof(xdsp_func_ptr[0]); # 117 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino" uint8_t XdspPresent(void) { return xdsp_present; } bool XdspCall(uint8_t Function) { bool result = false; DEBUG_TRACE_LOG(PSTR("DSP: %d"), Function); for (uint32_t x = 0; x < xdsp_present; x++) { result = xdsp_func_ptr[x](Function); if (result && (FUNC_DISPLAY_MODEL == Function)) { break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino" #ifdef USE_LIGHT #ifdef USE_WS2812 # 38 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino" #define XLGT_01 1 const uint8_t WS2812_SCHEMES = 8; const char kWs2812Commands[] PROGMEM = "|" D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH ; void (* const Ws2812Command[])(void) PROGMEM = { &CmndLed, &CmndPixels, &CmndRotation, &CmndWidth }; #include #if (USE_WS2812_CTYPE == NEO_GRB) typedef NeoGrbFeature selectedNeoFeatureType; #elif (USE_WS2812_CTYPE == NEO_BRG) typedef NeoBrgFeature selectedNeoFeatureType; #elif (USE_WS2812_CTYPE == NEO_RBG) typedef NeoRbgFeature selectedNeoFeatureType; #elif (USE_WS2812_CTYPE == NEO_RGBW) typedef NeoRgbwFeature selectedNeoFeatureType; #elif (USE_WS2812_CTYPE == NEO_GRBW) typedef NeoGrbwFeature selectedNeoFeatureType; #else typedef NeoRgbFeature selectedNeoFeatureType; #endif #ifdef USE_WS2812_DMA #if (USE_WS2812_HARDWARE == NEO_HW_WS2812X) typedef NeoEsp8266DmaWs2812xMethod selectedNeoSpeedType; #elif (USE_WS2812_HARDWARE == NEO_HW_SK6812) typedef NeoEsp8266DmaSk6812Method selectedNeoSpeedType; #elif (USE_WS2812_HARDWARE == NEO_HW_APA106) typedef NeoEsp8266DmaApa106Method selectedNeoSpeedType; #else typedef NeoEsp8266Dma800KbpsMethod selectedNeoSpeedType; #endif #else #if (USE_WS2812_HARDWARE == NEO_HW_WS2812X) typedef NeoEsp8266BitBangWs2812xMethod selectedNeoSpeedType; #elif (USE_WS2812_HARDWARE == NEO_HW_SK6812) typedef NeoEsp8266BitBangSk6812Method selectedNeoSpeedType; #else typedef NeoEsp8266BitBang800KbpsMethod selectedNeoSpeedType; #endif #endif NeoPixelBus *strip = nullptr; struct WsColor { uint8_t red, green, blue; }; struct ColorScheme { WsColor* colors; uint8_t count; }; WsColor kIncandescent[2] = { 255,140,20, 0,0,0 }; WsColor kRgb[3] = { 255,0,0, 0,255,0, 0,0,255 }; WsColor kChristmas[2] = { 255,0,0, 0,255,0 }; WsColor kHanukkah[2] = { 0,0,255, 255,255,255 }; WsColor kwanzaa[3] = { 255,0,0, 0,0,0, 0,255,0 }; WsColor kRainbow[7] = { 255,0,0, 255,128,0, 255,255,0, 0,255,0, 0,0,255, 128,0,255, 255,0,255 }; WsColor kFire[3] = { 255,0,0, 255,102,0, 255,192,0 }; ColorScheme kSchemes[WS2812_SCHEMES -1] = { kIncandescent, 2, kRgb, 3, kChristmas, 2, kHanukkah, 2, kwanzaa, 3, kRainbow, 7, kFire, 3 }; uint8_t kWidth[5] = { 1, 2, 4, 8, 255 }; uint8_t kWsRepeat[5] = { 8, 6, 4, 2, 1 }; struct WS2812 { uint8_t show_next = 1; uint8_t scheme_offset = 0; bool suspend_update = false; } Ws2812; void Ws2812StripShow(void) { #if (USE_WS2812_CTYPE > NEO_3LED) RgbwColor c; #else RgbColor c; #endif if (Settings.light_correction) { for (uint32_t i = 0; i < Settings.light_pixels; i++) { c = strip->GetPixelColor(i); c.R = ledGamma(c.R); c.G = ledGamma(c.G); c.B = ledGamma(c.B); #if (USE_WS2812_CTYPE > NEO_3LED) c.W = ledGamma(c.W); #endif strip->SetPixelColor(i, c); } } strip->Show(); } int mod(int a, int b) { int ret = a % b; if (ret < 0) ret += b; return ret; } void Ws2812UpdatePixelColor(int position, struct WsColor hand_color, float offset) { #if (USE_WS2812_CTYPE > NEO_3LED) RgbwColor color; #else RgbColor color; #endif uint32_t mod_position = mod(position, (int)Settings.light_pixels); color = strip->GetPixelColor(mod_position); float dimmer = 100 / (float)Settings.light_dimmer; color.R = tmin(color.R + ((hand_color.red / dimmer) * offset), 255); color.G = tmin(color.G + ((hand_color.green / dimmer) * offset), 255); color.B = tmin(color.B + ((hand_color.blue / dimmer) * offset), 255); strip->SetPixelColor(mod_position, color); } void Ws2812UpdateHand(int position, uint32_t index) { uint32_t width = Settings.light_width; if (index < WS_MARKER) { width = Settings.ws_width[index]; } if (!width) { return; } position = (position + Settings.light_rotation) % Settings.light_pixels; if (Settings.flag.ws_clock_reverse) { position = Settings.light_pixels -position; } WsColor hand_color = { Settings.ws_color[index][WS_RED], Settings.ws_color[index][WS_GREEN], Settings.ws_color[index][WS_BLUE] }; Ws2812UpdatePixelColor(position, hand_color, 1); uint32_t range = ((width -1) / 2) +1; for (uint32_t h = 1; h < range; h++) { float offset = (float)(range - h) / (float)range; Ws2812UpdatePixelColor(position -h, hand_color, offset); Ws2812UpdatePixelColor(position +h, hand_color, offset); } } void Ws2812Clock(void) { strip->ClearTo(0); int clksize = 60000 / (int)Settings.light_pixels; Ws2812UpdateHand((RtcTime.second * 1000) / clksize, WS_SECOND); Ws2812UpdateHand((RtcTime.minute * 1000) / clksize, WS_MINUTE); Ws2812UpdateHand((((RtcTime.hour % 12) * 5000) + ((RtcTime.minute * 1000) / 12 )) / clksize, WS_HOUR); if (Settings.ws_color[WS_MARKER][WS_RED] + Settings.ws_color[WS_MARKER][WS_GREEN] + Settings.ws_color[WS_MARKER][WS_BLUE]) { for (uint32_t i = 0; i < 12; i++) { Ws2812UpdateHand((i * 5000) / clksize, WS_MARKER); } } Ws2812StripShow(); } void Ws2812GradientColor(uint32_t schemenr, struct WsColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i) { ColorScheme scheme = kSchemes[schemenr]; uint32_t curRange = i / range; uint32_t rangeIndex = i % range; uint32_t colorIndex = rangeIndex / gradRange; uint32_t start = colorIndex; uint32_t end = colorIndex +1; if (curRange % 2 != 0) { start = (scheme.count -1) - start; end = (scheme.count -1) - end; } float dimmer = 100 / (float)Settings.light_dimmer; float fmyRed = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].red, scheme.colors[end].red) / dimmer; float fmyGrn = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].green, scheme.colors[end].green) / dimmer; float fmyBlu = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].blue, scheme.colors[end].blue) / dimmer; mColor->red = (uint8_t)fmyRed; mColor->green = (uint8_t)fmyGrn; mColor->blue = (uint8_t)fmyBlu; } void Ws2812Gradient(uint32_t schemenr) { #if (USE_WS2812_CTYPE > NEO_3LED) RgbwColor c; c.W = 0; #else RgbColor c; #endif ColorScheme scheme = kSchemes[schemenr]; if (scheme.count < 2) { return; } uint32_t repeat = kWsRepeat[Settings.light_width]; uint32_t range = (uint32_t)ceil((float)Settings.light_pixels / (float)repeat); uint32_t gradRange = (uint32_t)ceil((float)range / (float)(scheme.count - 1)); uint32_t speed = ((Settings.light_speed * 2) -1) * (STATES / 10); uint32_t offset = speed > 0 ? Light.strip_timer_counter / speed : 0; WsColor oldColor, currentColor; Ws2812GradientColor(schemenr, &oldColor, range, gradRange, offset); currentColor = oldColor; for (uint32_t i = 0; i < Settings.light_pixels; i++) { if (kWsRepeat[Settings.light_width] > 1) { Ws2812GradientColor(schemenr, ¤tColor, range, gradRange, i +offset); } if (Settings.light_speed > 0) { c.R = map(Light.strip_timer_counter % speed, 0, speed, oldColor.red, currentColor.red); c.G = map(Light.strip_timer_counter % speed, 0, speed, oldColor.green, currentColor.green); c.B = map(Light.strip_timer_counter % speed, 0, speed, oldColor.blue, currentColor.blue); } else { c.R = currentColor.red; c.G = currentColor.green; c.B = currentColor.blue; } strip->SetPixelColor(i, c); oldColor = currentColor; } Ws2812StripShow(); } void Ws2812Bars(uint32_t schemenr) { #if (USE_WS2812_CTYPE > NEO_3LED) RgbwColor c; c.W = 0; #else RgbColor c; #endif ColorScheme scheme = kSchemes[schemenr]; uint32_t maxSize = Settings.light_pixels / scheme.count; if (kWidth[Settings.light_width] > maxSize) { maxSize = 0; } uint32_t speed = ((Settings.light_speed * 2) -1) * (STATES / 10); uint32_t offset = (speed > 0) ? Light.strip_timer_counter / speed : 0; WsColor mcolor[scheme.count]; memcpy(mcolor, scheme.colors, sizeof(mcolor)); float dimmer = 100 / (float)Settings.light_dimmer; for (uint32_t i = 0; i < scheme.count; i++) { float fmyRed = (float)mcolor[i].red / dimmer; float fmyGrn = (float)mcolor[i].green / dimmer; float fmyBlu = (float)mcolor[i].blue / dimmer; mcolor[i].red = (uint8_t)fmyRed; mcolor[i].green = (uint8_t)fmyGrn; mcolor[i].blue = (uint8_t)fmyBlu; } uint32_t colorIndex = offset % scheme.count; for (uint32_t i = 0; i < Settings.light_pixels; i++) { if (maxSize) { colorIndex = ((i + offset) % (scheme.count * kWidth[Settings.light_width])) / kWidth[Settings.light_width]; } c.R = mcolor[colorIndex].red; c.G = mcolor[colorIndex].green; c.B = mcolor[colorIndex].blue; strip->SetPixelColor(i, c); } Ws2812StripShow(); } void Ws2812Clear(void) { strip->ClearTo(0); strip->Show(); Ws2812.show_next = 1; } void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white) { #if (USE_WS2812_CTYPE > NEO_3LED) RgbwColor lcolor; lcolor.W = white; #else RgbColor lcolor; #endif lcolor.R = red; lcolor.G = green; lcolor.B = blue; if (led) { strip->SetPixelColor(led -1, lcolor); } else { for (uint32_t i = 0; i < Settings.light_pixels; i++) { strip->SetPixelColor(i, lcolor); } } if (!Ws2812.suspend_update) { strip->Show(); Ws2812.show_next = 1; } } char* Ws2812GetColor(uint32_t led, char* scolor) { uint8_t sl_ledcolor[4]; #if (USE_WS2812_CTYPE > NEO_3LED) RgbwColor lcolor = strip->GetPixelColor(led -1); sl_ledcolor[3] = lcolor.W; #else RgbColor lcolor = strip->GetPixelColor(led -1); #endif sl_ledcolor[0] = lcolor.R; sl_ledcolor[1] = lcolor.G; sl_ledcolor[2] = lcolor.B; scolor[0] = '\0'; for (uint32_t i = 0; i < Light.subtype; i++) { if (Settings.flag.decimal_text) { snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", sl_ledcolor[i]); } else { snprintf_P(scolor, 25, PSTR("%s%02X"), scolor, sl_ledcolor[i]); } } return scolor; } void Ws2812ForceSuspend (void) { Ws2812.suspend_update = true; } void Ws2812ForceUpdate (void) { Ws2812.suspend_update = false; strip->Show(); Ws2812.show_next = 1; } bool Ws2812SetChannels(void) { uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]); return true; } void Ws2812ShowScheme(void) { uint32_t scheme = Settings.light_scheme - Ws2812.scheme_offset; switch (scheme) { case 0: if ((1 == state_250mS) || (Ws2812.show_next)) { Ws2812Clock(); Ws2812.show_next = 0; } break; default: if (1 == Settings.light_fade) { Ws2812Gradient(scheme -1); } else { Ws2812Bars(scheme -1); } Ws2812.show_next = 1; break; } } void Ws2812ModuleSelected(void) { if (pin[GPIO_WS2812] < 99) { strip = new NeoPixelBus(WS2812_MAX_LEDS, pin[GPIO_WS2812]); strip->Begin(); Ws2812Clear(); Ws2812.scheme_offset = Light.max_scheme +1; Light.max_scheme += WS2812_SCHEMES; #if (USE_WS2812_CTYPE > NEO_3LED) light_type = LT_RGBW; #else light_type = LT_RGB; #endif light_flg = XLGT_01; } } void CmndLed(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings.light_pixels)) { if (XdrvMailbox.data_len > 0) { char *p; uint16_t idx = XdrvMailbox.index; Ws2812ForceSuspend(); for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(nullptr, " ", &p)) { if (LightColorEntry(color, strlen(color))) { Ws2812SetColor(idx, Light.entry_color[0], Light.entry_color[1], Light.entry_color[2], Light.entry_color[3]); idx++; if (idx > Settings.light_pixels) { break; } } else { break; } } Ws2812ForceUpdate(); } char scolor[LIGHT_COLOR_SIZE]; ResponseCmndIdxChar(Ws2812GetColor(XdrvMailbox.index, scolor)); } } void CmndPixels(void) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) { Settings.light_pixels = XdrvMailbox.payload; Settings.light_rotation = 0; Ws2812Clear(); Light.update = true; } ResponseCmndNumber(Settings.light_pixels); } void CmndRotation(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings.light_pixels)) { Settings.light_rotation = XdrvMailbox.payload; } ResponseCmndNumber(Settings.light_rotation); } void CmndWidth(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) { if (1 == XdrvMailbox.index) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) { Settings.light_width = XdrvMailbox.payload; } ResponseCmndNumber(Settings.light_width); } else { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32)) { Settings.ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload; } ResponseCmndIdxNumber(Settings.ws_width[XdrvMailbox.index -2]); } } } bool Xlgt01(uint8_t function) { bool result = false; switch (function) { case FUNC_SET_CHANNELS: result = Ws2812SetChannels(); break; case FUNC_SET_SCHEME: Ws2812ShowScheme(); break; case FUNC_COMMAND: result = DecodeCommand(kWs2812Commands, Ws2812Command); break; case FUNC_MODULE_INIT: Ws2812ModuleSelected(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_02_my92x1.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_02_my92x1.ino" #ifdef USE_LIGHT #ifdef USE_MY92X1 #define XLGT_02 2 struct MY92X1 { uint8_t pdi_pin = 0; uint8_t pdcki_pin = 0; uint8_t model = 0; } My92x1; extern "C" { void os_delay_us(unsigned int); } void LightDiPulse(uint8_t times) { for (uint32_t i = 0; i < times; i++) { digitalWrite(My92x1.pdi_pin, HIGH); digitalWrite(My92x1.pdi_pin, LOW); } } void LightDckiPulse(uint8_t times) { for (uint32_t i = 0; i < times; i++) { digitalWrite(My92x1.pdcki_pin, HIGH); digitalWrite(My92x1.pdcki_pin, LOW); } } void LightMy92x1Write(uint8_t data) { for (uint32_t i = 0; i < 4; i++) { digitalWrite(My92x1.pdcki_pin, LOW); digitalWrite(My92x1.pdi_pin, (data & 0x80)); digitalWrite(My92x1.pdcki_pin, HIGH); data = data << 1; digitalWrite(My92x1.pdi_pin, (data & 0x80)); digitalWrite(My92x1.pdcki_pin, LOW); digitalWrite(My92x1.pdi_pin, LOW); data = data << 1; } } void LightMy92x1Init(void) { uint8_t chips[3] = { 1, 2, 2 }; LightDckiPulse(chips[My92x1.model] * 32); os_delay_us(12); LightDiPulse(12); os_delay_us(12); for (uint32_t n = 0; n < chips[My92x1.model]; n++) { LightMy92x1Write(0x18); } os_delay_us(12); LightDiPulse(16); os_delay_us(12); } void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c) { uint8_t channels[3] = { 4, 6, 6 }; uint8_t duty[3][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 }, { duty_w, duty_c, 0, duty_g, duty_r, duty_b }, { duty_r, duty_g, duty_b, duty_w, duty_w, duty_w }}; os_delay_us(12); for (uint32_t channel = 0; channel < channels[My92x1.model]; channel++) { LightMy92x1Write(duty[My92x1.model][channel]); } os_delay_us(12); LightDiPulse(8); os_delay_us(12); } bool My92x1SetChannels(void) { uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; LightMy92x1Duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]); return true; } void My92x1ModuleSelected(void) { if ((pin[GPIO_DCKI] < 99) && (pin[GPIO_DI] < 99)) { My92x1.pdi_pin = pin[GPIO_DI]; My92x1.pdcki_pin = pin[GPIO_DCKI]; pinMode(My92x1.pdi_pin, OUTPUT); pinMode(My92x1.pdcki_pin, OUTPUT); digitalWrite(My92x1.pdi_pin, LOW); digitalWrite(My92x1.pdcki_pin, LOW); My92x1.model = 2; light_type = LT_RGBW; if (AILIGHT == my_module_type) { My92x1.model = 0; } else if (SONOFF_B1 == my_module_type) { My92x1.model = 1; light_type = LT_RGBWC; } LightMy92x1Init(); light_flg = XLGT_02; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: MY29x1 Found")); } } bool Xlgt02(uint8_t function) { bool result = false; switch (function) { case FUNC_SET_CHANNELS: result = My92x1SetChannels(); break; case FUNC_MODULE_INIT: My92x1ModuleSelected(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino" #ifdef USE_LIGHT #ifdef USE_SM16716 #define XLGT_03 3 #define D_LOG_SM16716 "SM16716: " struct SM16716 { uint8_t pin_clk = 0; uint8_t pin_dat = 0; uint8_t pin_sel = 0; bool enabled = false; } Sm16716; void SM16716_SendBit(uint8_t v) { digitalWrite(Sm16716.pin_dat, (v != 0) ? HIGH : LOW); digitalWrite(Sm16716.pin_clk, HIGH); digitalWrite(Sm16716.pin_clk, LOW); } void SM16716_SendByte(uint8_t v) { uint8_t mask; for (mask = 0x80; mask; mask >>= 1) { SM16716_SendBit(v & mask); } } void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b) { if (Sm16716.pin_sel < 99) { bool should_enable = (duty_r | duty_g | duty_b); if (!Sm16716.enabled && should_enable) { DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color on")); Sm16716.enabled = true; digitalWrite(Sm16716.pin_sel, HIGH); delayMicroseconds(1000); SM16716_Init(); } else if (Sm16716.enabled && !should_enable) { DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color off")); Sm16716.enabled = false; digitalWrite(Sm16716.pin_sel, LOW); } } DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "Update; rgb=%02x%02x%02x"), duty_r, duty_g, duty_b); SM16716_SendBit(1); SM16716_SendByte(duty_r); SM16716_SendByte(duty_g); SM16716_SendByte(duty_b); SM16716_SendBit(0); SM16716_SendByte(0); SM16716_SendByte(0); SM16716_SendByte(0); } # 111 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino" void SM16716_Init(void) { for (uint32_t t_init = 0; t_init < 50; ++t_init) { SM16716_SendBit(0); } } bool Sm16716SetChannels(void) { # 132 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino" uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; SM16716_Update(cur_col[0], cur_col[1], cur_col[2]); return true; } void Sm16716ModuleSelected(void) { if ((pin[GPIO_SM16716_CLK] < 99) && (pin[GPIO_SM16716_DAT] < 99)) { Sm16716.pin_clk = pin[GPIO_SM16716_CLK]; Sm16716.pin_dat = pin[GPIO_SM16716_DAT]; Sm16716.pin_sel = pin[GPIO_SM16716_SEL]; # 157 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino" pinMode(Sm16716.pin_clk, OUTPUT); digitalWrite(Sm16716.pin_clk, LOW); pinMode(Sm16716.pin_dat, OUTPUT); digitalWrite(Sm16716.pin_dat, LOW); if (Sm16716.pin_sel < 99) { pinMode(Sm16716.pin_sel, OUTPUT); digitalWrite(Sm16716.pin_sel, LOW); } else { SM16716_Init(); } LightPwmOffset(LST_RGB); light_type += LST_RGB; light_flg = XLGT_03; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: SM16716 Found")); } } bool Xlgt03(uint8_t function) { bool result = false; switch (function) { case FUNC_SET_CHANNELS: result = Sm16716SetChannels(); break; case FUNC_MODULE_INIT: Sm16716ModuleSelected(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino" #ifdef USE_LIGHT #ifdef USE_SM2135 #define XLGT_04 4 #define SM2135_ADDR_MC 0xC0 #define SM2135_ADDR_CH 0xC1 #define SM2135_ADDR_R 0xC2 #define SM2135_ADDR_G 0xC3 #define SM2135_ADDR_B 0xC4 #define SM2135_ADDR_C 0xC5 #define SM2135_ADDR_W 0xC6 #define SM2135_RGB 0x00 #define SM2135_CW 0x80 #define SM2135_10MA 0x00 #define SM2135_15MA 0x01 #define SM2135_20MA 0x02 #define SM2135_25MA 0x03 #define SM2135_30MA 0x04 #define SM2135_35MA 0x05 #define SM2135_40MA 0x06 #define SM2135_45MA 0x07 #define SM2135_50MA 0x08 #define SM2135_55MA 0x09 #define SM2135_60MA 0x0A const uint8_t SM2135_CURRENT = (SM2135_20MA << 4) | SM2135_15MA; struct SM2135 { uint8_t clk = 0; uint8_t data = 0; } Sm2135; uint8_t Sm2135Write(uint8_t data) { for (uint32_t i = 0; i < 8; i++) { digitalWrite(Sm2135.clk, LOW); digitalWrite(Sm2135.data, (data & 0x80)); digitalWrite(Sm2135.clk, HIGH); data = data << 1; } digitalWrite(Sm2135.clk, LOW); digitalWrite(Sm2135.data, HIGH); pinMode(Sm2135.data, INPUT); digitalWrite(Sm2135.clk, HIGH); uint8_t ack = digitalRead(Sm2135.data); pinMode(Sm2135.data, OUTPUT); return ack; } void Sm2135Send(uint8_t *buffer, uint8_t size) { digitalWrite(Sm2135.data, LOW); for (uint32_t i = 0; i < size; i++) { Sm2135Write(buffer[i]); } digitalWrite(Sm2135.clk, LOW); digitalWrite(Sm2135.clk, HIGH); digitalWrite(Sm2135.data, HIGH); } bool Sm2135SetChannels(void) { uint8_t *cur_col = (uint8_t*)XdrvMailbox.data; uint8_t data[6]; if ((0 == cur_col[0]) && (0 == cur_col[1]) && (0 == cur_col[2])) { # 106 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino" data[0] = SM2135_ADDR_MC; data[1] = SM2135_CURRENT; data[2] = SM2135_CW; Sm2135Send(data, 3); delay(1); data[0] = SM2135_ADDR_C; data[1] = cur_col[4]; data[2] = cur_col[3]; Sm2135Send(data, 3); } else { data[0] = SM2135_ADDR_MC; data[1] = SM2135_CURRENT; data[2] = SM2135_RGB; data[3] = cur_col[1]; data[4] = cur_col[0]; data[5] = cur_col[2]; Sm2135Send(data, 6); } return true; } void Sm2135ModuleSelected(void) { if ((pin[GPIO_SM2135_CLK] < 99) && (pin[GPIO_SM2135_DAT] < 99)) { Sm2135.clk = pin[GPIO_SM2135_CLK]; Sm2135.data = pin[GPIO_SM2135_DAT]; pinMode(Sm2135.data, OUTPUT); digitalWrite(Sm2135.data, HIGH); pinMode(Sm2135.clk, OUTPUT); digitalWrite(Sm2135.clk, HIGH); light_type = LT_RGBWC; light_flg = XLGT_04; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: SM2135 Found")); } } bool Xlgt04(uint8_t function) { bool result = false; switch (function) { case FUNC_SET_CHANNELS: result = Sm2135SetChannels(); break; case FUNC_MODULE_INIT: Sm2135ModuleSelected(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_05_sonoff_l1.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_05_sonoff_l1.ino" #ifdef USE_LIGHT #ifdef USE_SONOFF_L1 #define XLGT_05 5 #define SONOFF_L1_BUFFER_SIZE 140 #define SONOFF_L1_MODE_COLORFUL 1 #define SONOFF_L1_MODE_COLORFUL_GRADIENT 2 #define SONOFF_L1_MODE_COLORFUL_BREATH 3 #define SONOFF_L1_MODE_DIY_GRADIENT 4 #define SONOFF_L1_MODE_DIY_PULSE 5 #define SONOFF_L1_MODE_DIY_BREATH 6 #define SONOFF_L1_MODE_DIY_STROBE 7 #define SONOFF_L1_MODE_RGB_GRADIENT 8 #define SONOFF_L1_MODE_RGB_PULSE 9 #define SONOFF_L1_MODE_RGB_BREATH 10 #define SONOFF_L1_MODE_RGB_STROBE 11 #define SONOFF_L1_MODE_SYNC_TO_MUSIC 12 struct SNFL1 { uint32_t unlock = 0; bool receive_ready = true; } Snfl1; void SnfL1Send(const char *buffer) { Serial.print(buffer); Serial.write(0x1B); Serial.flush(); } void SnfL1SerialSendOk(void) { char buffer[16]; snprintf_P(buffer, sizeof(buffer), PSTR("AT+SEND=ok")); SnfL1Send(buffer); } bool SnfL1SerialInput(void) { if (serial_in_byte != 0x1B) { if (serial_in_byte_counter >= 140) { serial_in_byte_counter = 0; } if (serial_in_byte_counter || (!serial_in_byte_counter && ('A' == serial_in_byte))) { serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; } } else { serial_in_buffer[serial_in_byte_counter++] = 0x00; if (!strncmp(serial_in_buffer +3, "RESULT", 6)) { Snfl1.receive_ready = true; } else if (!strncmp(serial_in_buffer +3, "UPDATE", 6)) { char cmnd_dimmer[20]; char cmnd_color[20]; char *end_str; char *string = serial_in_buffer +10; char *token = strtok_r(string, ",", &end_str); bool color_updated[3] = { false, false, false }; uint8_t current_color[3]; memcpy(current_color, Settings.light_color, 3); bool switch_state = false; bool is_power_change = false; bool is_color_change = false; bool is_brightness_change = false; while (token != nullptr) { char* end_token; char* token2 = strtok_r(token, ":", &end_token); char* token3 = strtok_r(nullptr, ":", &end_token); if (!strncmp(token2, "\"sequence\"", 10)) { token = nullptr; } else if (!strncmp(token2, "\"switch\"", 8)) { switch_state = !strncmp(token3, "\"on\"", 4) ? true : false; is_power_change = (switch_state != Light.power); } else if (!strncmp(token2, "\"color", 6)) { char color_channel_name = token2[6]; int color_index; switch(color_channel_name) { case 'R': color_index = 0; break; case 'G': color_index = 1; break; case 'B': color_index = 2; break; } int color_value = atoi(token3); current_color[color_index] = color_value; color_updated[color_index] = true; bool all_color_channels_updated = color_updated[0] && color_updated[1] && color_updated[2]; if (all_color_channels_updated) { is_color_change = (Light.power && (memcmp(current_color, Settings.light_color, 3) != 0)); } snprintf_P(cmnd_color, sizeof(cmnd_color), PSTR(D_CMND_COLOR "2 %02x%02x%02x"), current_color[0], current_color[1], current_color[2]); } else if (!strncmp(token2, "\"bright\"", 8)) { uint8_t dimmer = atoi(token3); is_brightness_change = (Light.power && (dimmer > 0) && (dimmer != Settings.light_dimmer)); snprintf_P(cmnd_dimmer, sizeof(cmnd_dimmer), PSTR(D_CMND_DIMMER " %d"), dimmer); } token = strtok_r(nullptr, ",", &end_str); } if (is_power_change) { if (Settings.light_scheme > 0) { if (!switch_state) { char cmnd_scheme[20]; snprintf_P(cmnd_scheme, sizeof(cmnd_scheme), PSTR(D_CMND_SCHEME " 0")); ExecuteCommand(cmnd_scheme, SRC_SWITCH); } } else { ExecuteCommandPower(1, switch_state, SRC_SWITCH); } } else if (is_brightness_change) { ExecuteCommand(cmnd_dimmer, SRC_SWITCH); } else if (Light.power && is_color_change) { if (0 == Settings.light_scheme) { if (Settings.light_fade) { char cmnd_fade[20]; snprintf_P(cmnd_fade, sizeof(cmnd_fade), PSTR(D_CMND_FADE " 0")); ExecuteCommand(cmnd_fade, SRC_SWITCH); } ExecuteCommand(cmnd_color, SRC_SWITCH); } } } SnfL1SerialSendOk(); return true; } serial_in_byte = 0; return false; } bool SnfL1SetChannels(void) { if (Snfl1.receive_ready || TimeReached(Snfl1.unlock)) { uint8_t *scale_col = (uint8_t*)XdrvMailbox.topic; char buffer[140]; snprintf_P(buffer, sizeof(buffer), PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"light_type\":1,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"bright\":%d,\"mode\":%d"), LocalTime(), millis()%1000, Light.power ? "on" : "off", scale_col[0], scale_col[1], scale_col[2], light_state.getDimmer(), SONOFF_L1_MODE_COLORFUL); SnfL1Send(buffer); Snfl1.unlock = millis() + 500; Snfl1.receive_ready = false; } return true; } void SnfL1ModuleSelected(void) { if (SONOFF_L1 == my_module_type) { if ((pin[GPIO_RXD] < 99) && (pin[GPIO_TXD] < 99)) { SetSerial(19200, TS_SERIAL_8N1); light_type = LT_RGB; light_flg = XLGT_05; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: Sonoff L1 Found")); } } } bool Xlgt05(uint8_t function) { bool result = false; switch (function) { case FUNC_SERIAL: result = SnfL1SerialInput(); break; case FUNC_SET_CHANNELS: result = SnfL1SetChannels(); break; case FUNC_MODULE_INIT: SnfL1ModuleSelected(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_interface.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_interface.ino" #ifdef USE_LIGHT #ifdef XFUNC_PTR_IN_ROM bool (* const xlgt_func_ptr[])(uint8_t) PROGMEM = { #else bool (* const xlgt_func_ptr[])(uint8_t) = { #endif #ifdef XLGT_01 &Xlgt01, #endif #ifdef XLGT_02 &Xlgt02, #endif #ifdef XLGT_03 &Xlgt03, #endif #ifdef XLGT_04 &Xlgt04, #endif #ifdef XLGT_05 &Xlgt05, #endif #ifdef XLGT_06 &Xlgt06, #endif #ifdef XLGT_07 &Xlgt07, #endif #ifdef XLGT_08 &Xlgt08, #endif #ifdef XLGT_09 &Xlgt09, #endif #ifdef XLGT_10 &Xlgt10, #endif #ifdef XLGT_11 &Xlgt11, #endif #ifdef XLGT_12 &Xlgt12, #endif #ifdef XLGT_13 &Xlgt13, #endif #ifdef XLGT_14 &Xlgt14, #endif #ifdef XLGT_15 &Xlgt15, #endif #ifdef XLGT_16 &Xlgt16 #endif }; const uint8_t xlgt_present = sizeof(xlgt_func_ptr) / sizeof(xlgt_func_ptr[0]); uint8_t xlgt_active = 0; bool XlgtCall(uint8_t function) { DEBUG_TRACE_LOG(PSTR("LGT: %d"), function); if (FUNC_MODULE_INIT == function) { for (uint32_t x = 0; x < xlgt_present; x++) { xlgt_func_ptr[x](function); if (light_flg) { xlgt_active = x; return true; } } } else if (light_flg) { return xlgt_func_ptr[xlgt_active](function); } return false; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_01_hlw8012.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_01_hlw8012.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_HLW8012 #define XNRG_01 1 #define HLW_PREF 10000 #define HLW_UREF 2200 #define HLW_IREF 4545 #define HJL_PREF 1362 #define HJL_UREF 822 #define HJL_IREF 3300 #define HLW_POWER_PROBE_TIME 10 #define HLW_SAMPLE_COUNT 10 struct HLW { #ifdef HLW_DEBUG unsigned long debug[HLW_SAMPLE_COUNT]; #endif unsigned long cf_pulse_length = 0; unsigned long cf_pulse_last_time = 0; unsigned long cf_power_pulse_length = 0; unsigned long cf1_pulse_length = 0; unsigned long cf1_pulse_last_time = 0; unsigned long cf1_summed_pulse_length = 0; unsigned long cf1_pulse_counter = 0; unsigned long cf1_voltage_pulse_length = 0; unsigned long cf1_current_pulse_length = 0; unsigned long energy_period_counter = 0; unsigned long power_ratio = 0; unsigned long voltage_ratio = 0; unsigned long current_ratio = 0; uint8_t model_type = 0; uint8_t cf1_timer = 0; uint8_t power_retry = 0; bool select_ui_flag = false; bool ui_flag = true; bool load_off = true; } Hlw; #ifndef USE_WS2812_DMA void HlwCfInterrupt(void) ICACHE_RAM_ATTR; void HlwCf1Interrupt(void) ICACHE_RAM_ATTR; #endif void HlwCfInterrupt(void) { unsigned long us = micros(); if (Hlw.load_off) { Hlw.cf_pulse_last_time = us; Hlw.load_off = false; } else { Hlw.cf_pulse_length = us - Hlw.cf_pulse_last_time; Hlw.cf_pulse_last_time = us; Hlw.energy_period_counter++; } Energy.data_valid[0] = 0; } void HlwCf1Interrupt(void) { unsigned long us = micros(); Hlw.cf1_pulse_length = us - Hlw.cf1_pulse_last_time; Hlw.cf1_pulse_last_time = us; if ((Hlw.cf1_timer > 2) && (Hlw.cf1_timer < 8)) { Hlw.cf1_summed_pulse_length += Hlw.cf1_pulse_length; #ifdef HLW_DEBUG Hlw.debug[Hlw.cf1_pulse_counter] = Hlw.cf1_pulse_length; #endif Hlw.cf1_pulse_counter++; if (HLW_SAMPLE_COUNT == Hlw.cf1_pulse_counter) { Hlw.cf1_timer = 8; } } Energy.data_valid[0] = 0; } void HlwEvery200ms(void) { unsigned long cf1_pulse_length = 0; unsigned long hlw_w = 0; unsigned long hlw_u = 0; unsigned long hlw_i = 0; if (micros() - Hlw.cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) { Hlw.cf_pulse_length = 0; Hlw.load_off = true; } Hlw.cf_power_pulse_length = Hlw.cf_pulse_length; if (Hlw.cf_power_pulse_length && Energy.power_on && !Hlw.load_off) { hlw_w = (Hlw.power_ratio * Settings.energy_power_calibration) / Hlw.cf_power_pulse_length ; Energy.active_power[0] = (float)hlw_w / 10; Hlw.power_retry = 1; } else { if (Hlw.power_retry) { Hlw.power_retry--; } else { Energy.active_power[0] = 0; } } if (pin[GPIO_NRG_CF1] < 99) { Hlw.cf1_timer++; if (Hlw.cf1_timer >= 8) { Hlw.cf1_timer = 0; Hlw.select_ui_flag = (Hlw.select_ui_flag) ? false : true; DigitalWrite(GPIO_NRG_SEL, Hlw.select_ui_flag); if (Hlw.cf1_pulse_counter) { cf1_pulse_length = Hlw.cf1_summed_pulse_length / Hlw.cf1_pulse_counter; } #ifdef HLW_DEBUG char stemp[100]; stemp[0] = '\0'; for (uint32_t i = 0; i < Hlw.cf1_pulse_counter; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%s %d"), stemp, Hlw.debug[i]); } for (uint32_t i = 0; i < Hlw.cf1_pulse_counter; i++) { for (uint32_t j = i + 1; j < Hlw.cf1_pulse_counter; j++) { if (Hlw.debug[i] > Hlw.debug[j]) { std::swap(Hlw.debug[i], Hlw.debug[j]); } } } unsigned long median = Hlw.debug[(Hlw.cf1_pulse_counter +1) / 2]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NRG: power %d, ui %d, cnt %d, smpl%s, sum %d, mean %d, median %d"), Hlw.cf_power_pulse_length , Hlw.select_ui_flag, Hlw.cf1_pulse_counter, stemp, Hlw.cf1_summed_pulse_length, cf1_pulse_length, median); #endif if (Hlw.select_ui_flag == Hlw.ui_flag) { Hlw.cf1_voltage_pulse_length = cf1_pulse_length; if (Hlw.cf1_voltage_pulse_length && Energy.power_on) { hlw_u = (Hlw.voltage_ratio * Settings.energy_voltage_calibration) / Hlw.cf1_voltage_pulse_length ; Energy.voltage[0] = (float)hlw_u / 10; } else { Energy.voltage[0] = 0; } } else { Hlw.cf1_current_pulse_length = cf1_pulse_length; if (Hlw.cf1_current_pulse_length && Energy.active_power[0]) { hlw_i = (Hlw.current_ratio * Settings.energy_current_calibration) / Hlw.cf1_current_pulse_length; Energy.current[0] = (float)hlw_i / 1000; } else { Energy.current[0] = 0; } } Hlw.cf1_summed_pulse_length = 0; Hlw.cf1_pulse_counter = 0; } } } void HlwEverySecond(void) { if (Energy.data_valid[0] > ENERGY_WATCHDOG) { Hlw.cf1_voltage_pulse_length = 0; Hlw.cf1_current_pulse_length = 0; Hlw.cf_power_pulse_length = 0; } else { unsigned long hlw_len; if (Hlw.energy_period_counter) { hlw_len = 10000 / Hlw.energy_period_counter; Hlw.energy_period_counter = 0; if (hlw_len) { Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) / hlw_len) / 36; EnergyUpdateToday(); } } } } void HlwSnsInit(void) { if (!Settings.energy_power_calibration || (4975 == Settings.energy_power_calibration)) { Settings.energy_power_calibration = HLW_PREF_PULSE; Settings.energy_voltage_calibration = HLW_UREF_PULSE; Settings.energy_current_calibration = HLW_IREF_PULSE; } if (Hlw.model_type) { Hlw.power_ratio = HJL_PREF; Hlw.voltage_ratio = HJL_UREF; Hlw.current_ratio = HJL_IREF; } else { Hlw.power_ratio = HLW_PREF; Hlw.voltage_ratio = HLW_UREF; Hlw.current_ratio = HLW_IREF; } if (pin[GPIO_NRG_SEL] < 99) { pinMode(pin[GPIO_NRG_SEL], OUTPUT); digitalWrite(pin[GPIO_NRG_SEL], Hlw.select_ui_flag); } if (pin[GPIO_NRG_CF1] < 99) { pinMode(pin[GPIO_NRG_CF1], INPUT_PULLUP); attachInterrupt(pin[GPIO_NRG_CF1], HlwCf1Interrupt, FALLING); } pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP); attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING); } void HlwDrvInit(void) { Hlw.model_type = 0; if (pin[GPIO_HJL_CF] < 99) { pin[GPIO_HLW_CF] = pin[GPIO_HJL_CF]; pin[GPIO_HJL_CF] = 99; Hlw.model_type = 1; } if (pin[GPIO_HLW_CF] < 99) { Hlw.ui_flag = true; if (pin[GPIO_NRG_SEL_INV] < 99) { pin[GPIO_NRG_SEL] = pin[GPIO_NRG_SEL_INV]; pin[GPIO_NRG_SEL_INV] = 99; Hlw.ui_flag = false; } if (pin[GPIO_NRG_CF1] < 99) { if (99 == pin[GPIO_NRG_SEL]) { Energy.current_available = false; } } else { Energy.current_available = false; Energy.voltage_available = false; } energy_flg = XNRG_01; } } bool HlwCommand(void) { bool serviced = true; if ((CMND_POWERCAL == Energy.command_code) || (CMND_VOLTAGECAL == Energy.command_code) || (CMND_CURRENTCAL == Energy.command_code)) { } else if (CMND_POWERSET == Energy.command_code) { if (XdrvMailbox.data_len && Hlw.cf_power_pulse_length ) { Settings.energy_power_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data) * 10) * Hlw.cf_power_pulse_length ) / Hlw.power_ratio; } } else if (CMND_VOLTAGESET == Energy.command_code) { if (XdrvMailbox.data_len && Hlw.cf1_voltage_pulse_length ) { Settings.energy_voltage_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data) * 10) * Hlw.cf1_voltage_pulse_length ) / Hlw.voltage_ratio; } } else if (CMND_CURRENTSET == Energy.command_code) { if (XdrvMailbox.data_len && Hlw.cf1_current_pulse_length) { Settings.energy_current_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data)) * Hlw.cf1_current_pulse_length) / Hlw.current_ratio; } } else serviced = false; return serviced; } bool Xnrg01(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_200_MSECOND: HlwEvery200ms(); break; case FUNC_ENERGY_EVERY_SECOND: HlwEverySecond(); break; case FUNC_COMMAND: result = HlwCommand(); break; case FUNC_INIT: HlwSnsInit(); break; case FUNC_PRE_INIT: HlwDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_02_cse7766.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_02_cse7766.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_CSE7766 #define XNRG_02 2 #define CSE_MAX_INVALID_POWER 128 #define CSE_NOT_CALIBRATED 0xAA #define CSE_PULSES_NOT_INITIALIZED -1 #define CSE_PREF 1000 #define CSE_UREF 100 #define CSE_BUFFER_SIZE 25 #include TasmotaSerial *CseSerial = nullptr; struct CSE { long voltage_cycle = 0; long current_cycle = 0; long power_cycle = 0; long power_cycle_first = 0; long cf_pulses = 0; long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; int byte_counter = 0; uint8_t *rx_buffer = nullptr; uint8_t power_invalid = 0; bool received = false; } Cse; void CseReceived(void) { uint8_t header = Cse.rx_buffer[0]; if ((header & 0xFC) == 0xFC) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); return; } if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) { long voltage_coefficient = 191200; if (CSE_NOT_CALIBRATED != header) { voltage_coefficient = Cse.rx_buffer[2] << 16 | Cse.rx_buffer[3] << 8 | Cse.rx_buffer[4]; } Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF; } if (HLW_IREF_PULSE == Settings.energy_current_calibration) { long current_coefficient = 16140; if (CSE_NOT_CALIBRATED != header) { current_coefficient = Cse.rx_buffer[8] << 16 | Cse.rx_buffer[9] << 8 | Cse.rx_buffer[10]; } Settings.energy_current_calibration = current_coefficient; } if (HLW_PREF_PULSE == Settings.energy_power_calibration) { long power_coefficient = 5364000; if (CSE_NOT_CALIBRATED != header) { power_coefficient = Cse.rx_buffer[14] << 16 | Cse.rx_buffer[15] << 8 | Cse.rx_buffer[16]; } Settings.energy_power_calibration = power_coefficient / CSE_PREF; } uint8_t adjustement = Cse.rx_buffer[20]; Cse.voltage_cycle = Cse.rx_buffer[5] << 16 | Cse.rx_buffer[6] << 8 | Cse.rx_buffer[7]; Cse.current_cycle = Cse.rx_buffer[11] << 16 | Cse.rx_buffer[12] << 8 | Cse.rx_buffer[13]; Cse.power_cycle = Cse.rx_buffer[17] << 16 | Cse.rx_buffer[18] << 8 | Cse.rx_buffer[19]; Cse.cf_pulses = Cse.rx_buffer[21] << 8 | Cse.rx_buffer[22]; if (Energy.power_on) { if (adjustement & 0x40) { Energy.voltage[0] = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)Cse.voltage_cycle; } if (adjustement & 0x10) { Cse.power_invalid = 0; if ((header & 0xF2) == 0xF2) { Energy.active_power[0] = 0; } else { if (0 == Cse.power_cycle_first) { Cse.power_cycle_first = Cse.power_cycle; } if (Cse.power_cycle_first != Cse.power_cycle) { Cse.power_cycle_first = -1; Energy.active_power[0] = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)Cse.power_cycle; } else { Energy.active_power[0] = 0; } } } else { if (Cse.power_invalid < Settings.param[P_CSE7766_INVALID_POWER]) { Cse.power_invalid++; } else { Cse.power_cycle_first = 0; Energy.active_power[0] = 0; } } if (adjustement & 0x20) { if (0 == Energy.active_power[0]) { Energy.current[0] = 0; } else { Energy.current[0] = (float)Settings.energy_current_calibration / (float)Cse.current_cycle; } } } else { Cse.power_cycle_first = 0; Energy.voltage[0] = 0; Energy.active_power[0] = 0; Energy.current[0] = 0; } } bool CseSerialInput(void) { while (CseSerial->available()) { yield(); uint8_t serial_in_byte = CseSerial->read(); if (Cse.received) { Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte; if (24 == Cse.byte_counter) { AddLogBuffer(LOG_LEVEL_DEBUG_MORE, Cse.rx_buffer, 24); uint8_t checksum = 0; for (uint32_t i = 2; i < 23; i++) { checksum += Cse.rx_buffer[i]; } if (checksum == Cse.rx_buffer[23]) { Energy.data_valid[0] = 0; CseReceived(); Cse.received = false; return true; } else { AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); do { memmove(Cse.rx_buffer, Cse.rx_buffer +1, 24); Cse.byte_counter--; } while ((Cse.byte_counter > 2) && (0x5A != Cse.rx_buffer[1])); if (0x5A != Cse.rx_buffer[1]) { Cse.received = false; Cse.byte_counter = 0; } } } } else { if ((0x5A == serial_in_byte) && (1 == Cse.byte_counter)) { Cse.received = true; } else { Cse.byte_counter = 0; } Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte; } } } void CseEverySecond(void) { if (Energy.data_valid[0] > ENERGY_WATCHDOG) { Cse.voltage_cycle = 0; Cse.current_cycle = 0; Cse.power_cycle = 0; } else { long cf_frequency = 0; if (CSE_PULSES_NOT_INITIALIZED == Cse.cf_pulses_last_time) { Cse.cf_pulses_last_time = Cse.cf_pulses; } else { if (Cse.cf_pulses < Cse.cf_pulses_last_time) { cf_frequency = (65536 - Cse.cf_pulses_last_time) + Cse.cf_pulses; } else { cf_frequency = Cse.cf_pulses - Cse.cf_pulses_last_time; } if (cf_frequency && Energy.active_power[0]) { unsigned long delta = (cf_frequency * Settings.energy_power_calibration) / 36; if (delta <= (4000*100/36) * 10 ) { Cse.cf_pulses_last_time = Cse.cf_pulses; Energy.kWhtoday_delta += delta; } else { AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Load overflow")); Cse.cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; } EnergyUpdateToday(); } } } } void CseSnsInit(void) { CseSerial = new TasmotaSerial(pin[GPIO_CSE7766_RX], -1, 1); if (CseSerial->begin(4800, 2)) { if (CseSerial->hardwareSerial()) { SetSerial(4800, TS_SERIAL_8E1); ClaimSerial(); } if (0 == Settings.param[P_CSE7766_INVALID_POWER]) { Settings.param[P_CSE7766_INVALID_POWER] = CSE_MAX_INVALID_POWER; } Cse.power_invalid = Settings.param[P_CSE7766_INVALID_POWER]; } else { energy_flg = ENERGY_NONE; } } void CseDrvInit(void) { Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE)); if (Cse.rx_buffer != nullptr) { if (pin[GPIO_CSE7766_RX] < 99) { energy_flg = XNRG_02; } } } bool CseCommand(void) { bool serviced = true; if (CMND_POWERSET == Energy.command_code) { if (XdrvMailbox.data_len && Cse.power_cycle) { Settings.energy_power_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.power_cycle) / CSE_PREF; } } else if (CMND_VOLTAGESET == Energy.command_code) { if (XdrvMailbox.data_len && Cse.voltage_cycle) { Settings.energy_voltage_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.voltage_cycle) / CSE_UREF; } } else if (CMND_CURRENTSET == Energy.command_code) { if (XdrvMailbox.data_len && Cse.current_cycle) { Settings.energy_current_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.current_cycle) / 1000; } } else serviced = false; return serviced; } bool Xnrg02(uint8_t function) { bool result = false; switch (function) { case FUNC_LOOP: if (CseSerial) { CseSerialInput(); } break; case FUNC_ENERGY_EVERY_SECOND: CseEverySecond(); break; case FUNC_COMMAND: result = CseCommand(); break; case FUNC_INIT: CseSnsInit(); break; case FUNC_PRE_INIT: CseDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_PZEM004T # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino" #define XNRG_03 3 const uint32_t PZEM_STABILIZE = 30; #include TasmotaSerial *PzemSerial = nullptr; #define PZEM_VOLTAGE (uint8_t)0xB0 #define RESP_VOLTAGE (uint8_t)0xA0 #define PZEM_CURRENT (uint8_t)0xB1 #define RESP_CURRENT (uint8_t)0xA1 #define PZEM_POWER (uint8_t)0xB2 #define RESP_POWER (uint8_t)0xA2 #define PZEM_ENERGY (uint8_t)0xB3 #define RESP_ENERGY (uint8_t)0xA3 #define PZEM_SET_ADDRESS (uint8_t)0xB4 #define RESP_SET_ADDRESS (uint8_t)0xA4 #define PZEM_POWER_ALARM (uint8_t)0xB5 #define RESP_POWER_ALARM (uint8_t)0xA5 #define PZEM_DEFAULT_READ_TIMEOUT 500 struct PZEM { float energy = 0; float last_energy = 0; uint8_t send_retry = 0; uint8_t read_state = 0; uint8_t phase = 0; uint8_t address = 0; } Pzem; struct PZEMCommand { uint8_t command; uint8_t addr[4]; uint8_t data; uint8_t crc; }; uint8_t PzemCrc(uint8_t *data) { uint16_t crc = 0; for (uint32_t i = 0; i < sizeof(PZEMCommand) -1; i++) { crc += *data++; } return (uint8_t)(crc & 0xFF); } void PzemSend(uint8_t cmd) { PZEMCommand pzem; pzem.command = cmd; pzem.addr[0] = 192; pzem.addr[1] = 168; pzem.addr[2] = 1; pzem.addr[3] = ((PZEM_SET_ADDRESS == cmd) && Pzem.address) ? Pzem.address : 1 + Pzem.phase; pzem.data = 0; uint8_t *bytes = (uint8_t*)&pzem; pzem.crc = PzemCrc(bytes); PzemSerial->flush(); PzemSerial->write(bytes, sizeof(pzem)); Pzem.address = 0; } bool PzemReceiveReady(void) { return PzemSerial->available() >= (int)sizeof(PZEMCommand); } bool PzemRecieve(uint8_t resp, float *data) { # 124 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino" uint8_t buffer[sizeof(PZEMCommand)] = { 0 }; unsigned long start = millis(); uint8_t len = 0; while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) { if (PzemSerial->available() > 0) { uint8_t c = (uint8_t)PzemSerial->read(); if (!len && ((c & 0xF8) != 0xA0)) { continue; } buffer[len++] = c; } } AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, len); if (len != sizeof(PZEMCommand)) { return false; } if (buffer[6] != PzemCrc(buffer)) { return false; } if (buffer[0] != resp) { return false; } switch (resp) { case RESP_VOLTAGE: *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0); break; case RESP_CURRENT: *data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0); break; case RESP_POWER: *data = (float)(buffer[1] << 8) + buffer[2]; break; case RESP_ENERGY: *data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3]; break; } return true; } const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY }; const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY }; void PzemEvery250ms(void) { bool data_ready = PzemReceiveReady(); if (data_ready) { float value = 0; if (PzemRecieve(pzem_responses[Pzem.read_state], &value)) { Energy.data_valid[Pzem.phase] = 0; switch (Pzem.read_state) { case 1: Energy.voltage[Pzem.phase] = value; break; case 2: Energy.current[Pzem.phase] = value; break; case 3: Energy.active_power[Pzem.phase] = value; break; case 4: Pzem.energy += value; if (Pzem.phase == Energy.phase_count -1) { if (Pzem.energy > Pzem.last_energy) { if (uptime > PZEM_STABILIZE) { EnergyUpdateTotal(Pzem.energy, false); } Pzem.last_energy = Pzem.energy; } Pzem.energy = 0; } break; } Pzem.read_state++; if (5 == Pzem.read_state) { Pzem.read_state = 1; } } } if (0 == Pzem.send_retry || data_ready) { if (1 == Pzem.read_state) { if (0 == Pzem.phase) { Pzem.phase = Energy.phase_count -1; } else { Pzem.phase--; } } if (Pzem.address) { Pzem.read_state = 0; } Pzem.send_retry = 5; PzemSend(pzem_commands[Pzem.read_state]); } else { Pzem.send_retry--; if ((Energy.phase_count > 1) && (0 == Pzem.send_retry) && (uptime < PZEM_STABILIZE)) { Energy.phase_count--; } } } void PzemSnsInit(void) { PzemSerial = new TasmotaSerial(pin[GPIO_PZEM004_RX], pin[GPIO_PZEM0XX_TX], 1); if (PzemSerial->begin(9600)) { if (PzemSerial->hardwareSerial()) { ClaimSerial(); } Energy.phase_count = 3; Pzem.phase = 0; Pzem.read_state = 1; } else { energy_flg = ENERGY_NONE; } } void PzemDrvInit(void) { if ((pin[GPIO_PZEM004_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { energy_flg = XNRG_03; } } bool PzemCommand(void) { bool serviced = true; if (CMND_MODULEADDRESS == Energy.command_code) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4)) { Pzem.address = XdrvMailbox.payload; } } else serviced = false; return serviced; } bool Xnrg03(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: if (PzemSerial && (uptime > 4)) { PzemEvery250ms(); } break; case FUNC_COMMAND: result = PzemCommand(); break; case FUNC_INIT: PzemSnsInit(); break; case FUNC_PRE_INIT: PzemDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_04_mcp39f501.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_04_mcp39f501.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_MCP39F501 #define XNRG_04 4 #define MCP_BAUDRATE 4800 #define MCP_TIMEOUT 4 #define MCP_CALIBRATION_TIMEOUT 2 #define MCP_CALIBRATE_POWER 0x001 #define MCP_CALIBRATE_VOLTAGE 0x002 #define MCP_CALIBRATE_CURRENT 0x004 #define MCP_CALIBRATE_FREQUENCY 0x008 #define MCP_SINGLE_WIRE_FLAG 0x100 #define MCP_START_FRAME 0xA5 #define MCP_ACK_FRAME 0x06 #define MCP_ERROR_NAK 0x15 #define MCP_ERROR_CRC 0x51 #define MCP_SINGLE_WIRE 0xAB #define MCP_SET_ADDRESS 0x41 #define MCP_READ 0x4E #define MCP_READ_16 0x52 #define MCP_READ_32 0x44 #define MCP_WRITE 0x4D #define MCP_WRITE_16 0x57 #define MCP_WRITE_32 0x45 #define MCP_SAVE_REGISTERS 0x53 #define MCP_CALIBRATION_BASE 0x0028 #define MCP_CALIBRATION_LEN 52 #define MCP_FREQUENCY_REF_BASE 0x0094 #define MCP_FREQUENCY_GAIN_BASE 0x00AE #define MCP_FREQUENCY_LEN 4 #define MCP_BUFFER_SIZE 60 #include TasmotaSerial *McpSerial = nullptr; typedef struct mcp_cal_registers_type { uint16_t gain_current_rms; uint16_t gain_voltage_rms; uint16_t gain_active_power; uint16_t gain_reactive_power; sint32_t offset_current_rms; sint32_t offset_active_power; sint32_t offset_reactive_power; sint16_t dc_offset_current; sint16_t phase_compensation; uint16_t apparent_power_divisor; uint32_t system_configuration; uint16_t dio_configuration; uint32_t range; uint32_t calibration_current; uint16_t calibration_voltage; uint32_t calibration_active_power; uint32_t calibration_reactive_power; uint16_t accumulation_interval; } mcp_cal_registers_type; char *mcp_buffer = nullptr; unsigned long mcp_window = 0; unsigned long mcp_kWhcounter = 0; uint32_t mcp_system_configuration = 0x03000000; uint32_t mcp_active_power; uint32_t mcp_current_rms; uint16_t mcp_voltage_rms; uint16_t mcp_line_frequency; uint8_t mcp_address = 0; uint8_t mcp_calibration_active = 0; uint8_t mcp_init = 0; uint8_t mcp_timeout = 0; uint8_t mcp_calibrate = 0; uint8_t mcp_byte_counter = 0; uint8_t McpChecksum(uint8_t *data) { uint8_t checksum = 0; uint8_t offset = 0; uint8_t len = data[1] -1; for (uint32_t i = offset; i < len; i++) { checksum += data[i]; } return checksum; } unsigned long McpExtractInt(char *data, uint8_t offset, uint8_t size) { unsigned long result = 0; unsigned long pow = 1; for (uint32_t i = 0; i < size; i++) { result = result + (uint8_t)data[offset + i] * pow; pow = pow * 256; } return result; } void McpSetInt(unsigned long value, uint8_t *data, uint8_t offset, size_t size) { for (uint32_t i = 0; i < size; i++) { data[offset + i] = ((value >> (i * 8)) & 0xFF); } } void McpSend(uint8_t *data) { if (mcp_timeout) { return; } mcp_timeout = MCP_TIMEOUT; data[0] = MCP_START_FRAME; data[data[1] -1] = McpChecksum(data); for (uint32_t i = 0; i < data[1]; i++) { McpSerial->write(data[i]); } } void McpGetAddress(void) { uint8_t data[] = { MCP_START_FRAME, 7, MCP_SET_ADDRESS, 0x00, 0x26, MCP_READ_16, 0x00 }; McpSend(data); } void McpAddressReceive(void) { mcp_address = mcp_buffer[3]; } void McpGetCalibration(void) { if (mcp_calibration_active) { return; } mcp_calibration_active = MCP_CALIBRATION_TIMEOUT; uint8_t data[] = { MCP_START_FRAME, 8, MCP_SET_ADDRESS, (MCP_CALIBRATION_BASE >> 8) & 0xFF, MCP_CALIBRATION_BASE & 0xFF, MCP_READ, MCP_CALIBRATION_LEN, 0x00 }; McpSend(data); } void McpParseCalibration(void) { bool action = false; mcp_cal_registers_type cal_registers; cal_registers.gain_current_rms = McpExtractInt(mcp_buffer, 2, 2); cal_registers.gain_voltage_rms = McpExtractInt(mcp_buffer, 4, 2); cal_registers.gain_active_power = McpExtractInt(mcp_buffer, 6, 2); cal_registers.gain_reactive_power = McpExtractInt(mcp_buffer, 8, 2); cal_registers.offset_current_rms = McpExtractInt(mcp_buffer, 10, 4); cal_registers.offset_active_power = McpExtractInt(mcp_buffer, 14, 4); cal_registers.offset_reactive_power = McpExtractInt(mcp_buffer, 18, 4); cal_registers.dc_offset_current = McpExtractInt(mcp_buffer, 22, 2); cal_registers.phase_compensation = McpExtractInt(mcp_buffer, 24, 2); cal_registers.apparent_power_divisor = McpExtractInt(mcp_buffer, 26, 2); cal_registers.system_configuration = McpExtractInt(mcp_buffer, 28, 4); cal_registers.dio_configuration = McpExtractInt(mcp_buffer, 32, 2); cal_registers.range = McpExtractInt(mcp_buffer, 34, 4); cal_registers.calibration_current = McpExtractInt(mcp_buffer, 38, 4); cal_registers.calibration_voltage = McpExtractInt(mcp_buffer, 42, 2); cal_registers.calibration_active_power = McpExtractInt(mcp_buffer, 44, 4); cal_registers.calibration_reactive_power = McpExtractInt(mcp_buffer, 48, 4); cal_registers.accumulation_interval = McpExtractInt(mcp_buffer, 52, 2); if (mcp_calibrate & MCP_CALIBRATE_POWER) { cal_registers.calibration_active_power = Settings.energy_power_calibration; if (McpCalibrationCalc(&cal_registers, 16)) { action = true; } } if (mcp_calibrate & MCP_CALIBRATE_VOLTAGE) { cal_registers.calibration_voltage = Settings.energy_voltage_calibration; if (McpCalibrationCalc(&cal_registers, 0)) { action = true; } } if (mcp_calibrate & MCP_CALIBRATE_CURRENT) { cal_registers.calibration_current = Settings.energy_current_calibration; if (McpCalibrationCalc(&cal_registers, 8)) { action = true; } } mcp_timeout = 0; if (action) { McpSetCalibration(&cal_registers); } mcp_calibrate = 0; Settings.energy_power_calibration = cal_registers.calibration_active_power; Settings.energy_voltage_calibration = cal_registers.calibration_voltage; Settings.energy_current_calibration = cal_registers.calibration_current; mcp_system_configuration = cal_registers.system_configuration; if (mcp_system_configuration & MCP_SINGLE_WIRE_FLAG) { mcp_system_configuration &= ~MCP_SINGLE_WIRE_FLAG; McpSetSystemConfiguration(2); } } bool McpCalibrationCalc(struct mcp_cal_registers_type *cal_registers, uint8_t range_shift) { uint32_t measured; uint32_t expected; uint16_t *gain; uint32_t new_gain; if (range_shift == 0) { measured = mcp_voltage_rms; expected = cal_registers->calibration_voltage; gain = &(cal_registers->gain_voltage_rms); } else if (range_shift == 8) { measured = mcp_current_rms; expected = cal_registers->calibration_current; gain = &(cal_registers->gain_current_rms); } else if (range_shift == 16) { measured = mcp_active_power; expected = cal_registers->calibration_active_power; gain = &(cal_registers->gain_active_power); } else { return false; } if (measured == 0) { return false; } uint32_t range = (cal_registers->range >> range_shift) & 0xFF; calc: new_gain = (*gain) * expected / measured; if (new_gain < 25000) { range++; if (measured > 6) { measured = measured / 2; goto calc; } } if (new_gain > 55000) { range--; measured = measured * 2; goto calc; } *gain = new_gain; uint32_t old_range = (cal_registers->range >> range_shift) & 0xFF; cal_registers->range = cal_registers->range ^ (old_range << range_shift); cal_registers->range = cal_registers->range | (range << range_shift); return true; } void McpSetCalibration(struct mcp_cal_registers_type *cal_registers) { uint8_t data[7 + MCP_CALIBRATION_LEN + 2 + 1]; data[1] = sizeof(data); data[2] = MCP_SET_ADDRESS; data[3] = (MCP_CALIBRATION_BASE >> 8) & 0xFF; data[4] = (MCP_CALIBRATION_BASE >> 0) & 0xFF; data[5] = MCP_WRITE; data[6] = MCP_CALIBRATION_LEN; McpSetInt(cal_registers->gain_current_rms, data, 0+7, 2); McpSetInt(cal_registers->gain_voltage_rms, data, 2+7, 2); McpSetInt(cal_registers->gain_active_power, data, 4+7, 2); McpSetInt(cal_registers->gain_reactive_power, data, 6+7, 2); McpSetInt(cal_registers->offset_current_rms, data, 8+7, 4); McpSetInt(cal_registers->offset_active_power, data, 12+7, 4); McpSetInt(cal_registers->offset_reactive_power, data, 16+7, 4); McpSetInt(cal_registers->dc_offset_current, data, 20+7, 2); McpSetInt(cal_registers->phase_compensation, data, 22+7, 2); McpSetInt(cal_registers->apparent_power_divisor, data, 24+7, 2); McpSetInt(cal_registers->system_configuration, data, 26+7, 4); McpSetInt(cal_registers->dio_configuration, data, 30+7, 2); McpSetInt(cal_registers->range, data, 32+7, 4); McpSetInt(cal_registers->calibration_current, data, 36+7, 4); McpSetInt(cal_registers->calibration_voltage, data, 40+7, 2); McpSetInt(cal_registers->calibration_active_power, data, 42+7, 4); McpSetInt(cal_registers->calibration_reactive_power, data, 46+7, 4); McpSetInt(cal_registers->accumulation_interval, data, 50+7, 2); data[MCP_CALIBRATION_LEN+7] = MCP_SAVE_REGISTERS; data[MCP_CALIBRATION_LEN+8] = mcp_address; McpSend(data); } void McpSetSystemConfiguration(uint16 interval) { uint8_t data[17]; data[ 1] = sizeof(data); data[ 2] = MCP_SET_ADDRESS; data[ 3] = 0x00; data[ 4] = 0x42; data[ 5] = MCP_WRITE_32; data[ 6] = (mcp_system_configuration >> 24) & 0xFF; data[ 7] = (mcp_system_configuration >> 16) & 0xFF; data[ 8] = (mcp_system_configuration >> 8) & 0xFF; data[ 9] = (mcp_system_configuration >> 0) & 0xFF; data[10] = MCP_SET_ADDRESS; data[11] = 0x00; data[12] = 0x5A; data[13] = MCP_WRITE_16; data[14] = (interval >> 8) & 0xFF; data[15] = (interval >> 0) & 0xFF; McpSend(data); } void McpGetFrequency(void) { if (mcp_calibration_active) { return; } mcp_calibration_active = MCP_CALIBRATION_TIMEOUT; uint8_t data[] = { MCP_START_FRAME, 11, MCP_SET_ADDRESS, (MCP_FREQUENCY_REF_BASE >> 8) & 0xFF, MCP_FREQUENCY_REF_BASE & 0xFF, MCP_READ_16, MCP_SET_ADDRESS, (MCP_FREQUENCY_GAIN_BASE >> 8) & 0xFF, MCP_FREQUENCY_GAIN_BASE & 0xFF, MCP_READ_16, 0x00 }; McpSend(data); } void McpParseFrequency(void) { uint16_t line_frequency_ref = mcp_buffer[2] * 256 + mcp_buffer[3]; uint16_t gain_line_frequency = mcp_buffer[4] * 256 + mcp_buffer[5]; if (mcp_calibrate & MCP_CALIBRATE_FREQUENCY) { line_frequency_ref = Settings.energy_frequency_calibration; if ((0xFFFF == mcp_line_frequency) || (0 == gain_line_frequency)) { mcp_line_frequency = 50000; gain_line_frequency = 0x8000; } gain_line_frequency = gain_line_frequency * line_frequency_ref / mcp_line_frequency; mcp_timeout = 0; McpSetFrequency(line_frequency_ref, gain_line_frequency); } Settings.energy_frequency_calibration = line_frequency_ref; mcp_calibrate = 0; } void McpSetFrequency(uint16_t line_frequency_ref, uint16_t gain_line_frequency) { uint8_t data[17]; data[ 1] = sizeof(data); data[ 2] = MCP_SET_ADDRESS; data[ 3] = (MCP_FREQUENCY_REF_BASE >> 8) & 0xFF; data[ 4] = (MCP_FREQUENCY_REF_BASE >> 0) & 0xFF; data[ 5] = MCP_WRITE_16; data[ 6] = (line_frequency_ref >> 8) & 0xFF; data[ 7] = (line_frequency_ref >> 0) & 0xFF; data[ 8] = MCP_SET_ADDRESS; data[ 9] = (MCP_FREQUENCY_GAIN_BASE >> 8) & 0xFF; data[10] = (MCP_FREQUENCY_GAIN_BASE >> 0) & 0xFF; data[11] = MCP_WRITE_16; data[12] = (gain_line_frequency >> 8) & 0xFF; data[13] = (gain_line_frequency >> 0) & 0xFF; data[14] = MCP_SAVE_REGISTERS; data[15] = mcp_address; McpSend(data); } void McpGetData(void) { uint8_t data[] = { MCP_START_FRAME, 8, MCP_SET_ADDRESS, 0x00, 0x04, MCP_READ, 22, 0x00 }; McpSend(data); } void McpParseData(void) { mcp_current_rms = McpExtractInt(mcp_buffer, 2, 4); mcp_voltage_rms = McpExtractInt(mcp_buffer, 6, 2); mcp_active_power = McpExtractInt(mcp_buffer, 8, 4); mcp_line_frequency = McpExtractInt(mcp_buffer, 22, 2); if (Energy.power_on) { Energy.data_valid[0] = 0; Energy.frequency[0] = (float)mcp_line_frequency / 1000; Energy.voltage[0] = (float)mcp_voltage_rms / 10; Energy.active_power[0] = (float)mcp_active_power / 100; if (0 == Energy.active_power[0]) { Energy.current[0] = 0; } else { Energy.current[0] = (float)mcp_current_rms / 10000; } } else { Energy.data_valid[0] = ENERGY_WATCHDOG; } } void McpSerialInput(void) { while ((McpSerial->available()) && (mcp_byte_counter < MCP_BUFFER_SIZE)) { yield(); mcp_buffer[mcp_byte_counter++] = McpSerial->read(); mcp_window = millis(); } if ((mcp_byte_counter) && (millis() - mcp_window > (24000 / MCP_BAUDRATE) +1)) { AddLogBuffer(LOG_LEVEL_DEBUG_MORE, (uint8_t*)mcp_buffer, mcp_byte_counter); if (MCP_BUFFER_SIZE == mcp_byte_counter) { } else if (1 == mcp_byte_counter) { if (MCP_ERROR_CRC == mcp_buffer[0]) { mcp_timeout = 0; } else if (MCP_ERROR_NAK == mcp_buffer[0]) { mcp_timeout = 0; } } else if (MCP_ACK_FRAME == mcp_buffer[0]) { if (mcp_byte_counter == mcp_buffer[1]) { if (McpChecksum((uint8_t *)mcp_buffer) != mcp_buffer[mcp_byte_counter -1]) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("MCP: " D_CHECKSUM_FAILURE)); } else { if (5 == mcp_buffer[1]) { McpAddressReceive(); } if (25 == mcp_buffer[1]) { McpParseData(); } if (MCP_CALIBRATION_LEN + 3 == mcp_buffer[1]) { McpParseCalibration(); } if (MCP_FREQUENCY_LEN + 3 == mcp_buffer[1]) { McpParseFrequency(); } } } mcp_timeout = 0; } else if (MCP_SINGLE_WIRE == mcp_buffer[0]) { mcp_timeout = 0; } mcp_byte_counter = 0; McpSerial->flush(); } } void McpEverySecond(void) { if (Energy.data_valid[0] > ENERGY_WATCHDOG) { mcp_voltage_rms = 0; mcp_current_rms = 0; mcp_active_power = 0; mcp_line_frequency = 0; } if (mcp_active_power) { Energy.kWhtoday_delta += ((mcp_active_power * 10) / 36); EnergyUpdateToday(); } if (mcp_timeout) { mcp_timeout--; } else if (mcp_calibration_active) { mcp_calibration_active--; } else if (mcp_init) { if (2 == mcp_init) { McpGetCalibration(); } else if (1 == mcp_init) { McpGetFrequency(); } mcp_init--; } else if (!mcp_address) { McpGetAddress(); } else { McpGetData(); } } void McpSnsInit(void) { McpSerial = new TasmotaSerial(pin[GPIO_MCP39F5_RX], pin[GPIO_MCP39F5_TX], 1); if (McpSerial->begin(MCP_BAUDRATE)) { if (McpSerial->hardwareSerial()) { ClaimSerial(); mcp_buffer = serial_in_buffer; } else { mcp_buffer = (char*)(malloc(MCP_BUFFER_SIZE)); } DigitalWrite(GPIO_MCP39F5_RST, 1); } else { energy_flg = ENERGY_NONE; } } void McpDrvInit(void) { if ((pin[GPIO_MCP39F5_RX] < 99) && (pin[GPIO_MCP39F5_TX] < 99)) { if (pin[GPIO_MCP39F5_RST] < 99) { pinMode(pin[GPIO_MCP39F5_RST], OUTPUT); digitalWrite(pin[GPIO_MCP39F5_RST], 0); } mcp_calibrate = 0; mcp_timeout = 2; mcp_init = 2; energy_flg = XNRG_04; } } bool McpCommand(void) { bool serviced = true; unsigned long value = 0; if (CMND_POWERSET == Energy.command_code) { if (XdrvMailbox.data_len && mcp_active_power) { value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 100); if ((value > 100) && (value < 200000)) { Settings.energy_power_calibration = value; mcp_calibrate |= MCP_CALIBRATE_POWER; McpGetCalibration(); } } } else if (CMND_VOLTAGESET == Energy.command_code) { if (XdrvMailbox.data_len && mcp_voltage_rms) { value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 10); if ((value > 1000) && (value < 2600)) { Settings.energy_voltage_calibration = value; mcp_calibrate |= MCP_CALIBRATE_VOLTAGE; McpGetCalibration(); } } } else if (CMND_CURRENTSET == Energy.command_code) { if (XdrvMailbox.data_len && mcp_current_rms) { value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 10); if ((value > 100) && (value < 80000)) { Settings.energy_current_calibration = value; mcp_calibrate |= MCP_CALIBRATE_CURRENT; McpGetCalibration(); } } } else if (CMND_FREQUENCYSET == Energy.command_code) { if (XdrvMailbox.data_len && mcp_line_frequency) { value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 1000); if ((value > 45000) && (value < 65000)) { Settings.energy_frequency_calibration = value; mcp_calibrate |= MCP_CALIBRATE_FREQUENCY; McpGetFrequency(); } } } else serviced = false; return serviced; } bool Xnrg04(uint8_t function) { bool result = false; switch (function) { case FUNC_LOOP: if (McpSerial) { McpSerialInput(); } break; case FUNC_ENERGY_EVERY_SECOND: if (McpSerial) { McpEverySecond(); } break; case FUNC_COMMAND: result = McpCommand(); break; case FUNC_INIT: McpSnsInit(); break; case FUNC_PRE_INIT: McpDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_PZEM_AC # 33 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino" #define XNRG_05 5 const uint8_t PZEM_AC_DEVICE_ADDRESS = 0x01; const uint32_t PZEM_AC_STABILIZE = 30; #include TasmotaModbus *PzemAcModbus; struct PZEMAC { float energy = 0; float last_energy = 0; uint8_t send_retry = 0; uint8_t phase = 0; uint8_t address = 0; uint8_t address_step = ADDR_IDLE; } PzemAc; void PzemAcEverySecond(void) { bool data_ready = PzemAcModbus->ReceiveReady(); if (data_ready) { uint8_t buffer[30]; uint8_t registers = 10; if (ADDR_RECEIVE == PzemAc.address_step) { registers = 2; PzemAc.address_step--; } uint8_t error = PzemAcModbus->ReceiveBuffer(buffer, registers); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, PzemAcModbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAC: PzemAc %d error %d"), PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, error); } else { Energy.data_valid[PzemAc.phase] = 0; if (10 == registers) { Energy.voltage[PzemAc.phase] = (float)((buffer[3] << 8) + buffer[4]) / 10.0; Energy.current[PzemAc.phase] = (float)((buffer[7] << 24) + (buffer[8] << 16) + (buffer[5] << 8) + buffer[6]) / 1000.0; Energy.active_power[PzemAc.phase] = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[9] << 8) + buffer[10]) / 10.0; Energy.frequency[PzemAc.phase] = (float)((buffer[17] << 8) + buffer[18]) / 10.0; Energy.power_factor[PzemAc.phase] = (float)((buffer[19] << 8) + buffer[20]) / 100.0; PzemAc.energy += (float)((buffer[15] << 24) + (buffer[16] << 16) + (buffer[13] << 8) + buffer[14]); if (PzemAc.phase == Energy.phase_count -1) { if (PzemAc.energy > PzemAc.last_energy) { if (uptime > PZEM_AC_STABILIZE) { EnergyUpdateTotal(PzemAc.energy, false); } PzemAc.last_energy = PzemAc.energy; } PzemAc.energy = 0; } } } } if (0 == PzemAc.send_retry || data_ready) { if (0 == PzemAc.phase) { PzemAc.phase = Energy.phase_count -1; } else { PzemAc.phase--; } PzemAc.send_retry = ENERGY_WATCHDOG; if (ADDR_SEND == PzemAc.address_step) { PzemAcModbus->Send(0xF8, 0x06, 0x0002, (uint16_t)PzemAc.address); PzemAc.address_step--; } else { PzemAcModbus->Send(PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, 0x04, 0, 10); } } else { PzemAc.send_retry--; if ((Energy.phase_count > 1) && (0 == PzemAc.send_retry) && (uptime < PZEM_AC_STABILIZE)) { Energy.phase_count--; } } } void PzemAcSnsInit(void) { PzemAcModbus = new TasmotaModbus(pin[GPIO_PZEM016_RX], pin[GPIO_PZEM0XX_TX]); uint8_t result = PzemAcModbus->Begin(9600); if (result) { if (2 == result) { ClaimSerial(); } Energy.phase_count = 3; PzemAc.phase = 0; } else { energy_flg = ENERGY_NONE; } } void PzemAcDrvInit(void) { if ((pin[GPIO_PZEM016_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { energy_flg = XNRG_05; } } bool PzemAcCommand(void) { bool serviced = true; if (CMND_MODULEADDRESS == Energy.command_code) { PzemAc.address = XdrvMailbox.payload; PzemAc.address_step = ADDR_SEND; } else serviced = false; return serviced; } bool Xnrg05(uint8_t function) { bool result = false; switch (function) { case FUNC_ENERGY_EVERY_SECOND: if (uptime > 4) { PzemAcEverySecond(); } break; case FUNC_COMMAND: result = PzemAcCommand(); break; case FUNC_INIT: PzemAcSnsInit(); break; case FUNC_PRE_INIT: PzemAcDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_PZEM_DC # 32 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino" #define XNRG_06 6 const uint8_t PZEM_DC_DEVICE_ADDRESS = 0x01; const uint32_t PZEM_DC_STABILIZE = 30; #include TasmotaModbus *PzemDcModbus; struct PZEMDC { float energy = 0; float last_energy = 0; uint8_t send_retry = 0; uint8_t channel = 0; uint8_t address = 0; uint8_t address_step = ADDR_IDLE; } PzemDc; void PzemDcEverySecond(void) { bool data_ready = PzemDcModbus->ReceiveReady(); if (data_ready) { uint8_t buffer[26]; uint8_t registers = 8; if (ADDR_RECEIVE == PzemDc.address_step) { registers = 2; PzemDc.address_step--; } uint8_t error = PzemDcModbus->ReceiveBuffer(buffer, registers); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, PzemDcModbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PDC: PzemDc %d error %d"), PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, error); } else { Energy.data_valid[PzemDc.channel] = 0; if (8 == registers) { Energy.voltage[PzemDc.channel] = (float)((buffer[3] << 8) + buffer[4]) / 100.0; Energy.current[PzemDc.channel] = (float)((buffer[5] << 8) + buffer[6]) / 100.0; Energy.active_power[PzemDc.channel] = (float)((buffer[9] << 24) + (buffer[10] << 16) + (buffer[7] << 8) + buffer[8]) / 10.0; PzemDc.energy += (float)((buffer[13] << 24) + (buffer[14] << 16) + (buffer[11] << 8) + buffer[12]); if (PzemDc.channel == Energy.phase_count -1) { if (PzemDc.energy > PzemDc.last_energy) { if (uptime > PZEM_DC_STABILIZE) { EnergyUpdateTotal(PzemDc.energy, false); } PzemDc.last_energy = PzemDc.energy; } PzemDc.energy = 0; } } } } if (0 == PzemDc.send_retry || data_ready) { if (0 == PzemDc.channel) { PzemDc.channel = Energy.phase_count -1; } else { PzemDc.channel--; } PzemDc.send_retry = ENERGY_WATCHDOG; if (ADDR_SEND == PzemDc.address_step) { PzemDcModbus->Send(0xF8, 0x06, 0x0002, (uint16_t)PzemDc.address); PzemDc.address_step--; } else { PzemDcModbus->Send(PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, 0x04, 0, 8); } } else { PzemDc.send_retry--; if ((Energy.phase_count > 1) && (0 == PzemDc.send_retry) && (uptime < PZEM_DC_STABILIZE)) { Energy.phase_count--; } } } void PzemDcSnsInit(void) { PzemDcModbus = new TasmotaModbus(pin[GPIO_PZEM017_RX], pin[GPIO_PZEM0XX_TX]); uint8_t result = PzemDcModbus->Begin(9600, 2); if (result) { if (2 == result) { ClaimSerial(); } Energy.type_dc = true; Energy.phase_count = 3; PzemDc.channel = 0; } else { energy_flg = ENERGY_NONE; } } void PzemDcDrvInit(void) { if ((pin[GPIO_PZEM017_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) { energy_flg = XNRG_06; } } bool PzemDcCommand(void) { bool serviced = true; if (CMND_MODULEADDRESS == Energy.command_code) { PzemDc.address = XdrvMailbox.payload; PzemDc.address_step = ADDR_SEND; } else serviced = false; return serviced; } bool Xnrg06(uint8_t function) { bool result = false; switch (function) { case FUNC_ENERGY_EVERY_SECOND: if (uptime > 4) { PzemDcEverySecond(); } break; case FUNC_COMMAND: result = PzemDcCommand(); break; case FUNC_INIT: PzemDcSnsInit(); break; case FUNC_PRE_INIT: PzemDcDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino" #ifdef USE_I2C #ifdef USE_ENERGY_SENSOR #ifdef USE_ADE7953 # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino" #define XNRG_07 7 #define XI2C_07 7 #define ADE7953_PREF 1540 #define ADE7953_UREF 26000 #define ADE7953_IREF 10000 #define ADE7953_ADDR 0x38 const uint16_t Ade7953Registers[] { 0x31B, 0x313, 0x311, 0x315, 0x31A, 0x312, 0x310, 0x314, 0x31C, 0x10E }; struct Ade7953 { uint32_t voltage_rms = 0; uint32_t period = 0; uint32_t current_rms[2] = { 0, 0 }; uint32_t active_power[2] = { 0, 0 }; uint8_t init_step = 0; } Ade7953; int Ade7953RegSize(uint16_t reg) { int size = 0; switch ((reg >> 8) & 0x0F) { case 0x03: size++; case 0x02: size++; case 0x01: size++; case 0x00: case 0x07: case 0x08: size++; } return size; } void Ade7953Write(uint16_t reg, uint32_t val) { int size = Ade7953RegSize(reg); if (size) { Wire.beginTransmission(ADE7953_ADDR); Wire.write((reg >> 8) & 0xFF); Wire.write(reg & 0xFF); while (size--) { Wire.write((val >> (8 * size)) & 0xFF); } Wire.endTransmission(); delayMicroseconds(5); } } int32_t Ade7953Read(uint16_t reg) { uint32_t response = 0; int size = Ade7953RegSize(reg); if (size) { Wire.beginTransmission(ADE7953_ADDR); Wire.write((reg >> 8) & 0xFF); Wire.write(reg & 0xFF); Wire.endTransmission(0); Wire.requestFrom(ADE7953_ADDR, size); if (size <= Wire.available()) { for (uint32_t i = 0; i < size; i++) { response = response << 8 | Wire.read(); } } } return response; } void Ade7953Init(void) { Ade7953Write(0x102, 0x0004); Ade7953Write(0x0FE, 0x00AD); Ade7953Write(0x120, 0x0030); } void Ade7953GetData(void) { int32_t reg[2][4]; for (uint32_t i = 0; i < sizeof(Ade7953Registers)/sizeof(uint16_t); i++) { int32_t value = Ade7953Read(Ade7953Registers[i]); if (8 == i) { Ade7953.voltage_rms = value; } else if (9 == i) { Ade7953.period = value; } else { reg[i >> 2][i &3] = value; } } AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ADE: %d, %d, [%d, %d, %d, %d], [%d, %d, %d, %d]"), Ade7953.voltage_rms, Ade7953.period, reg[0][0], reg[0][1], reg[0][2], reg[0][3], reg[1][0], reg[1][1], reg[1][2], reg[1][3]); uint32_t apparent_power[2] = { 0, 0 }; uint32_t reactive_power[2] = { 0, 0 }; for (uint32_t channel = 0; channel < 2; channel++) { Ade7953.current_rms[channel] = reg[channel][0]; if (Ade7953.current_rms[channel] < 2000) { Ade7953.current_rms[channel] = 0; Ade7953.active_power[channel] = 0; } else { Ade7953.active_power[channel] = abs(reg[channel][1]); apparent_power[channel] = abs(reg[channel][2]); reactive_power[channel] = abs(reg[channel][3]); } } uint32_t current_rms_sum = Ade7953.current_rms[0] + Ade7953.current_rms[1]; uint32_t active_power_sum = Ade7953.active_power[0] + Ade7953.active_power[1]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ADE: U %d, C %d, I %d + %d = %d, P %d + %d = %d"), Ade7953.voltage_rms, Ade7953.period, Ade7953.current_rms[0], Ade7953.current_rms[1], current_rms_sum, Ade7953.active_power[0], Ade7953.active_power[1], active_power_sum); if (Energy.power_on) { Energy.voltage[0] = (float)Ade7953.voltage_rms / Settings.energy_voltage_calibration; Energy.frequency[0] = 223750.0f / ( (float)Ade7953.period + 1); for (uint32_t channel = 0; channel < 2; channel++) { Energy.data_valid[channel] = 0; Energy.active_power[channel] = (float)Ade7953.active_power[channel] / (Settings.energy_power_calibration / 10); Energy.reactive_power[channel] = (float)reactive_power[channel] / (Settings.energy_power_calibration / 10); Energy.apparent_power[channel] = (float)apparent_power[channel] / (Settings.energy_power_calibration / 10); if (0 == Energy.active_power[channel]) { Energy.current[channel] = 0; } else { Energy.current[channel] = (float)Ade7953.current_rms[channel] / (Settings.energy_current_calibration * 10); } } } else { Energy.data_valid[0] = ENERGY_WATCHDOG; Energy.data_valid[1] = ENERGY_WATCHDOG; } if (active_power_sum) { Energy.kWhtoday_delta += ((active_power_sum * (100000 / (Settings.energy_power_calibration / 10))) / 3600); EnergyUpdateToday(); } } void Ade7953EnergyEverySecond(void) { if (Ade7953.init_step) { if (1 == Ade7953.init_step) { Ade7953Init(); } Ade7953.init_step--; } else { Ade7953GetData(); } } void Ade7953DrvInit(void) { if (pin[GPIO_ADE7953_IRQ] < 99) { delay(100); if (I2cSetDevice(ADE7953_ADDR)) { if (HLW_PREF_PULSE == Settings.energy_power_calibration) { Settings.energy_power_calibration = ADE7953_PREF; Settings.energy_voltage_calibration = ADE7953_UREF; Settings.energy_current_calibration = ADE7953_IREF; } I2cSetActiveFound(ADE7953_ADDR, "ADE7953"); Ade7953.init_step = 2; Energy.phase_count = 2; Energy.voltage_common = true; energy_flg = XNRG_07; } } } bool Ade7953Command(void) { bool serviced = true; uint32_t channel = (2 == XdrvMailbox.index) ? 1 : 0; uint32_t value = (uint32_t)(CharToFloat(XdrvMailbox.data) * 100); if (CMND_POWERCAL == Energy.command_code) { if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; } } else if (CMND_VOLTAGECAL == Energy.command_code) { if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_UREF; } } else if (CMND_CURRENTCAL == Energy.command_code) { if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_IREF; } } else if (CMND_POWERSET == Energy.command_code) { if (XdrvMailbox.data_len && Ade7953.active_power[channel]) { if ((value > 100) && (value < 200000)) { Settings.energy_power_calibration = (Ade7953.active_power[channel] * 1000) / value; } } } else if (CMND_VOLTAGESET == Energy.command_code) { if (XdrvMailbox.data_len && Ade7953.voltage_rms) { if ((value > 10000) && (value < 26000)) { Settings.energy_voltage_calibration = (Ade7953.voltage_rms * 100) / value; } } } else if (CMND_CURRENTSET == Energy.command_code) { if (XdrvMailbox.data_len && Ade7953.current_rms[channel]) { if ((value > 2000) && (value < 1000000)) { Settings.energy_current_calibration = ((Ade7953.current_rms[channel] * 100) / value) * 100; } } } else serviced = false; return serviced; } bool Xnrg07(uint8_t function) { if (!I2cEnabled(XI2C_07)) { return false; } bool result = false; switch (function) { case FUNC_ENERGY_EVERY_SECOND: Ade7953EnergyEverySecond(); break; case FUNC_COMMAND: result = Ade7953Command(); break; case FUNC_PRE_INIT: Ade7953DrvInit(); break; } return result; } #endif #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_08_sdm120.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_08_sdm120.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_SDM120 #define XNRG_08 8 #ifndef SDM120_SPEED #define SDM120_SPEED 2400 #endif #ifndef SDM120_ADDR #define SDM120_ADDR 1 #endif #include TasmotaModbus *Sdm120Modbus; const uint8_t sdm120_table = 8; const uint8_t sdm220_table = 13; const uint16_t sdm120_start_addresses[] { 0x0000, 0x0006, 0x000C, 0x0012, 0x0018, 0x001E, 0x0046, 0x0156, 0X0048, 0X004A, 0X004C, 0X004E, 0X0024 }; struct SDM120 { float total_active = 0; float import_active = NAN; float import_reactive = 0; float export_reactive = 0; float phase_angle = 0; uint8_t read_state = 0; uint8_t send_retry = 0; uint8_t start_address_count = sdm220_table; } Sdm120; void SDM120Every250ms(void) { bool data_ready = Sdm120Modbus->ReceiveReady(); if (data_ready) { uint8_t buffer[14]; uint32_t error = Sdm120Modbus->ReceiveBuffer(buffer, 2); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Sdm120Modbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM120 error %d"), error); } else { Energy.data_valid[0] = 0; float value; ((uint8_t*)&value)[3] = buffer[3]; ((uint8_t*)&value)[2] = buffer[4]; ((uint8_t*)&value)[1] = buffer[5]; ((uint8_t*)&value)[0] = buffer[6]; switch(Sdm120.read_state) { case 0: Energy.voltage[0] = value; break; case 1: Energy.current[0] = value; break; case 2: Energy.active_power[0] = value; break; case 3: Energy.apparent_power[0] = value; break; case 4: Energy.reactive_power[0] = value; break; case 5: Energy.power_factor[0] = value; break; case 6: Energy.frequency[0] = value; break; case 7: Sdm120.total_active = value; break; case 8: Sdm120.import_active = value; break; case 9: Energy.export_active = value; break; case 10: Sdm120.import_reactive = value; break; case 11: Sdm120.export_reactive = value; break; case 12: Sdm120.phase_angle = value; break; } Sdm120.read_state++; if (Sdm120.read_state == Sdm120.start_address_count) { Sdm120.read_state = 0; if (Sdm120.start_address_count > sdm120_table) { if (!isnan(Sdm120.import_active)) { Sdm120.total_active = Sdm120.import_active; } else { Sdm120.start_address_count = sdm120_table; } } EnergyUpdateTotal(Sdm120.total_active, true); } } } if (0 == Sdm120.send_retry || data_ready) { Sdm120.send_retry = 5; Sdm120Modbus->Send(SDM120_ADDR, 0x04, sdm120_start_addresses[Sdm120.read_state], 2); } else { Sdm120.send_retry--; } } void Sdm120SnsInit(void) { Sdm120Modbus = new TasmotaModbus(pin[GPIO_SDM120_RX], pin[GPIO_SDM120_TX]); uint8_t result = Sdm120Modbus->Begin(SDM120_SPEED); if (result) { if (2 == result) { ClaimSerial(); } } else { energy_flg = ENERGY_NONE; } } void Sdm120DrvInit(void) { if ((pin[GPIO_SDM120_RX] < 99) && (pin[GPIO_SDM120_TX] < 99)) { energy_flg = XNRG_08; } } void Sdm220Reset(void) { if (isnan(Sdm120.import_active)) { return; } Sdm120.import_active = 0; Sdm120.import_reactive = 0; Sdm120.export_reactive = 0; Sdm120.phase_angle = 0; } #ifdef USE_WEBSERVER const char HTTP_ENERGY_SDM220[] PROGMEM = "{s}" D_IMPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}" "{s}" D_EXPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}" "{s}" D_PHASE_ANGLE "{m}%s " D_UNIT_ANGLE "{e}"; #endif void Sdm220Show(bool json) { if (isnan(Sdm120.import_active)) { return; } char import_active_chr[FLOATSZ]; dtostrfd(Sdm120.import_active, Settings.flag2.energy_resolution, import_active_chr); char import_reactive_chr[FLOATSZ]; dtostrfd(Sdm120.import_reactive, Settings.flag2.energy_resolution, import_reactive_chr); char export_reactive_chr[FLOATSZ]; dtostrfd(Sdm120.export_reactive, Settings.flag2.energy_resolution, export_reactive_chr); char phase_angle_chr[FLOATSZ]; dtostrfd(Sdm120.phase_angle, 2, phase_angle_chr); if (json) { ResponseAppend_P(PSTR(",\"" D_JSON_IMPORT_ACTIVE "\":%s,\"" D_JSON_IMPORT_REACTIVE "\":%s,\"" D_JSON_EXPORT_REACTIVE "\":%s,\"" D_JSON_PHASE_ANGLE "\":%s"), import_active_chr, import_reactive_chr, export_reactive_chr, phase_angle_chr); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_ENERGY_SDM220, import_reactive_chr, export_reactive_chr, phase_angle_chr); #endif } } bool Xnrg08(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: if (uptime > 4) { SDM120Every250ms(); } break; case FUNC_JSON_APPEND: Sdm220Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Sdm220Show(0); break; #endif case FUNC_ENERGY_RESET: Sdm220Reset(); break; case FUNC_INIT: Sdm120SnsInit(); break; case FUNC_PRE_INIT: Sdm120DrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_DDS2382 #define XNRG_09 9 #ifndef DDS2382_SPEED #define DDS2382_SPEED 9600 #endif #ifndef DDS2382_ADDR #define DDS2382_ADDR 1 #endif #include TasmotaModbus *Dds2382Modbus; uint8_t Dds2382_send_retry = 0; void Dds2382EverySecond(void) { bool data_ready = Dds2382Modbus->ReceiveReady(); if (data_ready) { uint8_t buffer[46]; uint32_t error = Dds2382Modbus->ReceiveBuffer(buffer, 18); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Dds2382Modbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "DDS2382 response error %d"), error); } else { Energy.data_valid[0] = 0; # 67 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino" Energy.voltage[0] = (float)((buffer[27] << 8) + buffer[28]) / 10.0; Energy.current[0] = (float)((buffer[29] << 8) + buffer[30]) / 100.0; Energy.active_power[0] = (float)((buffer[31] << 8) + buffer[32]); Energy.reactive_power[0] = (float)((buffer[33] << 8) + buffer[34]); Energy.power_factor[0] = (float)((buffer[35] << 8) + buffer[36]) / 1000.0; Energy.frequency[0] = (float)((buffer[37] << 8) + buffer[38]) / 100.0; uint8_t offset = 11; if (Settings.flag3.dds2382_model) { offset = 19; } Energy.export_active = (float)((buffer[offset] << 24) + (buffer[offset +1] << 16) + (buffer[offset +2] << 8) + buffer[offset +3]) / 100.0; float import_active = (float)((buffer[offset +4] << 24) + (buffer[offset +5] << 16) + (buffer[offset +6] << 8) + buffer[offset +7]) / 100.0; EnergyUpdateTotal(import_active, true); } } if (0 == Dds2382_send_retry || data_ready) { Dds2382_send_retry = 5; Dds2382Modbus->Send(DDS2382_ADDR, 0x03, 0, 18); } else { Dds2382_send_retry--; } } void Dds2382SnsInit(void) { Dds2382Modbus = new TasmotaModbus(pin[GPIO_DDS2382_RX], pin[GPIO_DDS2382_TX]); uint8_t result = Dds2382Modbus->Begin(DDS2382_SPEED); if (result) { if (2 == result) { ClaimSerial(); } } else { energy_flg = ENERGY_NONE; } } void Dds2382DrvInit(void) { if ((pin[GPIO_DDS2382_RX] < 99) && (pin[GPIO_DDS2382_TX] < 99)) { energy_flg = XNRG_09; } } bool Xnrg09(uint8_t function) { bool result = false; switch (function) { case FUNC_ENERGY_EVERY_SECOND: if (uptime > 4) { Dds2382EverySecond(); } break; case FUNC_INIT: Dds2382SnsInit(); break; case FUNC_PRE_INIT: Dds2382DrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_10_sdm630.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_10_sdm630.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_SDM630 #define XNRG_10 10 #ifndef SDM630_SPEED #define SDM630_SPEED 9600 #endif #ifndef SDM630_ADDR #define SDM630_ADDR 1 #endif #include TasmotaModbus *Sdm630Modbus; const uint16_t sdm630_start_addresses[] { 0x0000, 0x0002, 0x0004, 0x0006, 0x0008, 0x000A, 0x000C, 0x000E, 0x0010, 0x0018, 0x001A, 0x001C, 0x001E, 0x0020, 0x0022, 0x0156 }; struct SDM630 { uint8_t read_state = 0; uint8_t send_retry = 0; } Sdm630; void SDM630Every250ms(void) { bool data_ready = Sdm630Modbus->ReceiveReady(); if (data_ready) { uint8_t buffer[14]; uint32_t error = Sdm630Modbus->ReceiveBuffer(buffer, 2); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Sdm630Modbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM630 error %d"), error); } else { Energy.data_valid[0] = 0; Energy.data_valid[1] = 0; Energy.data_valid[2] = 0; float value; ((uint8_t*)&value)[3] = buffer[3]; ((uint8_t*)&value)[2] = buffer[4]; ((uint8_t*)&value)[1] = buffer[5]; ((uint8_t*)&value)[0] = buffer[6]; switch(Sdm630.read_state) { case 0: Energy.voltage[0] = value; break; case 1: Energy.voltage[1] = value; break; case 2: Energy.voltage[2] = value; break; case 3: Energy.current[0] = value; break; case 4: Energy.current[1] = value; break; case 5: Energy.current[2] = value; break; case 6: Energy.active_power[0] = value; break; case 7: Energy.active_power[1] = value; break; case 8: Energy.active_power[2] = value; break; case 9: Energy.reactive_power[0] = value; break; case 10: Energy.reactive_power[1] = value; break; case 11: Energy.reactive_power[2] = value; break; case 12: Energy.power_factor[0] = value; break; case 13: Energy.power_factor[1] = value; break; case 14: Energy.power_factor[2] = value; break; case 15: EnergyUpdateTotal(value, true); break; } Sdm630.read_state++; if (sizeof(sdm630_start_addresses)/2 == Sdm630.read_state) { Sdm630.read_state = 0; } } } if (0 == Sdm630.send_retry || data_ready) { Sdm630.send_retry = 5; Sdm630Modbus->Send(SDM630_ADDR, 0x04, sdm630_start_addresses[Sdm630.read_state], 2); } else { Sdm630.send_retry--; } } void Sdm630SnsInit(void) { Sdm630Modbus = new TasmotaModbus(pin[GPIO_SDM630_RX], pin[GPIO_SDM630_TX]); uint8_t result = Sdm630Modbus->Begin(SDM630_SPEED); if (result) { if (2 == result) { ClaimSerial(); } Energy.phase_count = 3; } else { energy_flg = ENERGY_NONE; } } void Sdm630DrvInit(void) { if ((pin[GPIO_SDM630_RX] < 99) && (pin[GPIO_SDM630_TX] < 99)) { energy_flg = XNRG_10; } } bool Xnrg10(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: if (uptime > 4) { SDM630Every250ms(); } break; case FUNC_INIT: Sdm630SnsInit(); break; case FUNC_PRE_INIT: Sdm630DrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_11_ddsu666.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_11_ddsu666.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_DDSU666 #define XNRG_11 11 #ifndef DDSU666_SPEED #define DDSU666_SPEED 9600 #endif #ifndef DDSU666_ADDR #define DDSU666_ADDR 1 #endif #include TasmotaModbus *Ddsu666Modbus; const uint16_t Ddsu666_start_addresses[] { 0x2000, 0x2002, 0x2004, 0x2006, 0x200A, 0x200E, 0X4000, 0X400A, }; struct DDSU666 { float import_active = NAN; uint8_t read_state = 0; uint8_t send_retry = 0; } Ddsu666; void DDSU666Every250ms(void) { bool data_ready = Ddsu666Modbus->ReceiveReady(); if (data_ready) { uint8_t buffer[14]; uint32_t error = Ddsu666Modbus->ReceiveBuffer(buffer, 2); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Ddsu666Modbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: Ddsu666 error %d"), error); } else { Energy.data_valid[0] = 0; float value; ((uint8_t*)&value)[3] = buffer[3]; ((uint8_t*)&value)[2] = buffer[4]; ((uint8_t*)&value)[1] = buffer[5]; ((uint8_t*)&value)[0] = buffer[6]; switch(Ddsu666.read_state) { case 0: Energy.voltage[0] = value; break; case 1: Energy.current[0] = value; break; case 2: Energy.active_power[0] = value * 1000; break; case 3: Energy.reactive_power[0] = value * 1000; break; case 4: Energy.power_factor[0] = value; break; case 5: Energy.frequency[0] = value; break; case 6: Ddsu666.import_active = value; break; case 7: Energy.export_active = value; break; } Ddsu666.read_state++; if (Ddsu666.read_state == 8) { Ddsu666.read_state = 0; EnergyUpdateTotal(Ddsu666.import_active, true); } } } if (0 == Ddsu666.send_retry || data_ready) { Ddsu666.send_retry = 5; Ddsu666Modbus->Send(DDSU666_ADDR, 0x04, Ddsu666_start_addresses[Ddsu666.read_state], 2); } else { Ddsu666.send_retry--; } } void Ddsu666SnsInit(void) { Ddsu666Modbus = new TasmotaModbus(pin[GPIO_DDSU666_RX], pin[GPIO_DDSU666_TX]); uint8_t result = Ddsu666Modbus->Begin(DDSU666_SPEED); if (result) { if (2 == result) { ClaimSerial(); } } else { energy_flg = ENERGY_NONE; } } void Ddsu666DrvInit(void) { if ((pin[GPIO_DDSU666_RX] < 99) && (pin[GPIO_DDSU666_TX] < 99)) { energy_flg = XNRG_11; } } bool Xnrg11(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: if (uptime > 4) { DDSU666Every250ms(); } break; case FUNC_INIT: Ddsu666SnsInit(); break; case FUNC_PRE_INIT: Ddsu666DrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_12_solaxX1.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_12_solaxX1.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_SOLAX_X1 #define XNRG_12 12 #ifndef SOLAXX1_SPEED #define SOLAXX1_SPEED 9600 #endif #define INVERTER_ADDRESS 0x0A #define D_SOLAX_X1 "SolaxX1" #include enum solaxX1_Error { solaxX1_ERR_NO_ERROR, solaxX1_ERR_CRC_ERROR }; union { uint32_t ErrMessage; struct { uint8_t TzProtectFault:1; uint8_t MainsLostFault:1; uint8_t GridVoltFault:1; uint8_t GridFreqFault:1; uint8_t PLLLostFault:1; uint8_t BusVoltFault:1; uint8_t ErrBit06:1; uint8_t OciFault:1; uint8_t Dci_OCP_Fault:1; uint8_t ResidualCurrentFault:1; uint8_t PvVoltFault:1; uint8_t Ac10Mins_Voltage_Fault:1; uint8_t IsolationFault:1; uint8_t TemperatureOverFault:1; uint8_t FanFault:1; uint8_t ErrBit15:1; uint8_t SpiCommsFault:1; uint8_t SciCommsFault:1; uint8_t ErrBit18:1; uint8_t InputConfigFault:1; uint8_t EepromFault:1; uint8_t RelayFault:1; uint8_t SampleConsistenceFault:1; uint8_t ResidualCurrent_DeviceFault:1; uint8_t ErrBit24:1; uint8_t ErrBit25:1; uint8_t ErrBit26:1; uint8_t ErrBit27:1; uint8_t ErrBit28:1; uint8_t DCI_DeviceFault:1; uint8_t OtherDeviceFault:1; uint8_t ErrBit31:1; }; } ErrCode; const char kSolaxMode[] PROGMEM = D_WAITING "|" D_CHECKING "|" D_WORKING "|" D_FAILURE; const char kSolaxError[] PROGMEM = D_SOLAX_ERROR_0 "|" D_SOLAX_ERROR_1 "|" D_SOLAX_ERROR_2 "|" D_SOLAX_ERROR_3 "|" D_SOLAX_ERROR_4 "|" D_SOLAX_ERROR_5 "|" D_SOLAX_ERROR_6 "|" D_SOLAX_ERROR_7 "|" D_SOLAX_ERROR_8; TasmotaSerial *solaxX1Serial; uint8_t solaxX1_Init = 1; struct SOLAXX1 { float temperature = 0; float energy_today = 0; float dc1_voltage = 0; float dc2_voltage = 0; float dc1_current = 0; float dc2_current = 0; float energy_total = 0; float runtime_total = 0; float dc1_power = 0; float dc2_power = 0; uint8_t status = 0; uint32_t errorCode = 0; } solaxX1; union { uint8_t status; struct { uint8_t freeBit7:1; uint8_t freeBit6:1; uint8_t freeBit5:1; uint8_t queryOffline:1; uint8_t queryOfflineSend:1; uint8_t hasAddress:1; uint8_t inverterAddressSend:1; uint8_t inverterSnReceived:1; }; } protocolStatus; uint8_t header[2] = {0xAA, 0x55}; uint8_t source[2] = {0x00, 0x00}; uint8_t destination[2] = {0x00, 0x00}; uint8_t controlCode[1] = {0x00}; uint8_t functionCode[1] = {0x00}; uint8_t dataLength[1] = {0x00}; uint8_t data[16] = {0}; uint8_t message[30]; bool solaxX1_RS485ReceiveReady(void) { return (solaxX1Serial->available() > 1); } void solaxX1_RS485Send(uint16_t msgLen) { memcpy(message, header, 2); memcpy(message + 2, source, 2); memcpy(message + 4, destination, 2); memcpy(message + 6, controlCode, 1); memcpy(message + 7, functionCode, 1); memcpy(message + 8, dataLength, 1); memcpy(message + 9, data, sizeof(data)); uint16_t crc = solaxX1_calculateCRC(message, msgLen); while (solaxX1Serial->available() > 0) { solaxX1Serial->read(); } solaxX1Serial->flush(); solaxX1Serial->write(message, msgLen); solaxX1Serial->write(highByte(crc)); solaxX1Serial->write(lowByte(crc)); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, message, msgLen); } uint8_t solaxX1_RS485Receive(uint8_t *value) { uint8_t len = 0; while (solaxX1Serial->available() > 0) { value[len++] = (uint8_t)solaxX1Serial->read(); } AddLogBuffer(LOG_LEVEL_DEBUG_MORE, value, len); uint16_t crc = solaxX1_calculateCRC(value, len - 2); if (value[len - 1] == lowByte(crc) && value[len - 2] == highByte(crc)) { return solaxX1_ERR_NO_ERROR; } else { return solaxX1_ERR_CRC_ERROR; } } uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen) { uint8_t i; uint16_t wChkSum; wChkSum = 0; for (i = 0; i < bLen; i++) { wChkSum = wChkSum + bExternTxPackage[i]; } return wChkSum; } void solaxX1_SendInverterAddress(void) { source[0] = 0x00; destination[0] = 0x00; destination[1] = 0x00; controlCode[0] = 0x10; functionCode[0] = 0x01; dataLength[0] = 0x0F; data[14] = INVERTER_ADDRESS; solaxX1_RS485Send(24); } void solaxX1_QueryLiveData(void) { source[0] = 0x01; destination[0] = 0x00; destination[1] = INVERTER_ADDRESS; controlCode[0] = 0x11; functionCode[0] = 0x02; dataLength[0] = 0x00; solaxX1_RS485Send(9); } uint8_t solaxX1_ParseErrorCode(uint32_t code){ ErrCode.ErrMessage = code; if (code == 0) return 0; if (ErrCode.MainsLostFault) return 1; if (ErrCode.GridVoltFault) return 2; if (ErrCode.GridFreqFault) return 3; if (ErrCode.PvVoltFault) return 4; if (ErrCode.IsolationFault) return 5; if (ErrCode.TemperatureOverFault) return 6; if (ErrCode.FanFault) return 7; if (ErrCode.OtherDeviceFault) return 8; } uint8_t solaxX1_send_retry = 0; uint8_t solaxX1_nodata_count = 0; void solaxX1250MSecond(void) { uint8_t value[61] = {0}; bool data_ready = solaxX1_RS485ReceiveReady(); if (protocolStatus.hasAddress && (data_ready || solaxX1_send_retry == 0)) { if (data_ready) { uint8_t error = solaxX1_RS485Receive(value); if (error) { DEBUG_SENSOR_LOG(PSTR("SX1: Data response CRC error")); } else { solaxX1_nodata_count = 0; solaxX1_send_retry = 12; Energy.data_valid[0] = 0; solaxX1.temperature = (float)((value[9] << 8) | value[10]); solaxX1.energy_today = (float)((value[11] << 8) | value[12]) * 0.1f; solaxX1.dc1_voltage = (float)((value[13] << 8) | value[14]) * 0.1f; solaxX1.dc2_voltage = (float)((value[15] << 8) | value[16]) * 0.1f; solaxX1.dc1_current = (float)((value[17] << 8) | value[18]) * 0.1f; solaxX1.dc2_current = (float)((value[19] << 8) | value[20]) * 0.1f; Energy.current[0] = (float)((value[21] << 8) | value[22]) * 0.1f; Energy.voltage[0] = (float)((value[23] << 8) | value[24]) * 0.1f; Energy.frequency[0] = (float)((value[25] << 8) | value[26]) * 0.01f; Energy.active_power[0] = (float)((value[27] << 8) | value[28]); solaxX1.energy_total = (float)((value[31] << 8) | (value[32] << 8) | (value[33] << 8) | value[34]) * 0.1f; solaxX1.runtime_total = (float)((value[35] << 8) | (value[36] << 8) | (value[37] << 8) | value[38]); solaxX1.status = (uint8_t)((value[39] << 8) | value[40]); solaxX1.errorCode = (uint32_t)((value[58] << 8) | (value[57] << 8) | (value[56] << 8) | value[55]); solaxX1.dc1_power = solaxX1.dc1_voltage * solaxX1.dc1_current; solaxX1.dc2_power = solaxX1.dc2_voltage * solaxX1.dc2_current; solaxX1_QueryLiveData(); EnergyUpdateTotal(solaxX1.energy_total, true); } } if (0 == solaxX1_send_retry && 255 != solaxX1_nodata_count) { solaxX1_send_retry = 12; solaxX1_QueryLiveData(); } if (255 == solaxX1_nodata_count) { solaxX1_nodata_count = 0; solaxX1_send_retry = 12; } } else { if ((solaxX1_nodata_count % 4) == 0) { DEBUG_SENSOR_LOG(PSTR("SX1: No Data count: %d"), solaxX1_nodata_count); } if (solaxX1_nodata_count < 10 * 4) { solaxX1_nodata_count++; } else if (255 != solaxX1_nodata_count) { solaxX1_nodata_count = 255; solaxX1_send_retry = 12; protocolStatus.status = 0b00001000; Energy.data_valid[0] = ENERGY_WATCHDOG; solaxX1.temperature = solaxX1.dc1_voltage = solaxX1.dc2_voltage = solaxX1.dc1_current = solaxX1.dc2_current = solaxX1.dc1_power = 0; solaxX1.dc2_power = solaxX1.status = Energy.current[0] = Energy.voltage[0] = Energy.frequency[0] = Energy.active_power[0] = 0; } } if (!protocolStatus.hasAddress && (data_ready || solaxX1_send_retry == 0)) { if (data_ready) { if (protocolStatus.inverterAddressSend) { uint8_t error = solaxX1_RS485Receive(value); if (error) { DEBUG_SENSOR_LOG(PSTR("SX1: Address confirmation response CRC error")); } else { if (value[6] == 0x10 && value[7] == 0x81 && value[9] == 0x06) { DEBUG_SENSOR_LOG(PSTR("SX1: Set hasAddress")); protocolStatus.status = 0b00100000; } } } if (protocolStatus.queryOfflineSend) { uint8_t error = solaxX1_RS485Receive(value); if (error) { DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline response CRC error")); } else { if (value[6] == 0x10 && value[7] == 0x80 && protocolStatus.inverterSnReceived == false) { for (uint8_t i = 9; i <= 22; i++) { data[i - 9] = value[i]; } solaxX1_SendInverterAddress(); protocolStatus.status = 0b1100000; DEBUG_SENSOR_LOG(PSTR("SX1: Set inverterSnReceived and inverterAddressSend")); } } } } if (solaxX1_send_retry == 0) { if (protocolStatus.queryOfflineSend) { protocolStatus.status = 0b00001000; DEBUG_SENSOR_LOG(PSTR("SX1: Set Query Offline")); } solaxX1_send_retry = 12; } if (protocolStatus.queryOffline) { source[0] = 0x01; destination[1] = 0x00; controlCode[0] = 0x10; functionCode[0] = 0x00; dataLength[0] = 0x00; solaxX1_RS485Send(9); protocolStatus.status = 0b00010000; DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline Send")); } } if (!data_ready) solaxX1_send_retry--; } void solaxX1SnsInit(void) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("SX1: Solax X1 Inverter Init")); DEBUG_SENSOR_LOG(PSTR("SX1: RX pin: %d, TX pin: %d"), pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX]); protocolStatus.status = 0b00100000; solaxX1Serial = new TasmotaSerial(pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX], 1); if (solaxX1Serial->begin(SOLAXX1_SPEED)) { if (solaxX1Serial->hardwareSerial()) { ClaimSerial(); } } else { energy_flg = ENERGY_NONE; } } void solaxX1DrvInit(void) { if ((pin[GPIO_SOLAXX1_RX] < 99) && (pin[GPIO_SOLAXX1_TX] < 99)) { energy_flg = XNRG_12; } } #ifdef USE_WEBSERVER const char HTTP_SNS_solaxX1_DATA1[] PROGMEM = "{s}" D_SOLAX_X1 " " D_SOLAR_POWER "{m}%s " D_UNIT_WATT "{e}" "{s}" D_SOLAX_X1 " " D_PV1_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" "{s}" D_SOLAX_X1 " " D_PV1_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" "{s}" D_SOLAX_X1 " " D_PV1_POWER "{m}%s " D_UNIT_WATT "{e}"; #ifdef SOLAXX1_PV2 const char HTTP_SNS_solaxX1_DATA2[] PROGMEM = "{s}" D_SOLAX_X1 " " D_PV2_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" "{s}" D_SOLAX_X1 " " D_PV2_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" "{s}" D_SOLAX_X1 " " D_PV2_POWER "{m}%s " D_UNIT_WATT "{e}"; #endif const char HTTP_SNS_solaxX1_DATA3[] PROGMEM = "{s}" D_SOLAX_X1 " " D_UPTIME "{m}%s " D_UNIT_HOUR "{e}" "{s}" D_SOLAX_X1 " " D_STATUS "{m}%s" "{s}" D_SOLAX_X1 " " D_ERROR "{m}%s"; #endif void solaxX1Show(bool json) { char solar_power[33]; dtostrfd(solaxX1.dc1_power + solaxX1.dc2_power, Settings.flag2.wattage_resolution, solar_power); char pv1_voltage[33]; dtostrfd(solaxX1.dc1_voltage, Settings.flag2.voltage_resolution, pv1_voltage); char pv1_current[33]; dtostrfd(solaxX1.dc1_current, Settings.flag2.current_resolution, pv1_current); char pv1_power[33]; dtostrfd(solaxX1.dc1_power, Settings.flag2.wattage_resolution, pv1_power); #ifdef SOLAXX1_PV2 char pv2_voltage[33]; dtostrfd(solaxX1.dc2_voltage, Settings.flag2.voltage_resolution, pv2_voltage); char pv2_current[33]; dtostrfd(solaxX1.dc2_current, Settings.flag2.current_resolution, pv2_current); char pv2_power[33]; dtostrfd(solaxX1.dc2_power, Settings.flag2.wattage_resolution, pv2_power); #endif char temperature[33]; dtostrfd(solaxX1.temperature, Settings.flag2.temperature_resolution, temperature); char runtime[33]; dtostrfd(solaxX1.runtime_total, 0, runtime); char status[33]; GetTextIndexed(status, sizeof(status), solaxX1.status, kSolaxMode); if (json) { ResponseAppend_P(PSTR(",\"" D_JSON_SOLAR_POWER "\":%s,\"" D_JSON_PV1_VOLTAGE "\":%s,\"" D_JSON_PV1_CURRENT "\":%s,\"" D_JSON_PV1_POWER "\":%s"), solar_power, pv1_voltage, pv1_current, pv1_power); #ifdef SOLAXX1_PV2 ResponseAppend_P(PSTR(",\"" D_JSON_PV2_VOLTAGE "\":%s,\"" D_JSON_PV2_CURRENT "\":%s,\"" D_JSON_PV2_POWER "\":%s"), pv2_voltage, pv2_current, pv2_power); #endif ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RUNTIME "\":%s,\"" D_JSON_STATUS "\":\"%s\",\"" D_JSON_ERROR "\":%d"), temperature, runtime, status, solaxX1.errorCode); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_solaxX1_DATA1, solar_power, pv1_voltage, pv1_current, pv1_power); #ifdef SOLAXX1_PV2 WSContentSend_PD(HTTP_SNS_solaxX1_DATA2, pv2_voltage, pv2_current, pv2_power); #endif WSContentSend_PD(HTTP_SNS_TEMP, D_SOLAX_X1, temperature, TempUnit()); char errorCodeString[33]; WSContentSend_PD(HTTP_SNS_solaxX1_DATA3, runtime, status, GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError)); #endif } } bool Xnrg12(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: if (uptime > 4) { solaxX1250MSecond(); } break; case FUNC_JSON_APPEND: solaxX1Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: solaxX1Show(0); break; #endif case FUNC_INIT: solaxX1SnsInit(); break; case FUNC_PRE_INIT: solaxX1DrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" #ifdef USE_ENERGY_SENSOR #ifdef USE_LE01MR # 71 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" #define XNRG_13 13 #ifndef LE01MR_SPEED #define LE01MR_SPEED 2400 #endif #ifndef LE01MR_ADDR #define LE01MR_ADDR 1 #endif #include TasmotaModbus *FifLEModbus; const uint8_t le01mr_table_sz = 9; const uint16_t le01mr_register_addresses[] { 0x0130, 0x0131, 0x0158, 0x0139, 0x0140, 0x0148, 0x0150, 0xA000, 0xA01E }; struct LE01MR { float total_active = 0; float total_reactive = 0; uint8_t read_state = 0; uint8_t send_retry = 0; uint8_t start_address_count = le01mr_table_sz; } Le01mr; void FifLEEvery250ms(void) { bool data_ready = FifLEModbus->ReceiveReady(); if (data_ready) { uint8_t buffer[14]; uint8_t reg_count = 2; if (Le01mr.read_state < 3) { reg_count=1; } uint32_t error = FifLEModbus->ReceiveBuffer(buffer, reg_count); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, FifLEModbus->ReceiveCount()); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("FiF-LE: LE01MR Modbus error %d"), error); } else { Energy.data_valid[0] = 0; # 146 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino" uint32_t value_buff = 0; if (Le01mr.read_state >= 0 && Le01mr.read_state < 3) { value_buff = ((uint32_t)buffer[3])<<8 | buffer[4]; } else { value_buff = ((uint32_t)buffer[3])<<24 | ((uint32_t)buffer[4])<<16 | ((uint32_t)buffer[5])<<8 | buffer[6]; } switch(Le01mr.read_state) { case 0: Energy.frequency[0] = value_buff * 0.01f; break; case 1: Energy.voltage[0] = value_buff * 0.01f; break; case 2: Energy.power_factor[0] = ((int16_t)value_buff) * 0.001f; break; case 3: Energy.current[0] = value_buff * 0.001f; break; case 4: Energy.active_power[0] = value_buff * 1.0f; break; case 5: Energy.reactive_power[0] = value_buff * 1.0f; break; case 6: Energy.apparent_power[0] = value_buff * 1.0f; break; case 7: Le01mr.total_active = value_buff * 0.01f; break; case 8: Le01mr.total_reactive = value_buff * 0.01f; break; } Le01mr.read_state++; if (Le01mr.read_state == Le01mr.start_address_count) { Le01mr.read_state = 0; EnergyUpdateTotal(Le01mr.total_active, true); } } } if (0 == Le01mr.send_retry || data_ready) { uint8_t reg_count = 2; Le01mr.send_retry = 5; if (Le01mr.read_state < 3) reg_count=1; FifLEModbus->Send(LE01MR_ADDR, 0x03, le01mr_register_addresses[Le01mr.read_state], reg_count); } else { Le01mr.send_retry--; } } void FifLESnsInit(void) { FifLEModbus = new TasmotaModbus(pin[GPIO_LE01MR_RX], pin[GPIO_LE01MR_TX]); uint8_t result = FifLEModbus->Begin(LE01MR_SPEED); if (result) { if (2 == result) { ClaimSerial(); } } else { energy_flg = ENERGY_NONE; } } void FifLEDrvInit(void) { if ((pin[GPIO_LE01MR_RX] < 99) && (pin[GPIO_LE01MR_TX] < 99)) { energy_flg = XNRG_13; } } void FifLEReset(void) { Le01mr.total_active = 0; Le01mr.total_reactive = 0; } #ifdef USE_WEBSERVER const char HTTP_ENERGY_LE01MR[] PROGMEM = "{s}" D_TOTAL_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}" "{s}" D_TOTAL_REACTIVE "{m}%s " D_UNIT_KWARH "{e}" ; #endif void FifLEShow(bool json) { char total_reactive_chr[FLOATSZ]; dtostrfd(Le01mr.total_reactive, Settings.flag2.energy_resolution, total_reactive_chr); char total_active_chr[FLOATSZ]; dtostrfd(Le01mr.total_active, Settings.flag2.energy_resolution, total_active_chr); if (json) { ResponseAppend_P(PSTR(",\"" D_JSON_TOTAL_ACTIVE "\":%s,\"" D_JSON_TOTAL_REACTIVE "\":%s"), total_active_chr, total_reactive_chr); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_ENERGY_LE01MR, total_active_chr, total_reactive_chr); #endif } } bool Xnrg13(uint8_t function) { bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: if (uptime > 4) { FifLEEvery250ms(); } break; case FUNC_JSON_APPEND: FifLEShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: FifLEShow(0); break; #endif case FUNC_ENERGY_RESET: FifLEReset(); break; case FUNC_INIT: FifLESnsInit(); break; case FUNC_PRE_INIT: FifLEDrvInit(); break; } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_interface.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_interface.ino" #ifdef USE_ENERGY_SENSOR #ifdef XFUNC_PTR_IN_ROM bool (* const xnrg_func_ptr[])(uint8_t) PROGMEM = { #else bool (* const xnrg_func_ptr[])(uint8_t) = { #endif #ifdef XNRG_01 &Xnrg01, #endif #ifdef XNRG_02 &Xnrg02, #endif #ifdef XNRG_03 &Xnrg03, #endif #ifdef XNRG_04 &Xnrg04, #endif #ifdef XNRG_05 &Xnrg05, #endif #ifdef XNRG_06 &Xnrg06, #endif #ifdef XNRG_07 &Xnrg07, #endif #ifdef XNRG_08 &Xnrg08, #endif #ifdef XNRG_09 &Xnrg09, #endif #ifdef XNRG_10 &Xnrg10, #endif #ifdef XNRG_11 &Xnrg11, #endif #ifdef XNRG_12 &Xnrg12, #endif #ifdef XNRG_13 &Xnrg13, #endif #ifdef XNRG_14 &Xnrg14, #endif #ifdef XNRG_15 &Xnrg15, #endif #ifdef XNRG_16 &Xnrg16 #endif }; const uint8_t xnrg_present = sizeof(xnrg_func_ptr) / sizeof(xnrg_func_ptr[0]); uint8_t xnrg_active = 0; bool XnrgCall(uint8_t function) { DEBUG_TRACE_LOG(PSTR("NRG: %d"), function); if (FUNC_PRE_INIT == function) { for (uint32_t x = 0; x < xnrg_present; x++) { xnrg_func_ptr[x](function); if (energy_flg) { xnrg_active = x; return true; } } } else if (energy_flg) { return xnrg_func_ptr[xnrg_active](function); } return false; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_01_counter.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_01_counter.ino" #ifdef USE_COUNTER #define XSNS_01 1 #define D_PRFX_COUNTER "Counter" #define D_CMND_COUNTERTYPE "Type" #define D_CMND_COUNTERDEBOUNCE "Debounce" const char kCounterCommands[] PROGMEM = D_PRFX_COUNTER "|" "|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE ; void (* const CounterCommand[])(void) PROGMEM = { &CmndCounter, &CmndCounterType, &CmndCounterDebounce }; struct COUNTER { uint32_t timer[MAX_COUNTERS]; uint8_t no_pullup = 0; bool any_counter = false; } Counter; #ifndef ARDUINO_ESP8266_RELEASE_2_3_0 void CounterUpdate(uint8_t index) ICACHE_RAM_ATTR; void CounterUpdate1(void) ICACHE_RAM_ATTR; void CounterUpdate2(void) ICACHE_RAM_ATTR; void CounterUpdate3(void) ICACHE_RAM_ATTR; void CounterUpdate4(void) ICACHE_RAM_ATTR; #endif void CounterUpdate(uint8_t index) { uint32_t time = micros(); uint32_t debounce_time = time - Counter.timer[index]; if (debounce_time > Settings.pulse_counter_debounce * 1000) { Counter.timer[index] = time; if (bitRead(Settings.pulse_counter_type, index)) { RtcSettings.pulse_counter[index] = debounce_time; } else { RtcSettings.pulse_counter[index]++; } } } void CounterUpdate1(void) { CounterUpdate(0); } void CounterUpdate2(void) { CounterUpdate(1); } void CounterUpdate3(void) { CounterUpdate(2); } void CounterUpdate4(void) { CounterUpdate(3); } bool CounterPinState(void) { if ((XdrvMailbox.index >= GPIO_CNTR1_NP) && (XdrvMailbox.index < (GPIO_CNTR1_NP + MAX_COUNTERS))) { bitSet(Counter.no_pullup, XdrvMailbox.index - GPIO_CNTR1_NP); XdrvMailbox.index -= (GPIO_CNTR1_NP - GPIO_CNTR1); return true; } return false; } void CounterInit(void) { typedef void (*function) () ; function counter_callbacks[] = { CounterUpdate1, CounterUpdate2, CounterUpdate3, CounterUpdate4 }; for (uint32_t i = 0; i < MAX_COUNTERS; i++) { if (pin[GPIO_CNTR1 +i] < 99) { Counter.any_counter = true; pinMode(pin[GPIO_CNTR1 +i], bitRead(Counter.no_pullup, i) ? INPUT : INPUT_PULLUP); attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING); } } } void CounterEverySecond(void) { for (uint32_t i = 0; i < MAX_COUNTERS; i++) { if (pin[GPIO_CNTR1 +i] < 99) { if (bitRead(Settings.pulse_counter_type, i)) { uint32_t time = micros() - Counter.timer[i]; if (time > 4200000000) { RtcSettings.pulse_counter[i] = 4200000000; } } } } } void CounterSaveState(void) { for (uint32_t i = 0; i < MAX_COUNTERS; i++) { if (pin[GPIO_CNTR1 +i] < 99) { Settings.pulse_counter[i] = RtcSettings.pulse_counter[i]; } } } void CounterShow(bool json) { bool header = false; uint8_t dsxflg = 0; for (uint32_t i = 0; i < MAX_COUNTERS; i++) { if (pin[GPIO_CNTR1 +i] < 99) { char counter[33]; if (bitRead(Settings.pulse_counter_type, i)) { dtostrfd((double)RtcSettings.pulse_counter[i] / 1000000, 6, counter); } else { dsxflg++; snprintf_P(counter, sizeof(counter), PSTR("%lu"), RtcSettings.pulse_counter[i]); } if (json) { if (!header) { ResponseAppend_P(PSTR(",\"COUNTER\":{")); } ResponseAppend_P(PSTR("%s\"C%d\":%s"), (header)?",":"", i +1, counter); header = true; #ifdef USE_DOMOTICZ if ((0 == tele_period) && (1 == dsxflg)) { DomoticzSensor(DZ_COUNT, RtcSettings.pulse_counter[i]); dsxflg++; } #endif if ((0 == tele_period ) && (Settings.flag3.counter_reset_on_tele)) { RtcSettings.pulse_counter[i] = 0; } #ifdef USE_WEBSERVER } else { WSContentSend_PD(PSTR("{s}" D_COUNTER "%d{m}%s%s{e}"), i +1, counter, (bitRead(Settings.pulse_counter_type, i)) ? " " D_UNIT_SECOND : ""); #endif } } } if (header) { ResponseJsonEnd(); } } void CmndCounter(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) { if ((XdrvMailbox.data_len > 0) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) { if ((XdrvMailbox.data[0] == '-') || (XdrvMailbox.data[0] == '+')) { RtcSettings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload; Settings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload; } else { RtcSettings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload; Settings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload; } } ResponseCmndIdxNumber(RtcSettings.pulse_counter[XdrvMailbox.index -1]); } } void CmndCounterType(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) { bitWrite(Settings.pulse_counter_type, XdrvMailbox.index -1, XdrvMailbox.payload &1); RtcSettings.pulse_counter[XdrvMailbox.index -1] = 0; Settings.pulse_counter[XdrvMailbox.index -1] = 0; } ResponseCmndIdxNumber(bitRead(Settings.pulse_counter_type, XdrvMailbox.index -1)); } } void CmndCounterDebounce(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) { Settings.pulse_counter_debounce = XdrvMailbox.payload; } ResponseCmndNumber(Settings.pulse_counter_debounce); } bool Xsns01(uint8_t function) { bool result = false; if (Counter.any_counter) { switch (function) { case FUNC_EVERY_SECOND: CounterEverySecond(); break; case FUNC_JSON_APPEND: CounterShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: CounterShow(0); break; #endif case FUNC_SAVE_BEFORE_RESTART: case FUNC_SAVE_AT_MIDNIGHT: CounterSaveState(); break; case FUNC_COMMAND: result = DecodeCommand(kCounterCommands, CounterCommand); break; } } else { switch (function) { case FUNC_INIT: CounterInit(); break; case FUNC_PIN_STATE: result = CounterPinState(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino" #ifndef USE_ADC_VCC #define XSNS_02 2 #define TO_CELSIUS(x) ((x) - 273.15) #define TO_KELVIN(x) ((x) + 273.15) #define ANALOG_V33 3.3 #define ANALOG_T0 TO_KELVIN(25.0) #define ANALOG_NTC_BRIDGE_RESISTANCE 32000 #define ANALOG_NTC_RESISTANCE 10000 #define ANALOG_NTC_B_COEFFICIENT 3350 #define ANALOG_LDR_BRIDGE_RESISTANCE 10000 #define ANALOG_LDR_LUX_CALC_SCALAR 12518931 #define ANALOG_LDR_LUX_CALC_EXPONENT -1.4050 # 58 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino" #define ANALOG_CT_FLAGS 0 #define ANALOG_CT_MULTIPLIER 2146 #define ANALOG_CT_VOLTAGE 2300 #define CT_FLAG_ENERGY_RESET (1 << 0) struct { float temperature = 0; float current = 0; float energy = 0; uint32_t previous_millis = 0; uint16_t last_value = 0; } Adc; void AdcInit(void) { if ((Settings.adc_param_type != my_adc0) || (Settings.adc_param1 > 1000000)) { if (ADC0_TEMP == my_adc0) { Settings.adc_param_type = ADC0_TEMP; Settings.adc_param1 = ANALOG_NTC_BRIDGE_RESISTANCE; Settings.adc_param2 = ANALOG_NTC_RESISTANCE; Settings.adc_param3 = ANALOG_NTC_B_COEFFICIENT * 10000; } else if (ADC0_LIGHT == my_adc0) { Settings.adc_param_type = ADC0_LIGHT; Settings.adc_param1 = ANALOG_LDR_BRIDGE_RESISTANCE; Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR; Settings.adc_param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000; } else if (ADC0_RANGE == my_adc0) { Settings.adc_param_type = ADC0_RANGE; Settings.adc_param1 = 0; Settings.adc_param2 = 1023; Settings.adc_param3 = 0; Settings.adc_param4 = 100; } else if (ADC0_CT_POWER == my_adc0) { Settings.adc_param_type = ADC0_CT_POWER; Settings.adc_param1 = ANALOG_CT_FLAGS; Settings.adc_param2 = ANALOG_CT_MULTIPLIER; Settings.adc_param3 = ANALOG_CT_VOLTAGE; } } } uint16_t AdcRead(uint8_t factor) { uint8_t samples = 1 << factor; uint16_t analog = 0; for (uint32_t i = 0; i < samples; i++) { analog += analogRead(A0); delay(1); } analog >>= factor; return analog; } #ifdef USE_RULES void AdcEvery250ms(void) { if (ADC0_INPUT == my_adc0) { uint16_t new_value = AdcRead(5); if ((new_value < Adc.last_value -10) || (new_value > Adc.last_value +10)) { Adc.last_value = new_value; uint16_t value = Adc.last_value / 10; Response_P(PSTR("{\"ANALOG\":{\"A0div10\":%d}}"), (value > 99) ? 100 : value); XdrvRulesProcess(); } } } #endif uint16_t AdcGetLux(void) { int adc = AdcRead(2); double resistorVoltage = ((double)adc / 1023) * ANALOG_V33; double ldrVoltage = ANALOG_V33 - resistorVoltage; double ldrResistance = ldrVoltage / resistorVoltage * (double)Settings.adc_param1; double ldrLux = (double)Settings.adc_param2 * FastPrecisePow(ldrResistance, (double)Settings.adc_param3 / 10000); return (uint16_t)ldrLux; } uint16_t AdcGetRange(void) { int adc = AdcRead(2); double adcrange = ( ((double)Settings.adc_param2 - (double)adc) / ( ((double)Settings.adc_param2 - (double)Settings.adc_param1)) * ((double)Settings.adc_param3 - (double)Settings.adc_param4) + (double)Settings.adc_param4 ); return (uint16_t)adcrange; } void AdcGetCurrentPower(uint8_t factor) { uint8_t samples = 1 << factor; uint16_t analog = 0; uint16_t analog_min = 1023; uint16_t analog_max = 0; for (uint32_t i = 0; i < samples; i++) { analog = analogRead(A0); if (analog < analog_min) { analog_min = analog; } if (analog > analog_max) { analog_max = analog; } delay(1); } Adc.current = (float)(analog_max-analog_min) * ((float)(Settings.adc_param2) / 100000); float power = Adc.current * (float)(Settings.adc_param3) / 10; uint32_t current_millis = millis(); Adc.energy = Adc.energy + ((power * (current_millis - Adc.previous_millis)) / 3600000000); Adc.previous_millis = current_millis; } void AdcEverySecond(void) { if (ADC0_TEMP == my_adc0) { int adc = AdcRead(2); double Rt = (adc * Settings.adc_param1) / (1024.0 * ANALOG_V33 - (double)adc); double BC = (double)Settings.adc_param3 / 10000; double T = BC / (BC / ANALOG_T0 + TaylorLog(Rt / (double)Settings.adc_param2)); Adc.temperature = ConvertTemp(TO_CELSIUS(T)); } else if (ADC0_CT_POWER == my_adc0) { AdcGetCurrentPower(5); } } void AdcShow(bool json) { if (ADC0_INPUT == my_adc0) { uint16_t analog = AdcRead(5); if (json) { ResponseAppend_P(PSTR(",\"ANALOG\":{\"A0\":%d}"), analog); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_ANALOG, "", 0, analog); #endif } } else if (ADC0_TEMP == my_adc0) { char temperature[33]; dtostrfd(Adc.temperature, Settings.flag2.temperature_resolution, temperature); if (json) { ResponseAppend_P(JSON_SNS_TEMP, "ANALOG", temperature); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_TEMP, temperature); } #endif #ifdef USE_KNX if (0 == tele_period) { KnxSensor(KNX_TEMPERATURE, Adc.temperature); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit()); #endif } } else if (ADC0_LIGHT == my_adc0) { uint16_t adc_light = AdcGetLux(); if (json) { ResponseAppend_P(JSON_SNS_ILLUMINANCE, "ANALOG", adc_light); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, adc_light); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", adc_light); #endif } } else if (ADC0_RANGE == my_adc0) { uint16_t adc_range = AdcGetRange(); if (json) { ResponseAppend_P(JSON_SNS_RANGE, "ANALOG", adc_range); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_RANGE, "", adc_range); #endif } } else if (ADC0_CT_POWER == my_adc0) { AdcGetCurrentPower(5); float voltage = (float)(Settings.adc_param3) / 10; char voltage_chr[FLOATSZ]; dtostrfd(voltage, Settings.flag2.voltage_resolution, voltage_chr); char current_chr[FLOATSZ]; dtostrfd(Adc.current, Settings.flag2.current_resolution, current_chr); char power_chr[FLOATSZ]; dtostrfd(voltage * Adc.current, Settings.flag2.wattage_resolution, power_chr); char energy_chr[FLOATSZ]; dtostrfd(Adc.energy, Settings.flag2.energy_resolution, energy_chr); if (json) { ResponseAppend_P(PSTR(",\"ANALOG\":{\"" D_JSON_ENERGY "\":%s,\"" D_JSON_POWERUSAGE "\":%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s}"), energy_chr, power_chr, voltage_chr, current_chr); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_POWER_ENERGY, power_chr); DomoticzSensor(DZ_VOLTAGE, voltage_chr); DomoticzSensor(DZ_CURRENT, current_chr); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_VOLTAGE, voltage_chr); WSContentSend_PD(HTTP_SNS_CURRENT, current_chr); WSContentSend_PD(HTTP_SNS_POWER, power_chr); WSContentSend_PD(HTTP_SNS_ENERGY_TOTAL, energy_chr); #endif } } } const char kAdcCommands[] PROGMEM = "|" D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM; void (* const AdcCommand[])(void) PROGMEM = { &CmndAdc, &CmndAdcs, &CmndAdcParam }; void CmndAdc(void) { if (ValidAdc() && (XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < ADC0_END)) { Settings.my_adc0 = XdrvMailbox.payload; restart_flag = 2; } char stemp1[TOPSZ]; Response_P(PSTR("{\"" D_CMND_ADC "0\":{\"%d\":\"%s\"}}"), Settings.my_adc0, GetTextIndexed(stemp1, sizeof(stemp1), Settings.my_adc0, kAdc0Names)); } void CmndAdcs(void) { Response_P(PSTR("{\"" D_CMND_ADCS "\":{")); bool jsflg = false; char stemp1[TOPSZ]; for (uint32_t i = 0; i < ADC0_END; i++) { if (jsflg) { ResponseAppend_P(PSTR(",")); } jsflg = true; ResponseAppend_P(PSTR("\"%d\":\"%s\""), i, GetTextIndexed(stemp1, sizeof(stemp1), i, kAdc0Names)); } ResponseJsonEndEnd(); } void CmndAdcParam(void) { if (XdrvMailbox.data_len) { if ((ADC0_TEMP == XdrvMailbox.payload) || (ADC0_LIGHT == XdrvMailbox.payload) || (ADC0_RANGE == XdrvMailbox.payload) || (ADC0_CT_POWER == XdrvMailbox.payload)) { if (strstr(XdrvMailbox.data, ",") != nullptr) { char sub_string[XdrvMailbox.data_len +1]; Settings.adc_param_type = XdrvMailbox.payload; Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); Settings.adc_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10); if (ADC0_RANGE == XdrvMailbox.payload) { Settings.adc_param3 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 4), nullptr, 10)); Settings.adc_param4 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 5), nullptr, 10)); } else { Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000); } if (ADC0_CT_POWER == XdrvMailbox.payload) { if ((Settings.adc_param1 & CT_FLAG_ENERGY_RESET) > 0) { Adc.energy = 0; Settings.adc_param1 ^= CT_FLAG_ENERGY_RESET; } } } else { Settings.adc_param_type = 0; AdcInit(); } } } Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d"), Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2); if (ADC0_RANGE == my_adc0) { ResponseAppend_P(PSTR(",%d,%d"), Settings.adc_param3, Settings.adc_param4); } else { int value = Settings.adc_param3; uint8_t precision; for (precision = 4; precision > 0; precision--) { if (value % 10) { break; } value /= 10; } char param3[33]; dtostrfd(((double)Settings.adc_param3)/10000, precision, param3); ResponseAppend_P(PSTR(",%s"), param3); } ResponseAppend_P(PSTR("]}")); } bool Xsns02(uint8_t function) { bool result = false; switch (function) { case FUNC_COMMAND: result = DecodeCommand(kAdcCommands, AdcCommand); break; default: if ((ADC0_INPUT == my_adc0) || (ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0) || (ADC0_RANGE == my_adc0) || (ADC0_CT_POWER == my_adc0)) { switch (function) { #ifdef USE_RULES case FUNC_EVERY_250_MSECOND: AdcEvery250ms(); break; #endif case FUNC_EVERY_SECOND: AdcEverySecond(); break; case FUNC_INIT: AdcInit(); break; case FUNC_JSON_APPEND: AdcShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: AdcShow(0); break; #endif } } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino" #ifdef USE_SONOFF_SC # 57 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino" #define XSNS_04 4 uint16_t sc_value[5] = { 0 }; void SonoffScSend(const char *data) { Serial.write(data); Serial.write('\x1B'); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_SERIAL D_TRANSMIT " %s"), data); } void SonoffScInit(void) { SonoffScSend("AT+START"); } void SonoffScSerialInput(char *rcvstat) { char *p; char *str; uint16_t value[5] = { 0 }; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_SERIAL D_RECEIVED " %s"), rcvstat); if (!strncasecmp_P(rcvstat, PSTR("AT+UPDATE="), 10)) { int8_t i = -1; for (str = strtok_r(rcvstat, ":", &p); str && i < 5; str = strtok_r(nullptr, ":", &p)) { value[i++] = atoi(str); } if (value[0] > 0) { for (uint32_t i = 0; i < 5; i++) { sc_value[i] = value[i]; } sc_value[2] = (11 - sc_value[2]) * 10; sc_value[3] *= 10; sc_value[4] = (11 - sc_value[4]) * 10; SonoffScSend("AT+SEND=ok"); } else { SonoffScSend("AT+SEND=fail"); } } else if (!strcasecmp_P(rcvstat, PSTR("AT+STATUS?"))) { SonoffScSend("AT+STATUS=4"); } } #ifdef USE_WEBSERVER const char HTTP_SNS_SCPLUS[] PROGMEM = "{s}" D_LIGHT "{m}%d%%{e}{s}" D_NOISE "{m}%d%%{e}{s}" D_AIR_QUALITY "{m}%d%%{e}"; #endif void SonoffScShow(bool json) { if (sc_value[0] > 0) { float t = ConvertTemp(sc_value[1]); float h = ConvertHumidity(sc_value[0]); char temperature[33]; dtostrfd(t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(h, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(PSTR(",\"SonoffSC\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_LIGHT "\":%d,\"" D_JSON_NOISE "\":%d,\"" D_JSON_AIRQUALITY "\":%d}"), temperature, humidity, sc_value[2], sc_value[3], sc_value[4]); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzTempHumSensor(temperature, humidity); DomoticzSensor(DZ_ILLUMINANCE, sc_value[2]); DomoticzSensor(DZ_COUNT, sc_value[3]); DomoticzSensor(DZ_AIRQUALITY, 500 + ((100 - sc_value[4]) * 20)); } #endif #ifdef USE_KNX if (0 == tele_period) { KnxSensor(KNX_TEMPERATURE, t); KnxSensor(KNX_HUMIDITY, h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, "", humidity); WSContentSend_PD(HTTP_SNS_SCPLUS, sc_value[2], sc_value[3], sc_value[4]); #endif } } } bool Xsns04(uint8_t function) { bool result = false; if (SONOFF_SC == my_module_type) { switch (function) { case FUNC_JSON_APPEND: SonoffScShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: SonoffScShow(0); break; #endif case FUNC_INIT: SonoffScInit(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_05_ds18x20.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_05_ds18x20.ino" #ifdef USE_DS18x20 #define XSNS_05 5 #define DS18S20_CHIPID 0x10 #define DS1822_CHIPID 0x22 #define DS18B20_CHIPID 0x28 #define MAX31850_CHIPID 0x3B #define W1_SKIP_ROM 0xCC #define W1_CONVERT_TEMP 0x44 #define W1_WRITE_EEPROM 0x48 #define W1_WRITE_SCRATCHPAD 0x4E #define W1_READ_SCRATCHPAD 0xBE #define DS18X20_MAX_SENSORS 8 const char kDs18x20Types[] PROGMEM = "DS18x20|DS18S20|DS1822|DS18B20|MAX31850"; uint8_t ds18x20_chipids[] = { 0, DS18S20_CHIPID, DS1822_CHIPID, DS18B20_CHIPID, MAX31850_CHIPID }; struct DS18X20STRUCT { uint8_t address[8]; uint8_t index; uint8_t valid; float temperature; } ds18x20_sensor[DS18X20_MAX_SENSORS]; uint8_t ds18x20_sensors = 0; uint8_t ds18x20_pin = 0; uint8_t ds18x20_pin_out = 0; bool ds18x20_dual_mode = false; char ds18x20_types[12]; #ifdef W1_PARASITE_POWER uint8_t ds18x20_sensor_curr = 0; unsigned long w1_power_until = 0; #endif #define W1_MATCH_ROM 0x55 #define W1_SEARCH_ROM 0xF0 uint8_t onewire_last_discrepancy = 0; uint8_t onewire_last_family_discrepancy = 0; bool onewire_last_device_flag = false; unsigned char onewire_rom_id[8] = { 0 }; uint8_t OneWireReset(void) { uint8_t retries = 125; if (!ds18x20_dual_mode) { pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); do { if (--retries == 0) { return 0; } delayMicroseconds(2); } while (!digitalRead(ds18x20_pin)); pinMode(ds18x20_pin, OUTPUT); digitalWrite(ds18x20_pin, LOW); delayMicroseconds(480); pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); } else { digitalWrite(ds18x20_pin_out, HIGH); do { if (--retries == 0) { return 0; } delayMicroseconds(2); } while (!digitalRead(ds18x20_pin)); digitalWrite(ds18x20_pin_out, LOW); delayMicroseconds(480); digitalWrite(ds18x20_pin_out, HIGH); } delayMicroseconds(70); uint8_t r = !digitalRead(ds18x20_pin); delayMicroseconds(410); return r; } void OneWireWriteBit(uint8_t v) { static const uint8_t delay_low[2] = { 65, 10 }; static const uint8_t delay_high[2] = { 5, 55 }; v &= 1; if (!ds18x20_dual_mode) { digitalWrite(ds18x20_pin, LOW); pinMode(ds18x20_pin, OUTPUT); delayMicroseconds(delay_low[v]); digitalWrite(ds18x20_pin, HIGH); } else { digitalWrite(ds18x20_pin_out, LOW); delayMicroseconds(delay_low[v]); digitalWrite(ds18x20_pin_out, HIGH); } delayMicroseconds(delay_high[v]); } uint8_t OneWireReadBit(void) { if (!ds18x20_dual_mode) { pinMode(ds18x20_pin, OUTPUT); digitalWrite(ds18x20_pin, LOW); delayMicroseconds(3); pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); } else { digitalWrite(ds18x20_pin_out, LOW); delayMicroseconds(3); digitalWrite(ds18x20_pin_out, HIGH); } delayMicroseconds(10); uint8_t r = digitalRead(ds18x20_pin); delayMicroseconds(53); return r; } void OneWireWrite(uint8_t v) { for (uint8_t bit_mask = 0x01; bit_mask; bit_mask <<= 1) { OneWireWriteBit((bit_mask & v) ? 1 : 0); } } uint8_t OneWireRead(void) { uint8_t r = 0; for (uint8_t bit_mask = 0x01; bit_mask; bit_mask <<= 1) { if (OneWireReadBit()) { r |= bit_mask; } } return r; } void OneWireSelect(const uint8_t rom[8]) { OneWireWrite(W1_MATCH_ROM); for (uint32_t i = 0; i < 8; i++) { OneWireWrite(rom[i]); } } void OneWireResetSearch(void) { onewire_last_discrepancy = 0; onewire_last_device_flag = false; onewire_last_family_discrepancy = 0; for (uint32_t i = 0; i < 8; i++) { onewire_rom_id[i] = 0; } } uint8_t OneWireSearch(uint8_t *newAddr) { uint8_t id_bit_number = 1; uint8_t last_zero = 0; uint8_t rom_byte_number = 0; uint8_t search_result = 0; uint8_t id_bit; uint8_t cmp_id_bit; unsigned char rom_byte_mask = 1; unsigned char search_direction; if (!onewire_last_device_flag) { if (!OneWireReset()) { onewire_last_discrepancy = 0; onewire_last_device_flag = false; onewire_last_family_discrepancy = 0; return false; } OneWireWrite(W1_SEARCH_ROM); do { id_bit = OneWireReadBit(); cmp_id_bit = OneWireReadBit(); if ((id_bit == 1) && (cmp_id_bit == 1)) { break; } else { if (id_bit != cmp_id_bit) { search_direction = id_bit; } else { if (id_bit_number < onewire_last_discrepancy) { search_direction = ((onewire_rom_id[rom_byte_number] & rom_byte_mask) > 0); } else { search_direction = (id_bit_number == onewire_last_discrepancy); } if (search_direction == 0) { last_zero = id_bit_number; if (last_zero < 9) { onewire_last_family_discrepancy = last_zero; } } } if (search_direction == 1) { onewire_rom_id[rom_byte_number] |= rom_byte_mask; } else { onewire_rom_id[rom_byte_number] &= ~rom_byte_mask; } OneWireWriteBit(search_direction); id_bit_number++; rom_byte_mask <<= 1; if (rom_byte_mask == 0) { rom_byte_number++; rom_byte_mask = 1; } } } while (rom_byte_number < 8); if (!(id_bit_number < 65)) { onewire_last_discrepancy = last_zero; if (onewire_last_discrepancy == 0) { onewire_last_device_flag = true; } search_result = true; } } if (!search_result || !onewire_rom_id[0]) { onewire_last_discrepancy = 0; onewire_last_device_flag = false; onewire_last_family_discrepancy = 0; search_result = false; } for (uint32_t i = 0; i < 8; i++) { newAddr[i] = onewire_rom_id[i]; } return search_result; } bool OneWireCrc8(uint8_t *addr) { uint8_t crc = 0; uint8_t len = 8; while (len--) { uint8_t inbyte = *addr++; for (uint32_t i = 8; i; i--) { uint8_t mix = (crc ^ inbyte) & 0x01; crc >>= 1; if (mix) { crc ^= 0x8C; } inbyte >>= 1; } } return (crc == *addr); } void Ds18x20Init(void) { uint64_t ids[DS18X20_MAX_SENSORS]; ds18x20_pin = pin[GPIO_DSB]; if (pin[GPIO_DSB_OUT] < 99) { ds18x20_pin_out = pin[GPIO_DSB_OUT]; ds18x20_dual_mode = true; pinMode(ds18x20_pin_out, OUTPUT); pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT); } OneWireResetSearch(); ds18x20_sensors = 0; while (ds18x20_sensors < DS18X20_MAX_SENSORS) { if (!OneWireSearch(ds18x20_sensor[ds18x20_sensors].address)) { break; } if (OneWireCrc8(ds18x20_sensor[ds18x20_sensors].address) && ((ds18x20_sensor[ds18x20_sensors].address[0] == DS18S20_CHIPID) || (ds18x20_sensor[ds18x20_sensors].address[0] == DS1822_CHIPID) || (ds18x20_sensor[ds18x20_sensors].address[0] == DS18B20_CHIPID) || (ds18x20_sensor[ds18x20_sensors].address[0] == MAX31850_CHIPID))) { ds18x20_sensor[ds18x20_sensors].index = ds18x20_sensors; ids[ds18x20_sensors] = ds18x20_sensor[ds18x20_sensors].address[0]; for (uint32_t j = 6; j > 0; j--) { ids[ds18x20_sensors] = ids[ds18x20_sensors] << 8 | ds18x20_sensor[ds18x20_sensors].address[j]; } ds18x20_sensors++; } } for (uint32_t i = 0; i < ds18x20_sensors; i++) { for (uint32_t j = i + 1; j < ds18x20_sensors; j++) { if (ids[ds18x20_sensor[i].index] > ids[ds18x20_sensor[j].index]) { std::swap(ds18x20_sensor[i].index, ds18x20_sensor[j].index); } } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSORS_FOUND " %d"), ds18x20_sensors); } void Ds18x20Convert(void) { OneWireReset(); #ifdef W1_PARASITE_POWER if (++ds18x20_sensor_curr >= ds18x20_sensors) ds18x20_sensor_curr = 0; OneWireSelect(ds18x20_sensor[ds18x20_sensor_curr].address); #else OneWireWrite(W1_SKIP_ROM); #endif OneWireWrite(W1_CONVERT_TEMP); } bool Ds18x20Read(uint8_t sensor) { uint8_t data[9]; int8_t sign = 1; uint8_t index = ds18x20_sensor[sensor].index; if (ds18x20_sensor[index].valid) { ds18x20_sensor[index].valid--; } for (uint32_t retry = 0; retry < 3; retry++) { OneWireReset(); OneWireSelect(ds18x20_sensor[index].address); OneWireWrite(W1_READ_SCRATCHPAD); for (uint32_t i = 0; i < 9; i++) { data[i] = OneWireRead(); } if (OneWireCrc8(data)) { switch(ds18x20_sensor[index].address[0]) { case DS18S20_CHIPID: { if (data[1] > 0x80) { data[0] = (~data[0]) +1; sign = -1; } float temp9 = (float)(data[0] >> 1) * sign; ds18x20_sensor[index].temperature = ConvertTemp((temp9 - 0.25) + ((16.0 - data[6]) / 16.0)); ds18x20_sensor[index].valid = SENSOR_MAX_MISS; return true; } case DS1822_CHIPID: case DS18B20_CHIPID: { if (data[4] != 0x7F) { data[4] = 0x7F; OneWireReset(); OneWireSelect(ds18x20_sensor[index].address); OneWireWrite(W1_WRITE_SCRATCHPAD); OneWireWrite(data[2]); OneWireWrite(data[3]); OneWireWrite(data[4]); OneWireSelect(ds18x20_sensor[index].address); OneWireWrite(W1_WRITE_EEPROM); #ifdef W1_PARASITE_POWER w1_power_until = millis() + 10; #endif } uint16_t temp12 = (data[1] << 8) + data[0]; if (temp12 > 2047) { temp12 = (~temp12) +1; sign = -1; } ds18x20_sensor[index].temperature = ConvertTemp(sign * temp12 * 0.0625); ds18x20_sensor[index].valid = SENSOR_MAX_MISS; return true; } case MAX31850_CHIPID: { int16_t temp14 = (data[1] << 8) + (data[0] & 0xFC); ds18x20_sensor[index].temperature = ConvertTemp(temp14 * 0.0625); ds18x20_sensor[index].valid = SENSOR_MAX_MISS; return true; } } } } AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSOR_CRC_ERROR)); return false; } void Ds18x20Name(uint8_t sensor) { uint8_t index = sizeof(ds18x20_chipids); while (index) { if (ds18x20_sensor[ds18x20_sensor[sensor].index].address[0] == ds18x20_chipids[index]) { break; } index--; } GetTextIndexed(ds18x20_types, sizeof(ds18x20_types), index, kDs18x20Types); if (ds18x20_sensors > 1) { snprintf_P(ds18x20_types, sizeof(ds18x20_types), PSTR("%s%c%d"), ds18x20_types, IndexSeparator(), sensor +1); } } void Ds18x20EverySecond(void) { #ifdef W1_PARASITE_POWER unsigned long now = millis(); if (now < w1_power_until) return; #endif if (uptime & 1 #ifdef W1_PARASITE_POWER || ds18x20_sensors >= 2 #endif ) { Ds18x20Convert(); } else { for (uint32_t i = 0; i < ds18x20_sensors; i++) { if (!Ds18x20Read(i)) { Ds18x20Name(i); AddLogMissed(ds18x20_types, ds18x20_sensor[ds18x20_sensor[i].index].valid); #ifdef USE_DS18x20_RECONFIGURE if (!ds18x20_sensor[ds18x20_sensor[i].index].valid) { memset(&ds18x20_sensor, 0, sizeof(ds18x20_sensor)); Ds18x20Init(); } #endif } } } } void Ds18x20Show(bool json) { for (uint32_t i = 0; i < ds18x20_sensors; i++) { uint8_t index = ds18x20_sensor[i].index; if (ds18x20_sensor[index].valid) { char temperature[33]; dtostrfd(ds18x20_sensor[index].temperature, Settings.flag2.temperature_resolution, temperature); Ds18x20Name(i); if (json) { char address[17]; for (uint32_t j = 0; j < 6; j++) { sprintf(address+2*j, "%02X", ds18x20_sensor[index].address[6-j]); } ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, address, temperature); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzSensor(DZ_TEMP, temperature); } #endif #ifdef USE_KNX if ((0 == tele_period) && (0 == i)) { KnxSensor(KNX_TEMPERATURE, ds18x20_sensor[index].temperature); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, ds18x20_types, temperature, TempUnit()); #endif } } } } bool Xsns05(uint8_t function) { bool result = false; if (pin[GPIO_DSB] < 99) { switch (function) { case FUNC_INIT: Ds18x20Init(); break; case FUNC_EVERY_SECOND: Ds18x20EverySecond(); break; case FUNC_JSON_APPEND: Ds18x20Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Ds18x20Show(0); break; #endif } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino" #ifdef USE_DHT_OLD # 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino" #define XSNS_06 6 #define DHT_MAX_SENSORS 4 #define DHT_MAX_RETRY 8 uint32_t dht_max_cycles; uint8_t dht_data[5]; uint8_t dht_sensors = 0; uint8_t dht_pin_out = 0; bool dht_active = true; bool dht_dual_mode = false; struct DHTSTRUCT { uint8_t pin; uint8_t type; char stype[12]; uint32_t lastreadtime; uint8_t lastresult; float t = NAN; float h = NAN; } Dht[DHT_MAX_SENSORS]; void DhtReadPrep(void) { for (uint32_t i = 0; i < dht_sensors; i++) { if (!dht_dual_mode) { digitalWrite(Dht[i].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } } } int32_t DhtExpectPulse(uint8_t sensor, bool level) { int32_t count = 0; while (digitalRead(Dht[sensor].pin) == level) { if (count++ >= (int32_t)dht_max_cycles) { return -1; } } return count; } bool DhtRead(uint8_t sensor) { int32_t cycles[80]; uint8_t error = 0; dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; if (Dht[sensor].lastresult > DHT_MAX_RETRY) { Dht[sensor].lastresult = 0; if (!dht_dual_mode) { digitalWrite(Dht[sensor].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } delay(250); } if (!dht_dual_mode) { pinMode(Dht[sensor].pin, OUTPUT); digitalWrite(Dht[sensor].pin, LOW); } else { digitalWrite(dht_pin_out, LOW); } if (GPIO_SI7021 == Dht[sensor].type) { delayMicroseconds(500); } else { delay(20); } noInterrupts(); if (!dht_dual_mode) { digitalWrite(Dht[sensor].pin, HIGH); delayMicroseconds(40); pinMode(Dht[sensor].pin, INPUT_PULLUP); } else { digitalWrite(dht_pin_out, HIGH); delayMicroseconds(40); } delayMicroseconds(10); if (-1 == DhtExpectPulse(sensor, LOW)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); error = 1; } else if (-1 == DhtExpectPulse(sensor, HIGH)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE)); error = 1; } else { for (uint32_t i = 0; i < 80; i += 2) { cycles[i] = DhtExpectPulse(sensor, LOW); cycles[i+1] = DhtExpectPulse(sensor, HIGH); } } interrupts(); if (error) { return false; } for (uint32_t i = 0; i < 40; ++i) { int32_t lowCycles = cycles[2*i]; int32_t highCycles = cycles[2*i+1]; if ((-1 == lowCycles) || (-1 == highCycles)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE)); return false; } dht_data[i/8] <<= 1; if (highCycles > lowCycles) { dht_data[i / 8] |= 1; } } uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; if (dht_data[4] != checksum) { char hex_char[15]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); return false; } return true; } void DhtReadTempHum(uint8_t sensor) { if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { Dht[sensor].t = NAN; Dht[sensor].h = NAN; } if (DhtRead(sensor)) { switch (Dht[sensor].type) { case GPIO_DHT11: Dht[sensor].h = dht_data[0]; Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); break; case GPIO_DHT22: case GPIO_SI7021: Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; if (dht_data[2] & 0x80) { Dht[sensor].t *= -1; } break; } Dht[sensor].t = ConvertTemp(Dht[sensor].t); Dht[sensor].h = ConvertHumidity(Dht[sensor].h); Dht[sensor].lastresult = 0; } else { Dht[sensor].lastresult++; } } bool DhtPinState() { if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { if (dht_sensors < DHT_MAX_SENSORS) { Dht[dht_sensors].pin = XdrvMailbox.payload; Dht[dht_sensors].type = XdrvMailbox.index; dht_sensors++; XdrvMailbox.index = GPIO_DHT11; } else { XdrvMailbox.index = 0; } return true; } return false; } void DhtInit(void) { if (dht_sensors) { dht_max_cycles = microsecondsToClockCycles(1000); if (pin[GPIO_DHT11_OUT] < 99) { dht_pin_out = pin[GPIO_DHT11_OUT]; dht_dual_mode = true; dht_sensors = 1; pinMode(dht_pin_out, OUTPUT); } for (uint32_t i = 0; i < dht_sensors; i++) { pinMode(Dht[i].pin, INPUT_PULLUP); Dht[i].lastreadtime = 0; Dht[i].lastresult = 0; GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); if (dht_sensors > 1) { snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_SENSORS_FOUND " %d"), dht_sensors); } else { dht_active = false; } } void DhtEverySecond(void) { if (uptime &1) { DhtReadPrep(); } else { for (uint32_t i = 0; i < dht_sensors; i++) { DhtReadTempHum(i); } } } void DhtShow(bool json) { for (uint32_t i = 0; i < dht_sensors; i++) { char temperature[33]; dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if ((0 == tele_period) && (0 == i)) { KnxSensor(KNX_TEMPERATURE, Dht[i].t); KnxSensor(KNX_HUMIDITY, Dht[i].h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); #endif } } } bool Xsns06(uint8_t function) { bool result = false; if (dht_active) { switch (function) { case FUNC_EVERY_SECOND: DhtEverySecond(); break; case FUNC_JSON_APPEND: DhtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: DhtShow(0); break; #endif case FUNC_INIT: DhtInit(); break; case FUNC_PIN_STATE: result = DhtPinState(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino" #ifdef USE_DHT_V2 # 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino" #define XSNS_06 6 #define DHT_MAX_SENSORS 4 #define DHT_MAX_RETRY 8 uint32_t dht_max_cycles; uint8_t dht_data[5]; uint8_t dht_sensors = 0; uint8_t dht_pin_out = 0; bool dht_active = true; bool dht_dual_mode = false; struct DHTSTRUCT { uint8_t pin; uint8_t type; char stype[12]; uint32_t lastreadtime; uint8_t lastresult; float t = NAN; float h = NAN; } Dht[DHT_MAX_SENSORS]; void DhtReadPrep(void) { for (uint32_t i = 0; i < dht_sensors; i++) { if (!dht_dual_mode) { digitalWrite(Dht[i].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } } } int32_t DhtExpectPulse(uint8_t sensor, bool level) { int32_t count = 0; while (digitalRead(Dht[sensor].pin) == level) { if (count++ >= (int32_t)dht_max_cycles) { return -1; } } return count; } bool DhtRead(uint8_t sensor) { int32_t cycles[80]; uint8_t error = 0; dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; if (Dht[sensor].lastresult > DHT_MAX_RETRY) { Dht[sensor].lastresult = 0; if (!dht_dual_mode) { digitalWrite(Dht[sensor].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } delay(250); } noInterrupts(); if (!dht_dual_mode) { pinMode(Dht[sensor].pin, OUTPUT); digitalWrite(Dht[sensor].pin, LOW); } else { digitalWrite(dht_pin_out, LOW); } switch (Dht[sensor].type) { case GPIO_SI7021: # 114 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino" delayMicroseconds(500); if (!dht_dual_mode) { digitalWrite(Dht[sensor].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } delayMicroseconds(40); break; case GPIO_DHT22: # 133 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino" delayMicroseconds(1100); if (!dht_dual_mode) { digitalWrite(Dht[sensor].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } delayMicroseconds(30); break; case GPIO_DHT11: # 151 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino" default: delay(20); if (!dht_dual_mode) { digitalWrite(Dht[sensor].pin, HIGH); } else { digitalWrite(dht_pin_out, HIGH); } delayMicroseconds(30); break; } pinMode(Dht[sensor].pin, INPUT_PULLUP); if (-1 == DhtExpectPulse(sensor, LOW)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); error = 1; } else if (-1 == DhtExpectPulse(sensor, HIGH)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE)); error = 1; } else { for (uint32_t i = 0; i < 80; i += 2) { cycles[i] = DhtExpectPulse(sensor, LOW); cycles[i+1] = DhtExpectPulse(sensor, HIGH); } } interrupts(); if (error) { return false; } for (uint32_t i = 0; i < 40; ++i) { int32_t lowCycles = cycles[2*i]; int32_t highCycles = cycles[2*i+1]; if ((-1 == lowCycles) || (-1 == highCycles)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE)); return false; } dht_data[i/8] <<= 1; if (highCycles > lowCycles) { dht_data[i / 8] |= 1; } } uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; if (dht_data[4] != checksum) { char hex_char[15]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); return false; } return true; } void DhtReadTempHum(uint8_t sensor) { if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { Dht[sensor].t = NAN; Dht[sensor].h = NAN; } if (DhtRead(sensor)) { switch (Dht[sensor].type) { case GPIO_DHT11: Dht[sensor].h = dht_data[0]; Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); break; case GPIO_DHT22: case GPIO_SI7021: Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; if (dht_data[2] & 0x80) { Dht[sensor].t *= -1; } break; } Dht[sensor].t = ConvertTemp(Dht[sensor].t); Dht[sensor].h = ConvertHumidity(Dht[sensor].h); Dht[sensor].lastresult = 0; } else { Dht[sensor].lastresult++; } } bool DhtPinState() { if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { if (dht_sensors < DHT_MAX_SENSORS) { Dht[dht_sensors].pin = XdrvMailbox.payload; Dht[dht_sensors].type = XdrvMailbox.index; dht_sensors++; XdrvMailbox.index = GPIO_DHT11; } else { XdrvMailbox.index = 0; } return true; } return false; } void DhtInit(void) { if (dht_sensors) { dht_max_cycles = microsecondsToClockCycles(1000); if (pin[GPIO_DHT11_OUT] < 99) { dht_pin_out = pin[GPIO_DHT11_OUT]; dht_dual_mode = true; dht_sensors = 1; pinMode(dht_pin_out, OUTPUT); } for (uint32_t i = 0; i < dht_sensors; i++) { pinMode(Dht[i].pin, INPUT_PULLUP); Dht[i].lastreadtime = 0; Dht[i].lastresult = 0; GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); if (dht_sensors > 1) { snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v2) " D_SENSORS_FOUND " %d"), dht_sensors); } else { dht_active = false; } } void DhtEverySecond(void) { if (uptime &1) { DhtReadPrep(); } else { for (uint32_t i = 0; i < dht_sensors; i++) { DhtReadTempHum(i); } } } void DhtShow(bool json) { for (uint32_t i = 0; i < dht_sensors; i++) { char temperature[33]; dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if ((0 == tele_period) && (0 == i)) { KnxSensor(KNX_TEMPERATURE, Dht[i].t); KnxSensor(KNX_HUMIDITY, Dht[i].h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); #endif } } } bool Xsns06(uint8_t function) { bool result = false; if (dht_active) { switch (function) { case FUNC_EVERY_SECOND: DhtEverySecond(); break; case FUNC_JSON_APPEND: DhtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: DhtShow(0); break; #endif case FUNC_INIT: DhtInit(); break; case FUNC_PIN_STATE: result = DhtPinState(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino" #ifdef USE_DHT_V3 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino" #define XSNS_06 6 #define DHT_MAX_SENSORS 4 #define DHT_MAX_RETRY 8 uint8_t dht_data[5]; uint8_t dht_sensors = 0; uint8_t dht_pin_out = 0; bool dht_active = true; bool dht_dual_mode = false; struct DHTSTRUCT { uint8_t pin; uint8_t type; char stype[12]; uint32_t lastreadtime; uint8_t lastresult; float t = NAN; float h = NAN; } Dht[DHT_MAX_SENSORS]; bool DhtExpectPulse(uint8_t sensor, int level) { unsigned long timeout = micros() + 100; while (digitalRead(Dht[sensor].pin) != level) { if (micros() > timeout) { return false; } delayMicroseconds(1); } return true; } int DhtReadDat(uint8_t sensor) { uint8_t result = 0; for (uint32_t i = 0; i < 8; i++) { if (!DhtExpectPulse(sensor, HIGH)) { return -1; } delayMicroseconds(35); if (digitalRead(Dht[sensor].pin)) { result |= (1 << (7 - i)); } if (!DhtExpectPulse(sensor, LOW)) { return -1; } } return result; } bool DhtRead(uint8_t sensor) { dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; if (!dht_dual_mode) { pinMode(Dht[sensor].pin, OUTPUT); digitalWrite(Dht[sensor].pin, LOW); } else { digitalWrite(dht_pin_out, LOW); } switch (Dht[sensor].type) { case GPIO_DHT11: delay(19); break; case GPIO_DHT22: delay(2); break; case GPIO_SI7021: delayMicroseconds(500); break; } if (!dht_dual_mode) { pinMode(Dht[sensor].pin, INPUT_PULLUP); } else { digitalWrite(dht_pin_out, HIGH); } switch (Dht[sensor].type) { case GPIO_DHT11: case GPIO_DHT22: delayMicroseconds(50); break; case GPIO_SI7021: delayMicroseconds(20); break; } noInterrupts(); if (!DhtExpectPulse(sensor, LOW)) { interrupts(); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); return false; } if (!DhtExpectPulse(sensor, HIGH)) { interrupts(); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE)); return false; } if (!DhtExpectPulse(sensor, LOW)) { interrupts(); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE)); return false; } int data = 0; for (uint32_t i = 0; i < 5; i++) { data = DhtReadDat(sensor); if (-1 == data) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE)); break; } dht_data[i] = data; } interrupts(); if (-1 == data) { return false; } uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; if (dht_data[4] != checksum) { char hex_char[15]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); return false; } return true; } void DhtReadTempHum(uint8_t sensor) { if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { Dht[sensor].t = NAN; Dht[sensor].h = NAN; } if (DhtRead(sensor)) { switch (Dht[sensor].type) { case GPIO_DHT11: Dht[sensor].h = dht_data[0]; Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); break; case GPIO_DHT22: case GPIO_SI7021: Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; if (dht_data[2] & 0x80) { Dht[sensor].t *= -1; } break; } Dht[sensor].t = ConvertTemp(Dht[sensor].t); Dht[sensor].h = ConvertHumidity(Dht[sensor].h); Dht[sensor].lastresult = 0; } else { Dht[sensor].lastresult++; } } bool DhtPinState() { if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { if (dht_sensors < DHT_MAX_SENSORS) { Dht[dht_sensors].pin = XdrvMailbox.payload; Dht[dht_sensors].type = XdrvMailbox.index; dht_sensors++; XdrvMailbox.index = GPIO_DHT11; } else { XdrvMailbox.index = 0; } return true; } return false; } void DhtInit(void) { if (dht_sensors) { if (pin[GPIO_DHT11_OUT] < 99) { dht_pin_out = pin[GPIO_DHT11_OUT]; dht_dual_mode = true; dht_sensors = 1; pinMode(dht_pin_out, OUTPUT); } for (uint32_t i = 0; i < dht_sensors; i++) { pinMode(Dht[i].pin, INPUT_PULLUP); Dht[i].lastreadtime = 0; Dht[i].lastresult = 0; GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); if (dht_sensors > 1) { snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v3) " D_SENSORS_FOUND " %d"), dht_sensors); } else { dht_active = false; } } void DhtEverySecond(void) { if (uptime &1) { } else { for (uint32_t i = 0; i < dht_sensors; i++) { DhtReadTempHum(i); } } } void DhtShow(bool json) { for (uint32_t i = 0; i < dht_sensors; i++) { char temperature[33]; dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if ((0 == tele_period) && (0 == i)) { KnxSensor(KNX_TEMPERATURE, Dht[i].t); KnxSensor(KNX_HUMIDITY, Dht[i].h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); #endif } } } bool Xsns06(uint8_t function) { bool result = false; if (dht_active) { switch (function) { case FUNC_EVERY_SECOND: DhtEverySecond(); break; case FUNC_JSON_APPEND: DhtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: DhtShow(0); break; #endif case FUNC_INIT: DhtInit(); break; case FUNC_PIN_STATE: result = DhtPinState(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino" #ifdef USE_DHT_V4 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino" #define XSNS_06 6 #define DHT_MAX_SENSORS 4 #define DHT_MAX_RETRY 8 uint8_t dht_data[5]; uint8_t dht_sensors = 0; uint8_t dht_pin_out = 0; bool dht_active = true; bool dht_dual_mode = false; struct DHTSTRUCT { uint8_t pin; uint8_t type; char stype[12]; uint32_t lastreadtime; uint8_t lastresult; float t = NAN; float h = NAN; } Dht[DHT_MAX_SENSORS]; bool DhtExpectPulse(uint32_t sensor, uint32_t level) { unsigned long timeout = micros() + 100; while (digitalRead(Dht[sensor].pin) != level) { if (micros() > timeout) { return false; } delayMicroseconds(1); } return true; } bool DhtRead(uint32_t sensor) { dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; if (!dht_dual_mode) { pinMode(Dht[sensor].pin, OUTPUT); digitalWrite(Dht[sensor].pin, LOW); } else { digitalWrite(dht_pin_out, LOW); } switch (Dht[sensor].type) { case GPIO_DHT11: delay(19); break; case GPIO_DHT22: delay(2); break; case GPIO_SI7021: delayMicroseconds(500); break; } if (!dht_dual_mode) { pinMode(Dht[sensor].pin, INPUT_PULLUP); } else { digitalWrite(dht_pin_out, HIGH); } switch (Dht[sensor].type) { case GPIO_DHT11: case GPIO_DHT22: delayMicroseconds(50); break; case GPIO_SI7021: delayMicroseconds(20); break; } uint32_t level = 9; noInterrupts(); for (uint32_t i = 0; i < 3; i++) { level = i &1; if (!DhtExpectPulse(sensor, level)) { break; } level = 9; } if (9 == level) { int data = 0; for (uint32_t i = 0; i < 5; i++) { data = 0; for (uint32_t j = 0; j < 8; j++) { level = 1; if (!DhtExpectPulse(sensor, level)) { break; } delayMicroseconds(35); if (digitalRead(Dht[sensor].pin)) { data |= (1 << (7 - j)); } level = 0; if (!DhtExpectPulse(sensor, level)) { break; } level = 9; } if (level < 2) { break; } dht_data[i] = data; } } interrupts(); if (level < 2) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " %s " D_PULSE), (0 == level) ? D_START_SIGNAL_LOW : D_START_SIGNAL_HIGH); return false; } uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; if (dht_data[4] != checksum) { char hex_char[15]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); return false; } return true; } void DhtReadTempHum(uint32_t sensor) { if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) { Dht[sensor].t = NAN; Dht[sensor].h = NAN; } if (DhtRead(sensor)) { switch (Dht[sensor].type) { case GPIO_DHT11: Dht[sensor].h = dht_data[0]; Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f); break; case GPIO_DHT22: case GPIO_SI7021: Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1; Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; if (dht_data[2] & 0x80) { Dht[sensor].t *= -1; } break; } Dht[sensor].t = ConvertTemp(Dht[sensor].t); if (Dht[sensor].h > 100) { Dht[sensor].h = 100.0; } if (Dht[sensor].h < 0) { Dht[sensor].h = 0.0; } Dht[sensor].h = ConvertHumidity(Dht[sensor].h); Dht[sensor].lastresult = 0; } else { Dht[sensor].lastresult++; } } bool DhtPinState() { if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { if (dht_sensors < DHT_MAX_SENSORS) { Dht[dht_sensors].pin = XdrvMailbox.payload; Dht[dht_sensors].type = XdrvMailbox.index; dht_sensors++; XdrvMailbox.index = GPIO_DHT11; } else { XdrvMailbox.index = 0; } return true; } return false; } void DhtInit(void) { if (dht_sensors) { if (pin[GPIO_DHT11_OUT] < 99) { dht_pin_out = pin[GPIO_DHT11_OUT]; dht_dual_mode = true; dht_sensors = 1; pinMode(dht_pin_out, OUTPUT); } for (uint32_t i = 0; i < dht_sensors; i++) { pinMode(Dht[i].pin, INPUT_PULLUP); Dht[i].lastreadtime = 0; Dht[i].lastresult = 0; GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); if (dht_sensors > 1) { snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v4) " D_SENSORS_FOUND " %d"), dht_sensors); } else { dht_active = false; } } void DhtEverySecond(void) { if (uptime &1) { } else { for (uint32_t i = 0; i < dht_sensors; i++) { DhtReadTempHum(i); } } } void DhtShow(bool json) { for (uint32_t i = 0; i < dht_sensors; i++) { char temperature[33]; dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if ((0 == tele_period) && (0 == i)) { KnxSensor(KNX_TEMPERATURE, Dht[i].t); KnxSensor(KNX_HUMIDITY, Dht[i].h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); #endif } } } bool Xsns06(uint8_t function) { bool result = false; if (dht_active) { switch (function) { case FUNC_EVERY_SECOND: DhtEverySecond(); break; case FUNC_JSON_APPEND: DhtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: DhtShow(0); break; #endif case FUNC_INIT: DhtInit(); break; case FUNC_PIN_STATE: result = DhtPinState(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino" #ifdef USE_DHT # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino" #define XSNS_06 6 #define DHT_MAX_SENSORS 4 #define DHT_MAX_RETRY 8 uint8_t dht_data[5]; uint8_t dht_sensors = 0; uint8_t dht_pin_out = 0; bool dht_active = true; bool dht_dual_mode = false; struct DHTSTRUCT { uint8_t pin; uint8_t type; uint8_t lastresult; char stype[12]; float t = NAN; float h = NAN; } Dht[DHT_MAX_SENSORS]; bool DhtWaitState(uint32_t sensor, uint32_t level) { unsigned long timeout = micros() + 100; while (digitalRead(Dht[sensor].pin) != level) { if (TimeReachedUsec(timeout)) { PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " %s " D_PULSE), (level) ? D_START_SIGNAL_HIGH : D_START_SIGNAL_LOW); return false; } delayMicroseconds(1); } return true; } bool DhtRead(uint32_t sensor) { dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0; if (!dht_dual_mode) { pinMode(Dht[sensor].pin, OUTPUT); digitalWrite(Dht[sensor].pin, LOW); } else { digitalWrite(dht_pin_out, LOW); } switch (Dht[sensor].type) { case GPIO_DHT11: delay(19); break; case GPIO_DHT22: delay(2); break; case GPIO_SI7021: delayMicroseconds(500); break; } if (!dht_dual_mode) { pinMode(Dht[sensor].pin, INPUT_PULLUP); } else { digitalWrite(dht_pin_out, HIGH); } switch (Dht[sensor].type) { case GPIO_DHT11: case GPIO_DHT22: delayMicroseconds(50); break; case GPIO_SI7021: delayMicroseconds(20); break; } bool error = false; noInterrupts(); if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) { for (uint32_t i = 0; i < 5; i++) { int data = 0; for (uint32_t j = 0; j < 8; j++) { if (!DhtWaitState(sensor, 1)) { error = true; break; } delayMicroseconds(35); if (digitalRead(Dht[sensor].pin)) { data |= (1 << (7 - j)); } if (!DhtWaitState(sensor, 0)) { error = true; break; } } if (error) { break; } dht_data[i] = data; } } else { error = true; } interrupts(); if (error) { return false; } uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; if (dht_data[4] != checksum) { char hex_char[15]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"), ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum); return false; } float temperature = NAN; float humidity = NAN; switch (Dht[sensor].type) { case GPIO_DHT11: humidity = dht_data[0]; temperature = dht_data[2] + ((float)dht_data[3] * 0.1f); break; case GPIO_DHT22: case GPIO_SI7021: humidity = ((dht_data[0] << 8) | dht_data[1]) * 0.1; temperature = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1; if (dht_data[2] & 0x80) { temperature *= -1; } break; } if (isnan(temperature) || isnan(humidity)) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "Invalid NAN reading")); return false; } if (humidity > 100) { humidity = 100.0; } if (humidity < 0) { humidity = 0.1; } Dht[sensor].h = ConvertHumidity(humidity); Dht[sensor].t = ConvertTemp(temperature); Dht[sensor].lastresult = 0; return true; } bool DhtPinState() { if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) { if (dht_sensors < DHT_MAX_SENSORS) { Dht[dht_sensors].pin = XdrvMailbox.payload; Dht[dht_sensors].type = XdrvMailbox.index; dht_sensors++; XdrvMailbox.index = GPIO_DHT11; } else { XdrvMailbox.index = 0; } return true; } return false; } void DhtInit(void) { if (dht_sensors) { if (pin[GPIO_DHT11_OUT] < 99) { dht_pin_out = pin[GPIO_DHT11_OUT]; dht_dual_mode = true; dht_sensors = 1; pinMode(dht_pin_out, OUTPUT); } for (uint32_t i = 0; i < dht_sensors; i++) { pinMode(Dht[i].pin, INPUT_PULLUP); Dht[i].lastresult = DHT_MAX_RETRY; GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames); if (dht_sensors > 1) { snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin); } } AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v5) " D_SENSORS_FOUND " %d"), dht_sensors); } else { dht_active = false; } } void DhtEverySecond(void) { if (uptime &1) { for (uint32_t sensor = 0; sensor < dht_sensors; sensor++) { if (!DhtRead(sensor)) { Dht[sensor].lastresult++; if (Dht[sensor].lastresult > DHT_MAX_RETRY) { Dht[sensor].t = NAN; Dht[sensor].h = NAN; } } } } } void DhtShow(bool json) { for (uint32_t i = 0; i < dht_sensors; i++) { char temperature[33]; dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if ((0 == tele_period) && (0 == i)) { KnxSensor(KNX_TEMPERATURE, Dht[i].t); KnxSensor(KNX_HUMIDITY, Dht[i].h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity); #endif } } } bool Xsns06(uint8_t function) { bool result = false; if (dht_active) { switch (function) { case FUNC_EVERY_SECOND: DhtEverySecond(); break; case FUNC_JSON_APPEND: DhtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: DhtShow(0); break; #endif case FUNC_INIT: DhtInit(); break; case FUNC_PIN_STATE: result = DhtPinState(); break; } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino" #ifdef USE_I2C #ifdef USE_SHT # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino" #define XSNS_07 7 #define XI2C_08 8 enum { SHT1X_CMD_MEASURE_TEMP = B00000011, SHT1X_CMD_MEASURE_RH = B00000101, SHT1X_CMD_SOFT_RESET = B00011110 }; uint8_t sht_sda_pin; uint8_t sht_scl_pin; uint8_t sht_type = 0; char sht_types[] = "SHT1X"; uint8_t sht_valid = 0; float sht_temperature = 0; float sht_humidity = 0; bool ShtReset(void) { pinMode(sht_sda_pin, INPUT_PULLUP); pinMode(sht_scl_pin, OUTPUT); delay(11); for (uint32_t i = 0; i < 9; i++) { digitalWrite(sht_scl_pin, HIGH); digitalWrite(sht_scl_pin, LOW); } bool success = ShtSendCommand(SHT1X_CMD_SOFT_RESET); delay(11); return success; } bool ShtSendCommand(const uint8_t cmd) { pinMode(sht_sda_pin, OUTPUT); digitalWrite(sht_sda_pin, HIGH); digitalWrite(sht_scl_pin, HIGH); digitalWrite(sht_sda_pin, LOW); digitalWrite(sht_scl_pin, LOW); digitalWrite(sht_scl_pin, HIGH); digitalWrite(sht_sda_pin, HIGH); digitalWrite(sht_scl_pin, LOW); shiftOut(sht_sda_pin, sht_scl_pin, MSBFIRST, cmd); bool ackerror = false; digitalWrite(sht_scl_pin, HIGH); pinMode(sht_sda_pin, INPUT_PULLUP); if (digitalRead(sht_sda_pin) != LOW) { ackerror = true; } digitalWrite(sht_scl_pin, LOW); delayMicroseconds(1); if (digitalRead(sht_sda_pin) != HIGH) { ackerror = true; } if (ackerror) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_DID_NOT_ACK_COMMAND)); } return (!ackerror); } bool ShtAwaitResult(void) { for (uint32_t i = 0; i < 16; i++) { if (LOW == digitalRead(sht_sda_pin)) { return true; } delay(20); } AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_BUSY)); return false; } int ShtReadData(void) { int val = 0; val = shiftIn(sht_sda_pin, sht_scl_pin, 8); val <<= 8; pinMode(sht_sda_pin, OUTPUT); digitalWrite(sht_sda_pin, LOW); digitalWrite(sht_scl_pin, HIGH); digitalWrite(sht_scl_pin, LOW); pinMode(sht_sda_pin, INPUT_PULLUP); val |= shiftIn(sht_sda_pin, sht_scl_pin, 8); digitalWrite(sht_scl_pin, HIGH); digitalWrite(sht_scl_pin, LOW); return val; } bool ShtRead(void) { if (sht_valid) { sht_valid--; } if (!ShtReset()) { return false; } if (!ShtSendCommand(SHT1X_CMD_MEASURE_TEMP)) { return false; } if (!ShtAwaitResult()) { return false; } float tempRaw = ShtReadData(); if (!ShtSendCommand(SHT1X_CMD_MEASURE_RH)) { return false; } if (!ShtAwaitResult()) { return false; } float humRaw = ShtReadData(); const float d1 = -39.7; const float d2 = 0.01; sht_temperature = d1 + (tempRaw * d2); const float c1 = -2.0468; const float c2 = 0.0367; const float c3 = -1.5955E-6; const float t1 = 0.01; const float t2 = 0.00008; float rhLinear = c1 + c2 * humRaw + c3 * humRaw * humRaw; sht_humidity = (sht_temperature - 25) * (t1 + t2 * humRaw) + rhLinear; sht_temperature = ConvertTemp(sht_temperature); ConvertHumidity(sht_humidity); sht_valid = SENSOR_MAX_MISS; return true; } void ShtDetect(void) { sht_sda_pin = pin[GPIO_I2C_SDA]; sht_scl_pin = pin[GPIO_I2C_SCL]; if (ShtRead()) { sht_type = 1; AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SHT1X_FOUND)); } else { Wire.begin(sht_sda_pin, sht_scl_pin); sht_type = 0; } } void ShtEverySecond(void) { if (!(uptime %4)) { if (!ShtRead()) { AddLogMissed(sht_types, sht_valid); } } } void ShtShow(bool json) { if (sht_valid) { char temperature[33]; dtostrfd(sht_temperature, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(sht_humidity, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, sht_types, temperature, humidity); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if (0 == tele_period) { KnxSensor(KNX_TEMPERATURE, sht_temperature); KnxSensor(KNX_HUMIDITY, sht_humidity); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, sht_types, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, sht_types, humidity); #endif } } } bool Xsns07(uint8_t function) { if (!I2cEnabled(XI2C_08)) { return false; } bool result = false; if (FUNC_INIT == function) { ShtDetect(); } else if (sht_type) { switch (function) { case FUNC_EVERY_SECOND: ShtEverySecond(); break; case FUNC_JSON_APPEND: ShtShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: ShtShow(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino" #ifdef USE_I2C #ifdef USE_HTU # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino" #define XSNS_08 8 #define XI2C_09 9 #define HTU21_ADDR 0x40 #define SI7013_CHIPID 0x0D #define SI7020_CHIPID 0x14 #define SI7021_CHIPID 0x15 #define HTU21_CHIPID 0x32 #define HTU21_READTEMP 0xE3 #define HTU21_READHUM 0xE5 #define HTU21_WRITEREG 0xE6 #define HTU21_READREG 0xE7 #define HTU21_RESET 0xFE #define HTU21_HEATER_WRITE 0x51 #define HTU21_HEATER_READ 0x11 #define HTU21_SERIAL2_READ1 0xFC #define HTU21_SERIAL2_READ2 0xC9 #define HTU21_HEATER_ON 0x04 #define HTU21_HEATER_OFF 0xFB #define HTU21_RES_RH12_T14 0x00 #define HTU21_RES_RH8_T12 0x01 #define HTU21_RES_RH10_T13 0x80 #define HTU21_RES_RH11_T11 0x81 #define HTU21_CRC8_POLYNOM 0x13100 const char kHtuTypes[] PROGMEM = "HTU21|SI7013|SI7020|SI7021|T/RH?"; uint8_t htu_address; uint8_t htu_type = 0; uint8_t htu_delay_temp; uint8_t htu_delay_humidity = 50; uint8_t htu_valid = 0; float htu_temperature = 0; float htu_humidity = 0; char htu_types[7]; uint8_t HtuCheckCrc8(uint16_t data) { for (uint32_t bit = 0; bit < 16; bit++) { if (data & 0x8000) { data = (data << 1) ^ HTU21_CRC8_POLYNOM; } else { data <<= 1; } } return data >>= 8; } uint8_t HtuReadDeviceId(void) { uint16_t deviceID = 0; uint8_t checksum = 0; Wire.beginTransmission(HTU21_ADDR); Wire.write(HTU21_SERIAL2_READ1); Wire.write(HTU21_SERIAL2_READ2); Wire.endTransmission(); Wire.requestFrom(HTU21_ADDR, 3); deviceID = Wire.read() << 8; deviceID |= Wire.read(); checksum = Wire.read(); if (HtuCheckCrc8(deviceID) == checksum) { deviceID = deviceID >> 8; } else { deviceID = 0; } return (uint8_t)deviceID; } void HtuSetResolution(uint8_t resolution) { uint8_t current = I2cRead8(HTU21_ADDR, HTU21_READREG); current &= 0x7E; current |= resolution; I2cWrite8(HTU21_ADDR, HTU21_WRITEREG, current); } void HtuReset(void) { Wire.beginTransmission(HTU21_ADDR); Wire.write(HTU21_RESET); Wire.endTransmission(); delay(15); } void HtuHeater(uint8_t heater) { uint8_t current = I2cRead8(HTU21_ADDR, HTU21_READREG); switch(heater) { case HTU21_HEATER_ON : current |= heater; break; case HTU21_HEATER_OFF : current &= heater; break; default : current &= heater; break; } I2cWrite8(HTU21_ADDR, HTU21_WRITEREG, current); } void HtuInit(void) { HtuReset(); HtuHeater(HTU21_HEATER_OFF); HtuSetResolution(HTU21_RES_RH12_T14); } bool HtuRead(void) { uint8_t checksum = 0; uint16_t sensorval = 0; if (htu_valid) { htu_valid--; } Wire.beginTransmission(HTU21_ADDR); Wire.write(HTU21_READTEMP); if (Wire.endTransmission() != 0) { return false; } delay(htu_delay_temp); Wire.requestFrom(HTU21_ADDR, 3); if (3 == Wire.available()) { sensorval = Wire.read() << 8; sensorval |= Wire.read(); checksum = Wire.read(); } if (HtuCheckCrc8(sensorval) != checksum) { return false; } htu_temperature = ConvertTemp(0.002681 * (float)sensorval - 46.85); Wire.beginTransmission(HTU21_ADDR); Wire.write(HTU21_READHUM); if (Wire.endTransmission() != 0) { return false; } delay(htu_delay_humidity); Wire.requestFrom(HTU21_ADDR, 3); if (3 <= Wire.available()) { sensorval = Wire.read() << 8; sensorval |= Wire.read(); checksum = Wire.read(); } if (HtuCheckCrc8(sensorval) != checksum) { return false; } sensorval ^= 0x02; htu_humidity = 0.001907 * (float)sensorval - 6; if (htu_humidity > 100) { htu_humidity = 100.0; } if (htu_humidity < 0) { htu_humidity = 0.01; } if ((0.00 == htu_humidity) && (0.00 == htu_temperature)) { htu_humidity = 0.0; } if ((htu_temperature > 0.00) && (htu_temperature < 80.00)) { htu_humidity = (-0.15) * (25 - htu_temperature) + htu_humidity; } ConvertHumidity(htu_humidity); htu_valid = SENSOR_MAX_MISS; return true; } void HtuDetect(void) { htu_address = HTU21_ADDR; if (I2cActive(htu_address)) { return; } htu_type = HtuReadDeviceId(); if (htu_type) { uint8_t index = 0; HtuInit(); switch (htu_type) { case HTU21_CHIPID: htu_delay_temp = 50; htu_delay_humidity = 16; break; case SI7021_CHIPID: index++; case SI7020_CHIPID: index++; case SI7013_CHIPID: index++; htu_delay_temp = 12; htu_delay_humidity = 23; break; default: index = 4; htu_delay_temp = 50; htu_delay_humidity = 23; } GetTextIndexed(htu_types, sizeof(htu_types), index, kHtuTypes); I2cSetActiveFound(htu_address, htu_types); } } void HtuEverySecond(void) { if (uptime &1) { if (!HtuRead()) { AddLogMissed(htu_types, htu_valid); } } } void HtuShow(bool json) { if (htu_valid) { char temperature[33]; dtostrfd(htu_temperature, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(htu_humidity, Settings.flag2.humidity_resolution, humidity); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, htu_types, temperature, humidity); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if (0 == tele_period) { KnxSensor(KNX_TEMPERATURE, htu_temperature); KnxSensor(KNX_HUMIDITY, htu_humidity); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, htu_types, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, htu_types, humidity); #endif } } } bool Xsns08(uint8_t function) { if (!I2cEnabled(XI2C_09)) { return false; } bool result = false; if (FUNC_INIT == function) { HtuDetect(); } else if (htu_type) { switch (function) { case FUNC_EVERY_SECOND: HtuEverySecond(); break; case FUNC_JSON_APPEND: HtuShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: HtuShow(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino" #ifdef USE_I2C #ifdef USE_BMP # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino" #define XSNS_09 9 #define XI2C_10 10 #define BMP_ADDR1 0x76 #define BMP_ADDR2 0x77 #define BMP180_CHIPID 0x55 #define BMP280_CHIPID 0x58 #define BME280_CHIPID 0x60 #define BME680_CHIPID 0x61 #define BMP_REGISTER_CHIPID 0xD0 #define BMP_REGISTER_RESET 0xE0 #define BMP_CMND_RESET 0xB6 #define BMP_MAX_SENSORS 2 const char kBmpTypes[] PROGMEM = "BMP180|BMP280|BME280|BME680"; typedef struct { uint8_t bmp_address; char bmp_name[7]; uint8_t bmp_type; uint8_t bmp_model; #ifdef USE_BME680 uint8_t bme680_state; float bmp_gas_resistance; #endif float bmp_temperature; float bmp_pressure; float bmp_humidity; } bmp_sensors_t; uint8_t bmp_addresses[] = { BMP_ADDR1, BMP_ADDR2 }; uint8_t bmp_count = 0; uint8_t bmp_once = 1; bmp_sensors_t *bmp_sensors = nullptr; #define BMP180_REG_CONTROL 0xF4 #define BMP180_REG_RESULT 0xF6 #define BMP180_TEMPERATURE 0x2E #define BMP180_PRESSURE3 0xF4 #define BMP180_AC1 0xAA #define BMP180_AC2 0xAC #define BMP180_AC3 0xAE #define BMP180_AC4 0xB0 #define BMP180_AC5 0xB2 #define BMP180_AC6 0xB4 #define BMP180_VB1 0xB6 #define BMP180_VB2 0xB8 #define BMP180_MB 0xBA #define BMP180_MC 0xBC #define BMP180_MD 0xBE #define BMP180_OSS 3 typedef struct { int16_t cal_ac1; int16_t cal_ac2; int16_t cal_ac3; int16_t cal_b1; int16_t cal_b2; int16_t cal_mc; int16_t cal_md; uint16_t cal_ac4; uint16_t cal_ac5; uint16_t cal_ac6; } bmp180_cal_data_t; bmp180_cal_data_t *bmp180_cal_data = nullptr; bool Bmp180Calibration(uint8_t bmp_idx) { if (!bmp180_cal_data) { bmp180_cal_data = (bmp180_cal_data_t*)malloc(BMP_MAX_SENSORS * sizeof(bmp180_cal_data_t)); } if (!bmp180_cal_data) { return false; } bmp180_cal_data[bmp_idx].cal_ac1 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC1); bmp180_cal_data[bmp_idx].cal_ac2 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC2); bmp180_cal_data[bmp_idx].cal_ac3 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC3); bmp180_cal_data[bmp_idx].cal_ac4 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC4); bmp180_cal_data[bmp_idx].cal_ac5 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC5); bmp180_cal_data[bmp_idx].cal_ac6 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC6); bmp180_cal_data[bmp_idx].cal_b1 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_VB1); bmp180_cal_data[bmp_idx].cal_b2 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_VB2); bmp180_cal_data[bmp_idx].cal_mc = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_MC); bmp180_cal_data[bmp_idx].cal_md = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_MD); if (!bmp180_cal_data[bmp_idx].cal_ac1 | !bmp180_cal_data[bmp_idx].cal_ac2 | !bmp180_cal_data[bmp_idx].cal_ac3 | !bmp180_cal_data[bmp_idx].cal_ac4 | !bmp180_cal_data[bmp_idx].cal_ac5 | !bmp180_cal_data[bmp_idx].cal_ac6 | !bmp180_cal_data[bmp_idx].cal_b1 | !bmp180_cal_data[bmp_idx].cal_b2 | !bmp180_cal_data[bmp_idx].cal_mc | !bmp180_cal_data[bmp_idx].cal_md) { return false; } if ((bmp180_cal_data[bmp_idx].cal_ac1 == (int16_t)0xFFFF) | (bmp180_cal_data[bmp_idx].cal_ac2 == (int16_t)0xFFFF) | (bmp180_cal_data[bmp_idx].cal_ac3 == (int16_t)0xFFFF) | (bmp180_cal_data[bmp_idx].cal_ac4 == 0xFFFF) | (bmp180_cal_data[bmp_idx].cal_ac5 == 0xFFFF) | (bmp180_cal_data[bmp_idx].cal_ac6 == 0xFFFF) | (bmp180_cal_data[bmp_idx].cal_b1 == (int16_t)0xFFFF) | (bmp180_cal_data[bmp_idx].cal_b2 == (int16_t)0xFFFF) | (bmp180_cal_data[bmp_idx].cal_mc == (int16_t)0xFFFF) | (bmp180_cal_data[bmp_idx].cal_md == (int16_t)0xFFFF)) { return false; } return true; } void Bmp180Read(uint8_t bmp_idx) { if (!bmp180_cal_data) { return; } I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_CONTROL, BMP180_TEMPERATURE); delay(5); int ut = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_RESULT); int32_t xt1 = (ut - (int32_t)bmp180_cal_data[bmp_idx].cal_ac6) * ((int32_t)bmp180_cal_data[bmp_idx].cal_ac5) >> 15; int32_t xt2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_mc << 11) / (xt1 + (int32_t)bmp180_cal_data[bmp_idx].cal_md); int32_t bmp180_b5 = xt1 + xt2; bmp_sensors[bmp_idx].bmp_temperature = ((bmp180_b5 + 8) >> 4) / 10.0; I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_CONTROL, BMP180_PRESSURE3); delay(2 + (4 << BMP180_OSS)); uint32_t up = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_RESULT); up >>= (8 - BMP180_OSS); int32_t b6 = bmp180_b5 - 4000; int32_t x1 = ((int32_t)bmp180_cal_data[bmp_idx].cal_b2 * ((b6 * b6) >> 12)) >> 11; int32_t x2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_ac2 * b6) >> 11; int32_t x3 = x1 + x2; int32_t b3 = ((((int32_t)bmp180_cal_data[bmp_idx].cal_ac1 * 4 + x3) << BMP180_OSS) + 2) >> 2; x1 = ((int32_t)bmp180_cal_data[bmp_idx].cal_ac3 * b6) >> 13; x2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_b1 * ((b6 * b6) >> 12)) >> 16; x3 = ((x1 + x2) + 2) >> 2; uint32_t b4 = ((uint32_t)bmp180_cal_data[bmp_idx].cal_ac4 * (uint32_t)(x3 + 32768)) >> 15; uint32_t b7 = ((uint32_t)up - b3) * (uint32_t)(50000UL >> BMP180_OSS); int32_t p; if (b7 < 0x80000000) { p = (b7 * 2) / b4; } else { p = (b7 / b4) * 2; } x1 = (p >> 8) * (p >> 8); x1 = (x1 * 3038) >> 16; x2 = (-7357 * p) >> 16; p += ((x1 + x2 + (int32_t)3791) >> 4); bmp_sensors[bmp_idx].bmp_pressure = (float)p / 100.0; } #define BME280_REGISTER_CONTROLHUMID 0xF2 #define BME280_REGISTER_CONTROL 0xF4 #define BME280_REGISTER_CONFIG 0xF5 #define BME280_REGISTER_PRESSUREDATA 0xF7 #define BME280_REGISTER_TEMPDATA 0xFA #define BME280_REGISTER_HUMIDDATA 0xFD #define BME280_REGISTER_DIG_T1 0x88 #define BME280_REGISTER_DIG_T2 0x8A #define BME280_REGISTER_DIG_T3 0x8C #define BME280_REGISTER_DIG_P1 0x8E #define BME280_REGISTER_DIG_P2 0x90 #define BME280_REGISTER_DIG_P3 0x92 #define BME280_REGISTER_DIG_P4 0x94 #define BME280_REGISTER_DIG_P5 0x96 #define BME280_REGISTER_DIG_P6 0x98 #define BME280_REGISTER_DIG_P7 0x9A #define BME280_REGISTER_DIG_P8 0x9C #define BME280_REGISTER_DIG_P9 0x9E #define BME280_REGISTER_DIG_H1 0xA1 #define BME280_REGISTER_DIG_H2 0xE1 #define BME280_REGISTER_DIG_H3 0xE3 #define BME280_REGISTER_DIG_H4 0xE4 #define BME280_REGISTER_DIG_H5 0xE5 #define BME280_REGISTER_DIG_H6 0xE7 typedef struct { uint16_t dig_T1; int16_t dig_T2; int16_t dig_T3; uint16_t dig_P1; int16_t dig_P2; int16_t dig_P3; int16_t dig_P4; int16_t dig_P5; int16_t dig_P6; int16_t dig_P7; int16_t dig_P8; int16_t dig_P9; int16_t dig_H2; int16_t dig_H4; int16_t dig_H5; uint8_t dig_H1; uint8_t dig_H3; int8_t dig_H6; } Bme280CalibrationData_t; Bme280CalibrationData_t *Bme280CalibrationData = nullptr; bool Bmx280Calibrate(uint8_t bmp_idx) { if (!Bme280CalibrationData) { Bme280CalibrationData = (Bme280CalibrationData_t*)malloc(BMP_MAX_SENSORS * sizeof(Bme280CalibrationData_t)); } if (!Bme280CalibrationData) { return false; } Bme280CalibrationData[bmp_idx].dig_T1 = I2cRead16LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T1); Bme280CalibrationData[bmp_idx].dig_T2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T2); Bme280CalibrationData[bmp_idx].dig_T3 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T3); Bme280CalibrationData[bmp_idx].dig_P1 = I2cRead16LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P1); Bme280CalibrationData[bmp_idx].dig_P2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P2); Bme280CalibrationData[bmp_idx].dig_P3 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P3); Bme280CalibrationData[bmp_idx].dig_P4 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P4); Bme280CalibrationData[bmp_idx].dig_P5 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P5); Bme280CalibrationData[bmp_idx].dig_P6 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P6); Bme280CalibrationData[bmp_idx].dig_P7 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P7); Bme280CalibrationData[bmp_idx].dig_P8 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P8); Bme280CalibrationData[bmp_idx].dig_P9 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P9); if (BME280_CHIPID == bmp_sensors[bmp_idx].bmp_type) { Bme280CalibrationData[bmp_idx].dig_H1 = I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H1); Bme280CalibrationData[bmp_idx].dig_H2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H2); Bme280CalibrationData[bmp_idx].dig_H3 = I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H3); Bme280CalibrationData[bmp_idx].dig_H4 = (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H4) << 4) | (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H4 + 1) & 0xF); Bme280CalibrationData[bmp_idx].dig_H5 = (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H5 + 1) << 4) | (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H5) >> 4); Bme280CalibrationData[bmp_idx].dig_H6 = (int8_t)I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H6); I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0x00); I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROLHUMID, 0x01); I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONFIG, 0xA0); I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0x27); } else { I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0xB7); } return true; } void Bme280Read(uint8_t bmp_idx) { if (!Bme280CalibrationData) { return; } int32_t adc_T = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_TEMPDATA); adc_T >>= 4; int32_t vart1 = ((((adc_T >> 3) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1 << 1))) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_T2)) >> 11; int32_t vart2 = (((((adc_T >> 4) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1)) * ((adc_T >> 4) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1))) >> 12) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_T3)) >> 14; int32_t t_fine = vart1 + vart2; float T = (t_fine * 5 + 128) >> 8; bmp_sensors[bmp_idx].bmp_temperature = T / 100.0; int32_t adc_P = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_PRESSUREDATA); adc_P >>= 4; int64_t var1 = ((int64_t)t_fine) - 128000; int64_t var2 = var1 * var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P6; var2 = var2 + ((var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P5) << 17); var2 = var2 + (((int64_t)Bme280CalibrationData[bmp_idx].dig_P4) << 35); var1 = ((var1 * var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P3) >> 8) + ((var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P2) << 12); var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)Bme280CalibrationData[bmp_idx].dig_P1) >> 33; if (0 == var1) { return; } int64_t p = 1048576 - adc_P; p = (((p << 31) - var2) * 3125) / var1; var1 = (((int64_t)Bme280CalibrationData[bmp_idx].dig_P9) * (p >> 13) * (p >> 13)) >> 25; var2 = (((int64_t)Bme280CalibrationData[bmp_idx].dig_P8) * p) >> 19; p = ((p + var1 + var2) >> 8) + (((int64_t)Bme280CalibrationData[bmp_idx].dig_P7) << 4); bmp_sensors[bmp_idx].bmp_pressure = (float)p / 25600.0; if (BMP280_CHIPID == bmp_sensors[bmp_idx].bmp_type) { return; } int32_t adc_H = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_HUMIDDATA); int32_t v_x1_u32r = (t_fine - ((int32_t)76800)); v_x1_u32r = (((((adc_H << 14) - (((int32_t)Bme280CalibrationData[bmp_idx].dig_H4) << 20) - (((int32_t)Bme280CalibrationData[bmp_idx].dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H6)) >> 10) * (((v_x1_u32r * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H2) + 8192) >> 14)); v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H1)) >> 4)); v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r; v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r; float h = (v_x1_u32r >> 12); bmp_sensors[bmp_idx].bmp_humidity = h / 1024.0; } #ifdef USE_BME680 #include struct bme680_dev *gas_sensor = nullptr; static void BmeDelayMs(uint32_t ms) { delay(ms); } bool Bme680Init(uint8_t bmp_idx) { if (!gas_sensor) { gas_sensor = (bme680_dev*)malloc(BMP_MAX_SENSORS * sizeof(bme680_dev)); } if (!gas_sensor) { return false; } gas_sensor[bmp_idx].dev_id = bmp_sensors[bmp_idx].bmp_address; gas_sensor[bmp_idx].intf = BME680_I2C_INTF; gas_sensor[bmp_idx].read = &I2cReadBuffer; gas_sensor[bmp_idx].write = &I2cWriteBuffer; gas_sensor[bmp_idx].delay_ms = BmeDelayMs; gas_sensor[bmp_idx].amb_temp = 25; int8_t rslt = BME680_OK; rslt = bme680_init(&gas_sensor[bmp_idx]); if (rslt != BME680_OK) { return false; } gas_sensor[bmp_idx].tph_sett.os_hum = BME680_OS_2X; gas_sensor[bmp_idx].tph_sett.os_pres = BME680_OS_4X; gas_sensor[bmp_idx].tph_sett.os_temp = BME680_OS_8X; gas_sensor[bmp_idx].tph_sett.filter = BME680_FILTER_SIZE_3; gas_sensor[bmp_idx].gas_sett.run_gas = BME680_ENABLE_GAS_MEAS; gas_sensor[bmp_idx].gas_sett.heatr_temp = 320; gas_sensor[bmp_idx].gas_sett.heatr_dur = 150; gas_sensor[bmp_idx].power_mode = BME680_FORCED_MODE; uint8_t set_required_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL; rslt = bme680_set_sensor_settings(set_required_settings,&gas_sensor[bmp_idx]); if (rslt != BME680_OK) { return false; } bmp_sensors[bmp_idx].bme680_state = 0; return true; } void Bme680Read(uint8_t bmp_idx) { if (!gas_sensor) { return; } int8_t rslt = BME680_OK; if (BME680_CHIPID == bmp_sensors[bmp_idx].bmp_type) { if (0 == bmp_sensors[bmp_idx].bme680_state) { rslt = bme680_set_sensor_mode(&gas_sensor[bmp_idx]); if (rslt != BME680_OK) { return; } bmp_sensors[bmp_idx].bme680_state = 1; } else { bmp_sensors[bmp_idx].bme680_state = 0; struct bme680_field_data data; rslt = bme680_get_sensor_data(&data, &gas_sensor[bmp_idx]); if (rslt != BME680_OK) { return; } bmp_sensors[bmp_idx].bmp_temperature = data.temperature / 100.0; bmp_sensors[bmp_idx].bmp_humidity = data.humidity / 1000.0; bmp_sensors[bmp_idx].bmp_pressure = data.pressure / 100.0; if (data.status & BME680_GASM_VALID_MSK) { bmp_sensors[bmp_idx].bmp_gas_resistance = data.gas_resistance / 1000.0; } else { bmp_sensors[bmp_idx].bmp_gas_resistance = 0; } } } return; } #endif void BmpDetect(void) { int bmp_sensor_size = BMP_MAX_SENSORS * sizeof(bmp_sensors_t); if (!bmp_sensors) { bmp_sensors = (bmp_sensors_t*)malloc(bmp_sensor_size); } if (!bmp_sensors) { return; } memset(bmp_sensors, 0, bmp_sensor_size); for (uint32_t i = 0; i < BMP_MAX_SENSORS; i++) { if (I2cActive(bmp_addresses[i])) { continue; } uint8_t bmp_type = I2cRead8(bmp_addresses[i], BMP_REGISTER_CHIPID); if (bmp_type) { bmp_sensors[bmp_count].bmp_address = bmp_addresses[i]; bmp_sensors[bmp_count].bmp_type = bmp_type; bmp_sensors[bmp_count].bmp_model = 0; bool success = false; switch (bmp_type) { case BMP180_CHIPID: success = Bmp180Calibration(bmp_count); break; case BME280_CHIPID: bmp_sensors[bmp_count].bmp_model++; case BMP280_CHIPID: bmp_sensors[bmp_count].bmp_model++; success = Bmx280Calibrate(bmp_count); break; #ifdef USE_BME680 case BME680_CHIPID: bmp_sensors[bmp_count].bmp_model = 3; success = Bme680Init(bmp_count); break; #endif } if (success) { GetTextIndexed(bmp_sensors[bmp_count].bmp_name, sizeof(bmp_sensors[bmp_count].bmp_name), bmp_sensors[bmp_count].bmp_model, kBmpTypes); I2cSetActiveFound(bmp_sensors[bmp_count].bmp_address, bmp_sensors[bmp_count].bmp_name); bmp_count++; } } } } void BmpRead(void) { for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) { switch (bmp_sensors[bmp_idx].bmp_type) { case BMP180_CHIPID: Bmp180Read(bmp_idx); break; case BMP280_CHIPID: case BME280_CHIPID: Bme280Read(bmp_idx); break; #ifdef USE_BME680 case BME680_CHIPID: Bme680Read(bmp_idx); break; #endif } } ConvertTemp(bmp_sensors[0].bmp_temperature); ConvertHumidity(bmp_sensors[0].bmp_humidity); } void BmpShow(bool json) { for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) { if (bmp_sensors[bmp_idx].bmp_type) { float bmp_sealevel = 0.0; if (bmp_sensors[bmp_idx].bmp_pressure != 0.0) { bmp_sealevel = (bmp_sensors[bmp_idx].bmp_pressure / FastPrecisePow(1.0 - ((float)Settings.altitude / 44330.0), 5.255)) - 21.6; bmp_sealevel = ConvertPressure(bmp_sealevel); } float bmp_temperature = ConvertTemp(bmp_sensors[bmp_idx].bmp_temperature); float bmp_pressure = ConvertPressure(bmp_sensors[bmp_idx].bmp_pressure); char name[10]; strlcpy(name, bmp_sensors[bmp_idx].bmp_name, sizeof(name)); if (bmp_count > 1) { snprintf_P(name, sizeof(name), PSTR("%s%c%02X"), name, IndexSeparator(), bmp_sensors[bmp_idx].bmp_address); } char temperature[33]; dtostrfd(bmp_temperature, Settings.flag2.temperature_resolution, temperature); char pressure[33]; dtostrfd(bmp_pressure, Settings.flag2.pressure_resolution, pressure); char sea_pressure[33]; dtostrfd(bmp_sealevel, Settings.flag2.pressure_resolution, sea_pressure); char humidity[33]; dtostrfd(bmp_sensors[bmp_idx].bmp_humidity, Settings.flag2.humidity_resolution, humidity); #ifdef USE_BME680 char gas_resistance[33]; dtostrfd(bmp_sensors[bmp_idx].bmp_gas_resistance, 2, gas_resistance); #endif if (json) { char json_humidity[40]; snprintf_P(json_humidity, sizeof(json_humidity), PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity); char json_sealevel[40]; snprintf_P(json_sealevel, sizeof(json_sealevel), PSTR(",\"" D_JSON_PRESSUREATSEALEVEL "\":%s"), sea_pressure); #ifdef USE_BME680 char json_gas[40]; snprintf_P(json_gas, sizeof(json_gas), PSTR(",\"" D_JSON_GAS "\":%s"), gas_resistance); ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s%s}"), name, temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : "", (bmp_sensors[bmp_idx].bmp_model >= 3) ? json_gas : ""); #else ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s}"), name, temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : ""); #endif #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == bmp_idx)) { DomoticzTempHumPressureSensor(temperature, humidity, pressure); #ifdef USE_BME680 if (bmp_sensors[bmp_idx].bmp_model >= 3) { DomoticzSensor(DZ_AIRQUALITY, (uint32_t)bmp_sensors[bmp_idx].bmp_gas_resistance); } #endif } #endif #ifdef USE_KNX if (0 == tele_period) { KnxSensor(KNX_TEMPERATURE, bmp_temperature); KnxSensor(KNX_HUMIDITY, bmp_sensors[bmp_idx].bmp_humidity); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, name, temperature, TempUnit()); if (bmp_sensors[bmp_idx].bmp_model >= 2) { WSContentSend_PD(HTTP_SNS_HUM, name, humidity); } WSContentSend_PD(HTTP_SNS_PRESSURE, name, pressure, PressureUnit().c_str()); if (Settings.altitude != 0) { WSContentSend_PD(HTTP_SNS_SEAPRESSURE, name, sea_pressure, PressureUnit().c_str()); } #ifdef USE_BME680 if (bmp_sensors[bmp_idx].bmp_model >= 3) { WSContentSend_PD(PSTR("{s}%s " D_GAS "{m}%s " D_UNIT_KILOOHM "{e}"), name, gas_resistance); } #endif #endif } } } } #ifdef USE_DEEPSLEEP void BMP_EnterSleep(void) { for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) { switch (bmp_sensors[bmp_idx].bmp_type) { case BMP180_CHIPID: case BMP280_CHIPID: case BME280_CHIPID: I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP_REGISTER_RESET, BMP_CMND_RESET); break; default: break; } } } #endif bool Xsns09(uint8_t function) { if (!I2cEnabled(XI2C_10)) { return false; } bool result = false; if (FUNC_INIT == function) { BmpDetect(); } else if (bmp_count) { switch (function) { case FUNC_EVERY_SECOND: BmpRead(); break; case FUNC_JSON_APPEND: BmpShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: BmpShow(0); break; #endif #ifdef USE_DEEPSLEEP case FUNC_SAVE_BEFORE_RESTART: BMP_EnterSleep(); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_10_bh1750.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_10_bh1750.ino" #ifdef USE_I2C #ifdef USE_BH1750 #define XSNS_10 10 #define XI2C_11 11 #define BH1750_ADDR1 0x23 #define BH1750_ADDR2 0x5C #define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 uint8_t bh1750_address; uint8_t bh1750_addresses[] = { BH1750_ADDR1, BH1750_ADDR2 }; uint8_t bh1750_type = 0; uint8_t bh1750_valid = 0; uint16_t bh1750_illuminance = 0; char bh1750_types[] = "BH1750"; bool Bh1750Read(void) { if (bh1750_valid) { bh1750_valid--; } if (2 != Wire.requestFrom(bh1750_address, (uint8_t)2)) { return false; } uint8_t msb = Wire.read(); uint8_t lsb = Wire.read(); bh1750_illuminance = ((msb << 8) | lsb) / 1.2; bh1750_valid = SENSOR_MAX_MISS; return true; } void Bh1750Detect(void) { for (uint32_t i = 0; i < sizeof(bh1750_addresses); i++) { bh1750_address = bh1750_addresses[i]; if (I2cActive(bh1750_address)) { continue; } Wire.beginTransmission(bh1750_address); Wire.write(BH1750_CONTINUOUS_HIGH_RES_MODE); if (!Wire.endTransmission()) { I2cSetActiveFound(bh1750_address, bh1750_types); bh1750_type = 1; break; } } } void Bh1750EverySecond(void) { if (!Bh1750Read()) { AddLogMissed(bh1750_types, bh1750_valid); } } void Bh1750Show(bool json) { if (bh1750_valid) { if (json) { ResponseAppend_P(JSON_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, bh1750_illuminance); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance); #endif } } } bool Xsns10(uint8_t function) { if (!I2cEnabled(XI2C_11)) { return false; } bool result = false; if (FUNC_INIT == function) { Bh1750Detect(); } else if (bh1750_type) { switch (function) { case FUNC_EVERY_SECOND: Bh1750EverySecond(); break; case FUNC_JSON_APPEND: Bh1750Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Bh1750Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_11_veml6070.ino" # 89 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_11_veml6070.ino" #ifdef USE_I2C #ifdef USE_VEML6070 #define XSNS_11 11 #define XI2C_12 12 #define VEML6070_ADDR_H 0x39 #define VEML6070_ADDR_L 0x38 #define VEML6070_INTEGRATION_TIME 3 #define VEML6070_ENABLE 1 #define VEML6070_DISABLE 0 #define VEML6070_RSET_DEFAULT 270000 #define VEML6070_UV_MAX_INDEX 15 #define VEML6070_UV_MAX_DEFAULT 11 #define VEML6070_POWER_COEFFCIENT 0.025 #define VEML6070_TABLE_COEFFCIENT 32.86270591 const char kVemlTypes[] PROGMEM = "VEML6070"; double uv_risk_map[VEML6070_UV_MAX_INDEX] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; double uvrisk = 0; double uvpower = 0; uint16_t uvlevel = 0; uint8_t veml6070_addr_low = VEML6070_ADDR_L; uint8_t veml6070_addr_high = VEML6070_ADDR_H; uint8_t itime = VEML6070_INTEGRATION_TIME; uint8_t veml6070_type = 0; char veml6070_name[9]; char str_uvrisk_text[10]; void Veml6070Detect(void) { if (I2cActive(VEML6070_ADDR_L)) { return; } Wire.beginTransmission(VEML6070_ADDR_L); Wire.write((itime << 2) | 0x02); uint8_t status = Wire.endTransmission(); if (!status) { veml6070_type = 1; Veml6070UvTableInit(); uint8_t veml_model = 0; GetTextIndexed(veml6070_name, sizeof(veml6070_name), veml_model, kVemlTypes); I2cSetActiveFound(VEML6070_ADDR_L, veml6070_name); } } void Veml6070UvTableInit(void) { for (uint32_t i = 0; i < VEML6070_UV_MAX_INDEX; i++) { #ifdef USE_VEML6070_RSET if ( (USE_VEML6070_RSET >= 220000) && (USE_VEML6070_RSET <= 1000000) ) { uv_risk_map[i] = ( (USE_VEML6070_RSET / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1); } else { uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 resistor error %d"), USE_VEML6070_RSET); } #else uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 resistor default used %d"), VEML6070_RSET_DEFAULT); #endif } } void Veml6070EverySecond(void) { Veml6070ModeCmd(1); uvlevel = Veml6070ReadUv(); uvrisk = Veml6070UvRiskLevel(uvlevel); uvpower = Veml6070UvPower(uvrisk); Veml6070ModeCmd(0); } void Veml6070ModeCmd(bool mode_cmd) { Wire.beginTransmission(VEML6070_ADDR_L); Wire.write((mode_cmd << 0) | 0x02 | (itime << 2)); uint8_t status = Wire.endTransmission(); if (!status) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 mode_cmd")); } } uint16_t Veml6070ReadUv(void) { uint16_t uv_raw = 0; if (Wire.requestFrom(VEML6070_ADDR_H, 1) != 1) { return -1; } uv_raw = Wire.read(); uv_raw <<= 8; if (Wire.requestFrom(VEML6070_ADDR_L, 1) != 1) { return -1; } uv_raw |= Wire.read(); return uv_raw; } double Veml6070UvRiskLevel(uint16_t uv_level) { double risk = 0; if (uv_level < uv_risk_map[VEML6070_UV_MAX_INDEX-1]) { risk = (double)uv_level / uv_risk_map[0]; if ( (risk >= 0) && (risk <= 2.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_1); } else if ( (risk >= 3.0) && (risk <= 5.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_2); } else if ( (risk >= 6.0) && (risk <= 7.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_3); } else if ( (risk >= 8.0) && (risk <= 10.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_4); } else if ( (risk >= 11.0) && (risk <= 12.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_5); } else if ( (risk >= 13.0) && (risk <= 25.0) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_6); } else { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7); } return risk; } else { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7); return ( risk = 99 ); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 out of range %d"), risk); } } double Veml6070UvPower(double uvrisk) { double power = 0; return ( power = VEML6070_POWER_COEFFCIENT * uvrisk ); } #ifdef USE_WEBSERVER #ifdef USE_VEML6070_SHOW_RAW const char HTTP_SNS_UV_LEVEL[] PROGMEM = "{s}VEML6070 " D_UV_LEVEL "{m}%s " D_UNIT_INCREMENTS "{e}"; #endif const char HTTP_SNS_UV_INDEX[] PROGMEM = "{s}VEML6070 " D_UV_INDEX "{m}%s %s{e}"; const char HTTP_SNS_UV_POWER[] PROGMEM = "{s}VEML6070 " D_UV_POWER "{m}%s " D_UNIT_WATT_METER_QUADRAT "{e}"; #endif void Veml6070Show(bool json) { char str_uvlevel[33]; dtostrfd((double)uvlevel, 0, str_uvlevel); char str_uvrisk[33]; dtostrfd(uvrisk, 2, str_uvrisk); char str_uvpower[33]; dtostrfd(uvpower, 3, str_uvpower); if (json) { #ifdef USE_VEML6070_SHOW_RAW ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_UV_LEVEL "\":%s,\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"), veml6070_name, str_uvlevel, str_uvrisk, str_uvrisk_text, str_uvpower); #else ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"), veml6070_name, str_uvrisk, str_uvrisk_text, str_uvpower); #endif #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, uvlevel); } #endif #ifdef USE_WEBSERVER } else { #ifdef USE_VEML6070_SHOW_RAW WSContentSend_PD(HTTP_SNS_UV_LEVEL, str_uvlevel); #endif WSContentSend_PD(HTTP_SNS_UV_INDEX, str_uvrisk, str_uvrisk_text); WSContentSend_PD(HTTP_SNS_UV_POWER, str_uvpower); #endif } } bool Xsns11(uint8_t function) { if (!I2cEnabled(XI2C_12)) { return false; } bool result = false; if (FUNC_INIT == function) { Veml6070Detect(); } else if (veml6070_type) { switch (function) { case FUNC_EVERY_SECOND: Veml6070EverySecond(); break; case FUNC_JSON_APPEND: Veml6070Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Veml6070Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino" #ifdef USE_I2C #ifdef USE_ADS1115 # 43 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino" #define XSNS_12 12 #define XI2C_13 13 #define ADS1115_ADDRESS_ADDR_GND 0x48 #define ADS1115_ADDRESS_ADDR_VDD 0x49 #define ADS1115_ADDRESS_ADDR_SDA 0x4A #define ADS1115_ADDRESS_ADDR_SCL 0x4B #define ADS1115_CONVERSIONDELAY (8) #define ADS1115_REG_POINTER_MASK (0x03) #define ADS1115_REG_POINTER_CONVERT (0x00) #define ADS1115_REG_POINTER_CONFIG (0x01) #define ADS1115_REG_POINTER_LOWTHRESH (0x02) #define ADS1115_REG_POINTER_HITHRESH (0x03) #define ADS1115_REG_CONFIG_OS_MASK (0x8000) #define ADS1115_REG_CONFIG_OS_SINGLE (0x8000) #define ADS1115_REG_CONFIG_OS_BUSY (0x0000) #define ADS1115_REG_CONFIG_OS_NOTBUSY (0x8000) #define ADS1115_REG_CONFIG_MUX_MASK (0x7000) #define ADS1115_REG_CONFIG_MUX_DIFF_0_1 (0x0000) #define ADS1115_REG_CONFIG_MUX_DIFF_0_3 (0x1000) #define ADS1115_REG_CONFIG_MUX_DIFF_1_3 (0x2000) #define ADS1115_REG_CONFIG_MUX_DIFF_2_3 (0x3000) #define ADS1115_REG_CONFIG_MUX_SINGLE_0 (0x4000) #define ADS1115_REG_CONFIG_MUX_SINGLE_1 (0x5000) #define ADS1115_REG_CONFIG_MUX_SINGLE_2 (0x6000) #define ADS1115_REG_CONFIG_MUX_SINGLE_3 (0x7000) #define ADS1115_REG_CONFIG_PGA_MASK (0x0E00) #define ADS1115_REG_CONFIG_PGA_6_144V (0x0000) #define ADS1115_REG_CONFIG_PGA_4_096V (0x0200) #define ADS1115_REG_CONFIG_PGA_2_048V (0x0400) #define ADS1115_REG_CONFIG_PGA_1_024V (0x0600) #define ADS1115_REG_CONFIG_PGA_0_512V (0x0800) #define ADS1115_REG_CONFIG_PGA_0_256V (0x0A00) #define ADS1115_REG_CONFIG_MODE_MASK (0x0100) #define ADS1115_REG_CONFIG_MODE_CONTIN (0x0000) #define ADS1115_REG_CONFIG_MODE_SINGLE (0x0100) #define ADS1115_REG_CONFIG_DR_MASK (0x00E0) #define ADS1115_REG_CONFIG_DR_128SPS (0x0000) #define ADS1115_REG_CONFIG_DR_250SPS (0x0020) #define ADS1115_REG_CONFIG_DR_490SPS (0x0040) #define ADS1115_REG_CONFIG_DR_920SPS (0x0060) #define ADS1115_REG_CONFIG_DR_1600SPS (0x0080) #define ADS1115_REG_CONFIG_DR_2400SPS (0x00A0) #define ADS1115_REG_CONFIG_DR_3300SPS (0x00C0) #define ADS1115_REG_CONFIG_DR_6000SPS (0x00E0) #define ADS1115_REG_CONFIG_CMODE_MASK (0x0010) #define ADS1115_REG_CONFIG_CMODE_TRAD (0x0000) #define ADS1115_REG_CONFIG_CMODE_WINDOW (0x0010) #define ADS1115_REG_CONFIG_CPOL_MASK (0x0008) #define ADS1115_REG_CONFIG_CPOL_ACTVLOW (0x0000) #define ADS1115_REG_CONFIG_CPOL_ACTVHI (0x0008) #define ADS1115_REG_CONFIG_CLAT_MASK (0x0004) #define ADS1115_REG_CONFIG_CLAT_NONLAT (0x0000) #define ADS1115_REG_CONFIG_CLAT_LATCH (0x0004) #define ADS1115_REG_CONFIG_CQUE_MASK (0x0003) #define ADS1115_REG_CONFIG_CQUE_1CONV (0x0000) #define ADS1115_REG_CONFIG_CQUE_2CONV (0x0001) #define ADS1115_REG_CONFIG_CQUE_4CONV (0x0002) #define ADS1115_REG_CONFIG_CQUE_NONE (0x0003) struct ADS1115 { uint8_t count = 0; uint8_t address; uint8_t addresses[4] = { ADS1115_ADDRESS_ADDR_GND, ADS1115_ADDRESS_ADDR_VDD, ADS1115_ADDRESS_ADDR_SDA, ADS1115_ADDRESS_ADDR_SCL }; uint8_t found[4] = {false,false,false,false}; } Ads1115; void Ads1115StartComparator(uint8_t channel, uint16_t mode) { uint16_t config = mode | ADS1115_REG_CONFIG_CQUE_NONE | ADS1115_REG_CONFIG_CLAT_NONLAT | ADS1115_REG_CONFIG_PGA_6_144V | ADS1115_REG_CONFIG_CPOL_ACTVLOW | ADS1115_REG_CONFIG_CMODE_TRAD | ADS1115_REG_CONFIG_DR_6000SPS; config |= (ADS1115_REG_CONFIG_MUX_SINGLE_0 + (0x1000 * channel)); I2cWrite16(Ads1115.address, ADS1115_REG_POINTER_CONFIG, config); } int16_t Ads1115GetConversion(uint8_t channel) { Ads1115StartComparator(channel, ADS1115_REG_CONFIG_MODE_SINGLE); delay(ADS1115_CONVERSIONDELAY); I2cRead16(Ads1115.address, ADS1115_REG_POINTER_CONVERT); Ads1115StartComparator(channel, ADS1115_REG_CONFIG_MODE_CONTIN); delay(ADS1115_CONVERSIONDELAY); uint16_t res = I2cRead16(Ads1115.address, ADS1115_REG_POINTER_CONVERT); return (int16_t)res; } void Ads1115Detect(void) { for (uint32_t i = 0; i < sizeof(Ads1115.addresses); i++) { if (!Ads1115.found[i]) { Ads1115.address = Ads1115.addresses[i]; if (I2cActive(Ads1115.address)) { continue; } uint16_t buffer; if (I2cValidRead16(&buffer, Ads1115.address, ADS1115_REG_POINTER_CONVERT) && I2cValidRead16(&buffer, Ads1115.address, ADS1115_REG_POINTER_CONFIG)) { Ads1115StartComparator(i, ADS1115_REG_CONFIG_MODE_CONTIN); I2cSetActiveFound(Ads1115.address, "ADS1115"); Ads1115.found[i] = 1; Ads1115.count++; } } } } void Ads1115Show(bool json) { int16_t values[4]; for (uint32_t t = 0; t < sizeof(Ads1115.addresses); t++) { if (Ads1115.found[t]) { uint8_t old_address = Ads1115.address; Ads1115.address = Ads1115.addresses[t]; for (uint32_t i = 0; i < 4; i++) { values[i] = Ads1115GetConversion(i); } Ads1115.address = old_address; char label[15]; if (1 == Ads1115.count) { snprintf_P(label, sizeof(label), PSTR("ADS1115")); } else { snprintf_P(label, sizeof(label), PSTR("ADS1115%c%02x"), IndexSeparator(), Ads1115.addresses[t]); } if (json) { ResponseAppend_P(PSTR(",\"%s\":{"), label); for (uint32_t i = 0; i < 4; i++) { ResponseAppend_P(PSTR("%s\"A%d\":%d"), (0 == i) ? "" : ",", i, values[i]); } ResponseJsonEnd(); } #ifdef USE_WEBSERVER else { for (uint32_t i = 0; i < 4; i++) { WSContentSend_PD(HTTP_SNS_ANALOG, label, i, values[i]); } } #endif } } } bool Xsns12(uint8_t function) { if (!I2cEnabled(XI2C_13)) { return false; } bool result = false; if (FUNC_INIT == function) { Ads1115Detect(); } else if (Ads1115.count) { switch (function) { case FUNC_JSON_APPEND: Ads1115Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Ads1115Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino" #ifdef USE_I2C #ifdef USE_INA219 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino" #define XSNS_13 13 #define XI2C_14 14 #define INA219_ADDRESS1 (0x40) #define INA219_ADDRESS2 (0x41) #define INA219_ADDRESS3 (0x44) #define INA219_ADDRESS4 (0x45) #define INA219_READ (0x01) #define INA219_REG_CONFIG (0x00) #define INA219_CONFIG_RESET (0x8000) #define INA219_CONFIG_BVOLTAGERANGE_MASK (0x2000) #define INA219_CONFIG_BVOLTAGERANGE_16V (0x0000) #define INA219_CONFIG_BVOLTAGERANGE_32V (0x2000) #define INA219_CONFIG_GAIN_MASK (0x1800) #define INA219_CONFIG_GAIN_1_40MV (0x0000) #define INA219_CONFIG_GAIN_2_80MV (0x0800) #define INA219_CONFIG_GAIN_4_160MV (0x1000) #define INA219_CONFIG_GAIN_8_320MV (0x1800) #define INA219_CONFIG_BADCRES_MASK (0x0780) #define INA219_CONFIG_BADCRES_9BIT (0x0080) #define INA219_CONFIG_BADCRES_10BIT (0x0100) #define INA219_CONFIG_BADCRES_11BIT (0x0200) #define INA219_CONFIG_BADCRES_12BIT (0x0400) #define INA219_CONFIG_SADCRES_MASK (0x0078) #define INA219_CONFIG_SADCRES_9BIT_1S_84US (0x0000) #define INA219_CONFIG_SADCRES_10BIT_1S_148US (0x0008) #define INA219_CONFIG_SADCRES_11BIT_1S_276US (0x0010) #define INA219_CONFIG_SADCRES_12BIT_1S_532US (0x0018) #define INA219_CONFIG_SADCRES_12BIT_2S_1060US (0x0048) #define INA219_CONFIG_SADCRES_12BIT_4S_2130US (0x0050) #define INA219_CONFIG_SADCRES_12BIT_8S_4260US (0x0058) #define INA219_CONFIG_SADCRES_12BIT_16S_8510US (0x0060) #define INA219_CONFIG_SADCRES_12BIT_32S_17MS (0x0068) #define INA219_CONFIG_SADCRES_12BIT_64S_34MS (0x0070) #define INA219_CONFIG_SADCRES_12BIT_128S_69MS (0x0078) #define INA219_CONFIG_MODE_MASK (0x0007) #define INA219_CONFIG_MODE_POWERDOWN (0x0000) #define INA219_CONFIG_MODE_SVOLT_TRIGGERED (0x0001) #define INA219_CONFIG_MODE_BVOLT_TRIGGERED (0x0002) #define INA219_CONFIG_MODE_SANDBVOLT_TRIGGERED (0x0003) #define INA219_CONFIG_MODE_ADCOFF (0x0004) #define INA219_CONFIG_MODE_SVOLT_CONTINUOUS (0x0005) #define INA219_CONFIG_MODE_BVOLT_CONTINUOUS (0x0006) #define INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS (0x0007) #define INA219_REG_SHUNTVOLTAGE (0x01) #define INA219_REG_BUSVOLTAGE (0x02) #define INA219_REG_POWER (0x03) #define INA219_REG_CURRENT (0x04) #define INA219_REG_CALIBRATION (0x05) uint8_t ina219_type[4] = {0,0,0,0}; uint8_t ina219_addresses[] = { INA219_ADDRESS1, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; uint32_t ina219_cal_value = 0; uint32_t ina219_current_divider_ma = 0; uint8_t ina219_valid[4] = {0,0,0,0}; float ina219_voltage[4] = {0,0,0,0}; float ina219_current[4] = {0,0,0,0}; char ina219_types[] = "INA219"; uint8_t ina219_count = 0; bool Ina219SetCalibration(uint8_t mode, uint16_t addr) { uint16_t config = 0; switch (mode &3) { case 0: case 3: ina219_cal_value = 4096; ina219_current_divider_ma = 10; config = INA219_CONFIG_BVOLTAGERANGE_32V | INA219_CONFIG_GAIN_8_320MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; break; case 1: ina219_cal_value = 10240; ina219_current_divider_ma = 25; config |= INA219_CONFIG_BVOLTAGERANGE_32V | INA219_CONFIG_GAIN_8_320MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; break; case 2: ina219_cal_value = 8192; ina219_current_divider_ma = 20; config |= INA219_CONFIG_BVOLTAGERANGE_16V | INA219_CONFIG_GAIN_1_40MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; break; } bool success = I2cWrite16(addr, INA219_REG_CALIBRATION, ina219_cal_value); if (success) { I2cWrite16(addr, INA219_REG_CONFIG, config); } return success; } float Ina219GetShuntVoltage_mV(uint16_t addr) { int16_t value = I2cReadS16(addr, INA219_REG_SHUNTVOLTAGE); return value * 0.01; } float Ina219GetBusVoltage_V(uint16_t addr) { int16_t value = (int16_t)(((uint16_t)I2cReadS16(addr, INA219_REG_BUSVOLTAGE) >> 3) * 4); return value * 0.001; } float Ina219GetCurrent_mA(uint16_t addr) { I2cWrite16(addr, INA219_REG_CALIBRATION, ina219_cal_value); float value = I2cReadS16(addr, INA219_REG_CURRENT); value /= ina219_current_divider_ma; return value; } bool Ina219Read(void) { for (int i=0; i= 0) && (XdrvMailbox.payload <= 2)) { Settings.ina219_mode = XdrvMailbox.payload; restart_flag = 2; } Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_13, Settings.ina219_mode); return true; } void Ina219Detect(void) { for (uint32_t i = 0; i < sizeof(ina219_type); i++) { uint16_t addr = ina219_addresses[i]; if (I2cActive(addr)) { continue; } if (Ina219SetCalibration(Settings.ina219_mode, addr)) { I2cSetActiveFound(addr, ina219_types); ina219_type[i] = 1; ina219_count++; } } } void Ina219EverySecond(void) { Ina219Read(); } #ifdef USE_WEBSERVER const char HTTP_SNS_INA219_DATA[] PROGMEM = "{s}%s " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}" "{s}%s " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}" "{s}%s " D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"; #endif void Ina219Show(bool json) { int num_found=0; for (int i=0; i1) snprintf_P(name, sizeof(name), PSTR("%s%c%d"), ina219_types, IndexSeparator(), sensor_num); else snprintf_P(name, sizeof(name), PSTR("%s"), ina219_types); if (json) { ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%02x,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"), name, ina219_addresses[i], voltage, current, power); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_VOLTAGE, voltage); DomoticzSensor(DZ_CURRENT, current); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_INA219_DATA, name, voltage, name, current, name, power); #endif } } } bool Xsns13(uint8_t function) { if (!I2cEnabled(XI2C_14)) { return false; } bool result = false; if (FUNC_INIT == function) { Ina219Detect(); } else if (ina219_count) { switch (function) { case FUNC_COMMAND_SENSOR: if (XSNS_13 == XdrvMailbox.index) { result = Ina219CommandSensor(); } break; case FUNC_EVERY_SECOND: Ina219EverySecond(); break; case FUNC_JSON_APPEND: Ina219Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Ina219Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_14_sht3x.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_14_sht3x.ino" #ifdef USE_I2C #ifdef USE_SHT3X #define XSNS_14 14 #define XI2C_15 15 #define SHT3X_ADDR_GND 0x44 #define SHT3X_ADDR_VDD 0x45 #define SHTC3_ADDR 0x70 #define SHT3X_MAX_SENSORS 3 const char kShtTypes[] PROGMEM = "SHT3X|SHT3X|SHTC3"; uint8_t sht3x_addresses[] = { SHT3X_ADDR_GND, SHT3X_ADDR_VDD, SHTC3_ADDR }; uint8_t sht3x_count = 0; struct SHT3XSTRUCT { uint8_t address; char types[6]; } sht3x_sensors[SHT3X_MAX_SENSORS]; bool Sht3xRead(float &t, float &h, uint8_t sht3x_address) { unsigned int data[6]; t = NAN; h = NAN; Wire.beginTransmission(sht3x_address); if (SHTC3_ADDR == sht3x_address) { Wire.write(0x35); Wire.write(0x17); Wire.endTransmission(); Wire.beginTransmission(sht3x_address); Wire.write(0x78); Wire.write(0x66); } else { Wire.write(0x2C); Wire.write(0x06); } if (Wire.endTransmission() != 0) { return false; } delay(30); Wire.requestFrom(sht3x_address, (uint8_t)6); for (uint32_t i = 0; i < 6; i++) { data[i] = Wire.read(); }; t = ConvertTemp((float)((((data[0] << 8) | data[1]) * 175) / 65535.0) - 45); h = ConvertHumidity((float)((((data[3] << 8) | data[4]) * 100) / 65535.0)); return (!isnan(t) && !isnan(h) && (h != 0)); } void Sht3xDetect(void) { for (uint32_t i = 0; i < SHT3X_MAX_SENSORS; i++) { if (I2cActive(sht3x_addresses[i])) { continue; } float t; float h; if (Sht3xRead(t, h, sht3x_addresses[i])) { sht3x_sensors[sht3x_count].address = sht3x_addresses[i]; GetTextIndexed(sht3x_sensors[sht3x_count].types, sizeof(sht3x_sensors[sht3x_count].types), i, kShtTypes); I2cSetActiveFound(sht3x_sensors[sht3x_count].address, sht3x_sensors[sht3x_count].types); sht3x_count++; } } } void Sht3xShow(bool json) { for (uint32_t i = 0; i < sht3x_count; i++) { float t; float h; if (Sht3xRead(t, h, sht3x_sensors[i].address)) { char temperature[33]; dtostrfd(t, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(h, Settings.flag2.humidity_resolution, humidity); char types[11]; snprintf_P(types, sizeof(types), PSTR("%s%c0x%02X"), sht3x_sensors[i].types, IndexSeparator(), sht3x_sensors[i].address); if (json) { ResponseAppend_P(JSON_SNS_TEMPHUM, types, temperature, humidity); #ifdef USE_DOMOTICZ if ((0 == tele_period) && (0 == i)) { DomoticzTempHumSensor(temperature, humidity); } #endif #ifdef USE_KNX if (0 == tele_period) { KnxSensor(KNX_TEMPERATURE, t); KnxSensor(KNX_HUMIDITY, h); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, types, humidity); #endif } } } } bool Xsns14(uint8_t function) { if (!I2cEnabled(XI2C_15)) { return false; } bool result = false; if (FUNC_INIT == function) { Sht3xDetect(); } else if (sht3x_count) { switch (function) { case FUNC_JSON_APPEND: Sht3xShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Sht3xShow(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino" #ifdef USE_MHZ19 # 33 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino" #define XSNS_15 15 enum MhzFilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FILTER_FAST, MHZ19_FILTER_MEDIUM, MHZ19_FILTER_SLOW}; #define MHZ19_FILTER_OPTION MHZ19_FILTER_FAST # 58 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino" #include #ifndef CO2_LOW #define CO2_LOW 800 #endif #ifndef CO2_HIGH #define CO2_HIGH 1200 #endif #define MHZ19_READ_TIMEOUT 400 #define MHZ19_RETRY_COUNT 8 TasmotaSerial *MhzSerial; const char kMhzModels[] PROGMEM = "|B"; const char ABC_ENABLED[] = "ABC is Enabled"; const char ABC_DISABLED[] = "ABC is Disabled"; enum MhzCommands { MHZ_CMND_READPPM, MHZ_CMND_ABCENABLE, MHZ_CMND_ABCDISABLE, MHZ_CMND_ZEROPOINT, MHZ_CMND_RESET, MHZ_CMND_RANGE_1000, MHZ_CMND_RANGE_2000, MHZ_CMND_RANGE_3000, MHZ_CMND_RANGE_5000 }; const uint8_t kMhzCommands[][4] PROGMEM = { {0x86,0x00,0x00,0x00}, {0x79,0xA0,0x00,0x00}, {0x79,0x00,0x00,0x00}, {0x87,0x00,0x00,0x00}, {0x8D,0x00,0x00,0x00}, {0x99,0x00,0x03,0xE8}, {0x99,0x00,0x07,0xD0}, {0x99,0x00,0x0B,0xB8}, {0x99,0x00,0x13,0x88}}; uint8_t mhz_type = 1; uint16_t mhz_last_ppm = 0; uint8_t mhz_filter = MHZ19_FILTER_OPTION; bool mhz_abc_must_apply = false; float mhz_temperature = 0; uint8_t mhz_retry = MHZ19_RETRY_COUNT; uint8_t mhz_received = 0; uint8_t mhz_state = 0; uint8_t MhzCalculateChecksum(uint8_t *array) { uint8_t checksum = 0; for (uint32_t i = 1; i < 8; i++) { checksum += array[i]; } checksum = 255 - checksum; return (checksum +1); } size_t MhzSendCmd(uint8_t command_id) { uint8_t mhz_send[9] = { 0 }; mhz_send[0] = 0xFF; mhz_send[1] = 0x01; memcpy_P(&mhz_send[2], kMhzCommands[command_id], sizeof(uint16_t)); memcpy_P(&mhz_send[6], kMhzCommands[command_id] + sizeof(uint16_t), sizeof(uint16_t)); mhz_send[8] = MhzCalculateChecksum(mhz_send); return MhzSerial->write(mhz_send, sizeof(mhz_send)); } bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s) { if (1 == s) { return false; } if (mhz_last_ppm < 400 || mhz_last_ppm > 5000) { mhz_last_ppm = ppm; return true; } int32_t difference = ppm - mhz_last_ppm; if (s > 0 && s < 64 && mhz_filter != MHZ19_FILTER_OFF) { # 154 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino" difference *= s; difference /= 64; } if (MHZ19_FILTER_OFF == mhz_filter) { if (s != 0 && s != 64) { return false; } } else { difference >>= (mhz_filter -1); } mhz_last_ppm = static_cast(mhz_last_ppm + difference); return true; } void MhzEverySecond(void) { mhz_state++; if (8 == mhz_state) { mhz_state = 0; if (mhz_retry) { mhz_retry--; if (!mhz_retry) { mhz_last_ppm = 0; mhz_temperature = 0; } } MhzSerial->flush(); MhzSendCmd(MHZ_CMND_READPPM); mhz_received = 0; } if ((mhz_state > 2) && !mhz_received) { uint8_t mhz_response[9]; unsigned long start = millis(); uint8_t counter = 0; while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) { if (MhzSerial->available() > 0) { mhz_response[counter++] = MhzSerial->read(); } else { delay(5); } } AddLogBuffer(LOG_LEVEL_DEBUG_MORE, mhz_response, counter); if (counter < 9) { return; } uint8_t crc = MhzCalculateChecksum(mhz_response); if (mhz_response[8] != crc) { return; } if (0xFF != mhz_response[0] || 0x86 != mhz_response[1]) { return; } mhz_received = 1; uint16_t u = (mhz_response[6] << 8) | mhz_response[7]; if (15000 == u) { if (Settings.SensorBits1.mhz19b_abc_disable) { mhz_abc_must_apply = true; } } else { uint16_t ppm = (mhz_response[2] << 8) | mhz_response[3]; mhz_temperature = ConvertTemp((float)mhz_response[4] - 40); uint8_t s = mhz_response[5]; mhz_type = (s) ? 1 : 2; if (MhzCheckAndApplyFilter(ppm, s)) { mhz_retry = MHZ19_RETRY_COUNT; LightSetSignal(CO2_LOW, CO2_HIGH, mhz_last_ppm); if (0 == s || 64 == s) { if (mhz_abc_must_apply) { mhz_abc_must_apply = false; if (!Settings.SensorBits1.mhz19b_abc_disable) { MhzSendCmd(MHZ_CMND_ABCENABLE); } else { MhzSendCmd(MHZ_CMND_ABCDISABLE); } } } } } } } # 266 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino" #define D_JSON_RANGE_1000 "1000 ppm range" #define D_JSON_RANGE_2000 "2000 ppm range" #define D_JSON_RANGE_3000 "3000 ppm range" #define D_JSON_RANGE_5000 "5000 ppm range" bool MhzCommandSensor(void) { bool serviced = true; switch (XdrvMailbox.payload) { case 0: Settings.SensorBits1.mhz19b_abc_disable = true; MhzSendCmd(MHZ_CMND_ABCDISABLE); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED); break; case 1: Settings.SensorBits1.mhz19b_abc_disable = false; MhzSendCmd(MHZ_CMND_ABCENABLE); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED); break; case 2: MhzSendCmd(MHZ_CMND_ZEROPOINT); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_ZERO_POINT_CALIBRATION); break; case 9: MhzSendCmd(MHZ_CMND_RESET); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RESET); break; case 1000: MhzSendCmd(MHZ_CMND_RANGE_1000); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_1000); break; case 2000: MhzSendCmd(MHZ_CMND_RANGE_2000); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_2000); break; case 3000: MhzSendCmd(MHZ_CMND_RANGE_3000); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_3000); break; case 5000: MhzSendCmd(MHZ_CMND_RANGE_5000); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_5000); break; default: if (!Settings.SensorBits1.mhz19b_abc_disable) { Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED); } else { Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED); } } return serviced; } void MhzInit(void) { mhz_type = 0; if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) { MhzSerial = new TasmotaSerial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD], 1); if (MhzSerial->begin(9600)) { if (MhzSerial->hardwareSerial()) { ClaimSerial(); } mhz_type = 1; } } } void MhzShow(bool json) { char types[7] = "MHZ19B"; char temperature[33]; dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature); char model[3]; GetTextIndexed(model, sizeof(model), mhz_type -1, kMhzModels); if (json) { ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s}"), types, model, mhz_last_ppm, temperature); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm); DomoticzSensor(DZ_TEMP, temperature); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_CO2, types, mhz_last_ppm); WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit()); #endif } } bool Xsns15(uint8_t function) { bool result = false; if (mhz_type) { switch (function) { case FUNC_INIT: MhzInit(); break; case FUNC_EVERY_SECOND: MhzEverySecond(); break; case FUNC_COMMAND_SENSOR: if (XSNS_15 == XdrvMailbox.index) { result = MhzCommandSensor(); } break; case FUNC_JSON_APPEND: MhzShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: MhzShow(0); break; #endif } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino" #ifdef USE_I2C #ifdef USE_TSL2561 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino" #define XSNS_16 16 #define XI2C_16 16 #include Tsl2561 Tsl(Wire); uint8_t tsl2561_type = 0; uint8_t tsl2561_valid = 0; uint32_t tsl2561_milliLux = 0; char tsl2561_types[] = "TSL2561"; bool Tsl2561Read(void) { if (tsl2561_valid) { tsl2561_valid--; } uint8_t id; bool gain; Tsl2561::exposure_t exposure; uint16_t scaledFull, scaledIr; uint32_t full, ir; if (Tsl.on()) { if (Tsl.id(id) && Tsl2561Util::autoGain(Tsl, gain, exposure, scaledFull, scaledIr) && Tsl2561Util::normalizedLuminosity(gain, exposure, full = scaledFull, ir = scaledIr) && Tsl2561Util::milliLux(full, ir, tsl2561_milliLux, Tsl2561::packageCS(id))) { } else{ tsl2561_milliLux = 0; } } tsl2561_valid = SENSOR_MAX_MISS; return true; } void Tsl2561Detect(void) { if (I2cSetDevice(0x29) || I2cSetDevice(0x39) || I2cSetDevice(0x49)) { uint8_t id; Tsl.begin(); if (!Tsl.id(id)) return; if (Tsl.on()) { tsl2561_type = 1; I2cSetActiveFound(Tsl.address(), tsl2561_types); } } } void Tsl2561EverySecond(void) { if (!(uptime %2)) { if (!Tsl2561Read()) { AddLogMissed(tsl2561_types, tsl2561_valid); } } } #ifdef USE_WEBSERVER const char HTTP_SNS_TSL2561[] PROGMEM = "{s}TSL2561 " D_ILLUMINANCE "{m}%u.%03u " D_UNIT_LUX "{e}"; #endif void Tsl2561Show(bool json) { if (tsl2561_valid) { if (json) { ResponseAppend_P(PSTR(",\"TSL2561\":{\"" D_JSON_ILLUMINANCE "\":%u.%03u}"), tsl2561_milliLux / 1000, tsl2561_milliLux % 1000); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, (tsl2561_milliLux + 500) / 1000); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TSL2561, tsl2561_milliLux / 1000, tsl2561_milliLux % 1000); #endif } } } bool Xsns16(uint8_t function) { if (!I2cEnabled(XI2C_16)) { return false; } bool result = false; if (FUNC_INIT == function) { Tsl2561Detect(); } else if (tsl2561_type) { switch (function) { case FUNC_EVERY_SECOND: Tsl2561EverySecond(); break; case FUNC_JSON_APPEND: Tsl2561Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Tsl2561Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino" #ifdef USE_SENSEAIR # 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino" #define XSNS_17 17 #define SENSEAIR_MODBUS_SPEED 9600 #define SENSEAIR_DEVICE_ADDRESS 0xFE #define SENSEAIR_READ_REGISTER 0x04 #ifndef CO2_LOW #define CO2_LOW 800 #endif #ifndef CO2_HIGH #define CO2_HIGH 1200 #endif #include TasmotaModbus *SenseairModbus; const char kSenseairTypes[] PROGMEM = "Kx0|S8"; uint8_t senseair_type = 1; char senseair_types[7]; uint16_t senseair_co2 = 0; float senseair_temperature = 0; float senseair_humidity = 0; const uint8_t start_addresses[] { 0x1A, 0x00, 0x03, 0x04, 0x05, 0x1C, 0x0A }; uint8_t senseair_read_state = 0; uint8_t senseair_send_retry = 0; void Senseair250ms(void) { uint16_t value = 0; bool data_ready = SenseairModbus->ReceiveReady(); if (data_ready) { uint8_t error = SenseairModbus->Receive16BitRegister(&value); if (error) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir response error %d"), error); } else { switch(senseair_read_state) { case 0: senseair_type = 2; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir type id low %04X"), value); break; case 1: if (value) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir error %04X"), value); } break; case 2: senseair_co2 = value; LightSetSignal(CO2_LOW, CO2_HIGH, senseair_co2); break; case 3: senseair_temperature = ConvertTemp((float)value / 100); break; case 4: senseair_humidity = ConvertHumidity((float)value / 100); break; case 5: { bool relay_state = value >> 8 & 1; AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir relay state %d"), relay_state); break; } case 6: AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir temp adjustment %d"), value); break; } } senseair_read_state++; if (2 == senseair_type) { if (3 == senseair_read_state) { senseair_read_state = 1; } } else { if (sizeof(start_addresses) == senseair_read_state) { senseair_read_state = 1; } } } if (0 == senseair_send_retry || data_ready) { senseair_send_retry = 5; SenseairModbus->Send(SENSEAIR_DEVICE_ADDRESS, SENSEAIR_READ_REGISTER, (uint16_t)start_addresses[senseair_read_state], 1); } else { senseair_send_retry--; } } void SenseairInit(void) { senseair_type = 0; if ((pin[GPIO_SAIR_RX] < 99) && (pin[GPIO_SAIR_TX] < 99)) { SenseairModbus = new TasmotaModbus(pin[GPIO_SAIR_RX], pin[GPIO_SAIR_TX]); uint8_t result = SenseairModbus->Begin(SENSEAIR_MODBUS_SPEED); if (result) { if (2 == result) { ClaimSerial(); } senseair_type = 1; } } } void SenseairShow(bool json) { char temperature[33]; dtostrfd(senseair_temperature, Settings.flag2.temperature_resolution, temperature); char humidity[33]; dtostrfd(senseair_humidity, Settings.flag2.temperature_resolution, humidity); GetTextIndexed(senseair_types, sizeof(senseair_types), senseair_type -1, kSenseairTypes); if (json) { ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_CO2 "\":%d"), senseair_types, senseair_co2); if (senseair_type != 2) { ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s"), temperature, humidity); } ResponseJsonEnd(); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, senseair_co2); #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_CO2, senseair_types, senseair_co2); if (senseair_type != 2) { WSContentSend_PD(HTTP_SNS_TEMP, senseair_types, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_HUM, senseair_types, humidity); } #endif } } bool Xsns17(uint8_t function) { bool result = false; if (senseair_type) { switch (function) { case FUNC_INIT: SenseairInit(); break; case FUNC_EVERY_250_MSECOND: Senseair250ms(); break; case FUNC_JSON_APPEND: SenseairShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: SenseairShow(0); break; #endif } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino" #ifdef USE_PMS5003 # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino" #define XSNS_18 18 #include TasmotaSerial *PmsSerial; uint8_t pms_type = 1; uint8_t pms_valid = 0; struct pmsX003data { uint16_t framelen; uint16_t pm10_standard, pm25_standard, pm100_standard; uint16_t pm10_env, pm25_env, pm100_env; #ifdef PMS_MODEL_PMS3003 uint16_t reserved1, reserved2, reserved3; #else uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um; uint16_t unused; #endif uint16_t checksum; } pms_data; bool PmsReadData(void) { if (! PmsSerial->available()) { return false; } while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) { PmsSerial->read(); } #ifdef PMS_MODEL_PMS3003 if (PmsSerial->available() < 22) { #else if (PmsSerial->available() < 32) { #endif return false; } #ifdef PMS_MODEL_PMS3003 uint8_t buffer[22]; PmsSerial->readBytes(buffer, 22); #else uint8_t buffer[32]; PmsSerial->readBytes(buffer, 32); #endif uint16_t sum = 0; PmsSerial->flush(); #ifdef PMS_MODEL_PMS3003 AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 22); #else AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 32); #endif #ifdef PMS_MODEL_PMS3003 for (uint32_t i = 0; i < 20; i++) { #else for (uint32_t i = 0; i < 30; i++) { #endif sum += buffer[i]; } #ifdef PMS_MODEL_PMS3003 uint16_t buffer_u16[10]; for (uint32_t i = 0; i < 10; i++) { #else uint16_t buffer_u16[15]; for (uint32_t i = 0; i < 15; i++) { #endif buffer_u16[i] = buffer[2 + i*2 + 1]; buffer_u16[i] += (buffer[2 + i*2] << 8); } #ifdef PMS_MODEL_PMS3003 if (sum != buffer_u16[9]) { #else if (sum != buffer_u16[14]) { #endif AddLog_P(LOG_LEVEL_DEBUG, PSTR("PMS: " D_CHECKSUM_FAILURE)); return false; } #ifdef PMS_MODEL_PMS3003 memcpy((void *)&pms_data, (void *)buffer_u16, 20); #else memcpy((void *)&pms_data, (void *)buffer_u16, 30); #endif pms_valid = 10; return true; } void PmsSecond(void) { if (PmsReadData()) { pms_valid = 10; } else { if (pms_valid) { pms_valid--; } } } void PmsInit(void) { pms_type = 0; if (pin[GPIO_PMS5003] < 99) { PmsSerial = new TasmotaSerial(pin[GPIO_PMS5003], -1, 1); if (PmsSerial->begin(9600)) { if (PmsSerial->hardwareSerial()) { ClaimSerial(); } pms_type = 1; } } } #ifdef USE_WEBSERVER #ifdef PMS_MODEL_PMS3003 const char HTTP_PMS3003_SNS[] PROGMEM = "{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" "{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" "{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; #else const char HTTP_PMS5003_SNS[] PROGMEM = "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" "{s}PMS5003 " D_PARTICALS_BEYOND " 0.3 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" "{s}PMS5003 " D_PARTICALS_BEYOND " 0.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" "{s}PMS5003 " D_PARTICALS_BEYOND " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" "{s}PMS5003 " D_PARTICALS_BEYOND " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" "{s}PMS5003 " D_PARTICALS_BEYOND " 5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" "{s}PMS5003 " D_PARTICALS_BEYOND " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"; #endif #endif void PmsShow(bool json) { if (pms_valid) { if (json) { #ifdef PMS_MODEL_PMS3003 ResponseAppend_P(PSTR(",\"PMS3003\":{\"CF1\":%d,\"CF2.5\":%d,\"CF10\":%d,\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d}"), pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard, pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env); #else ResponseAppend_P(PSTR(",\"PMS5003\":{\"CF1\":%d,\"CF2.5\":%d,\"CF10\":%d,\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d,\"PB0.3\":%d,\"PB0.5\":%d,\"PB1\":%d,\"PB2.5\":%d,\"PB5\":%d,\"PB10\":%d}"), pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard, pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env, pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um); #endif #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_COUNT, pms_data.pm10_env); DomoticzSensor(DZ_VOLTAGE, pms_data.pm25_env); DomoticzSensor(DZ_CURRENT, pms_data.pm100_env); } #endif #ifdef USE_WEBSERVER } else { #ifdef PMS_MODEL_PMS3003 WSContentSend_PD(HTTP_PMS3003_SNS, pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env); #else WSContentSend_PD(HTTP_PMS5003_SNS, pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env, pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um); #endif #endif } } } bool Xsns18(uint8_t function) { bool result = false; if (pms_type) { switch (function) { case FUNC_INIT: PmsInit(); break; case FUNC_EVERY_SECOND: PmsSecond(); break; case FUNC_JSON_APPEND: PmsShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: PmsShow(0); break; #endif } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_19_mgs.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_19_mgs.ino" #ifdef USE_I2C #ifdef USE_MGS #define XSNS_19 19 #define XI2C_17 17 #ifndef MGS_SENSOR_ADDR #define MGS_SENSOR_ADDR 0x04 #endif #include "MutichannelGasSensor.h" bool mgs_detected = false; void MGSInit(void) { gas.begin(MGS_SENSOR_ADDR); } void MGSPrepare(void) { if (I2cActive(MGS_SENSOR_ADDR)) { return; } gas.begin(MGS_SENSOR_ADDR); if (!gas.isError()) { I2cSetActiveFound(MGS_SENSOR_ADDR, "MultiGas"); mgs_detected = true; } } char* measure_gas(int gas_type, char* buffer) { float f = gas.calcGas(gas_type); dtostrfd(f, 2, buffer); return buffer; } #ifdef USE_WEBSERVER const char HTTP_MGS_GAS[] PROGMEM = "{s}MGS %s{m}%s " D_UNIT_PARTS_PER_MILLION "{e}"; #endif void MGSShow(bool json) { char buffer[33]; if (json) { ResponseAppend_P(PSTR(",\"MGS\":{\"NH3\":%s"), measure_gas(NH3, buffer)); ResponseAppend_P(PSTR(",\"CO\":%s"), measure_gas(CO, buffer)); ResponseAppend_P(PSTR(",\"NO2\":%s"), measure_gas(NO2, buffer)); ResponseAppend_P(PSTR(",\"C3H8\":%s"), measure_gas(C3H8, buffer)); ResponseAppend_P(PSTR(",\"C4H10\":%s"), measure_gas(C4H10, buffer)); ResponseAppend_P(PSTR(",\"CH4\":%s"), measure_gas(GAS_CH4, buffer)); ResponseAppend_P(PSTR(",\"H2\":%s"), measure_gas(H2, buffer)); ResponseAppend_P(PSTR(",\"C2H5OH\":%s}"), measure_gas(C2H5OH, buffer)); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_MGS_GAS, "NH3", measure_gas(NH3, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "CO", measure_gas(CO, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "NO2", measure_gas(NO2, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "C3H8", measure_gas(C3H8, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "C4H10", measure_gas(C4H10, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "CH4", measure_gas(GAS_CH4, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "H2", measure_gas(H2, buffer)); WSContentSend_PD(HTTP_MGS_GAS, "C2H5OH", measure_gas(C2H5OH, buffer)); #endif } } bool Xsns19(uint8_t function) { if (!I2cEnabled(XI2C_17)) { return false; } bool result = false; if (FUNC_INIT == function) { MGSPrepare(); } else if (mgs_detected) { switch (function) { case FUNC_JSON_APPEND: MGSShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: MGSShow(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino" #ifdef USE_NOVA_SDS # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino" #define XSNS_20 20 #include #ifndef STARTING_OFFSET #define STARTING_OFFSET 30 #endif #if STARTING_OFFSET < 10 #error "Please set STARTING_OFFSET >= 10" #endif #ifndef NOVA_SDS_RECDATA_TIMEOUT #define NOVA_SDS_RECDATA_TIMEOUT 150 #endif #ifndef NOVA_SDS_DEVICE_ID #define NOVA_SDS_DEVICE_ID 0xFFFF #endif TasmotaSerial *NovaSdsSerial; uint8_t novasds_type = 1; uint8_t novasds_valid = 0; uint8_t cont_mode = 1; struct sds011data { uint16_t pm100; uint16_t pm25; } novasds_data; uint16_t pm100_sum; uint16_t pm25_sum; #define NOVA_SDS_REPORTING_MODE 2 #define NOVA_SDS_QUERY_DATA 4 #define NOVA_SDS_SET_DEVICE_ID 5 #define NOVA_SDS_SLEEP_AND_WORK 6 #define NOVA_SDS_WORKING_PERIOD 8 #define NOVA_SDS_CHECK_FIRMWARE_VER 7 #define NOVA_SDS_QUERY_MODE 0 #define NOVA_SDS_SET_MODE 1 #define NOVA_SDS_REPORT_ACTIVE 0 #define NOVA_SDS_REPORT_QUERY 1 #define NOVA_SDS_SLEEP 0 #define NOVA_SDS_WORK 1 bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, uint8_t *buffer) { uint8_t novasds_cmnd[19] = {0xAA, 0xB4, byte1, byte2, byte3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (uint8_t)(sensorid & 0xFF), (uint8_t)((sensorid>>8) & 0xFF), 0x00, 0xAB}; for (uint32_t i = 2; i < 17; i++) { novasds_cmnd[17] += novasds_cmnd[i]; } NovaSdsSerial->write(novasds_cmnd, sizeof(novasds_cmnd)); NovaSdsSerial->flush(); unsigned long cmndtime = millis(); while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( ! NovaSdsSerial->available() ) ); if ( ! NovaSdsSerial->available() ) { return false; } uint8_t recbuf[10]; memset(recbuf, 0, sizeof(recbuf)); while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( NovaSdsSerial->available() > 0) && (0xAA != (recbuf[0] = NovaSdsSerial->read())) ); if ( 0xAA != recbuf[0] ) { return false; } NovaSdsSerial->readBytes(&recbuf[1], 9); AddLogBuffer(LOG_LEVEL_DEBUG_MORE, recbuf, sizeof(recbuf)); if ( nullptr != buffer ) { memcpy(buffer, recbuf, sizeof(recbuf)); } if ((0xAB != recbuf[9] ) || (recbuf[8] != ((recbuf[2] + recbuf[3] + recbuf[4] + recbuf[5] + recbuf[6] + recbuf[7]) & 0xFF))) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("SDS: " D_CHECKSUM_FAILURE)); return false; } return true; } void NovaSdsSetWorkPeriod(void) { NovaSdsCommand(NOVA_SDS_WORKING_PERIOD, NOVA_SDS_SET_MODE, 0, NOVA_SDS_DEVICE_ID, nullptr); NovaSdsCommand(NOVA_SDS_REPORTING_MODE, NOVA_SDS_SET_MODE, NOVA_SDS_REPORT_QUERY, NOVA_SDS_DEVICE_ID, nullptr); } bool NovaSdsReadData(void) { uint8_t d[10]; if ( ! NovaSdsCommand(NOVA_SDS_QUERY_DATA, 0, 0, NOVA_SDS_DEVICE_ID, d) ) { return false; } novasds_data.pm25 = (d[2] + 256 * d[3]); novasds_data.pm100 = (d[4] + 256 * d[5]); return true; } void NovaSdsSecond(void) { if (!novasds_valid) { NovaSdsSetWorkPeriod(); novasds_valid=1; } if((Settings.tele_period - Settings.novasds_startingoffset <= 0)) { if(!cont_mode) { cont_mode = 1; NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_WORK, NOVA_SDS_DEVICE_ID, nullptr); } } else cont_mode = 0; if(tele_period == Settings.tele_period - Settings.novasds_startingoffset && !cont_mode) { NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_WORK, NOVA_SDS_DEVICE_ID, nullptr); } if(tele_period >= Settings.tele_period-5 && tele_period <= Settings.tele_period-2) { if(!(NovaSdsReadData())) novasds_valid=0; pm100_sum += novasds_data.pm100; pm25_sum += novasds_data.pm25; } if(tele_period == Settings.tele_period-1) { novasds_data.pm100 = pm100_sum >> 2; novasds_data.pm25 = pm25_sum >> 2; if(!cont_mode) NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_SLEEP, NOVA_SDS_DEVICE_ID, nullptr); pm100_sum = pm25_sum = 0; } } bool NovaSdsCommandSensor(void) { if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 256)) { if( XdrvMailbox.payload < 10 ) Settings.novasds_startingoffset = 10; else Settings.novasds_startingoffset = XdrvMailbox.payload; } Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_20, Settings.novasds_startingoffset); return true; } void NovaSdsInit(void) { novasds_type = 0; if (pin[GPIO_SDS0X1_RX] < 99 && pin[GPIO_SDS0X1_TX] < 99) { NovaSdsSerial = new TasmotaSerial(pin[GPIO_SDS0X1_RX], pin[GPIO_SDS0X1_TX], 1); if (NovaSdsSerial->begin(9600)) { if (NovaSdsSerial->hardwareSerial()) { ClaimSerial(); } novasds_type = 1; NovaSdsSetWorkPeriod(); } } } #ifdef USE_WEBSERVER const char HTTP_SDS0X1_SNS[] PROGMEM = "{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" "{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; #endif void NovaSdsShow(bool json) { if (novasds_valid) { float pm10f = (float)(novasds_data.pm100) / 10.0f; float pm2_5f = (float)(novasds_data.pm25) / 10.0f; char pm10[33]; dtostrfd(pm10f, 1, pm10); char pm2_5[33]; dtostrfd(pm2_5f, 1, pm2_5); if (json) { ResponseAppend_P(PSTR(",\"SDS0X1\":{\"PM2.5\":%s,\"PM10\":%s}"), pm2_5, pm10); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_VOLTAGE, pm2_5); DomoticzSensor(DZ_CURRENT, pm10); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SDS0X1_SNS, pm2_5, pm10); #endif } } } bool Xsns20(uint8_t function) { bool result = false; if (novasds_type) { switch (function) { case FUNC_INIT: NovaSdsInit(); break; case FUNC_EVERY_SECOND: NovaSdsSecond(); break; case FUNC_COMMAND_SENSOR: if (XSNS_20 == XdrvMailbox.index) { result = NovaSdsCommandSensor(); } break; case FUNC_JSON_APPEND: NovaSdsShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: NovaSdsShow(0); break; #endif } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino" #ifdef USE_I2C #ifdef USE_SGP30 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino" #define XSNS_21 21 #define XI2C_18 18 #define SGP30_ADDRESS 0x58 #include "Adafruit_SGP30.h" Adafruit_SGP30 sgp; bool sgp30_type = false; bool sgp30_ready = false; float sgp30_abshum; void sgp30_Init(void) { if (I2cActive(SGP30_ADDRESS)) { return; } if (sgp.begin()) { sgp30_type = true; I2cSetActiveFound(SGP30_ADDRESS, "SGP30"); } } #define POW_FUNC FastPrecisePow float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit) { float temp = NAN; const float mw = 18.01534; const float r = 8.31447215; if (isnan(temperature) || isnan(humidity) ) { return NAN; } if (tempUnit != 'C') { temperature = (temperature - 32.0) * (5.0 / 9.0); } temp = POW_FUNC(2.718281828, (17.67 * temperature) / (temperature + 243.5)); return (6.112 * temp * humidity * mw) / ((273.15 + temperature) * r); } #define SAVE_PERIOD 30 void Sgp30Update(void) { sgp30_ready = false; if (!sgp.IAQmeasure()) { return; } if (global_update && (global_humidity > 0) && (global_temperature != 9999)) { sgp30_abshum=sgp30_AbsoluteHumidity(global_temperature,global_humidity,TempUnit()); sgp.setHumidity(sgp30_abshum*1000); } sgp30_ready = true; if (!(uptime%SAVE_PERIOD)) { uint16_t TVOC_base; uint16_t eCO2_base; if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return; } } #ifdef USE_WEBSERVER const char HTTP_SNS_SGP30[] PROGMEM = "{s}SGP30 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" "{s}SGP30 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; const char HTTP_SNS_AHUM[] PROGMEM = "{s}SGP30 Abs Humidity{m}%s g/m3{e}"; #endif #define D_JSON_AHUM "aHumidity" void Sgp30Show(bool json) { if (sgp30_ready) { char abs_hum[33]; if (json) { ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC); if (global_update && global_humidity>0 && global_temperature!=9999) { dtostrfd(sgp30_abshum,4,abs_hum); ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum); } ResponseJsonEnd(); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, sgp.eCO2); #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_SGP30, sgp.eCO2, sgp.TVOC); if (global_update) { WSContentSend_PD(HTTP_SNS_AHUM, abs_hum); } #endif } } } bool Xsns21(uint8_t function) { if (!I2cEnabled(XI2C_18)) { return false; } bool result = false; if (FUNC_INIT == function) { sgp30_Init(); } else if (sgp30_type) { switch (function) { case FUNC_EVERY_SECOND: Sgp30Update(); break; case FUNC_JSON_APPEND: Sgp30Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Sgp30Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino" #ifdef USE_SR04 #include #include # 32 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino" #define XSNS_22 22 uint8_t sr04_type = 1; int sr04_echo_pin = 0; int sr04_trig_pin = 0; real64_t distance; NewPing* sonar = nullptr; TasmotaSerial* sonar_serial = nullptr; uint8_t Sr04TModeDetect(void) { sr04_type = 0; if (pin[GPIO_SR04_ECHO]>=99) return sr04_type; sr04_echo_pin = pin[GPIO_SR04_ECHO]; sr04_trig_pin = (pin[GPIO_SR04_TRIG] < 99) ? pin[GPIO_SR04_TRIG] : -1; sonar_serial = new TasmotaSerial(sr04_echo_pin, sr04_trig_pin, 1); if (sonar_serial->begin(9600,1)) { DEBUG_SENSOR_LOG(PSTR("SR04: Detect mode")); if (sr04_trig_pin!=-1) { sr04_type = (Sr04TMiddleValue(Sr04TMode3Distance(),Sr04TMode3Distance(),Sr04TMode3Distance())!=NO_ECHO)?3:1; } else { sr04_type = 2; } } else { sr04_type = 1; } if (sr04_type < 2) { delete sonar_serial; sonar_serial = nullptr; sonar = new NewPing(sr04_trig_pin, sr04_echo_pin, 300); } else { if (sonar_serial->hardwareSerial()) { ClaimSerial(); } } AddLog_P2(LOG_LEVEL_INFO,PSTR("SR04: Mode %d"), sr04_type); return sr04_type; } uint16_t Sr04TMiddleValue(uint16_t first, uint16_t second, uint16_t third) { uint16_t ret = first; if (first > second) { first = second; second = ret; } if (third < first) { return first; } else if (third > second) { return second; } else { return third; } } uint16_t Sr04TMode3Distance() { sonar_serial->write(0x55); sonar_serial->flush(); return Sr04TMode2Distance(); } uint16_t Sr04TMode2Distance(void) { sonar_serial->setTimeout(300); const char startByte = 0xff; if (!sonar_serial->find(startByte)) { return NO_ECHO; } delay(5); uint8_t crc = sonar_serial->read(); uint16_t distance = ((uint16_t)crc) << 8; distance += sonar_serial->read(); crc += distance & 0x00ff; crc += 0x00FF; if (crc != sonar_serial->read()) { AddLog_P2(LOG_LEVEL_ERROR,PSTR("SR04: Reading CRC error.")); return NO_ECHO; } return distance; } void Sr04TReading(void) { if (sonar_serial==nullptr && sonar==nullptr) { Sr04TModeDetect(); } switch (sr04_type) { case 3: distance = (real64_t)(Sr04TMiddleValue(Sr04TMode3Distance(),Sr04TMode3Distance(),Sr04TMode3Distance()))/ 10; break; case 2: while(sonar_serial->available()) sonar_serial->read(); distance = (real64_t)(Sr04TMiddleValue(Sr04TMode2Distance(),Sr04TMode2Distance(),Sr04TMode2Distance()))/10; break; case 1: distance = (real64_t)(sonar->ping_median(5))/ US_ROUNDTRIP_CM; break; default: distance = NO_ECHO; } return; } #ifdef USE_WEBSERVER const char HTTP_SNS_DISTANCE[] PROGMEM = "{s}SR04 " D_DISTANCE "{m}%s" D_UNIT_CENTIMETER "{e}"; #endif void Sr04Show(bool json) { if (distance != 0) { char distance_chr[33]; dtostrfd(distance, 3, distance_chr); if(json) { ResponseAppend_P(PSTR(",\"SR04\":{\"" D_JSON_DISTANCE "\":%s}"), distance_chr); #ifdef USE_DOMOTICZ if (0 == tele_period) { DomoticzSensor(DZ_COUNT, distance_chr); } #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_DISTANCE, distance_chr); #endif } } } bool Xsns22(uint8_t function) { bool result = false; if (sr04_type) { switch (function) { case FUNC_INIT: result = (pin[GPIO_SR04_ECHO]<99); break; case FUNC_EVERY_SECOND: Sr04TReading(); result = true; break; case FUNC_JSON_APPEND: Sr04Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Sr04Show(0); break; #endif } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino" #ifdef USE_I2C #ifdef USE_SI1145 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino" #define XSNS_24 24 #define XI2C_19 19 #define SI114X_ADDR 0X60 #define SI114X_QUERY 0X80 #define SI114X_SET 0XA0 #define SI114X_NOP 0X00 #define SI114X_RESET 0X01 #define SI114X_BUSADDR 0X02 #define SI114X_PS_FORCE 0X05 #define SI114X_GET_CAL 0X12 #define SI114X_ALS_FORCE 0X06 #define SI114X_PSALS_FORCE 0X07 #define SI114X_PS_PAUSE 0X09 #define SI114X_ALS_PAUSE 0X0A #define SI114X_PSALS_PAUSE 0X0B #define SI114X_PS_AUTO 0X0D #define SI114X_ALS_AUTO 0X0E #define SI114X_PSALS_AUTO 0X0F #define SI114X_PART_ID 0X00 #define SI114X_REV_ID 0X01 #define SI114X_SEQ_ID 0X02 #define SI114X_INT_CFG 0X03 #define SI114X_IRQ_ENABLE 0X04 #define SI114X_IRQ_MODE1 0x05 #define SI114X_IRQ_MODE2 0x06 #define SI114X_HW_KEY 0X07 #define SI114X_MEAS_RATE0 0X08 #define SI114X_MEAS_RATE1 0X09 #define SI114X_PS_RATE 0X0A #define SI114X_PS_LED21 0X0F #define SI114X_PS_LED3 0X10 #define SI114X_UCOEFF0 0X13 #define SI114X_UCOEFF1 0X14 #define SI114X_UCOEFF2 0X15 #define SI114X_UCOEFF3 0X16 #define SI114X_WR 0X17 #define SI114X_COMMAND 0X18 #define SI114X_RESPONSE 0X20 #define SI114X_IRQ_STATUS 0X21 #define SI114X_ALS_VIS_DATA0 0X22 #define SI114X_ALS_VIS_DATA1 0X23 #define SI114X_ALS_IR_DATA0 0X24 #define SI114X_ALS_IR_DATA1 0X25 #define SI114X_PS1_DATA0 0X26 #define SI114X_PS1_DATA1 0X27 #define SI114X_PS2_DATA0 0X28 #define SI114X_PS2_DATA1 0X29 #define SI114X_PS3_DATA0 0X2A #define SI114X_PS3_DATA1 0X2B #define SI114X_AUX_DATA0_UVINDEX0 0X2C #define SI114X_AUX_DATA1_UVINDEX1 0X2D #define SI114X_RD 0X2E #define SI114X_CHIP_STAT 0X30 #define SI114X_CHLIST 0X01 #define SI114X_CHLIST_ENUV 0x80 #define SI114X_CHLIST_ENAUX 0x40 #define SI114X_CHLIST_ENALSIR 0x20 #define SI114X_CHLIST_ENALSVIS 0x10 #define SI114X_CHLIST_ENPS1 0x01 #define SI114X_CHLIST_ENPS2 0x02 #define SI114X_CHLIST_ENPS3 0x04 #define SI114X_PSLED12_SELECT 0X02 #define SI114X_PSLED3_SELECT 0X03 #define SI114X_PS_ENCODE 0X05 #define SI114X_ALS_ENCODE 0X06 #define SI114X_PS1_ADCMUX 0X07 #define SI114X_PS2_ADCMUX 0X08 #define SI114X_PS3_ADCMUX 0X09 #define SI114X_PS_ADC_COUNTER 0X0A #define SI114X_PS_ADC_GAIN 0X0B #define SI114X_PS_ADC_MISC 0X0C #define SI114X_ALS_IR_ADC_MUX 0X0E #define SI114X_AUX_ADC_MUX 0X0F #define SI114X_ALS_VIS_ADC_COUNTER 0X10 #define SI114X_ALS_VIS_ADC_GAIN 0X11 #define SI114X_ALS_VIS_ADC_MISC 0X12 #define SI114X_LED_REC 0X1C #define SI114X_ALS_IR_ADC_COUNTER 0X1D #define SI114X_ALS_IR_ADC_GAIN 0X1E #define SI114X_ALS_IR_ADC_MISC 0X1F #define SI114X_ADCMUX_SMALL_IR 0x00 #define SI114X_ADCMUX_VISIABLE 0x02 #define SI114X_ADCMUX_LARGE_IR 0x03 #define SI114X_ADCMUX_NO 0x06 #define SI114X_ADCMUX_GND 0x25 #define SI114X_ADCMUX_TEMPERATURE 0x65 #define SI114X_ADCMUX_VDD 0x75 #define SI114X_PSLED12_SELECT_PS1_NONE 0x00 #define SI114X_PSLED12_SELECT_PS1_LED1 0x01 #define SI114X_PSLED12_SELECT_PS1_LED2 0x02 #define SI114X_PSLED12_SELECT_PS1_LED3 0x04 #define SI114X_PSLED12_SELECT_PS2_NONE 0x00 #define SI114X_PSLED12_SELECT_PS2_LED1 0x10 #define SI114X_PSLED12_SELECT_PS2_LED2 0x20 #define SI114X_PSLED12_SELECT_PS2_LED3 0x40 #define SI114X_PSLED3_SELECT_PS2_NONE 0x00 #define SI114X_PSLED3_SELECT_PS2_LED1 0x10 #define SI114X_PSLED3_SELECT_PS2_LED2 0x20 #define SI114X_PSLED3_SELECT_PS2_LED3 0x40 #define SI114X_ADC_GAIN_DIV1 0X00 #define SI114X_ADC_GAIN_DIV2 0X01 #define SI114X_ADC_GAIN_DIV4 0X02 #define SI114X_ADC_GAIN_DIV8 0X03 #define SI114X_ADC_GAIN_DIV16 0X04 #define SI114X_ADC_GAIN_DIV32 0X05 #define SI114X_LED_CURRENT_5MA 0X01 #define SI114X_LED_CURRENT_11MA 0X02 #define SI114X_LED_CURRENT_22MA 0X03 #define SI114X_LED_CURRENT_45MA 0X04 #define SI114X_ADC_COUNTER_1ADCCLK 0X00 #define SI114X_ADC_COUNTER_7ADCCLK 0X01 #define SI114X_ADC_COUNTER_15ADCCLK 0X02 #define SI114X_ADC_COUNTER_31ADCCLK 0X03 #define SI114X_ADC_COUNTER_63ADCCLK 0X04 #define SI114X_ADC_COUNTER_127ADCCLK 0X05 #define SI114X_ADC_COUNTER_255ADCCLK 0X06 #define SI114X_ADC_COUNTER_511ADCCLK 0X07 #define SI114X_ADC_MISC_LOWRANGE 0X00 #define SI114X_ADC_MISC_HIGHRANGE 0X20 #define SI114X_ADC_MISC_ADC_NORMALPROXIMITY 0X00 #define SI114X_ADC_MISC_ADC_RAWADC 0X04 #define SI114X_INT_CFG_INTOE 0X01 #define SI114X_IRQEN_ALS 0x01 #define SI114X_IRQEN_PS1 0x04 #define SI114X_IRQEN_PS2 0x08 #define SI114X_IRQEN_PS3 0x10 uint16_t si1145_visible; uint16_t si1145_infrared; uint16_t si1145_uvindex; bool si1145_type = false; uint8_t si1145_valid = 0; uint8_t Si1145ReadByte(uint8_t reg) { return I2cRead8(SI114X_ADDR, reg); } uint16_t Si1145ReadHalfWord(uint8_t reg) { return I2cRead16LE(SI114X_ADDR, reg); } bool Si1145WriteByte(uint8_t reg, uint16_t val) { I2cWrite8(SI114X_ADDR, reg, val); } uint8_t Si1145WriteParamData(uint8_t p, uint8_t v) { Si1145WriteByte(SI114X_WR, v); Si1145WriteByte(SI114X_COMMAND, p | SI114X_SET); return Si1145ReadByte(SI114X_RD); } bool Si1145Present(void) { return (Si1145ReadByte(SI114X_PART_ID) == 0X45); } void Si1145Reset(void) { Si1145WriteByte(SI114X_MEAS_RATE0, 0); Si1145WriteByte(SI114X_MEAS_RATE1, 0); Si1145WriteByte(SI114X_IRQ_ENABLE, 0); Si1145WriteByte(SI114X_IRQ_MODE1, 0); Si1145WriteByte(SI114X_IRQ_MODE2, 0); Si1145WriteByte(SI114X_INT_CFG, 0); Si1145WriteByte(SI114X_IRQ_STATUS, 0xFF); Si1145WriteByte(SI114X_COMMAND, SI114X_RESET); delay(10); Si1145WriteByte(SI114X_HW_KEY, 0x17); delay(10); } void Si1145DeInit(void) { Si1145WriteByte(SI114X_UCOEFF0, 0x29); Si1145WriteByte(SI114X_UCOEFF1, 0x89); Si1145WriteByte(SI114X_UCOEFF2, 0x02); Si1145WriteByte(SI114X_UCOEFF3, 0x00); Si1145WriteParamData(SI114X_CHLIST, SI114X_CHLIST_ENUV | SI114X_CHLIST_ENALSIR | SI114X_CHLIST_ENALSVIS | SI114X_CHLIST_ENPS1); Si1145WriteParamData(SI114X_PS1_ADCMUX, SI114X_ADCMUX_LARGE_IR); Si1145WriteByte(SI114X_PS_LED21, SI114X_LED_CURRENT_22MA); Si1145WriteParamData(SI114X_PSLED12_SELECT, SI114X_PSLED12_SELECT_PS1_LED1); Si1145WriteParamData(SI114X_PS_ADC_GAIN, SI114X_ADC_GAIN_DIV1); Si1145WriteParamData(SI114X_PS_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK); Si1145WriteParamData(SI114X_PS_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE | SI114X_ADC_MISC_ADC_RAWADC); Si1145WriteParamData(SI114X_ALS_VIS_ADC_GAIN, SI114X_ADC_GAIN_DIV1); Si1145WriteParamData(SI114X_ALS_VIS_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK); Si1145WriteParamData(SI114X_ALS_VIS_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE); Si1145WriteParamData(SI114X_ALS_IR_ADC_GAIN, SI114X_ADC_GAIN_DIV1); Si1145WriteParamData(SI114X_ALS_IR_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK); Si1145WriteParamData(SI114X_ALS_IR_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE); Si1145WriteByte(SI114X_INT_CFG, SI114X_INT_CFG_INTOE); Si1145WriteByte(SI114X_IRQ_ENABLE, SI114X_IRQEN_ALS); Si1145WriteByte(SI114X_MEAS_RATE0, 0xFF); Si1145WriteByte(SI114X_COMMAND, SI114X_PSALS_AUTO); } bool Si1145Begin(void) { if (!Si1145Present()) { return false; } Si1145Reset(); Si1145DeInit(); return true; } uint16_t Si1145ReadUV(void) { return Si1145ReadHalfWord(SI114X_AUX_DATA0_UVINDEX0); } uint16_t Si1145ReadVisible(void) { return Si1145ReadHalfWord(SI114X_ALS_VIS_DATA0); } uint16_t Si1145ReadIR(void) { return Si1145ReadHalfWord(SI114X_ALS_IR_DATA0); } bool Si1145Read(void) { if (si1145_valid) { si1145_valid--; } if (!Si1145Present()) { return false; } si1145_visible = Si1145ReadVisible(); si1145_infrared = Si1145ReadIR(); si1145_uvindex = Si1145ReadUV(); si1145_valid = SENSOR_MAX_MISS; return true; } void Si1145Detect(void) { if (I2cActive(SI114X_ADDR)) { return; } if (Si1145Begin()) { si1145_type = true; I2cSetActiveFound(SI114X_ADDR, "SI1145"); } } void Si1145Update(void) { if (!Si1145Read()) { AddLogMissed("SI1145", si1145_valid); } } #ifdef USE_WEBSERVER const char HTTP_SNS_SI1145[] PROGMEM = "{s}SI1145 " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}" "{s}SI1145 " D_INFRARED "{m}%d " D_UNIT_LUX "{e}" "{s}SI1145 " D_UV_INDEX "{m}%d.%d{e}"; #endif void Si1145Show(bool json) { if (si1145_valid) { if (json) { ResponseAppend_P(PSTR(",\"SI1145\":{\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_INFRARED "\":%d,\"" D_JSON_UV_INDEX "\":%d.%d}"), si1145_visible, si1145_infrared, si1145_uvindex /100, si1145_uvindex %100); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_ILLUMINANCE, si1145_visible); #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_SI1145, si1145_visible, si1145_infrared, si1145_uvindex /100, si1145_uvindex %100); #endif } } } bool Xsns24(uint8_t function) { if (!I2cEnabled(XI2C_19)) { return false; } bool result = false; if (FUNC_INIT == function) { Si1145Detect(); } else if (si1145_type) { switch (function) { case FUNC_EVERY_SECOND: Si1145Update(); break; case FUNC_JSON_APPEND: Si1145Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Si1145Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino" #ifdef USE_I2C #ifdef USE_LM75AD # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino" #define XSNS_26 26 #define XI2C_20 20 #define LM75AD_ADDRESS1 0x48 #define LM75AD_ADDRESS2 0x49 #define LM75AD_ADDRESS3 0x4A #define LM75AD_ADDRESS4 0x4B #define LM75AD_ADDRESS5 0x4C #define LM75AD_ADDRESS6 0x4D #define LM75AD_ADDRESS7 0x4E #define LM75AD_ADDRESS8 0x4F #define LM75_TEMP_REGISTER 0x00 #define LM75_CONF_REGISTER 0x01 #define LM75_THYST_REGISTER 0x02 #define LM75_TOS_REGISTER 0x03 bool lm75ad_type = false; uint8_t lm75ad_address; uint8_t lm75ad_addresses[] = { LM75AD_ADDRESS1, LM75AD_ADDRESS2, LM75AD_ADDRESS3, LM75AD_ADDRESS4, LM75AD_ADDRESS5, LM75AD_ADDRESS6, LM75AD_ADDRESS7, LM75AD_ADDRESS8 }; void LM75ADDetect(void) { for (uint32_t i = 0; i < sizeof(lm75ad_addresses); i++) { lm75ad_address = lm75ad_addresses[i]; if (I2cActive(lm75ad_address)) { continue; } if (!I2cSetDevice(lm75ad_address)) { break; } uint16_t buffer; if (I2cValidRead16(&buffer, lm75ad_address, LM75_THYST_REGISTER)) { if (buffer == 0x4B00) { lm75ad_type = true; I2cSetActiveFound(lm75ad_address, "LM75AD"); break; } } } } float LM75ADGetTemp(void) { int16_t sign = 1; uint16_t t = I2cRead16(lm75ad_address, LM75_TEMP_REGISTER); if (t & 0x8000) { t = (~t) +0x20; sign = -1; } t = t >> 5; return ConvertTemp(sign * t * 0.125); } void LM75ADShow(bool json) { float t = LM75ADGetTemp(); char temperature[33]; dtostrfd(t, Settings.flag2.temperature_resolution, temperature); if (json) { ResponseAppend_P(PSTR(",\"LM75AD\":{\"" D_JSON_TEMPERATURE "\":%s}"), temperature); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_TEMP, temperature); #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, "LM75AD", temperature, TempUnit()); #endif } } bool Xsns26(uint8_t function) { if (!I2cEnabled(XI2C_20)) { return false; } bool result = false; if (FUNC_INIT == function) { LM75ADDetect(); } else if (lm75ad_type) { switch (function) { case FUNC_JSON_APPEND: LM75ADShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: LM75ADShow(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" # 28 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" #ifdef USE_I2C #ifdef USE_APDS9960 # 39 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" #define XSNS_27 27 #define XI2C_21 21 #if defined(USE_SHT) || defined(USE_VEML6070) || defined(USE_TSL2561) #warning **** Turned off conflicting drivers SHT and VEML6070 **** #ifdef USE_SHT #undef USE_SHT #endif #ifdef USE_VEML6070 #undef USE_VEML6070 #endif #ifdef USE_TSL2561 #undef USE_TSL2561 #endif #endif #define APDS9960_I2C_ADDR 0x39 #define APDS9960_CHIPID_1 0xAB #define APDS9960_CHIPID_2 0x9C #define APDS9930_CHIPID_1 0x12 #define APDS9930_CHIPID_2 0x39 #define GESTURE_THRESHOLD_OUT 10 #define GESTURE_SENSITIVITY_1 50 #define GESTURE_SENSITIVITY_2 20 uint8_t APDS9960addr; uint8_t APDS9960type = 0; char APDS9960stype[] = "APDS9960"; char currentGesture[6]; uint8_t gesture_mode = 1; volatile uint8_t recovery_loop_counter = 0; #define APDS9960_LONG_RECOVERY 50 #define APDS9960_MAX_GESTURE_CYCLES 50 bool APDS9960_overload = false; #ifdef USE_WEBSERVER const char HTTP_APDS_9960_SNS[] PROGMEM = "{s}" "Red" "{m}%s{e}" "{s}" "Green" "{m}%s{e}" "{s}" "Blue" "{m}%s{e}" "{s}" "Ambient" "{m}%s " D_UNIT_LUX "{e}" "{s}" "CCT" "{m}%s " "K" "{e}" "{s}" "Proximity" "{m}%s{e}"; #endif # 97 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" #define FIFO_PAUSE_TIME 30 #define APDS9960_ENABLE 0x80 #define APDS9960_ATIME 0x81 #define APDS9960_WTIME 0x83 #define APDS9960_AILTL 0x84 #define APDS9960_AILTH 0x85 #define APDS9960_AIHTL 0x86 #define APDS9960_AIHTH 0x87 #define APDS9960_PILT 0x89 #define APDS9960_PIHT 0x8B #define APDS9960_PERS 0x8C #define APDS9960_CONFIG1 0x8D #define APDS9960_PPULSE 0x8E #define APDS9960_CONTROL 0x8F #define APDS9960_CONFIG2 0x90 #define APDS9960_ID 0x92 #define APDS9960_STATUS 0x93 #define APDS9960_CDATAL 0x94 #define APDS9960_CDATAH 0x95 #define APDS9960_RDATAL 0x96 #define APDS9960_RDATAH 0x97 #define APDS9960_GDATAL 0x98 #define APDS9960_GDATAH 0x99 #define APDS9960_BDATAL 0x9A #define APDS9960_BDATAH 0x9B #define APDS9960_PDATA 0x9C #define APDS9960_POFFSET_UR 0x9D #define APDS9960_POFFSET_DL 0x9E #define APDS9960_CONFIG3 0x9F #define APDS9960_GPENTH 0xA0 #define APDS9960_GEXTH 0xA1 #define APDS9960_GCONF1 0xA2 #define APDS9960_GCONF2 0xA3 #define APDS9960_GOFFSET_U 0xA4 #define APDS9960_GOFFSET_D 0xA5 #define APDS9960_GOFFSET_L 0xA7 #define APDS9960_GOFFSET_R 0xA9 #define APDS9960_GPULSE 0xA6 #define APDS9960_GCONF3 0xAA #define APDS9960_GCONF4 0xAB #define APDS9960_GFLVL 0xAE #define APDS9960_GSTATUS 0xAF #define APDS9960_IFORCE 0xE4 #define APDS9960_PICLEAR 0xE5 #define APDS9960_CICLEAR 0xE6 #define APDS9960_AICLEAR 0xE7 #define APDS9960_GFIFO_U 0xFC #define APDS9960_GFIFO_D 0xFD #define APDS9960_GFIFO_L 0xFE #define APDS9960_GFIFO_R 0xFF #define APDS9960_PON 0b00000001 #define APDS9960_AEN 0b00000010 #define APDS9960_PEN 0b00000100 #define APDS9960_WEN 0b00001000 #define APSD9960_AIEN 0b00010000 #define APDS9960_PIEN 0b00100000 #define APDS9960_GEN 0b01000000 #define APDS9960_GVALID 0b00000001 #define OFF 0 #define ON 1 #define POWER 0 #define AMBIENT_LIGHT 1 #define PROXIMITY 2 #define WAIT 3 #define AMBIENT_LIGHT_INT 4 #define PROXIMITY_INT 5 #define GESTURE 6 #define ALL 7 #define LED_DRIVE_100MA 0 #define LED_DRIVE_50MA 1 #define LED_DRIVE_25MA 2 #define LED_DRIVE_12_5MA 3 #define PGAIN_1X 0 #define PGAIN_2X 1 #define PGAIN_4X 2 #define PGAIN_8X 3 #define AGAIN_1X 0 #define AGAIN_4X 1 #define AGAIN_16X 2 #define AGAIN_64X 3 #define GGAIN_1X 0 #define GGAIN_2X 1 #define GGAIN_4X 2 #define GGAIN_8X 3 #define LED_BOOST_100 0 #define LED_BOOST_150 1 #define LED_BOOST_200 2 #define LED_BOOST_300 3 #define GWTIME_0MS 0 #define GWTIME_2_8MS 1 #define GWTIME_5_6MS 2 #define GWTIME_8_4MS 3 #define GWTIME_14_0MS 4 #define GWTIME_22_4MS 5 #define GWTIME_30_8MS 6 #define GWTIME_39_2MS 7 #define DEFAULT_ATIME 0xdb #define DEFAULT_WTIME 246 #define DEFAULT_PROX_PPULSE 0x87 #define DEFAULT_GESTURE_PPULSE 0x89 #define DEFAULT_POFFSET_UR 0 #define DEFAULT_POFFSET_DL 0 #define DEFAULT_CONFIG1 0x60 #define DEFAULT_LDRIVE LED_DRIVE_100MA #define DEFAULT_PGAIN PGAIN_4X #define DEFAULT_AGAIN AGAIN_4X #define DEFAULT_PILT 0 #define DEFAULT_PIHT 50 #define DEFAULT_AILT 0xFFFF #define DEFAULT_AIHT 0 #define DEFAULT_PERS 0x11 #define DEFAULT_CONFIG2 0x01 #define DEFAULT_CONFIG3 0 #define DEFAULT_GPENTH 40 #define DEFAULT_GEXTH 30 #define DEFAULT_GCONF1 0x40 #define DEFAULT_GGAIN GGAIN_4X #define DEFAULT_GLDRIVE LED_DRIVE_100MA #define DEFAULT_GWTIME GWTIME_2_8MS #define DEFAULT_GOFFSET 0 #define DEFAULT_GPULSE 0xC9 #define DEFAULT_GCONF3 0 #define DEFAULT_GIEN 0 #define ERROR 0xFF enum { DIR_NONE, DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN, DIR_ALL }; enum { APDS9960_NA_STATE, APDS9960_ALL_STATE }; typedef struct gesture_data_type { uint8_t u_data[32]; uint8_t d_data[32]; uint8_t l_data[32]; uint8_t r_data[32]; uint8_t index; uint8_t total_gestures; uint8_t in_threshold; uint8_t out_threshold; } gesture_data_type; gesture_data_type gesture_data_; int16_t gesture_ud_delta_ = 0; int16_t gesture_lr_delta_ = 0; int16_t gesture_ud_count_ = 0; int16_t gesture_lr_count_ = 0; int16_t gesture_state_ = 0; int16_t gesture_motion_ = DIR_NONE; typedef struct color_data_type { uint16_t a; uint16_t r; uint16_t g; uint16_t b; uint8_t p; uint16_t cct; uint16_t lux; } color_data_type; color_data_type color_data; uint8_t APDS9960_aTime = DEFAULT_ATIME; # 306 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" bool wireWriteByte(uint8_t val) { Wire.beginTransmission(APDS9960_I2C_ADDR); Wire.write(val); if( Wire.endTransmission() != 0 ) { return false; } return true; } # 325 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" int8_t wireReadDataBlock( uint8_t reg, uint8_t *val, uint16_t len) { unsigned char i = 0; if (!wireWriteByte(reg)) { return -1; } Wire.requestFrom(APDS9960_I2C_ADDR, len); while (Wire.available()) { if (i >= len) { return -1; } val[i] = Wire.read(); i++; } return i; } void calculateColorTemperature(void) { float X, Y, Z; float xc, yc; float n; float cct; X = (-0.14282F * color_data.r) + (1.54924F * color_data.g) + (-0.95641F * color_data.b); Y = (-0.32466F * color_data.r) + (1.57837F * color_data.g) + (-0.73191F * color_data.b); Z = (-0.68202F * color_data.r) + (0.77073F * color_data.g) + ( 0.56332F * color_data.b); xc = (X) / (X + Y + Z); yc = (Y) / (X + Y + Z); n = (xc - 0.3320F) / (0.1858F - yc); color_data.cct = (449.0F * FastPrecisePowf(n, 3)) + (3525.0F * FastPrecisePowf(n, 2)) + (6823.3F * n) + 5520.33F; return; } # 392 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getProxIntLowThresh(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PILT) ; return val; } void setProxIntLowThresh(uint8_t threshold) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PILT, threshold); } uint8_t getProxIntHighThresh(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PIHT) ; return val; } void setProxIntHighThresh(uint8_t threshold) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PIHT, threshold); } # 448 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getLEDDrive(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ; val = (val >> 6) & 0b00000011; return val; } # 471 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setLEDDrive(uint8_t drive) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL); drive &= 0b00000011; drive = drive << 6; val &= 0b00111111; val |= drive; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val); } # 500 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getProximityGain(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ; val = (val >> 2) & 0b00000011; return val; } # 523 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setProximityGain(uint8_t drive) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL); drive &= 0b00000011; drive = drive << 2; val &= 0b11110011; val |= drive; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val); } # 564 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setAmbientLightGain(uint8_t drive) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL); drive &= 0b00000011; val &= 0b11111100; val |= drive; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val); } # 591 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getLEDBoost(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ; val = (val >> 4) & 0b00000011; return val; } # 615 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setLEDBoost(uint8_t boost) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ; boost &= 0b00000011; boost = boost << 4; val &= 0b11001111; val |= boost; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG2, val) ; } uint8_t getProxGainCompEnable(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; val = (val >> 5) & 0b00000001; return val; } void setProxGainCompEnable(uint8_t enable) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; enable &= 0b00000001; enable = enable << 5; val &= 0b11011111; val |= enable; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, val) ; } # 683 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getProxPhotoMask(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; val &= 0b00001111; return val; } # 708 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setProxPhotoMask(uint8_t mask) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ; mask &= 0b00001111; val &= 0b11110000; val |= mask; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, val) ; } uint8_t getGestureEnterThresh(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GPENTH) ; return val; } void setGestureEnterThresh(uint8_t threshold) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GPENTH, threshold) ; } uint8_t getGestureExitThresh(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GEXTH) ; return val; } void setGestureExitThresh(uint8_t threshold) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GEXTH, threshold) ; } # 786 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getGestureGain(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; val = (val >> 5) & 0b00000011; return val; } # 810 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setGestureGain(uint8_t gain) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; gain &= 0b00000011; gain = gain << 5; val &= 0b10011111; val |= gain; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ; } # 838 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getGestureLEDDrive(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; val = (val >> 3) & 0b00000011; return val; } # 862 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setGestureLEDDrive(uint8_t drive) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; drive &= 0b00000011; drive = drive << 3; val &= 0b11100111; val |= drive; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ; } # 894 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getGestureWaitTime(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; val &= 0b00000111; return val; } # 922 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void setGestureWaitTime(uint8_t time) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ; time &= 0b00000111; val &= 0b11111000; val |= time; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ; } void getLightIntLowThreshold(uint16_t &threshold) { uint8_t val_byte; threshold = 0; val_byte = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AILTL) ; threshold = val_byte; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTH, val_byte) ; threshold = threshold + ((uint16_t)val_byte << 8); } void setLightIntLowThreshold(uint16_t threshold) { uint8_t val_low; uint8_t val_high; val_low = threshold & 0x00FF; val_high = (threshold & 0xFF00) >> 8; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTL, val_low) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTH, val_high) ; } void getLightIntHighThreshold(uint16_t &threshold) { uint8_t val_byte; threshold = 0; val_byte = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AIHTL); threshold = val_byte; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTH, val_byte) ; threshold = threshold + ((uint16_t)val_byte << 8); } void setLightIntHighThreshold(uint16_t threshold) { uint8_t val_low; uint8_t val_high; val_low = threshold & 0x00FF; val_high = (threshold & 0xFF00) >> 8; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTL, val_low); I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTH, val_high) ; } void getProximityIntLowThreshold(uint8_t &threshold) { threshold = 0; threshold = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PILT); } void setProximityIntLowThreshold(uint8_t threshold) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PILT, threshold) ; } # 1055 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void getProximityIntHighThreshold(uint8_t &threshold) { threshold = 0; threshold = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PIHT) ; } void setProximityIntHighThreshold(uint8_t threshold) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PIHT, threshold) ; } uint8_t getAmbientLightIntEnable(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; val = (val >> 4) & 0b00000001; return val; } void setAmbientLightIntEnable(uint8_t enable) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE); enable &= 0b00000001; enable = enable << 4; val &= 0b11101111; val |= enable; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, val) ; } uint8_t getProximityIntEnable(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; val = (val >> 5) & 0b00000001; return val; } void setProximityIntEnable(uint8_t enable) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; enable &= 0b00000001; enable = enable << 5; val &= 0b11011111; val |= enable; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, val) ; } uint8_t getGestureIntEnable(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; val = (val >> 1) & 0b00000001; return val; } void setGestureIntEnable(uint8_t enable) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; enable &= 0b00000001; enable = enable << 1; val &= 0b11111101; val |= enable; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF4, val) ; } void clearAmbientLightInt(void) { uint8_t throwaway; throwaway = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AICLEAR); } void clearProximityInt(void) { uint8_t throwaway; throwaway = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PICLEAR) ; } uint8_t getGestureMode(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; val &= 0b00000001; return val; } void setGestureMode(uint8_t mode) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ; mode &= 0b00000001; val &= 0b11111110; val |= mode; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF4, val) ; } bool APDS9960_init(void) { I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, DEFAULT_ATIME) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_WTIME, DEFAULT_WTIME) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PPULSE, DEFAULT_PROX_PPULSE) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_POFFSET_UR, DEFAULT_POFFSET_UR) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_POFFSET_DL, DEFAULT_POFFSET_DL) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG1, DEFAULT_CONFIG1) ; setLEDDrive(DEFAULT_LDRIVE); setProximityGain(DEFAULT_PGAIN); setAmbientLightGain(DEFAULT_AGAIN); setProxIntLowThresh(DEFAULT_PILT) ; setProxIntHighThresh(DEFAULT_PIHT); setLightIntLowThreshold(DEFAULT_AILT) ; setLightIntHighThreshold(DEFAULT_AIHT) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PERS, DEFAULT_PERS) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG2, DEFAULT_CONFIG2) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, DEFAULT_CONFIG3) ; setGestureEnterThresh(DEFAULT_GPENTH); setGestureExitThresh(DEFAULT_GEXTH) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF1, DEFAULT_GCONF1) ; setGestureGain(DEFAULT_GGAIN) ; setGestureLEDDrive(DEFAULT_GLDRIVE) ; setGestureWaitTime(DEFAULT_GWTIME) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_U, DEFAULT_GOFFSET) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_D, DEFAULT_GOFFSET) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_L, DEFAULT_GOFFSET) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_R, DEFAULT_GOFFSET) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GPULSE, DEFAULT_GPULSE) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF3, DEFAULT_GCONF3) ; setGestureIntEnable(DEFAULT_GIEN); disablePower(); return true; } # 1333 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" uint8_t getMode(void) { uint8_t enable_value; enable_value = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ; return enable_value; } void setMode(uint8_t mode, uint8_t enable) { uint8_t reg_val; reg_val = getMode(); enable = enable & 0x01; if( mode >= 0 && mode <= 6 ) { if (enable) { reg_val |= (1 << mode); } else { reg_val &= ~(1 << mode); } } else if( mode == ALL ) { if (enable) { reg_val = 0x7F; } else { reg_val = 0x00; } } I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, reg_val) ; } void enableLightSensor(void) { setAmbientLightGain(DEFAULT_AGAIN); setAmbientLightIntEnable(0); enablePower() ; setMode(AMBIENT_LIGHT, 1) ; } void disableLightSensor(void) { setAmbientLightIntEnable(0) ; setMode(AMBIENT_LIGHT, 0) ; } void enableProximitySensor(void) { setProximityGain(DEFAULT_PGAIN); setLEDDrive(DEFAULT_LDRIVE) ; setProximityIntEnable(0) ; enablePower(); setMode(PROXIMITY, 1) ; } void disableProximitySensor(void) { setProximityIntEnable(0) ; setMode(PROXIMITY, 0) ; } void enableGestureSensor(void) { resetGestureParameters(); I2cWrite8(APDS9960_I2C_ADDR, APDS9960_WTIME, 0xFF) ; I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PPULSE, DEFAULT_GESTURE_PPULSE) ; setLEDBoost(LED_BOOST_100); setGestureIntEnable(0) ; setGestureMode(1); enablePower() ; setMode(WAIT, 1) ; setMode(PROXIMITY, 1) ; setMode(GESTURE, 1); } void disableGestureSensor(void) { resetGestureParameters(); setGestureIntEnable(0) ; setGestureMode(0) ; setMode(GESTURE, 0) ; } bool isGestureAvailable(void) { uint8_t val; val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GSTATUS) ; val &= APDS9960_GVALID; if( val == 1) { return true; } else { return false; } } int16_t readGesture(void) { uint8_t fifo_level = 0; uint8_t bytes_read = 0; uint8_t fifo_data[128]; uint8_t gstatus; uint16_t motion; uint16_t i; uint8_t gesture_loop_counter = 0; if( !isGestureAvailable() || !(getMode() & 0b01000001) ) { return DIR_NONE; } while(1) { if (gesture_loop_counter == APDS9960_MAX_GESTURE_CYCLES){ disableGestureSensor(); APDS9960_overload = true; AddLog_P(LOG_LEVEL_DEBUG, PSTR("Sensor overload")); } gesture_loop_counter += 1; delay(FIFO_PAUSE_TIME); gstatus = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GSTATUS); if( (gstatus & APDS9960_GVALID) == APDS9960_GVALID ) { fifo_level = I2cRead8(APDS9960_I2C_ADDR,APDS9960_GFLVL) ; if( fifo_level > 0) { bytes_read = wireReadDataBlock( APDS9960_GFIFO_U, (uint8_t*)fifo_data, (fifo_level * 4) ); if( bytes_read == -1 ) { return ERROR; } if( bytes_read >= 4 ) { for( i = 0; i < bytes_read; i += 4 ) { gesture_data_.u_data[gesture_data_.index] = \ fifo_data[i + 0]; gesture_data_.d_data[gesture_data_.index] = \ fifo_data[i + 1]; gesture_data_.l_data[gesture_data_.index] = \ fifo_data[i + 2]; gesture_data_.r_data[gesture_data_.index] = \ fifo_data[i + 3]; gesture_data_.index++; gesture_data_.total_gestures++; } if( processGestureData() ) { if( decodeGesture() ) { } } gesture_data_.index = 0; gesture_data_.total_gestures = 0; } } } else { delay(FIFO_PAUSE_TIME); decodeGesture(); motion = gesture_motion_; resetGestureParameters(); return motion; } } } void enablePower(void) { setMode(POWER, 1) ; } void disablePower(void) { setMode(POWER, 0) ; } # 1600 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void readAllColorAndProximityData(void) { if (I2cReadBuffer(APDS9960_I2C_ADDR, APDS9960_CDATAL, (uint8_t *) &color_data, (uint16_t)9)) { } } # 1616 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" void resetGestureParameters(void) { gesture_data_.index = 0; gesture_data_.total_gestures = 0; gesture_ud_delta_ = 0; gesture_lr_delta_ = 0; gesture_ud_count_ = 0; gesture_lr_count_ = 0; gesture_state_ = 0; gesture_motion_ = DIR_NONE; } bool processGestureData(void) { uint8_t u_first = 0; uint8_t d_first = 0; uint8_t l_first = 0; uint8_t r_first = 0; uint8_t u_last = 0; uint8_t d_last = 0; uint8_t l_last = 0; uint8_t r_last = 0; uint16_t ud_ratio_first; uint16_t lr_ratio_first; uint16_t ud_ratio_last; uint16_t lr_ratio_last; uint16_t ud_delta; uint16_t lr_delta; uint16_t i; if( gesture_data_.total_gestures <= 4 ) { return false; } if( (gesture_data_.total_gestures <= 32) && \ (gesture_data_.total_gestures > 0) ) { for( i = 0; i < gesture_data_.total_gestures; i++ ) { if( (gesture_data_.u_data[i] > GESTURE_THRESHOLD_OUT) && (gesture_data_.d_data[i] > GESTURE_THRESHOLD_OUT) && (gesture_data_.l_data[i] > GESTURE_THRESHOLD_OUT) && (gesture_data_.r_data[i] > GESTURE_THRESHOLD_OUT) ) { u_first = gesture_data_.u_data[i]; d_first = gesture_data_.d_data[i]; l_first = gesture_data_.l_data[i]; r_first = gesture_data_.r_data[i]; break; } } if( (u_first == 0) || (d_first == 0) || \ (l_first == 0) || (r_first == 0) ) { return false; } for( i = gesture_data_.total_gestures - 1; i >= 0; i-- ) { if( (gesture_data_.u_data[i] > GESTURE_THRESHOLD_OUT) && (gesture_data_.d_data[i] > GESTURE_THRESHOLD_OUT) && (gesture_data_.l_data[i] > GESTURE_THRESHOLD_OUT) && (gesture_data_.r_data[i] > GESTURE_THRESHOLD_OUT) ) { u_last = gesture_data_.u_data[i]; d_last = gesture_data_.d_data[i]; l_last = gesture_data_.l_data[i]; r_last = gesture_data_.r_data[i]; break; } } } ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first); lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first); ud_ratio_last = ((u_last - d_last) * 100) / (u_last + d_last); lr_ratio_last = ((l_last - r_last) * 100) / (l_last + r_last); ud_delta = ud_ratio_last - ud_ratio_first; lr_delta = lr_ratio_last - lr_ratio_first; gesture_ud_delta_ += ud_delta; gesture_lr_delta_ += lr_delta; if( gesture_ud_delta_ >= GESTURE_SENSITIVITY_1 ) { gesture_ud_count_ = 1; } else if( gesture_ud_delta_ <= -GESTURE_SENSITIVITY_1 ) { gesture_ud_count_ = -1; } else { gesture_ud_count_ = 0; } if( gesture_lr_delta_ >= GESTURE_SENSITIVITY_1 ) { gesture_lr_count_ = 1; } else if( gesture_lr_delta_ <= -GESTURE_SENSITIVITY_1 ) { gesture_lr_count_ = -1; } else { gesture_lr_count_ = 0; } return false; } bool decodeGesture(void) { if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == 0) ) { gesture_motion_ = DIR_UP; } else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == 0) ) { gesture_motion_ = DIR_DOWN; } else if( (gesture_ud_count_ == 0) && (gesture_lr_count_ == 1) ) { gesture_motion_ = DIR_RIGHT; } else if( (gesture_ud_count_ == 0) && (gesture_lr_count_ == -1) ) { gesture_motion_ = DIR_LEFT; } else if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == 1) ) { if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { gesture_motion_ = DIR_UP; } else { gesture_motion_ = DIR_RIGHT; } } else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == -1) ) { if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { gesture_motion_ = DIR_DOWN; } else { gesture_motion_ = DIR_LEFT; } } else if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == -1) ) { if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { gesture_motion_ = DIR_UP; } else { gesture_motion_ = DIR_LEFT; } } else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == 1) ) { if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) { gesture_motion_ = DIR_DOWN; } else { gesture_motion_ = DIR_RIGHT; } } else { return false; } return true; } void handleGesture(void) { if (isGestureAvailable() ) { switch (readGesture()) { case DIR_UP: AddLog_P(LOG_LEVEL_DEBUG, PSTR("UP")); snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Up")); break; case DIR_DOWN: AddLog_P(LOG_LEVEL_DEBUG, PSTR("DOWN")); snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Down")); break; case DIR_LEFT: AddLog_P(LOG_LEVEL_DEBUG, PSTR("LEFT")); snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Left")); break; case DIR_RIGHT: AddLog_P(LOG_LEVEL_DEBUG, PSTR("RIGHT")); snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Right")); break; default: if(APDS9960_overload) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("LONG")); snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Long")); } else{ AddLog_P(LOG_LEVEL_DEBUG, PSTR("NONE")); snprintf_P(currentGesture, sizeof(currentGesture), PSTR("None")); } } MqttPublishSensor(); } } void APDS9960_adjustATime(void) { I2cValidRead16LE(&color_data.a, APDS9960_I2C_ADDR, APDS9960_CDATAL); if (color_data.a < (uint16_t)20){ APDS9960_aTime = 0x40; } else if (color_data.a < (uint16_t)40){ APDS9960_aTime = 0x80; } else if (color_data.a < (uint16_t)50){ APDS9960_aTime = DEFAULT_ATIME; } else if (color_data.a < (uint16_t)70){ APDS9960_aTime = 0xc0; } if (color_data.a < 200){ APDS9960_aTime = 0xe9; } else{ APDS9960_aTime = 0xff; } I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, APDS9960_aTime); enablePower(); enableLightSensor(); delay(20); } void APDS9960_loop(void) { if (recovery_loop_counter > 0){ recovery_loop_counter -= 1; } if (recovery_loop_counter == 1 && APDS9960_overload){ enableGestureSensor(); APDS9960_overload = false; Response_P(PSTR("{\"Gesture\":\"On\"}")); MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); gesture_mode = 1; } if (gesture_mode) { if (recovery_loop_counter == 0){ handleGesture(); if (APDS9960_overload) { disableGestureSensor(); recovery_loop_counter = APDS9960_LONG_RECOVERY; Response_P(PSTR("{\"Gesture\":\"Off\"}")); MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); gesture_mode = 0; } } } } void APDS9960_detect(void) { if (APDS9960type || I2cActive(APDS9960_I2C_ADDR)) { return; } APDS9960type = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ID); if (APDS9960type == APDS9960_CHIPID_1 || APDS9960type == APDS9960_CHIPID_2) { if (APDS9960_init()) { I2cSetActiveFound(APDS9960_I2C_ADDR, APDS9960stype); enableProximitySensor(); enableGestureSensor(); } else { APDS9960type = 0; } } else { APDS9960type = 0; } currentGesture[0] = '\0'; } void APDS9960_show(bool json) { if (!APDS9960type) { return; } if (!gesture_mode && !APDS9960_overload) { char red_chr[10]; char green_chr[10]; char blue_chr[10]; char ambient_chr[10]; char cct_chr[10]; char prox_chr[10]; readAllColorAndProximityData(); sprintf (ambient_chr, "%u", color_data.a/4); sprintf (red_chr, "%u", color_data.r); sprintf (green_chr, "%u", color_data.g); sprintf (blue_chr, "%u", color_data.b ); sprintf (prox_chr, "%u", color_data.p ); calculateColorTemperature(); sprintf (cct_chr, "%u", color_data.cct); if (json) { ResponseAppend_P(PSTR(",\"%s\":{\"Red\":%s,\"Green\":%s,\"Blue\":%s,\"Ambient\":%s,\"CCT\":%s,\"Proximity\":%s}"), APDS9960stype, red_chr, green_chr, blue_chr, ambient_chr, cct_chr, prox_chr); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_APDS_9960_SNS, red_chr, green_chr, blue_chr, ambient_chr, cct_chr, prox_chr ); #endif } } else { if (json && (currentGesture[0] != '\0' )) { ResponseAppend_P(PSTR(",\"%s\":{\"%s\":1}"), APDS9960stype, currentGesture); currentGesture[0] = '\0'; } } } # 1961 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino" bool APDS9960CommandSensor(void) { bool serviced = true; switch (XdrvMailbox.payload) { case 0: disableGestureSensor(); gesture_mode = 0; enableLightSensor(); APDS9960_overload = false; break; case 1: if (APDS9960type) { setGestureGain(DEFAULT_GGAIN); setProximityGain(DEFAULT_PGAIN); disableLightSensor(); enableGestureSensor(); gesture_mode = 1; } break; case 2: if (APDS9960type) { setGestureGain(GGAIN_2X); setProximityGain(PGAIN_2X); disableLightSensor(); enableGestureSensor(); gesture_mode = 1; } break; default: int temp_aTime = (uint8_t)XdrvMailbox.payload; if (temp_aTime > 2 && temp_aTime < 256){ disablePower(); I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, temp_aTime); enablePower(); enableLightSensor(); } break; } Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_27, GetStateText(gesture_mode)); return serviced; } bool Xsns27(uint8_t function) { if (!I2cEnabled(XI2C_21)) { return false; } bool result = false; if (FUNC_INIT == function) { APDS9960_detect(); } else if (APDS9960type) { switch (function) { case FUNC_EVERY_50_MSECOND: APDS9960_loop(); break; case FUNC_COMMAND_SENSOR: if (XSNS_27 == XdrvMailbox.index) { result = APDS9960CommandSensor(); } break; case FUNC_JSON_APPEND: APDS9960_show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: APDS9960_show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino" #ifdef USE_TM1638 #define XSNS_28 28 #define TM1638_COLOR_NONE 0 #define TM1638_COLOR_RED 1 #define TM1638_COLOR_GREEN 2 #define TM1638_CLOCK_DELAY 1 uint8_t tm1638_type = 1; uint8_t tm1638_clock_pin = 0; uint8_t tm1638_data_pin = 0; uint8_t tm1638_strobe_pin = 0; uint8_t tm1638_displays = 8; uint8_t tm1638_active_display = 1; uint8_t tm1638_intensity = 0; uint8_t tm1638_state = 0; void Tm16XXSend(uint8_t data) { for (uint32_t i = 0; i < 8; i++) { digitalWrite(tm1638_data_pin, !!(data & (1 << i))); digitalWrite(tm1638_clock_pin, LOW); delayMicroseconds(TM1638_CLOCK_DELAY); digitalWrite(tm1638_clock_pin, HIGH); } } void Tm16XXSendCommand(uint8_t cmd) { digitalWrite(tm1638_strobe_pin, LOW); Tm16XXSend(cmd); digitalWrite(tm1638_strobe_pin, HIGH); } void TM16XXSendData(uint8_t address, uint8_t data) { Tm16XXSendCommand(0x44); digitalWrite(tm1638_strobe_pin, LOW); Tm16XXSend(0xC0 | address); Tm16XXSend(data); digitalWrite(tm1638_strobe_pin, HIGH); } uint8_t Tm16XXReceive(void) { uint8_t temp = 0; pinMode(tm1638_data_pin, INPUT); digitalWrite(tm1638_data_pin, HIGH); for (uint32_t i = 0; i < 8; ++i) { digitalWrite(tm1638_clock_pin, LOW); delayMicroseconds(TM1638_CLOCK_DELAY); temp |= digitalRead(tm1638_data_pin) << i; digitalWrite(tm1638_clock_pin, HIGH); } pinMode(tm1638_data_pin, OUTPUT); digitalWrite(tm1638_data_pin, LOW); return temp; } void Tm16XXClearDisplay(void) { for (uint32_t i = 0; i < tm1638_displays; i++) { TM16XXSendData(i << 1, 0); } } void Tm1638SetLED(uint8_t color, uint8_t pos) { TM16XXSendData((pos << 1) + 1, color); } void Tm1638SetLEDs(word leds) { for (uint32_t i = 0; i < tm1638_displays; i++) { uint8_t color = 0; if ((leds & (1 << i)) != 0) { color |= TM1638_COLOR_RED; } if ((leds & (1 << (i + 8))) != 0) { color |= TM1638_COLOR_GREEN; } Tm1638SetLED(color, i); } } uint8_t Tm1638GetButtons(void) { uint8_t keys = 0; digitalWrite(tm1638_strobe_pin, LOW); Tm16XXSend(0x42); for (uint32_t i = 0; i < 4; i++) { keys |= Tm16XXReceive() << i; } digitalWrite(tm1638_strobe_pin, HIGH); return keys; } void TmInit(void) { tm1638_type = 0; if ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99)) { tm1638_clock_pin = pin[GPIO_TM16CLK]; tm1638_data_pin = pin[GPIO_TM16DIO]; tm1638_strobe_pin = pin[GPIO_TM16STB]; pinMode(tm1638_data_pin, OUTPUT); pinMode(tm1638_clock_pin, OUTPUT); pinMode(tm1638_strobe_pin, OUTPUT); digitalWrite(tm1638_strobe_pin, HIGH); digitalWrite(tm1638_clock_pin, HIGH); Tm16XXSendCommand(0x40); Tm16XXSendCommand(0x80 | (tm1638_active_display ? 8 : 0) | tmin(7, tm1638_intensity)); digitalWrite(tm1638_strobe_pin, LOW); Tm16XXSend(0xC0); for (uint32_t i = 0; i < 16; i++) { Tm16XXSend(0x00); } digitalWrite(tm1638_strobe_pin, HIGH); tm1638_type = 1; tm1638_state = 1; } } void TmLoop(void) { if (tm1638_state) { uint8_t buttons = Tm1638GetButtons(); for (uint32_t i = 0; i < MAX_SWITCHES; i++) { SwitchSetVirtual(i, (buttons &1) ^1); uint8_t color = (SwitchGetVirtual(i)) ? TM1638_COLOR_NONE : TM1638_COLOR_RED; Tm1638SetLED(color, i); buttons >>= 1; } SwitchHandler(1); } } # 201 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino" bool Xsns28(uint8_t function) { bool result = false; if (tm1638_type) { switch (function) { case FUNC_INIT: TmInit(); break; case FUNC_EVERY_50_MSECOND: TmLoop(); break; # 223 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino" } } return result; } #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino" #ifdef USE_I2C #ifdef USE_MCP230xx # 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino" #define XSNS_29 29 #define XI2C_22 22 uint8_t MCP230xx_IODIR = 0x00; uint8_t MCP230xx_GPINTEN = 0x02; uint8_t MCP230xx_IOCON = 0x05; uint8_t MCP230xx_GPPU = 0x06; uint8_t MCP230xx_INTF = 0x07; uint8_t MCP230xx_INTCAP = 0x08; uint8_t MCP230xx_GPIO = 0x09; uint8_t mcp230xx_type = 0; uint8_t mcp230xx_pincount = 0; uint8_t mcp230xx_int_en = 0; uint8_t mcp230xx_int_prio_counter = 0; uint8_t mcp230xx_int_counter_en = 0; uint8_t mcp230xx_int_retainer_en = 0; uint8_t mcp230xx_int_sec_counter = 0; uint8_t mcp230xx_int_report_defer_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint16_t mcp230xx_int_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t mcp230xx_int_retainer[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; unsigned long int_millis[16]; const char MCP230XX_SENSOR_RESPONSE[] PROGMEM = "{\"Sensor29_D%i\":{\"MODE\":%i,\"PULL_UP\":\"%s\",\"INT_MODE\":\"%s\",\"STATE\":\"%s\"}}"; const char MCP230XX_INTCFG_RESPONSE[] PROGMEM = "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}"; #ifdef USE_MCP230xx_OUTPUT const char MCP230XX_CMND_RESPONSE[] PROGMEM = "{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"}}"; #endif void MCP230xx_CheckForIntCounter(void) { uint8_t en = 0; for (uint32_t ca=0;ca<16;ca++) { if (Settings.mcp230xx_config[ca].int_count_en) { en=1; } } if (!Settings.mcp230xx_int_timer) en=0; mcp230xx_int_counter_en=en; if (!mcp230xx_int_counter_en) { for (uint32_t ca=0;ca<16;ca++) { mcp230xx_int_counter[ca] = 0; } } } void MCP230xx_CheckForIntRetainer(void) { uint8_t en = 0; for (uint32_t ca=0;ca<16;ca++) { if (Settings.mcp230xx_config[ca].int_retain_flag) { en=1; } } mcp230xx_int_retainer_en=en; if (!mcp230xx_int_retainer_en) { for (uint32_t ca=0;ca<16;ca++) { mcp230xx_int_retainer[ca] = 0; } } } const char* ConvertNumTxt(uint8_t statu, uint8_t pinmod=0) { #ifdef USE_MCP230xx_OUTPUT if ((6 == pinmod) && (statu < 2)) { statu = abs(statu-1); } #endif switch (statu) { case 0: return "OFF"; break; case 1: return "ON"; break; #ifdef USE_MCP230xx_OUTPUT case 2: return "TOGGLE"; break; #endif } return ""; } const char* IntModeTxt(uint8_t intmo) { switch (intmo) { case 0: return "ALL"; break; case 1: return "EVENT"; break; case 2: return "TELE"; break; case 3: return "DISABLED"; break; } return ""; } uint8_t MCP230xx_readGPIO(uint8_t port) { return I2cRead8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port); } void MCP230xx_ApplySettings(void) { uint8_t int_en = 0; for (uint32_t mcp230xx_port = 0; mcp230xx_port < mcp230xx_type; mcp230xx_port++) { uint8_t reg_gppu = 0; uint8_t reg_gpinten = 0; uint8_t reg_iodir = 0xFF; #ifdef USE_MCP230xx_OUTPUT uint8_t reg_portpins = 0x00; #endif for (uint32_t idx = 0; idx < 8; idx++) { switch (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode) { case 0 ... 1: reg_iodir |= (1 << idx); break; case 2 ... 4: reg_iodir |= (1 << idx); reg_gpinten |= (1 << idx); int_en = 1; break; #ifdef USE_MCP230xx_OUTPUT case 5 ... 6: reg_iodir &= ~(1 << idx); if (Settings.flag.save_state) { reg_portpins |= (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].saved_state << idx); } else { if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) { reg_portpins |= (1 << idx); } } break; #endif default: break; } #ifdef USE_MCP230xx_OUTPUT if ((Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) && (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode < 5)) { reg_gppu |= (1 << idx); } #else if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) { reg_gppu |= (1 << idx); } #endif } I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPPU+mcp230xx_port, reg_gppu); I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPINTEN+mcp230xx_port, reg_gpinten); I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IODIR+mcp230xx_port, reg_iodir); #ifdef USE_MCP230xx_OUTPUT I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO+mcp230xx_port, reg_portpins); #endif } for (uint32_t idx=0;idx 0) { if (I2cValidRead8(&mcp230xx_intcap, USE_MCP230xx_ADDR, MCP230xx_INTCAP+mcp230xx_port)) { for (uint32_t intp = 0; intp < 8; intp++) { if ((intf >> intp) & 0x01) { report_int = 0; if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode > 1) { switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode) { case 2: report_int = 1; break; case 3: if (((mcp230xx_intcap >> intp) & 0x01) == 0) report_int = 1; break; case 4: if (((mcp230xx_intcap >> intp) & 0x01) == 1) report_int = 1; break; default: break; } if ((mcp230xx_int_counter_en) && (report_int)) { if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) { mcp230xx_int_counter[intp+(mcp230xx_port*8)]++; } } if (report_int) { if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) { mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]++; if (mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)] >= Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) { mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]=0; } else { report_int = 0; } } } if (report_int) { if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_retain_flag) { mcp230xx_int_retainer[intp+(mcp230xx_port*8)] = 1; report_int = 0; } } if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) { report_int = 0; } if (report_int) { bool int_tele = false; bool int_event = false; unsigned long millis_now = millis(); unsigned long millis_since_last_int = millis_now - int_millis[intp+(mcp230xx_port*8)]; int_millis[intp+(mcp230xx_port*8)]=millis_now; switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_mode) { case 0: int_tele=true; int_event=true; break; case 1: int_event=true; break; case 2: int_tele=true; break; } if (int_tele) { ResponseTime_P(PSTR(",\"MCP230XX_INT\":{\"D%i\":%i,\"MS\":%lu}}"), intp+(mcp230xx_port*8), ((mcp230xx_intcap >> intp) & 0x01),millis_since_last_int); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("MCP230XX_INT")); } if (int_event) { char command[19]; sprintf(command,"event MCPINT_D%i=%i",intp+(mcp230xx_port*8),((mcp230xx_intcap >> intp) & 0x01)); ExecuteCommand(command, SRC_RULE); } } } } } } } } } } void MCP230xx_Show(bool json) { if (json) { uint8_t gpio = MCP230xx_readGPIO(0); ResponseAppend_P(PSTR(",\"MCP230XX\":{\"D0\":%i,\"D1\":%i,\"D2\":%i,\"D3\":%i,\"D4\":%i,\"D5\":%i,\"D6\":%i,\"D7\":%i"), (gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1); if (2 == mcp230xx_type) { gpio = MCP230xx_readGPIO(1); ResponseAppend_P(PSTR(",\"D8\":%i,\"D9\":%i,\"D10\":%i,\"D11\":%i,\"D12\":%i,\"D13\":%i,\"D14\":%i,\"D15\":%i"), (gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1); } ResponseJsonEnd(); } } #ifdef USE_MCP230xx_OUTPUT void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate) { uint8_t portpins; uint8_t port = 0; uint8_t pinmo = Settings.mcp230xx_config[pin].pinmode; uint8_t interlock = Settings.flag.interlock; int pinadd = (pin % 2)+1-(3*(pin % 2)); char cmnd[7], stt[4]; if (pin > 7) { port = 1; } portpins = MCP230xx_readGPIO(port); if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) { if (pinstate < 2) { if (6 == pinmo) { if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins |= (1 << (pin+pinadd-(port*8))),portpins &= ~(1 << (pin-(port*8))); } else { if (pinstate) portpins &= ~(1 << (pin+pinadd-(port*8))),portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8))); } } else { if (6 == pinmo) { portpins |= (1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8))); } else { portpins &= ~(1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8))); } } } else { if (pinstate < 2) { if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8))); } else { portpins ^= (1 << (pin-(port*8))); } } I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port, portpins); if (Settings.flag.save_state) { Settings.mcp230xx_config[pin].saved_state=portpins>>(pin-(port*8))&1; Settings.mcp230xx_config[pin+pinadd].saved_state=portpins>>(pin+pinadd-(port*8))&1; } sprintf(cmnd,ConvertNumTxt(pinstate, pinmo)); sprintf(stt,ConvertNumTxt((portpins >> (pin-(port*8))&1), pinmo)); if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) { char stt1[4]; sprintf(stt1,ConvertNumTxt((portpins >> (pin+pinadd-(port*8))&1), pinmo)); Response_P(PSTR("{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"},\"S29cmnd_D%i\":{\"STATE\":\"%s\"}}"),pin, cmnd, stt, pin+pinadd, stt1); } else { Response_P(MCP230XX_CMND_RESPONSE, pin, cmnd, stt); } } #endif void MCP230xx_Reset(uint8_t pinmode) { uint8_t pullup = 0; if ((pinmode > 1) && (pinmode < 5)) { pullup=1; } for (uint32_t pinx=0;pinx<16;pinx++) { Settings.mcp230xx_config[pinx].pinmode=pinmode; Settings.mcp230xx_config[pinx].pullup=pullup; Settings.mcp230xx_config[pinx].saved_state=0; if ((pinmode > 1) && (pinmode < 5)) { Settings.mcp230xx_config[pinx].int_report_mode=0; } else { Settings.mcp230xx_config[pinx].int_report_mode=3; } Settings.mcp230xx_config[pinx].int_report_defer=0; Settings.mcp230xx_config[pinx].int_count_en=0; Settings.mcp230xx_config[pinx].int_retain_flag=0; Settings.mcp230xx_config[pinx].spare13=0; Settings.mcp230xx_config[pinx].spare14=0; Settings.mcp230xx_config[pinx].spare15=0; } Settings.mcp230xx_int_prio = 0; Settings.mcp230xx_int_timer = 0; MCP230xx_ApplySettings(); char pulluptxt[7]; char intmodetxt[9]; sprintf(pulluptxt,ConvertNumTxt(pullup)); uint8_t intmode = 3; if ((pinmode > 1) && (pinmode < 5)) { intmode = 0; } sprintf(intmodetxt,IntModeTxt(intmode)); Response_P(MCP230XX_SENSOR_RESPONSE,99,pinmode,pulluptxt,intmodetxt,""); } bool MCP230xx_Command(void) { bool serviced = true; bool validpin = false; uint8_t paramcount = 0; if (XdrvMailbox.data_len > 0) { paramcount=1; } else { serviced = false; return serviced; } char sub_string[XdrvMailbox.data_len]; for (uint32_t ca=0;ca 1) { uint8_t intpri = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); if ((intpri >= 0) && (intpri <= 20)) { Settings.mcp230xx_int_prio = intpri; Response_P(MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio); return serviced; } } else { Response_P(MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio); return serviced; } } if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTTIMER")) { if (paramcount > 1) { uint8_t inttim = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); if ((inttim >= 0) && (inttim <= 3600)) { Settings.mcp230xx_int_timer = inttim; MCP230xx_CheckForIntCounter(); Response_P(MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer); return serviced; } } else { Response_P(MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer); return serviced; } } if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTDEF")) { if (paramcount > 1) { uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); if (pin < mcp230xx_pincount) { if (pin == 0) { if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true; } else { validpin = true; } } if (validpin) { if (paramcount > 2) { uint8_t intdef = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); if ((intdef >= 0) && (intdef <= 15)) { Settings.mcp230xx_config[pin].int_report_defer=intdef; if (Settings.mcp230xx_config[pin].int_count_en) { Settings.mcp230xx_config[pin].int_count_en=0; MCP230xx_CheckForIntCounter(); AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled INTCNT for pin D%i"),pin); } Response_P(MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer); return serviced; } else { serviced=false; return serviced; } } else { Response_P(MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer); return serviced; } } serviced = false; return serviced; } else { serviced = false; return serviced; } } if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTCNT")) { if (paramcount > 1) { uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); if (pin < mcp230xx_pincount) { if (pin == 0) { if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true; } else { validpin = true; } } if (validpin) { if (paramcount > 2) { uint8_t intcnt = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); if ((intcnt >= 0) && (intcnt <= 1)) { Settings.mcp230xx_config[pin].int_count_en=intcnt; if (Settings.mcp230xx_config[pin].int_report_defer) { Settings.mcp230xx_config[pin].int_report_defer=0; AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled INTDEF for pin D%i"),pin); } if (Settings.mcp230xx_config[pin].int_report_mode < 3) { Settings.mcp230xx_config[pin].int_report_mode=3; AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled immediate interrupt/telemetry reporting for pin D%i"),pin); } if ((Settings.mcp230xx_config[pin].int_count_en) && (!Settings.mcp230xx_int_timer)) { AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - INTCNT enabled for pin D%i but global INTTIMER is disabled!"),pin); } MCP230xx_CheckForIntCounter(); Response_P(MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en); return serviced; } else { serviced=false; return serviced; } } else { Response_P(MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en); return serviced; } } serviced = false; return serviced; } else { serviced = false; return serviced; } } if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTRETAIN")) { if (paramcount > 1) { uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); if (pin < mcp230xx_pincount) { if (pin == 0) { if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true; } else { validpin = true; } } if (validpin) { if (paramcount > 2) { uint8_t int_retain = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); if ((int_retain >= 0) && (int_retain <= 1)) { Settings.mcp230xx_config[pin].int_retain_flag=int_retain; Response_P(MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag); MCP230xx_CheckForIntRetainer(); return serviced; } else { serviced=false; return serviced; } } else { Response_P(MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag); return serviced; } } serviced = false; return serviced; } else { serviced = false; return serviced; } } uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1)); if (pin < mcp230xx_pincount) { if (0 == pin) { if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1), "0")) validpin=true; } else { validpin=true; } } if (validpin && (paramcount > 1)) { if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "?")) { uint8_t port = 0; if (pin > 7) { port = 1; } uint8_t portdata = MCP230xx_readGPIO(port); char pulluptxtr[7],pinstatustxtr[7]; char intmodetxt[9]; sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode)); sprintf(pulluptxtr,ConvertNumTxt(Settings.mcp230xx_config[pin].pullup)); #ifdef USE_MCP230xx_OUTPUT uint8_t pinmod = Settings.mcp230xx_config[pin].pinmode; sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1,pinmod)); Response_P(MCP230XX_SENSOR_RESPONSE,pin,pinmod,pulluptxtr,intmodetxt,pinstatustxtr); #else sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1)); Response_P(MCP230XX_SENSOR_RESPONSE,pin,Settings.mcp230xx_config[pin].pinmode,pulluptxtr,intmodetxt,pinstatustxtr); #endif return serviced; } #ifdef USE_MCP230xx_OUTPUT if (Settings.mcp230xx_config[pin].pinmode >= 5) { uint8_t pincmd = Settings.mcp230xx_config[pin].pinmode - 5; if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "ON")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "1"))) { MCP230xx_SetOutPin(pin,abs(pincmd-1)); return serviced; } if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "OFF")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0"))) { MCP230xx_SetOutPin(pin,pincmd); return serviced; } if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "T")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "2"))) { MCP230xx_SetOutPin(pin,2); return serviced; } } #endif uint8_t pinmode = 0; uint8_t pullup = 0; uint8_t intmode = 0; if (paramcount > 1) { pinmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2)); } if (paramcount > 2) { pullup = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3)); } if (paramcount > 3) { intmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4)); } #ifdef USE_MCP230xx_OUTPUT if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 7) && (pullup < 2) && (paramcount > 2)) { #else if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 5) && (pullup < 2) && (paramcount > 2)) { #endif Settings.mcp230xx_config[pin].pinmode=pinmode; Settings.mcp230xx_config[pin].pullup=pullup; if ((pinmode > 1) && (pinmode < 5)) { if ((intmode >= 0) && (intmode <= 3)) { Settings.mcp230xx_config[pin].int_report_mode=intmode; } } else { Settings.mcp230xx_config[pin].int_report_mode=3; } MCP230xx_ApplySettings(); uint8_t port = 0; if (pin > 7) { port = 1; } uint8_t portdata = MCP230xx_readGPIO(port); char pulluptxtc[7], pinstatustxtc[7]; char intmodetxt[9]; sprintf(pulluptxtc,ConvertNumTxt(pullup)); sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode)); #ifdef USE_MCP230xx_OUTPUT sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1,Settings.mcp230xx_config[pin].pinmode)); #else sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1)); #endif Response_P(MCP230XX_SENSOR_RESPONSE,pin,pinmode,pulluptxtc,intmodetxt,pinstatustxtc); return serviced; } } else { serviced=false; return serviced; } return serviced; } #ifdef USE_MCP230xx_DISPLAYOUTPUT const char HTTP_SNS_MCP230xx_OUTPUT[] PROGMEM = "{s}MCP230XX D%d{m}%s{e}"; void MCP230xx_UpdateWebData(void) { uint8_t gpio1 = MCP230xx_readGPIO(0); uint8_t gpio2 = 0; if (2 == mcp230xx_type) { gpio2 = MCP230xx_readGPIO(1); } uint16_t gpio = (gpio2 << 8) + gpio1; for (uint32_t pin = 0; pin < mcp230xx_pincount; pin++) { if (Settings.mcp230xx_config[pin].pinmode >= 5) { char stt[7]; sprintf(stt,ConvertNumTxt((gpio>>pin)&1,Settings.mcp230xx_config[pin].pinmode)); WSContentSend_PD(HTTP_SNS_MCP230xx_OUTPUT, pin, stt); } } } #endif #ifdef USE_MCP230xx_OUTPUT void MCP230xx_OutputTelemetry(void) { uint8_t outputcount = 0; uint16_t gpiototal = 0; uint8_t gpioa = 0; uint8_t gpiob = 0; gpioa=MCP230xx_readGPIO(0); if (2 == mcp230xx_type) { gpiob=MCP230xx_readGPIO(1); } gpiototal=((uint16_t)gpiob << 8) | gpioa; for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { if (Settings.mcp230xx_config[pinx].pinmode >= 5) outputcount++; } if (outputcount) { char stt[7]; ResponseTime_P(PSTR(",\"MCP230_OUT\":{")); for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { if (Settings.mcp230xx_config[pinx].pinmode >= 5) { sprintf(stt,ConvertNumTxt(((gpiototal>>pinx)&1),Settings.mcp230xx_config[pinx].pinmode)); ResponseAppend_P(PSTR("\"OUT_D%i\":\"%s\","),pinx,stt); } } ResponseAppend_P(PSTR("\"END\":1}}")); MqttPublishTeleSensor(); } } #endif void MCP230xx_Interrupt_Counter_Report(void) { ResponseTime_P(PSTR(",\"MCP230_INTTIMER\":{")); for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { if (Settings.mcp230xx_config[pinx].int_count_en) { ResponseAppend_P(PSTR("\"INTCNT_D%i\":%i,"),pinx,mcp230xx_int_counter[pinx]); mcp230xx_int_counter[pinx]=0; } } ResponseAppend_P(PSTR("\"END\":1}}")); MqttPublishTeleSensor(); mcp230xx_int_sec_counter = 0; } void MCP230xx_Interrupt_Retain_Report(void) { uint16_t retainresult = 0; ResponseTime_P(PSTR(",\"MCP_INTRETAIN\":{")); for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) { if (Settings.mcp230xx_config[pinx].int_retain_flag) { ResponseAppend_P(PSTR("\"D%i\":%i,"),pinx,mcp230xx_int_retainer[pinx]); retainresult |= (((mcp230xx_int_retainer[pinx])&1) << pinx); mcp230xx_int_retainer[pinx]=0; } } ResponseAppend_P(PSTR("\"Value\":%u}}"),retainresult); MqttPublishTeleSensor(); } bool Xsns29(uint8_t function) { if (!I2cEnabled(XI2C_22)) { return false; } bool result = false; if (FUNC_INIT == function) { MCP230xx_Detect(); } else if (mcp230xx_type) { switch (function) { case FUNC_EVERY_50_MSECOND: if (mcp230xx_int_en) { mcp230xx_int_prio_counter++; if ((mcp230xx_int_prio_counter) >= (Settings.mcp230xx_int_prio)) { MCP230xx_CheckForInterrupt(); mcp230xx_int_prio_counter=0; } } break; case FUNC_EVERY_SECOND: if (mcp230xx_int_counter_en) { mcp230xx_int_sec_counter++; if (mcp230xx_int_sec_counter >= Settings.mcp230xx_int_timer) { MCP230xx_Interrupt_Counter_Report(); } } if (tele_period == 0) { if (mcp230xx_int_retainer_en) { MCP230xx_Interrupt_Retain_Report(); } #ifdef USE_MCP230xx_OUTPUT MCP230xx_OutputTelemetry(); #endif } break; case FUNC_JSON_APPEND: MCP230xx_Show(1); break; case FUNC_COMMAND_SENSOR: if (XSNS_29 == XdrvMailbox.index) { result = MCP230xx_Command(); } break; #ifdef USE_WEBSERVER #ifdef USE_MCP230xx_OUTPUT #ifdef USE_MCP230xx_DISPLAYOUTPUT case FUNC_WEB_SENSOR: MCP230xx_UpdateWebData(); break; #endif #endif #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino" # 46 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino" #ifdef USE_I2C #ifdef USE_MPR121 #define XSNS_30 30 #define XI2C_23 23 #define MPR121_ELEX_REG 0x00 #define MPR121_MHDR_REG 0x2B #define MPR121_MHDR_VAL 0x01 #define MPR121_NHDR_REG 0x2C #define MPR121_NHDR_VAL 0x01 #define MPR121_NCLR_REG 0x2D #define MPR121_NCLR_VAL 0x0E #define MPR121_MHDF_REG 0x2F #define MPR121_MHDF_VAL 0x01 #define MPR121_NHDF_REG 0x30 #define MPR121_NHDF_VAL 0x05 #define MPR121_NCLF_REG 0x31 #define MPR121_NCLF_VAL 0x01 #define MPR121_MHDPROXR_REG 0x36 #define MPR121_MHDPROXR_VAL 0x3F #define MPR121_NHDPROXR_REG 0x37 #define MPR121_NHDPROXR_VAL 0x5F #define MPR121_NCLPROXR_REG 0x38 #define MPR121_NCLPROXR_VAL 0x04 #define MPR121_FDLPROXR_REG 0x39 #define MPR121_FDLPROXR_VAL 0x00 #define MPR121_MHDPROXF_REG 0x3A #define MPR121_MHDPROXF_VAL 0x01 #define MPR121_NHDPROXF_REG 0x3B #define MPR121_NHDPROXF_VAL 0x01 #define MPR121_NCLPROXF_REG 0x3C #define MPR121_NCLPROXF_VAL 0x1F #define MPR121_FDLPROXF_REG 0x3D #define MPR121_FDLPROXF_VAL 0x04 #define MPR121_E0TTH_REG 0x41 #define MPR121_E0TTH_VAL 12 #define MPR121_E0RTH_REG 0x42 #define MPR121_E0RTH_VAL 6 #define MPR121_CDT_REG 0x5D #define MPR121_CDT_VAL 0x20 #define MPR121_ECR_REG 0x5E #define MPR121_ECR_VAL 0x8F #define MPR121_SRST_REG 0x80 #define MPR121_SRST_VAL 0x63 #define BITC(sensor,position) ((pS->current[sensor] >> position) & 1) #define BITP(sensor,position) ((pS->previous[sensor] >> position) & 1) # 195 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino" typedef struct mpr121 mpr121; struct mpr121 { const uint8_t i2c_addr[4] = { 0x5A, 0x5B, 0x5C, 0x5D }; const char id[4] = { 'A', 'B', 'C', 'D' }; bool connected[4] = { false, false, false, false }; bool running[4] = { false, false, false, false }; uint16_t current[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; uint16_t previous[4] = { 0x0000, 0x0000, 0x0000, 0x0000 }; }; bool mpr21_found = false; # 217 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino" void Mpr121Init(struct mpr121 *pS, bool initial) { for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) { if (initial && I2cActive(pS->i2c_addr[i])) { continue; } pS->connected[i] = (I2cWrite8(pS->i2c_addr[i], MPR121_SRST_REG, MPR121_SRST_VAL) && (0x24 == I2cRead8(pS->i2c_addr[i], 0x5D))); if (pS->connected[i]) { mpr21_found = true; char device_name[16]; snprintf_P(device_name, sizeof(device_name), PSTR("MPR121(%c)"), pS->id[i]); I2cSetActiveFound(pS->i2c_addr[i], device_name); for (uint32_t j = 0; j < 13; j++) { I2cWrite8(pS->i2c_addr[i], MPR121_E0TTH_REG + 2 * j, MPR121_E0TTH_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_E0RTH_REG + 2 * j, MPR121_E0RTH_VAL); } I2cWrite8(pS->i2c_addr[i], MPR121_MHDR_REG, MPR121_MHDR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NHDR_REG, MPR121_NHDR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NCLR_REG, MPR121_NCLR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_MHDF_REG, MPR121_MHDF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NHDF_REG, MPR121_NHDF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NCLF_REG, MPR121_NCLF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXR_REG, MPR121_MHDPROXR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXR_REG, MPR121_NHDPROXR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXR_REG, MPR121_NCLPROXR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXR_REG, MPR121_FDLPROXR_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXF_REG, MPR121_MHDPROXF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXF_REG, MPR121_NHDPROXF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXF_REG, MPR121_NCLPROXF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXF_REG, MPR121_FDLPROXF_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_CDT_REG, MPR121_CDT_VAL); I2cWrite8(pS->i2c_addr[i], MPR121_ECR_REG, MPR121_ECR_VAL); pS->running[i] = (0x00 != I2cRead8(pS->i2c_addr[i], MPR121_ECR_REG)); AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_I2C "MPR121%c: %sRunning"), pS->id[i], (pS->running[i]) ? "" : "NOT"); } else { pS->running[i] = false; } } if (!(pS->connected[0] || pS->connected[1] || pS->connected[2] || pS->connected[3])) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C "MPR121: No sensors found")); } } # 326 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino" void Mpr121Show(struct mpr121 *pS, uint8_t function) { for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) { if (pS->connected[i]) { if (!I2cValidRead16LE(&pS->current[i], pS->i2c_addr[i], MPR121_ELEX_REG)) { AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Cannot read data!"), pS->id[i]); Mpr121Init(pS, false); return; } if (BITC(i, 15)) { I2cWrite8(pS->i2c_addr[i], MPR121_ELEX_REG, 0x00); AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Excess current detected! Fix circuits if it happens repeatedly! Soft-resetting MPR121 ..."), pS->id[i]); Mpr121Init(pS, false); return; } } if (pS->running[i]) { if (FUNC_JSON_APPEND == function) { ResponseAppend_P(PSTR(",\"MPR121%c\":{"), pS->id[i]); } for (uint32_t j = 0; j < 13; j++) { if ((FUNC_EVERY_50_MSECOND == function) && (BITC(i, j) != BITP(i, j))) { Response_P(PSTR("{\"MPR121%c\":{\"Button%i\":%i}}"), pS->id[i], j, BITC(i, j)); MqttPublishPrefixTopic_P(RESULT_OR_STAT, mqtt_data); } #ifdef USE_WEBSERVER if (FUNC_WEB_SENSOR == function) { WSContentSend_PD(PSTR("{s}MPR121%c Button%d{m}%d{e}"), pS->id[i], j, BITC(i, j)); } #endif if (FUNC_JSON_APPEND == function) { ResponseAppend_P(PSTR("%s\"Button%i\":%i"), (j > 0 ? "," : ""), j, BITC(i, j)); } } pS->previous[i] = pS->current[i]; if (FUNC_JSON_APPEND == function) { ResponseJsonEnd(); } } } } # 410 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino" bool Xsns30(uint8_t function) { if (!I2cEnabled(XI2C_23)) { return false; } bool result = false; static struct mpr121 mpr121; if (FUNC_INIT == function) { Mpr121Init(&mpr121, true); } else if (mpr21_found) { switch (function) { case FUNC_EVERY_50_MSECOND: Mpr121Show(&mpr121, FUNC_EVERY_50_MSECOND); break; case FUNC_JSON_APPEND: Mpr121Show(&mpr121, FUNC_JSON_APPEND); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: Mpr121Show(&mpr121, FUNC_WEB_SENSOR); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino" #ifdef USE_I2C #ifdef USE_CCS811 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino" #define XSNS_31 31 #define XI2C_24 24 #define EVERYNSECONDS 5 #include "Adafruit_CCS811.h" Adafruit_CCS811 ccs; uint8_t CCS811_ready = 0; uint8_t CCS811_type = 0;; uint16_t eCO2; uint16_t TVOC; uint8_t tcnt = 0; uint8_t ecnt = 0; void CCS811Detect(void) { if (I2cActive(CCS811_ADDRESS)) { return; } if (!ccs.begin(CCS811_ADDRESS)) { CCS811_type = 1; I2cSetActiveFound(CCS811_ADDRESS, "CCS811"); } } void CCS811Update(void) { tcnt++; if (tcnt >= EVERYNSECONDS) { tcnt = 0; CCS811_ready = 0; if (ccs.available()) { if (!ccs.readData()){ TVOC = ccs.getTVOC(); eCO2 = ccs.geteCO2(); CCS811_ready = 1; if (global_update && global_humidity>0 && global_temperature!=9999) { ccs.setEnvironmentalData((uint8_t)global_humidity, global_temperature); } ecnt = 0; } } else { ecnt++; if (ecnt > 6) { ccs.begin(CCS811_ADDRESS); } } } } const char HTTP_SNS_CCS811[] PROGMEM = "{s}CCS811 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}" "{s}CCS811 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}"; void CCS811Show(bool json) { if (CCS811_ready) { if (json) { ResponseAppend_P(PSTR(",\"CCS811\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), eCO2,TVOC); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, eCO2); #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_CCS811, eCO2, TVOC); #endif } } } bool Xsns31(uint8_t function) { if (!I2cEnabled(XI2C_24)) { return false; } bool result = false; if (FUNC_INIT == function) { CCS811Detect(); } else if (CCS811_type) { switch (function) { case FUNC_EVERY_SECOND: CCS811Update(); break; case FUNC_JSON_APPEND: CCS811Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: CCS811Show(0); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino" #ifdef USE_I2C #ifdef USE_MPU6050 # 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino" #define XSNS_32 32 #define XI2C_25 25 #define D_SENSOR_MPU6050 "MPU6050" #define MPU_6050_ADDR_AD0_LOW 0x68 #define MPU_6050_ADDR_AD0_HIGH 0x69 uint8_t MPU_6050_address; uint8_t MPU_6050_addresses[] = { MPU_6050_ADDR_AD0_LOW, MPU_6050_ADDR_AD0_HIGH }; uint8_t MPU_6050_found; int16_t MPU_6050_ax = 0, MPU_6050_ay = 0, MPU_6050_az = 0; int16_t MPU_6050_gx = 0, MPU_6050_gy = 0, MPU_6050_gz = 0; int16_t MPU_6050_temperature = 0; #ifdef USE_MPU6050_DMP #include "MPU6050_6Axis_MotionApps20.h" #include "I2Cdev.h" #include typedef struct MPU6050_DMP{ uint8_t devStatus; uint16_t packetSize; uint16_t fifoCount; uint8_t fifoBuffer[64]; Quaternion q; VectorInt16 aa; VectorInt16 aaReal; VectorFloat gravity; float euler[3]; float yawPitchRoll[3]; } MPU6050_DMP; MPU6050_DMP MPU6050_dmp; #else #include #endif MPU6050 mpu6050; void MPU_6050PerformReading(void) { #ifdef USE_MPU6050_DMP mpu6050.resetFIFO(); MPU6050_dmp.fifoCount = mpu6050.getFIFOCount(); while (MPU6050_dmp.fifoCount < MPU6050_dmp.packetSize) MPU6050_dmp.fifoCount = mpu6050.getFIFOCount(); mpu6050.getFIFOBytes(MPU6050_dmp.fifoBuffer, MPU6050_dmp.packetSize); MPU6050_dmp.fifoCount -= MPU6050_dmp.packetSize; mpu6050.dmpGetQuaternion(&MPU6050_dmp.q, MPU6050_dmp.fifoBuffer); mpu6050.dmpGetEuler(MPU6050_dmp.euler, &MPU6050_dmp.q); mpu6050.dmpGetAccel(&MPU6050_dmp.aa, MPU6050_dmp.fifoBuffer); mpu6050.dmpGetGravity(&MPU6050_dmp.gravity, &MPU6050_dmp.q); mpu6050.dmpGetLinearAccel(&MPU6050_dmp.aaReal, &MPU6050_dmp.aa, &MPU6050_dmp.gravity); mpu6050.dmpGetYawPitchRoll(MPU6050_dmp.yawPitchRoll, &MPU6050_dmp.q, &MPU6050_dmp.gravity); MPU_6050_gx = MPU6050_dmp.euler[0] * 180/M_PI; MPU_6050_gy = MPU6050_dmp.euler[1] * 180/M_PI; MPU_6050_gz = MPU6050_dmp.euler[2] * 180/M_PI; MPU_6050_ax = MPU6050_dmp.aaReal.x; MPU_6050_ay = MPU6050_dmp.aaReal.y; MPU_6050_az = MPU6050_dmp.aaReal.z; #else mpu6050.getMotion6( &MPU_6050_ax, &MPU_6050_ay, &MPU_6050_az, &MPU_6050_gx, &MPU_6050_gy, &MPU_6050_gz ); #endif MPU_6050_temperature = mpu6050.getTemperature(); } # 119 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino" void MPU_6050Detect(void) { for (uint32_t i = 0; i < sizeof(MPU_6050_addresses); i++) { MPU_6050_address = MPU_6050_addresses[i]; if (!I2cSetDevice(MPU_6050_address)) { break; } mpu6050.setAddr(MPU_6050_addresses[i]); #ifdef USE_MPU6050_DMP MPU6050_dmp.devStatus = mpu6050.dmpInitialize(); mpu6050.setXGyroOffset(220); mpu6050.setYGyroOffset(76); mpu6050.setZGyroOffset(-85); mpu6050.setZAccelOffset(1788); if (MPU6050_dmp.devStatus == 0) { mpu6050.setDMPEnabled(true); MPU6050_dmp.packetSize = mpu6050.dmpGetFIFOPacketSize(); MPU_6050_found = true; } #else mpu6050.initialize(); MPU_6050_found = mpu6050.testConnection(); #endif Settings.flag2.axis_resolution = 2; } if (MPU_6050_found) { I2cSetActiveFound(MPU_6050_address, D_SENSOR_MPU6050); } } #define D_YAW "Yaw" #define D_PITCH "Pitch" #define D_ROLL "Roll" #ifdef USE_WEBSERVER const char HTTP_SNS_AXIS[] PROGMEM = "{s}" D_SENSOR_MPU6050 " " D_AX_AXIS "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_AY_AXIS "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_AZ_AXIS "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_GX_AXIS "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_GY_AXIS "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_GZ_AXIS "{m}%s{e}"; #ifdef USE_MPU6050_DMP const char HTTP_SNS_YPR[] PROGMEM = "{s}" D_SENSOR_MPU6050 " " D_YAW "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_PITCH "{m}%s{e}" "{s}" D_SENSOR_MPU6050 " " D_ROLL "{m}%s{e}"; #endif #endif #define D_JSON_AXIS_AX "AccelXAxis" #define D_JSON_AXIS_AY "AccelYAxis" #define D_JSON_AXIS_AZ "AccelZAxis" #define D_JSON_AXIS_GX "GyroXAxis" #define D_JSON_AXIS_GY "GyroYAxis" #define D_JSON_AXIS_GZ "GyroZAxis" #define D_JSON_YAW "Yaw" #define D_JSON_PITCH "Pitch" #define D_JSON_ROLL "Roll" void MPU_6050Show(bool json) { MPU_6050PerformReading(); double tempConv = (MPU_6050_temperature / 340.0 + 35.53); char temperature[33]; dtostrfd(tempConv, Settings.flag2.temperature_resolution, temperature); char axis_ax[33]; dtostrfd(MPU_6050_ax, Settings.flag2.axis_resolution, axis_ax); char axis_ay[33]; dtostrfd(MPU_6050_ay, Settings.flag2.axis_resolution, axis_ay); char axis_az[33]; dtostrfd(MPU_6050_az, Settings.flag2.axis_resolution, axis_az); char axis_gx[33]; dtostrfd(MPU_6050_gx, Settings.flag2.axis_resolution, axis_gx); char axis_gy[33]; dtostrfd(MPU_6050_gy, Settings.flag2.axis_resolution, axis_gy); char axis_gz[33]; dtostrfd(MPU_6050_gz, Settings.flag2.axis_resolution, axis_gz); #ifdef USE_MPU6050_DMP char axis_yaw[33]; dtostrfd(MPU6050_dmp.yawPitchRoll[0] / PI * 180.0, Settings.flag2.axis_resolution, axis_yaw); char axis_pitch[33]; dtostrfd(MPU6050_dmp.yawPitchRoll[1] / PI * 180.0, Settings.flag2.axis_resolution, axis_pitch); char axis_roll[33]; dtostrfd(MPU6050_dmp.yawPitchRoll[2] / PI * 180.0, Settings.flag2.axis_resolution, axis_roll); #endif if (json) { char json_axis_ax[25]; snprintf_P(json_axis_ax, sizeof(json_axis_ax), PSTR(",\"" D_JSON_AXIS_AX "\":%s"), axis_ax); char json_axis_ay[25]; snprintf_P(json_axis_ay, sizeof(json_axis_ay), PSTR(",\"" D_JSON_AXIS_AY "\":%s"), axis_ay); char json_axis_az[25]; snprintf_P(json_axis_az, sizeof(json_axis_az), PSTR(",\"" D_JSON_AXIS_AZ "\":%s"), axis_az); char json_axis_gx[25]; snprintf_P(json_axis_gx, sizeof(json_axis_gx), PSTR(",\"" D_JSON_AXIS_GX "\":%s"), axis_gx); char json_axis_gy[25]; snprintf_P(json_axis_gy, sizeof(json_axis_gy), PSTR(",\"" D_JSON_AXIS_GY "\":%s"), axis_gy); char json_axis_gz[25]; snprintf_P(json_axis_gz, sizeof(json_axis_gz), PSTR(",\"" D_JSON_AXIS_GZ "\":%s"), axis_gz); #ifdef USE_MPU6050_DMP char json_ypr_y[25]; snprintf_P(json_ypr_y, sizeof(json_ypr_y), PSTR(",\"" D_JSON_YAW "\":%s"), axis_yaw); char json_ypr_p[25]; snprintf_P(json_ypr_p, sizeof(json_ypr_p), PSTR(",\"" D_JSON_PITCH "\":%s"), axis_pitch); char json_ypr_r[25]; snprintf_P(json_ypr_r, sizeof(json_ypr_r), PSTR(",\"" D_JSON_ROLL "\":%s"), axis_roll); ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s%s%s%s}"), D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz, json_ypr_y, json_ypr_p, json_ypr_r); #else ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s}"), D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz); #endif #ifdef USE_DOMOTICZ DomoticzSensor(DZ_TEMP, temperature); #endif #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TEMP, D_SENSOR_MPU6050, temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_AXIS, axis_ax, axis_ay, axis_az, axis_gx, axis_gy, axis_gz); #ifdef USE_MPU6050_DMP WSContentSend_PD(HTTP_SNS_YPR, axis_yaw, axis_pitch, axis_roll); #endif #endif } } bool Xsns32(uint8_t function) { if (!I2cEnabled(XI2C_25)) { return false; } bool result = false; if (FUNC_INIT == function) { MPU_6050Detect(); } else if (MPU_6050_found) { switch (function) { case FUNC_EVERY_SECOND: if (tele_period == Settings.tele_period -3) { MPU_6050PerformReading(); } break; case FUNC_JSON_APPEND: MPU_6050Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: MPU_6050Show(0); MPU_6050PerformReading(); break; #endif } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino" #ifdef USE_I2C #ifdef USE_DS3231 # 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino" #define XSNS_33 33 #define XI2C_26 26 #ifndef USE_RTC_ADDR #define USE_RTC_ADDR 0x68 #endif #define RTC_SECONDS 0x00 #define RTC_MINUTES 0x01 #define RTC_HOURS 0x02 #define RTC_DAY 0x03 #define RTC_DATE 0x04 #define RTC_MONTH 0x05 #define RTC_YEAR 0x06 #define RTC_CONTROL 0x0E #define RTC_STATUS 0x0F #define OSF 7 #define EOSC 7 #define BBSQW 6 #define CONV 5 #define RS2 4 #define RS1 3 #define INTCN 2 #define HR1224 6 #define CENTURY 7 #define DYDT 6 bool ds3231ReadStatus = false; bool ds3231WriteStatus = false; bool DS3231chipDetected = false; void DS3231Detect(void) { if (I2cActive(USE_RTC_ADDR)) { return; } if (I2cValidRead(USE_RTC_ADDR, RTC_STATUS, 1)) { I2cSetActiveFound(USE_RTC_ADDR, "DS3231"); DS3231chipDetected = true; } } uint8_t bcd2dec(uint8_t n) { return n - 6 * (n >> 4); } uint8_t dec2bcd(uint8_t n) { return n + 6 * (n / 10); } uint32_t ReadFromDS3231(void) { TIME_T tm; tm.second = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_SECONDS)); tm.minute = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MINUTES)); tm.hour = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_HOURS) & ~_BV(HR1224)); tm.day_of_week = I2cRead8(USE_RTC_ADDR, RTC_DAY); tm.day_of_month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_DATE)); tm.month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MONTH) & ~_BV(CENTURY)); tm.year = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_YEAR)); return MakeTime(tm); } void SetDS3231Time (uint32_t epoch_time) { TIME_T tm; BreakTime(epoch_time, tm); I2cWrite8(USE_RTC_ADDR, RTC_SECONDS, dec2bcd(tm.second)); I2cWrite8(USE_RTC_ADDR, RTC_MINUTES, dec2bcd(tm.minute)); I2cWrite8(USE_RTC_ADDR, RTC_HOURS, dec2bcd(tm.hour)); I2cWrite8(USE_RTC_ADDR, RTC_DAY, tm.day_of_week); I2cWrite8(USE_RTC_ADDR, RTC_DATE, dec2bcd(tm.day_of_month)); I2cWrite8(USE_RTC_ADDR, RTC_MONTH, dec2bcd(tm.month)); I2cWrite8(USE_RTC_ADDR, RTC_YEAR, dec2bcd(tm.year)); I2cWrite8(USE_RTC_ADDR, RTC_STATUS, I2cRead8(USE_RTC_ADDR, RTC_STATUS) & ~_BV(OSF)); } void DS3231EverySecond(void) { TIME_T tmpTime; if (!ds3231ReadStatus && Rtc.utc_time < START_VALID_TIME ) { ntp_force_sync = true; Rtc.utc_time = ReadFromDS3231(); BreakTime(Rtc.utc_time, tmpTime); if (Rtc.utc_time < START_VALID_TIME ) { ds3231ReadStatus = true; } RtcTime.year = tmpTime.year + 1970; Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year); Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year); AddLog_P2(LOG_LEVEL_INFO, PSTR("Set time from DS3231 to RTC (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str()); if (Rtc.local_time < START_VALID_TIME) { rules_flag.time_init = 1; } else { rules_flag.time_set = 1; } } else if (!ds3231WriteStatus && Rtc.utc_time > START_VALID_TIME && abs(Rtc.utc_time - ReadFromDS3231()) > 60) { AddLog_P2(LOG_LEVEL_INFO, PSTR("Write Time TO DS3231 from NTP (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"), GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str()); SetDS3231Time (Rtc.utc_time); ds3231WriteStatus = true; } } bool Xsns33(uint8_t function) { if (!I2cEnabled(XI2C_26)) { return false; } bool result = false; if (FUNC_INIT == function) { DS3231Detect(); } else if (DS3231chipDetected) { switch (function) { case FUNC_EVERY_SECOND: DS3231EverySecond(); break; } } return result; } #endif #endif # 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino" # 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino" #ifdef USE_HX711 # 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino" #define XSNS_34 34 #ifndef HX_MAX_WEIGHT #define HX_MAX_WEIGHT 20000 #endif #ifndef HX_REFERENCE #define HX_REFERENCE 250 #endif #ifndef HX_SCALE #define HX_SCALE 120 #endif #define HX_TIMEOUT 120 #define HX_SAMPLES 10 #define HX_CAL_TIMEOUT 15 #define HX_GAIN_128 1 #define HX_GAIN_32 2 #define HX_GAIN_64 3 #define D_JSON_WEIGHT_REF "WeightRef" #define D_JSON_WEIGHT_CAL "WeightCal" #define D_JSON_WEIGHT_MAX "WeightMax" #define D_JSON_WEIGHT_ITEM "WeightItem" #define D_JSON_WEIGHT_CHANGE "WeightChange" #define D_JSON_WEIGHT_RAW "WeightRaw" #define D_JSON_WEIGHT_DELTA "WeightDelta" enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, HX_CAL_DONE, HX_CAL_FIRST, HX_CAL_RESET, HX_CAL_START }; const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|" D_HX_CAL_REFERENCE "|" D_HX_CAL_REMOVE; struct HX { long weight = 0; long raw = 0; long last_weight = 0; long sum_weight = 0; long sum_raw = 0; long offset = 0; long scale = 1; long weight_diff = 0; uint8_t type = 1; uint8_t sample_count = 0; uint8_t calibrate_step = HX_CAL_END; uint8_t calibrate_timer = 0; uint8_t calibrate_msg = 0; uint8_t pin_sck; uint8_t pin_dout; bool tare_flg = false; bool weight_changed = false; uint16_t weight_delta = 4; } Hx; bool HxIsReady(uint16_t timeout) { uint32_t start = millis(); while ((digitalRead(Hx.pin_dout) == HIGH) && (millis() - start < timeout)) { yield(); } return (digitalRead(Hx.pin_dout) == LOW); } long HxRead(void) { if (!HxIsReady(HX_TIMEOUT)) { return -1; } uint8_t data[3] = { 0 }; uint8_t filler = 0x00; data[2] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST); data[1] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST); data[0] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST); for (unsigned int i = 0; i < HX_GAIN_128; i++) { digitalWrite(Hx.pin_sck, HIGH); digitalWrite(Hx.pin_sck, LOW); } if (data[2] & 0x80) { filler = 0xFF; } unsigned long value = ( static_cast(filler) << 24 | static_cast(data[2]) << 16 | static_cast(data[1]) << 8 | static_cast(data[0]) ); return static_cast(value); } void HxResetPart(void) { Hx.tare_flg = true; Hx.sum_weight = 0; Hx.sample_count = 0; Hx.last_weight = 0; } void HxReset(void) { HxResetPart(); Settings.energy_frequency_calibration = 0; } void HxCalibrationStateTextJson(uint8_t msg_id) { char cal_text[30]; Hx.calibrate_msg = msg_id; Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates)); if (msg_id < 3) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("Sensor34")); } } void SetWeightDelta() { if (Settings.weight_change == 0) { Hx.weight_delta = 4; return; } if (Settings.weight_change > 100) { Hx.weight_delta = (Settings.weight_change - 100) * 10 + 100; return; } Hx.weight_delta = Settings.weight_change - 1; } # 192 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino" bool HxCommand(void) { bool serviced = true; bool show_parms = false; char sub_string[XdrvMailbox.data_len +1]; for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) { if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; } } switch (XdrvMailbox.payload) { case 1: HxReset(); Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, "Reset"); break; case 2: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); } Hx.scale = 1; HxReset(); Hx.calibrate_step = HX_CAL_START; Hx.calibrate_timer = 1; HxCalibrationStateTextJson(3); break; case 3: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); } show_parms = true; break; case 4: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.weight_calibration = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); Hx.scale = Settings.weight_calibration; } show_parms = true; break; case 5: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.weight_max = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) / 1000; } show_parms = true; break; case 6: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.weight_item = (unsigned long)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 2)) * 10); } show_parms = true; break; case 7: Settings.energy_frequency_calibration = Hx.weight; Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, D_JSON_DONE); break; case 8: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.SensorBits1.hx711_json_weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) & 1; } show_parms = true; break; case 9: if (strstr(XdrvMailbox.data, ",") != nullptr) { Settings.weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); SetWeightDelta(); } show_parms = true; break; default: show_parms = true; } if (show_parms) { char item[33]; dtostrfd((float)Settings.weight_item / 10, 1, item); Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\"" D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":%s,\"" D_JSON_WEIGHT_DELTA "\":%d}}"), Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, item, GetStateText(Settings.SensorBits1.hx711_json_weight_change), Settings.weight_change); } return serviced; } long HxWeight(void) { return (Hx.calibrate_step < HX_CAL_FAIL) ? Hx.weight : 0; } void HxInit(void) { Hx.type = 0; if ((pin[GPIO_HX711_DAT] < 99) && (pin[GPIO_HX711_SCK] < 99)) { Hx.pin_sck = pin[GPIO_HX711_SCK]; Hx.pin_dout = pin[GPIO_HX711_DAT]; pinMode(Hx.pin_sck, OUTPUT); pinMode(Hx.pin_dout, INPUT); digitalWrite(Hx.pin_sck, LOW); SetWeightDelta(); if (HxIsReady(8 * HX_TIMEOUT)) { if (!Settings.weight_max) { Settings.weight_max = HX_MAX_WEIGHT / 1000; } if (!Settings.weight_calibration) { Settings.weight_calibration = HX_SCALE; } if (!Settings.weight_reference) { Settings.weight_reference = HX_REFERENCE; } Hx.scale = Settings.weight_calibration; HxRead(); HxResetPart(); Hx.type = 1; } } } void HxEvery100mSecond(void) { long raw = HxRead(); Hx.sum_raw += raw; Hx.sum_weight += raw; Hx.sample_count++; if (HX_SAMPLES == Hx.sample_count) { long average = Hx.sum_weight / Hx.sample_count; long raw_average = Hx.sum_raw / Hx.sample_count; long value = average - Hx.offset; Hx.weight = value / Hx.scale; Hx.raw = raw_average / Hx.scale; if (Hx.weight < 0) { if (Settings.energy_frequency_calibration) { long difference = Settings.energy_frequency_calibration + Hx.weight; Hx.last_weight = difference; if (difference < 0) { HxReset(); } } Hx.weight = 0; } else { Hx.last_weight = Settings.energy_frequency_calibration; } if (Hx.tare_flg) { Hx.tare_flg = false; Hx.offset = average; } if (Hx.calibrate_step) { Hx.calibrate_timer--; if (HX_CAL_START == Hx.calibrate_step) { Hx.calibrate_step--; Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES); } else if (HX_CAL_RESET == Hx.calibrate_step) { if (Hx.calibrate_timer) { if (Hx.weight < (long)Settings.weight_reference) { Hx.calibrate_step--; Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES); HxCalibrationStateTextJson(2); } } else { Hx.calibrate_step = HX_CAL_FAIL; } } else if (HX_CAL_FIRST == Hx.calibrate_step) { if (Hx.calibrate_timer) { if (Hx.weight > (long)Settings.weight_reference) { Hx.calibrate_step--; } } else { Hx.calibrate_step = HX_CAL_FAIL; } } else if (HX_CAL_DONE == Hx.calibrate_step) { if (Hx.weight > (long)Settings.weight_reference) { Hx.calibrate_step = HX_CAL_FINISH; Settings.weight_calibration = Hx.weight / Settings.weight_reference; Hx.weight = 0; HxCalibrationStateTextJson(1); } else { Hx.calibrate_step = HX_CAL_FAIL; } } if (HX_CAL_FAIL == Hx.calibrate_step) { Hx.calibrate_step--; Hx.tare_flg = true; HxCalibrationStateTextJson(0); } if (HX_CAL_FINISH == Hx.calibrate_step) { Hx.calibrate_step--; Hx.calibrate_timer = 3 * (10 / HX_SAMPLES); Hx.scale = Settings.weight_calibration; } if (!Hx.calibrate_timer) { Hx.calibrate_step = HX_CAL_END; } } else { Hx.weight += Hx.last_weight; if (Settings.SensorBits1.hx711_json_weight_change) { if (abs(Hx.weight - Hx.weight_diff) > Hx.weight_delta) { Hx.weight_diff = Hx.weight; Hx.weight_changed = true; } else if (Hx.weight_changed && (Hx.weight == Hx.weight_diff)) { mqtt_data[0] = '\0'; ResponseAppendTime(); HxShow(true); ResponseJsonEnd(); MqttPublishTeleSensor(); Hx.weight_changed = false; } } } Hx.sum_weight = 0; Hx.sum_raw = 0; Hx.sample_count = 0; } } void HxSaveBeforeRestart(void) { Settings.energy_frequency_calibration = Hx.weight; Hx.sample_count = HX_SAMPLES +1; } #ifdef USE_WEBSERVER const char HTTP_HX711_WEIGHT[] PROGMEM = "{s}HX711 " D_WEIGHT "{m}%s " D_UNIT_KILOGRAM "{e}"; const char HTTP_HX711_COUNT[] PROGMEM = "{s}HX711 " D_COUNT "{m}%d{e}"; const char HTTP_HX711_CAL[] PROGMEM = "{s}HX711 %s{m}{e}"; #endif void HxShow(bool json) { char scount[30] = { 0 }; uint16_t count = 0; float weight = 0; if (Hx.calibrate_step < HX_CAL_FAIL) { if (Hx.weight && Settings.weight_item) { count = (Hx.weight * 10) / Settings.weight_item; if (count > 1) { snprintf_P(scount, sizeof(scount), PSTR(",\"" D_JSON_COUNT "\":%d"), count); } } weight = (float)Hx.weight / 1000; } char weight_chr[33]; dtostrfd(weight, Settings.flag2.weight_resolution, weight_chr); if (json) { ResponseAppend_P(PSTR(",\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s, \"" D_JSON_WEIGHT_RAW "\":%d}"), weight_chr, scount, Hx.raw); #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_HX711_WEIGHT, weight_chr); if (count > 1) { WSContentSend_PD(HTTP_HX711_COUNT, count); } if (Hx.calibrate_step) { char cal_text[30]; WSContentSend_PD(HTTP_HX711_CAL, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates)); } #endif } } #ifdef USE_WEBSERVER #ifdef USE_HX711_GUI #define WEB_HANDLE_HX711 "s34" const char S_CONFIGURE_HX711[] PROGMEM = D_CONFIGURE_HX711; const char HTTP_BTN_MENU_MAIN_HX711[] PROGMEM = "

"; const char HTTP_BTN_MENU_HX711[] PROGMEM = "

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

" D_REFERENCE_WEIGHT " (" D_UNIT_KILOGRAM ")

" "
" "
" "


" "
 " D_HX711_PARAMETERS " " "
" "

" D_ITEM_WEIGHT " (" D_UNIT_KILOGRAM ")

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