mirror of https://github.com/arendst/Tasmota.git
77284 lines
2.0 MiB
77284 lines
2.0 MiB
# 1 "C:\\Users\\martin\\AppData\\Local\\Temp\\tmpeqetuoko"
|
|
#include <Arduino.h>
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino"
|
|
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino"
|
|
#include <core_version.h>
|
|
#include "tasmota_version.h"
|
|
#include "tasmota.h"
|
|
#include "my_user_config.h"
|
|
#ifdef USE_MQTT_TLS
|
|
#include <t_bearssl.h>
|
|
#endif
|
|
#include "tasmota_post.h"
|
|
#include "i18n.h"
|
|
#include "tasmota_template.h"
|
|
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_4_0
|
|
#include "lwip/init.h"
|
|
#if LWIP_VERSION_MAJOR != 1
|
|
#error Please use stable lwIP v1.4
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#include <ESP8266HTTPClient.h>
|
|
#include <ESP8266httpUpdate.h>
|
|
#include <StreamString.h>
|
|
#include <ArduinoJson.h>
|
|
#ifdef USE_ARDUINO_OTA
|
|
#include <ArduinoOTA.h>
|
|
#ifndef USE_DISCOVERY
|
|
#define USE_DISCOVERY
|
|
#endif
|
|
#endif
|
|
#ifdef USE_DISCOVERY
|
|
#include <ESP8266mDNS.h>
|
|
#endif
|
|
#ifdef USE_I2C
|
|
#include <Wire.h>
|
|
#endif
|
|
#ifdef USE_SPI
|
|
#include <SPI.h>
|
|
#endif
|
|
|
|
|
|
#include "settings.h"
|
|
|
|
|
|
|
|
|
|
|
|
WiFiUDP PortUdp;
|
|
|
|
unsigned long feature_drv1;
|
|
unsigned long feature_drv2;
|
|
unsigned long feature_sns1;
|
|
unsigned long feature_sns2;
|
|
unsigned long feature5;
|
|
unsigned long feature6;
|
|
unsigned long serial_polling_window = 0;
|
|
unsigned long state_second = 0;
|
|
unsigned long state_50msecond = 0;
|
|
unsigned long state_100msecond = 0;
|
|
unsigned long state_250msecond = 0;
|
|
unsigned long pulse_timer[MAX_PULSETIMERS] = { 0 };
|
|
unsigned long blink_timer = 0;
|
|
unsigned long backlog_delay = 0;
|
|
power_t power = 0;
|
|
power_t last_power = 0;
|
|
power_t blink_power;
|
|
power_t blink_mask = 0;
|
|
power_t blink_powersave;
|
|
power_t latching_power = 0;
|
|
power_t rel_inverted = 0;
|
|
int serial_in_byte_counter = 0;
|
|
int ota_state_flag = 0;
|
|
int ota_result = 0;
|
|
int restart_flag = 0;
|
|
int wifi_state_flag = WIFI_RESTART;
|
|
int blinks = 201;
|
|
uint32_t uptime = 0;
|
|
uint32_t loop_load_avg = 0;
|
|
uint32_t global_update = 0;
|
|
uint32_t web_log_index = 1;
|
|
float global_temperature = 9999;
|
|
float global_humidity = 0;
|
|
float global_pressure = 0;
|
|
uint16_t tele_period = 9999;
|
|
uint16_t blink_counter = 0;
|
|
uint16_t seriallog_timer = 0;
|
|
uint16_t syslog_timer = 0;
|
|
int16_t save_data_counter;
|
|
RulesBitfield rules_flag;
|
|
uint8_t mqtt_cmnd_blocked = 0;
|
|
uint8_t mqtt_cmnd_blocked_reset = 0;
|
|
uint8_t state_250mS = 0;
|
|
uint8_t latching_relay_pulse = 0;
|
|
uint8_t sleep;
|
|
uint8_t blinkspeed = 1;
|
|
uint8_t pin[GPIO_MAX];
|
|
uint8_t active_device = 1;
|
|
uint8_t leds_present = 0;
|
|
uint8_t led_inverted = 0;
|
|
uint8_t led_power = 0;
|
|
uint8_t ledlnk_inverted = 0;
|
|
uint8_t pwm_inverted = 0;
|
|
uint8_t energy_flg = 0;
|
|
uint8_t light_flg = 0;
|
|
uint8_t light_type = 0;
|
|
uint8_t serial_in_byte;
|
|
uint8_t ota_retry_counter = OTA_ATTEMPTS;
|
|
uint8_t devices_present = 0;
|
|
uint8_t seriallog_level;
|
|
uint8_t syslog_level;
|
|
uint8_t my_module_type;
|
|
uint8_t my_adc0;
|
|
uint8_t last_source = 0;
|
|
uint8_t shutters_present = 0;
|
|
uint8_t prepped_loglevel = 0;
|
|
|
|
bool serial_local = false;
|
|
bool fallback_topic_flag = false;
|
|
bool backlog_mutex = false;
|
|
bool interlock_mutex = false;
|
|
bool stop_flash_rotate = false;
|
|
bool blinkstate = false;
|
|
|
|
bool pwm_present = false;
|
|
bool i2c_flg = false;
|
|
bool spi_flg = false;
|
|
bool soft_spi_flg = false;
|
|
bool ntp_force_sync = false;
|
|
bool is_8285 = false;
|
|
myio my_module;
|
|
gpio_flag my_module_flag;
|
|
StateBitfield global_state;
|
|
char my_version[33];
|
|
char my_image[33];
|
|
char my_hostname[33];
|
|
char mqtt_client[TOPSZ];
|
|
char mqtt_topic[TOPSZ];
|
|
char serial_in_buffer[INPUT_BUFFER_SIZE];
|
|
char mqtt_data[MESSZ];
|
|
char log_data[LOGSZ];
|
|
char web_log[WEB_LOG_SIZE] = {'\0'};
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
#include <LinkedList.h>
|
|
LinkedList<String> backlog;
|
|
#define BACKLOG_EMPTY (backlog.size() == 0)
|
|
#else
|
|
uint8_t backlog_index = 0;
|
|
uint8_t backlog_pointer = 0;
|
|
String backlog[MAX_BACKLOG];
|
|
#define BACKLOG_EMPTY (backlog_pointer == backlog_index)
|
|
#endif
|
|
void setup(void);
|
|
void BacklogLoop(void);
|
|
void loop(void);
|
|
uint16_t SendMail(char *buffer);
|
|
uint32_t GetRtcSettingsCrc(void);
|
|
void RtcSettingsSave(void);
|
|
void RtcSettingsLoad(void);
|
|
bool RtcSettingsValid(void);
|
|
uint32_t GetRtcRebootCrc(void);
|
|
void RtcRebootSave(void);
|
|
void RtcRebootReset(void);
|
|
void RtcRebootLoad(void);
|
|
bool RtcRebootValid(void);
|
|
void SetFlashModeDout(void);
|
|
bool VersionCompatible(void);
|
|
void SettingsBufferFree(void);
|
|
bool SettingsBufferAlloc(void);
|
|
uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size);
|
|
uint16_t GetSettingsCrc(void);
|
|
uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size);
|
|
uint32_t GetSettingsCrc32(void);
|
|
void SettingsSaveAll(void);
|
|
void UpdateQuickPowerCycle(bool update);
|
|
uint32_t GetSettingsTextLen(void);
|
|
bool SettingsUpdateText(uint32_t index, const char* replace_me);
|
|
char* SettingsText(uint32_t index);
|
|
uint32_t GetSettingsAddress(void);
|
|
void SettingsSave(uint8_t rotate);
|
|
void SettingsLoad(void);
|
|
void EspErase(uint32_t start_sector, uint32_t end_sector);
|
|
void SettingsErase(uint8_t type);
|
|
void SettingsSdkErase(void);
|
|
void SettingsDefault(void);
|
|
void SettingsDefaultSet1(void);
|
|
void SettingsDefaultSet2(void);
|
|
void SettingsResetStd(void);
|
|
void SettingsResetDst(void);
|
|
void SettingsDefaultWebColor(void);
|
|
void SettingsEnableAllI2cDrivers(void);
|
|
void SettingsDelta(void);
|
|
void OsWatchTicker(void);
|
|
void OsWatchInit(void);
|
|
void OsWatchLoop(void);
|
|
bool OsWatchBlockedLoop(void);
|
|
uint32_t ResetReason(void);
|
|
String GetResetReason(void);
|
|
size_t strchrspn(const char *str1, int character);
|
|
char* subStr(char* dest, char* str, const char *delim, int index);
|
|
float CharToFloat(const char *str);
|
|
int TextToInt(char *str);
|
|
char* ulltoa(unsigned long long value, char *str, int radix);
|
|
char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween);
|
|
char* Uint64toHex(uint64_t value, char *str, uint16_t bits);
|
|
char* dtostrfd(double number, unsigned char prec, char *s);
|
|
char* Unescape(char* buffer, uint32_t* size);
|
|
char* RemoveSpace(char* p);
|
|
char* ReplaceCommaWithDot(char* p);
|
|
char* LowerCase(char* dest, const char* source);
|
|
char* UpperCase(char* dest, const char* source);
|
|
char* UpperCase_P(char* dest, const char* source);
|
|
char* Trim(char* p);
|
|
char* RemoveAllSpaces(char* p);
|
|
char* NoAlNumToUnderscore(char* dest, const char* source);
|
|
char IndexSeparator(void);
|
|
void SetShortcutDefault(void);
|
|
uint8_t Shortcut(void);
|
|
bool ValidIpAddress(const char* str);
|
|
bool ParseIp(uint32_t* addr, const char* str);
|
|
uint32_t ParseParameters(uint32_t count, uint32_t *params);
|
|
bool NewerVersion(char* version_str);
|
|
char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option);
|
|
char* GetPowerDevice(char* dest, uint32_t idx, size_t size);
|
|
void GetEspHardwareType(void);
|
|
String GetDeviceHardware(void);
|
|
float ConvertTemp(float c);
|
|
float ConvertTempToCelsius(float c);
|
|
char TempUnit(void);
|
|
float ConvertHumidity(float h);
|
|
float ConvertPressure(float p);
|
|
String PressureUnit(void);
|
|
void ResetGlobalValues(void);
|
|
uint32_t SqrtInt(uint32_t num);
|
|
uint32_t RoundSqrtInt(uint32_t num);
|
|
char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack);
|
|
int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack);
|
|
int GetStateNumber(char *state_text);
|
|
String GetSerialConfig(void);
|
|
void SetSerialBegin();
|
|
void SetSerialConfig(uint32_t serial_config);
|
|
void SetSerialBaudrate(uint32_t baudrate);
|
|
void SetSerial(uint32_t baudrate, uint32_t serial_config);
|
|
void ClaimSerial(void);
|
|
void SerialSendRaw(char *codes);
|
|
uint32_t GetHash(const char *buffer, size_t size);
|
|
void ShowSource(uint32_t source);
|
|
void WebHexCode(uint32_t i, const char* code);
|
|
uint32_t WebColor(uint32_t i);
|
|
char* ResponseGetTime(uint32_t format, char* time_str);
|
|
int Response_P(const char* format, ...);
|
|
int ResponseTime_P(const char* format, ...);
|
|
int ResponseAppend_P(const char* format, ...);
|
|
int ResponseAppendTimeFormat(uint32_t format);
|
|
int ResponseAppendTime(void);
|
|
int ResponseJsonEnd(void);
|
|
int ResponseJsonEndEnd(void);
|
|
void DigitalWrite(uint32_t gpio_pin, uint32_t state);
|
|
uint8_t ModuleNr(void);
|
|
bool ValidTemplateModule(uint32_t index);
|
|
bool ValidModule(uint32_t index);
|
|
String AnyModuleName(uint32_t index);
|
|
String ModuleName(void);
|
|
void ModuleGpios(myio *gp);
|
|
gpio_flag ModuleFlag(void);
|
|
void ModuleDefault(uint32_t module);
|
|
void SetModuleType(void);
|
|
bool FlashPin(uint32_t pin);
|
|
uint8_t ValidPin(uint32_t pin, uint32_t gpio);
|
|
bool ValidGPIO(uint32_t pin, uint32_t gpio);
|
|
bool ValidAdc(void);
|
|
bool GetUsedInModule(uint32_t val, uint8_t *arr);
|
|
bool JsonTemplate(const char* dataBuf);
|
|
void TemplateJson(void);
|
|
inline int32_t TimeDifference(uint32_t prev, uint32_t next);
|
|
int32_t TimePassedSince(uint32_t timestamp);
|
|
bool TimeReached(uint32_t timer);
|
|
void SetNextTimeInterval(unsigned long& timer, const unsigned long step);
|
|
int32_t TimePassedSinceUsec(uint32_t timestamp);
|
|
bool TimeReachedUsec(uint32_t timer);
|
|
bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size);
|
|
bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg);
|
|
bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg);
|
|
bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg);
|
|
bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg);
|
|
bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg);
|
|
bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg);
|
|
uint8_t I2cRead8(uint8_t addr, uint8_t reg);
|
|
uint16_t I2cRead16(uint8_t addr, uint8_t reg);
|
|
int16_t I2cReadS16(uint8_t addr, uint8_t reg);
|
|
uint16_t I2cRead16LE(uint8_t addr, uint8_t reg);
|
|
int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg);
|
|
int32_t I2cRead24(uint8_t addr, uint8_t reg);
|
|
bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size);
|
|
bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val);
|
|
bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val);
|
|
int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len);
|
|
int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len);
|
|
void I2cScan(char *devs, unsigned int devs_len);
|
|
void I2cSetActiveFound(uint32_t addr, const char *types);
|
|
bool I2cActive(uint32_t addr);
|
|
bool I2cSetDevice(uint32_t addr);
|
|
void SetSeriallog(uint32_t loglevel);
|
|
void SetSyslog(uint32_t loglevel);
|
|
void GetLog(uint32_t idx, char** entry_pp, size_t* len_p);
|
|
void Syslog(void);
|
|
void AddLog(uint32_t loglevel);
|
|
void AddLog_P(uint32_t loglevel, const char *formatP);
|
|
void AddLog_P(uint32_t loglevel, const char *formatP, const char *formatP2);
|
|
void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...);
|
|
void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...);
|
|
void AddLog_Debug(PGM_P formatP, ...);
|
|
void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count);
|
|
void AddLogSerial(uint32_t loglevel);
|
|
void AddLogMissed(char *sensor, uint32_t misses);
|
|
void ButtonPullupFlag(uint8 button_bit);
|
|
void ButtonInvertFlag(uint8 button_bit);
|
|
void ButtonInit(void);
|
|
uint8_t ButtonSerial(uint8_t serial_in_byte);
|
|
void ButtonHandler(void);
|
|
void ButtonLoop(void);
|
|
void ResponseCmndNumber(int value);
|
|
void ResponseCmndFloat(float value, uint32_t decimals);
|
|
void ResponseCmndIdxNumber(int value);
|
|
void ResponseCmndChar(const char* value);
|
|
void ResponseCmndStateText(uint32_t value);
|
|
void ResponseCmndDone(void);
|
|
void ResponseCmndIdxChar(const char* value);
|
|
void ResponseCmndAll(uint32_t text_index, uint32_t count);
|
|
void ExecuteCommand(const char *cmnd, uint32_t source);
|
|
void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len);
|
|
void CmndBacklog(void);
|
|
void CmndDelay(void);
|
|
void CmndPower(void);
|
|
void CmndStatus(void);
|
|
void CmndState(void);
|
|
void CmndTempOffset(void);
|
|
void CmndSleep(void);
|
|
void CmndUpgrade(void);
|
|
void CmndOtaUrl(void);
|
|
void CmndSeriallog(void);
|
|
void CmndRestart(void);
|
|
void CmndPowerOnState(void);
|
|
void CmndPulsetime(void);
|
|
void CmndBlinktime(void);
|
|
void CmndBlinkcount(void);
|
|
void CmndSavedata(void);
|
|
void CmndSetoption(void);
|
|
void CmndTemperatureResolution(void);
|
|
void CmndHumidityResolution(void);
|
|
void CmndPressureResolution(void);
|
|
void CmndPowerResolution(void);
|
|
void CmndVoltageResolution(void);
|
|
void CmndFrequencyResolution(void);
|
|
void CmndCurrentResolution(void);
|
|
void CmndEnergyResolution(void);
|
|
void CmndWeightResolution(void);
|
|
void CmndModule(void);
|
|
void CmndModules(void);
|
|
void CmndGpio(void);
|
|
void CmndGpios(void);
|
|
void CmndTemplate(void);
|
|
void CmndPwm(void);
|
|
void CmndPwmfrequency(void);
|
|
void CmndPwmrange(void);
|
|
void CmndButtonDebounce(void);
|
|
void CmndSwitchDebounce(void);
|
|
void CmndBaudrate(void);
|
|
void CmndSerialConfig(void);
|
|
void CmndSerialSend(void);
|
|
void CmndSerialDelimiter(void);
|
|
void CmndSyslog(void);
|
|
void CmndLoghost(void);
|
|
void CmndLogport(void);
|
|
void CmndIpAddress(void);
|
|
void CmndNtpServer(void);
|
|
void CmndAp(void);
|
|
void CmndSsid(void);
|
|
void CmndPassword(void);
|
|
void CmndHostname(void);
|
|
void CmndWifiConfig(void);
|
|
void CmndFriendlyname(void);
|
|
void CmndSwitchMode(void);
|
|
void CmndInterlock(void);
|
|
void CmndTeleperiod(void);
|
|
void CmndReset(void);
|
|
void CmndTime(void);
|
|
void CmndTimezone(void);
|
|
void CmndTimeStdDst(uint32_t ts);
|
|
void CmndTimeStd(void);
|
|
void CmndTimeDst(void);
|
|
void CmndAltitude(void);
|
|
void CmndLedPower(void);
|
|
void CmndLedState(void);
|
|
void CmndLedMask(void);
|
|
void CmndWifiPower(void);
|
|
void CmndI2cScan(void);
|
|
void CmndI2cDriver(void);
|
|
void CmndSensor(void);
|
|
void CmndDriver(void);
|
|
void CmndCrash(void);
|
|
void CmndWDT(void);
|
|
void CmndBlockedLoop(void);
|
|
void CrashDumpClear(void);
|
|
bool CrashFlag(void);
|
|
void CrashDump(void);
|
|
static bool spiflash_is_ready(void);
|
|
static void spi_write_enable(void);
|
|
bool EsptoolEraseSector(uint32_t sector);
|
|
void EsptoolErase(uint32_t start_sector, uint32_t end_sector);
|
|
void GetFeatures(void);
|
|
float fmodf(float x, float y);
|
|
double FastPrecisePow(double a, double b);
|
|
float FastPrecisePowf(const float x, const float y);
|
|
double TaylorLog(double x);
|
|
inline float sinf(float x);
|
|
inline float cosf(float x);
|
|
inline float tanf(float x);
|
|
inline float atanf(float x);
|
|
inline float asinf(float x);
|
|
inline float acosf(float x);
|
|
inline float sqrtf(float x);
|
|
inline float powf(float x, float y);
|
|
float cos_52s(float x);
|
|
float cos_52(float x);
|
|
float sin_52(float x);
|
|
float tan_56s(float x);
|
|
float tan_56(float x);
|
|
float atan_66s(float x);
|
|
float atan_66(float x);
|
|
float asinf1(float x);
|
|
float acosf1(float x);
|
|
float sqrt1(const float x);
|
|
uint16_t changeUIntScale(uint16_t inum, uint16_t ifrom_min, uint16_t ifrom_max,
|
|
uint16_t ito_min, uint16_t ito_max);
|
|
void* memchr(const void* ptr, int value, size_t num);
|
|
size_t strcspn(const char *str1, const char *str2);
|
|
char* strpbrk(const char *s1, const char *s2);
|
|
void* memmove_P(void *dest, const void *src, size_t n);
|
|
void update_rotary(void);
|
|
bool RotaryButtonPressed(void);
|
|
void RotaryInit(void);
|
|
void RotaryHandler(void);
|
|
void RotaryLoop(void);
|
|
uint32_t UtcTime(void);
|
|
uint32_t LocalTime(void);
|
|
int32_t DriftTime(void);
|
|
uint32_t Midnight(void);
|
|
bool MidnightNow(void);
|
|
bool IsDst(void);
|
|
String GetBuildDateAndTime(void);
|
|
String GetMinuteTime(uint32_t minutes);
|
|
String GetTimeZone(void);
|
|
String GetDuration(uint32_t time);
|
|
String GetDT(uint32_t time);
|
|
String GetDateAndTime(uint8_t time_type);
|
|
String GetTime(int type);
|
|
uint32_t UpTime(void);
|
|
uint32_t MinutesUptime(void);
|
|
String GetUptime(void);
|
|
uint32_t MinutesPastMidnight(void);
|
|
void BreakTime(uint32_t time_input, TIME_T &tm);
|
|
uint32_t MakeTime(TIME_T &tm);
|
|
uint32_t RuleToTime(TimeRule r, int yr);
|
|
void RtcSecond(void);
|
|
void RtcSetTime(uint32_t epoch);
|
|
void RtcInit(void);
|
|
String GetStatistics(void);
|
|
String GetStatistics(void);
|
|
void SwitchPullupFlag(uint16 switch_bit);
|
|
void SwitchSetVirtual(uint32_t index, uint8_t state);
|
|
uint8_t SwitchGetVirtual(uint32_t index);
|
|
uint8_t SwitchLastState(uint32_t index);
|
|
bool SwitchState(uint32_t index);
|
|
void SwitchProbe(void);
|
|
void SwitchInit(void);
|
|
void SwitchHandler(uint8_t mode);
|
|
void SwitchLoop(void);
|
|
char* Format(char* output, const char* input, int size);
|
|
char* GetOtaUrl(char *otaurl, size_t otaurl_size);
|
|
char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic);
|
|
char* GetGroupTopic_P(char *stopic, const char* subtopic);
|
|
char* GetFallbackTopic_P(char *stopic, const char* subtopic);
|
|
char* GetStateText(uint32_t state);
|
|
void SetLatchingRelay(power_t lpower, uint32_t state);
|
|
void SetDevicePower(power_t rpower, uint32_t source);
|
|
void RestorePower(bool publish_power, uint32_t source);
|
|
void SetAllPower(uint32_t state, uint32_t source);
|
|
void SetPowerOnState(void);
|
|
void SetLedPowerIdx(uint32_t led, uint32_t state);
|
|
void SetLedPower(uint32_t state);
|
|
void SetLedPowerAll(uint32_t state);
|
|
void SetLedLink(uint32_t state);
|
|
void SetPulseTimer(uint32_t index, uint32_t time);
|
|
uint32_t GetPulseTimer(uint32_t index);
|
|
bool SendKey(uint32_t key, uint32_t device, uint32_t state);
|
|
void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source);
|
|
void StopAllPowerBlink(void);
|
|
void MqttShowPWMState(void);
|
|
void MqttShowState(void);
|
|
void MqttPublishTeleState(void);
|
|
bool MqttShowSensor(void);
|
|
void MqttPublishSensor(void);
|
|
void PerformEverySecond(void);
|
|
void Every100mSeconds(void);
|
|
void Every250mSeconds(void);
|
|
void ArduinoOTAInit(void);
|
|
void ArduinoOtaLoop(void);
|
|
void SerialInput(void);
|
|
void GpioInit(void);
|
|
bool UdpDisconnect(void);
|
|
bool UdpConnect(void);
|
|
void PollUdp(void);
|
|
int WifiGetRssiAsQuality(int rssi);
|
|
bool WifiConfigCounter(void);
|
|
void WifiConfig(uint8_t type);
|
|
void WifiSetMode(WiFiMode_t wifi_mode);
|
|
void WiFiSetSleepMode(void);
|
|
void WifiBegin(uint8_t flag, uint8_t channel);
|
|
void WifiBeginAfterScan(void);
|
|
uint16_t WifiLinkCount(void);
|
|
String WifiDowntime(void);
|
|
void WifiSetState(uint8_t state);
|
|
bool WifiCheckIPv6(void);
|
|
String WifiGetIPv6(void);
|
|
bool WifiCheckIPAddrStatus(void);
|
|
void WifiCheckIp(void);
|
|
void WifiCheck(uint8_t param);
|
|
int WifiState(void);
|
|
String WifiGetOutputPower(void);
|
|
void WifiSetOutputPower(void);
|
|
void WifiConnect(void);
|
|
void WifiDisconnect(void);
|
|
void WifiShutdown(void);
|
|
void EspRestart(void);
|
|
static void WebGetArg(const char* arg, char* out, size_t max);
|
|
static bool WifiIsInManagerMode();
|
|
void ShowWebSource(uint32_t source);
|
|
void ExecuteWebCommand(char* svalue, uint32_t source);
|
|
void StartWebserver(int type, IPAddress ipweb);
|
|
void StopWebserver(void);
|
|
void WifiManagerBegin(bool reset_only);
|
|
void PollDnsWebserver(void);
|
|
bool WebAuthenticate(void);
|
|
void HttpHeaderCors(void);
|
|
void WSHeaderSend(void);
|
|
void WSSend(int code, int ctype, const String& content);
|
|
void WSContentBegin(int code, int ctype);
|
|
void _WSContentSend(const String& content);
|
|
void WSContentFlush(void);
|
|
void _WSContentSendBuffer(void);
|
|
void WSContentSend_P(const char* formatP, ...);
|
|
void WSContentSend_PD(const char* formatP, ...);
|
|
void WSContentStart_P(const char* title, bool auth);
|
|
void WSContentStart_P(const char* title);
|
|
void WSContentSendStyle_P(const char* formatP, ...);
|
|
void WSContentSendStyle(void);
|
|
void WSContentButton(uint32_t title_index);
|
|
void WSContentSpaceButton(uint32_t title_index);
|
|
void WSContentEnd(void);
|
|
void WSContentStop(void);
|
|
void WebRestart(uint32_t type);
|
|
void HandleWifiLogin(void);
|
|
void WebSliderColdWarm(void);
|
|
void HandleRoot(void);
|
|
bool HandleRootStatusRefresh(void);
|
|
int32_t IsShutterWebButton(uint32_t idx);
|
|
void HandleConfiguration(void);
|
|
void HandleTemplateConfiguration(void);
|
|
void TemplateSaveSettings(void);
|
|
void HandleModuleConfiguration(void);
|
|
void ModuleSaveSettings(void);
|
|
String HtmlEscape(const String unescaped);
|
|
void HandleWifiConfiguration(void);
|
|
void WifiSaveSettings(void);
|
|
void HandleLoggingConfiguration(void);
|
|
void LoggingSaveSettings(void);
|
|
void HandleOtherConfiguration(void);
|
|
void OtherSaveSettings(void);
|
|
void HandleBackupConfiguration(void);
|
|
void HandleResetConfiguration(void);
|
|
void HandleRestoreConfiguration(void);
|
|
void HandleInformation(void);
|
|
void HandleUpgradeFirmware(void);
|
|
void HandleUpgradeFirmwareStart(void);
|
|
void HandleUploadDone(void);
|
|
void HandleUploadLoop(void);
|
|
void HandlePreflightRequest(void);
|
|
void HandleHttpCommand(void);
|
|
void HandleConsole(void);
|
|
void HandleConsoleRefresh(void);
|
|
void HandleNotFound(void);
|
|
bool CaptivePortal(void);
|
|
String UrlEncode(const String& text);
|
|
int WebSend(char *buffer);
|
|
bool JsonWebColor(const char* dataBuf);
|
|
void CmndEmulation(void);
|
|
void CmndSendmail(void);
|
|
void CmndWebServer(void);
|
|
void CmndWebPassword(void);
|
|
void CmndWeblog(void);
|
|
void CmndWebRefresh(void);
|
|
void CmndWebSend(void);
|
|
void CmndWebColor(void);
|
|
void CmndWebSensor(void);
|
|
void CmndWebButton(void);
|
|
void CmndCors(void);
|
|
bool Xdrv01(uint8_t function);
|
|
bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value);
|
|
void MakeValidMqtt(uint32_t option, char* str);
|
|
void MqttDiscoverServer(void);
|
|
void MqttInit(void);
|
|
bool MqttIsConnected(void);
|
|
void MqttDisconnect(void);
|
|
void MqttSubscribeLib(const char *topic);
|
|
void MqttUnsubscribeLib(const char *topic);
|
|
bool MqttPublishLib(const char* topic, bool retained);
|
|
void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len);
|
|
void MqttRetryCounter(uint8_t value);
|
|
void MqttSubscribe(const char *topic);
|
|
void MqttUnsubscribe(const char *topic);
|
|
void MqttPublishLogging(const char *mxtime);
|
|
void MqttPublish(const char* topic, bool retained);
|
|
void MqttPublish(const char* topic);
|
|
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained);
|
|
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic);
|
|
void MqttPublishTeleSensor(void);
|
|
void MqttPublishPowerState(uint32_t device);
|
|
void MqttPublishAllPowerState(void);
|
|
void MqttPublishPowerBlinkState(uint32_t device);
|
|
uint16_t MqttConnectCount(void);
|
|
void MqttDisconnected(int state);
|
|
void MqttConnected(void);
|
|
void MqttReconnect(void);
|
|
void MqttCheck(void);
|
|
void CmndMqttFingerprint(void);
|
|
void CmndMqttUser(void);
|
|
void CmndMqttPassword(void);
|
|
void CmndMqttlog(void);
|
|
void CmndMqttHost(void);
|
|
void CmndMqttPort(void);
|
|
void CmndMqttRetry(void);
|
|
void CmndStateText(void);
|
|
void CmndMqttClient(void);
|
|
void CmndFullTopic(void);
|
|
void CmndPrefix(void);
|
|
void CmndPublish(void);
|
|
void CmndGroupTopic(void);
|
|
void CmndTopic(void);
|
|
void CmndButtonTopic(void);
|
|
void CmndSwitchTopic(void);
|
|
void CmndButtonRetain(void);
|
|
void CmndSwitchRetain(void);
|
|
void CmndPowerRetain(void);
|
|
void CmndSensorRetain(void);
|
|
inline void TlsEraseBuffer(uint8_t *buffer);
|
|
void loadTlsDir(void);
|
|
void CmndTlsKey(void);
|
|
uint32_t bswap32(uint32_t x);
|
|
void CmndTlsDump(void);
|
|
void HandleMqttConfiguration(void);
|
|
void MqttSaveSettings(void);
|
|
bool Xdrv02(uint8_t function);
|
|
bool EnergyTariff1Active();
|
|
void EnergyUpdateToday(void);
|
|
void EnergyUpdateTotal(float value, bool kwh);
|
|
void Energy200ms(void);
|
|
void EnergySaveState(void);
|
|
bool EnergyMargin(bool type, uint16_t margin, uint16_t value, bool &flag, bool &save_flag);
|
|
void EnergyMarginCheck(void);
|
|
void EnergyMqttShow(void);
|
|
void EnergyEverySecond(void);
|
|
void EnergyCommandCalResponse(uint32_t nvalue);
|
|
void CmndEnergyReset(void);
|
|
void CmndTariff(void);
|
|
void CmndPowerCal(void);
|
|
void CmndVoltageCal(void);
|
|
void CmndCurrentCal(void);
|
|
void CmndPowerSet(void);
|
|
void CmndVoltageSet(void);
|
|
void CmndCurrentSet(void);
|
|
void CmndFrequencySet(void);
|
|
void CmndModuleAddress(void);
|
|
void CmndPowerDelta(void);
|
|
void CmndPowerLow(void);
|
|
void CmndPowerHigh(void);
|
|
void CmndVoltageLow(void);
|
|
void CmndVoltageHigh(void);
|
|
void CmndCurrentLow(void);
|
|
void CmndCurrentHigh(void);
|
|
void CmndMaxPower(void);
|
|
void CmndMaxPowerHold(void);
|
|
void CmndMaxPowerWindow(void);
|
|
void CmndSafePower(void);
|
|
void CmndSafePowerHold(void);
|
|
void CmndSafePowerWindow(void);
|
|
void CmndMaxEnergy(void);
|
|
void CmndMaxEnergyStart(void);
|
|
void EnergySnsInit(void);
|
|
void EnergyShow(bool json);
|
|
bool Xdrv03(uint8_t function);
|
|
bool Xsns03(uint8_t function);
|
|
power_t LightPower(void);
|
|
power_t LightPowerIRAM(void);
|
|
uint8_t LightDevice(void);
|
|
static uint32_t min3(uint32_t a, uint32_t b, uint32_t c);
|
|
uint16_t change8to10(uint8_t v);
|
|
uint8_t change10to8(uint16_t v);
|
|
uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr);
|
|
uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr);
|
|
uint16_t ledGamma10_10(uint16_t v);
|
|
uint16_t ledGamma10(uint8_t v);
|
|
uint8_t ledGamma(uint8_t v);
|
|
void LightPwmOffset(uint32_t offset);
|
|
bool LightModuleInit(void);
|
|
void LightInit(void);
|
|
void LightUpdateColorMapping(void);
|
|
uint8_t LightGetDimmer(uint8_t dimmer);
|
|
void LightSetDimmer(uint8_t dimmer);
|
|
void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri);
|
|
void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b);
|
|
uint8_t LightGetBri(uint8_t device);
|
|
void LightSetBri(uint8_t device, uint8_t bri);
|
|
void LightSetColorTemp(uint16_t ct);
|
|
uint16_t LightGetColorTemp(void);
|
|
void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value);
|
|
void LightPowerOn(void);
|
|
void LightState(uint8_t append);
|
|
void LightCycleColor(int8_t direction);
|
|
void LightSetPower(void);
|
|
void LightAnimate(void);
|
|
bool isChannelGammaCorrected(uint32_t channel);
|
|
uint16_t fadeGamma(uint32_t channel, uint16_t v);
|
|
uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg);
|
|
bool LightApplyFade(void);
|
|
void LightApplyPower(uint8_t new_color[LST_MAX], power_t power);
|
|
void LightSetOutputs(const uint16_t *cur_col_10);
|
|
void calcGammaMultiChannels(uint16_t cur_col_10[5]);
|
|
void calcGammaBulbs(uint16_t cur_col_10[5]);
|
|
bool LightColorEntry(char *buffer, uint32_t buffer_length);
|
|
void CmndSupportColor(void);
|
|
void CmndColor(void);
|
|
void CmndWhite(void);
|
|
void CmndChannel(void);
|
|
void CmndHsbColor(void);
|
|
void CmndScheme(void);
|
|
void CmndWakeup(void);
|
|
void CmndColorTemperature(void);
|
|
void CmndDimmer(void);
|
|
void CmndDimmerRange(void);
|
|
void CmndLedTable(void);
|
|
void CmndRgbwwTable(void);
|
|
void CmndFade(void);
|
|
void CmndSpeed(void);
|
|
void CmndWakeupDuration(void);
|
|
void CmndUndocA(void);
|
|
bool Xdrv04(uint8_t function);
|
|
void IrSendInit(void);
|
|
void IrReceiveUpdateThreshold(void);
|
|
void IrReceiveInit(void);
|
|
void IrReceiveCheck(void);
|
|
uint32_t IrRemoteCmndIrSendJson(void);
|
|
void CmndIrSend(void);
|
|
void IrRemoteCmndResponse(uint32_t error);
|
|
bool Xdrv05(uint8_t function);
|
|
void IrSendInit(void);
|
|
uint8_t reverseBitsInByte(uint8_t b);
|
|
uint64_t reverseBitsInBytes64(uint64_t b);
|
|
void IrReceiveUpdateThreshold(void);
|
|
void IrReceiveInit(void);
|
|
String sendIRJsonState(const struct decode_results &results);
|
|
void IrReceiveCheck(void);
|
|
String listSupportedProtocols(bool hvac);
|
|
uint32_t IrRemoteCmndIrHvacJson(void);
|
|
void CmndIrHvac(void);
|
|
uint32_t IrRemoteCmndIrSendJson(void);
|
|
uint32_t IrRemoteCmndIrSendRaw(void);
|
|
void CmndIrSend(void);
|
|
void IrRemoteCmndResponse(uint32_t error);
|
|
bool Xdrv05(uint8_t function);
|
|
ssize_t rf_find_hex_record_start(uint8_t *buf, size_t size);
|
|
ssize_t rf_find_hex_record_end(uint8_t *buf, size_t size);
|
|
ssize_t rf_glue_remnant_with_new_data_and_write(const uint8_t *remnant_data, uint8_t *new_data, size_t new_data_len);
|
|
ssize_t rf_decode_and_write(uint8_t *record, size_t size);
|
|
ssize_t rf_search_and_write(uint8_t *buf, size_t size);
|
|
uint8_t rf_erase_flash(void);
|
|
uint8_t SnfBrUpdateInit(void);
|
|
void SonoffBridgeReceivedRaw(void);
|
|
void SonoffBridgeLearnFailed(void);
|
|
void SonoffBridgeReceived(void);
|
|
bool SonoffBridgeSerialInput(void);
|
|
void SonoffBridgeSendCommand(uint8_t code);
|
|
void SonoffBridgeSendAck(void);
|
|
void SonoffBridgeSendCode(uint32_t code);
|
|
void SonoffBridgeSend(uint8_t idx, uint8_t key);
|
|
void SonoffBridgeLearn(uint8_t key);
|
|
void CmndRfBridge(void);
|
|
void CmndRfKey(void);
|
|
void CmndRfRaw(void);
|
|
bool Xdrv06(uint8_t function);
|
|
int DomoticzBatteryQuality(void);
|
|
int DomoticzRssiQuality(void);
|
|
void MqttPublishDomoticzFanState(void);
|
|
void DomoticzUpdateFanState(void);
|
|
void MqttPublishDomoticzPowerState(uint8_t device);
|
|
void DomoticzUpdatePowerState(uint8_t device);
|
|
void DomoticzMqttUpdate(void);
|
|
void DomoticzMqttSubscribe(void);
|
|
bool DomoticzMqttData(void);
|
|
bool DomoticzSendKey(uint8_t key, uint8_t device, uint8_t state, uint8_t svalflg);
|
|
uint8_t DomoticzHumidityState(char *hum);
|
|
void DomoticzSensor(uint8_t idx, char *data);
|
|
void DomoticzSensor(uint8_t idx, uint32_t value);
|
|
void DomoticzTempHumSensor(char *temp, char *hum);
|
|
void DomoticzTempHumPressureSensor(char *temp, char *hum, char *baro);
|
|
void DomoticzSensorPowerEnergy(int power, char *energy);
|
|
void DomoticzSensorP1SmartMeter(char *usage1, char *usage2, char *return1, char *return2, int power);
|
|
void CmndDomoticzIdx(void);
|
|
void CmndDomoticzKeyIdx(void);
|
|
void CmndDomoticzSwitchIdx(void);
|
|
void CmndDomoticzSensorIdx(void);
|
|
void CmndDomoticzUpdateTimer(void);
|
|
void HandleDomoticzConfiguration(void);
|
|
void DomoticzSaveSettings(void);
|
|
bool Xdrv07(uint8_t function);
|
|
void SerialBridgeInput(void);
|
|
void SerialBridgeInit(void);
|
|
void CmndSSerialSend(void);
|
|
void CmndSBaudrate(void);
|
|
bool Xdrv08(uint8_t function);
|
|
float JulianischesDatum(void);
|
|
float InPi(float x);
|
|
float eps(float T);
|
|
float BerechneZeitgleichung(float *DK,float T);
|
|
void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down);
|
|
void ApplyTimerOffsets(Timer *duskdawn);
|
|
String GetSun(uint32_t dawn);
|
|
uint16_t SunMinutes(uint32_t dawn);
|
|
void TimerSetRandomWindow(uint32_t index);
|
|
void TimerSetRandomWindows(void);
|
|
void TimerEverySecond(void);
|
|
void PrepShowTimer(uint32_t index);
|
|
void CmndTimer(void);
|
|
void CmndTimers(void);
|
|
void CmndLongitude(void);
|
|
void CmndLatitude(void);
|
|
void HandleTimerConfiguration(void);
|
|
void TimerSaveSettings(void);
|
|
bool Xdrv09(uint8_t function);
|
|
bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule);
|
|
int8_t parseCompareExpression(String &expr, String &leftExpr, String &rightExpr);
|
|
void RulesVarReplace(String &commands, const String &sfind, const String &replace);
|
|
bool RuleSetProcess(uint8_t rule_set, String &event_saved);
|
|
bool RulesProcessEvent(char *json_event);
|
|
bool RulesProcess(void);
|
|
void RulesInit(void);
|
|
void RulesEvery50ms(void);
|
|
void RulesEvery100ms(void);
|
|
void RulesEverySecond(void);
|
|
void RulesSaveBeforeRestart(void);
|
|
void RulesSetPower(void);
|
|
void RulesTeleperiod(void);
|
|
bool RulesMqttData(void);
|
|
void CmndSubscribe(void);
|
|
void CmndUnsubscribe(void);
|
|
bool findNextNumber(char * &pNumber, float &value);
|
|
bool findNextVariableValue(char * &pVarname, float &value);
|
|
bool findNextObjectValue(char * &pointer, float &value);
|
|
bool findNextOperator(char * &pointer, int8_t &op);
|
|
float calculateTwoValues(float v1, float v2, uint8_t op);
|
|
float evaluateExpression(const char * expression, unsigned int len);
|
|
void CmndIf(void);
|
|
bool evaluateComparisonExpression(const char *expression, int len);
|
|
bool findNextLogicOperator(char * &pointer, int8_t &op);
|
|
bool findNextLogicObjectValue(char * &pointer, bool &value);
|
|
bool evaluateLogicalExpression(const char * expression, int len);
|
|
int8_t findIfBlock(char * &pointer, int &lenWord, int8_t block_type);
|
|
void ExecuteCommandBlock(const char * commands, int len);
|
|
void ProcessIfStatement(const char* statements);
|
|
void RulesPreprocessCommand(char *pCommands);
|
|
void CmndRule(void);
|
|
void CmndRuleTimer(void);
|
|
void CmndEvent(void);
|
|
void CmndVariable(void);
|
|
void CmndMemory(void);
|
|
void CmndCalcResolution(void);
|
|
void CmndAddition(void);
|
|
void CmndSubtract(void);
|
|
void CmndMultiply(void);
|
|
void CmndScale(void);
|
|
float map_double(float x, float in_min, float in_max, float out_min, float out_max);
|
|
bool Xdrv10(uint8_t function);
|
|
void ScriptEverySecond(void);
|
|
void RulesTeleperiod(void);
|
|
int16_t Init_Scripter(void);
|
|
void ws2812_set_array(float *array ,uint8_t len);
|
|
float median_array(float *array,uint8_t len);
|
|
float Get_MFVal(uint8_t index,uint8_t bind);
|
|
void Set_MFVal(uint8_t index,uint8_t bind,float val);
|
|
float Get_MFilter(uint8_t index);
|
|
void Set_MFilter(uint8_t index, float invar);
|
|
float DoMedian5(uint8_t index, float in);
|
|
uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value);
|
|
uint16_t GetStack(void);
|
|
uint16_t GetStack(void);
|
|
void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize);
|
|
void toLog(const char *str);
|
|
void toLogN(const char *cp,uint8_t len);
|
|
void toLogEOL(const char *s1,const char *str);
|
|
void toSLog(const char *str);
|
|
int16_t Run_Scripter(const char *type, int8_t tlen, char *js);
|
|
void ScripterEvery100ms(void);
|
|
void Scripter_save_pvars(void);
|
|
void ListDir(char *path, uint8_t depth);
|
|
void Script_FileUploadConfiguration(void);
|
|
void ScriptFileUploadSuccess(void);
|
|
void script_upload(void);
|
|
uint8_t DownloadFile(char *file);
|
|
void HandleScriptTextareaConfiguration(void);
|
|
void HandleScriptConfiguration(void);
|
|
void ScriptSaveSettings(void);
|
|
void Script_HueStatus(String *response, uint16_t hue_devs);
|
|
void Script_Check_Hue(String *response);
|
|
void Script_Handle_Hue(String *path);
|
|
bool Script_SubCmd(void);
|
|
void execute_script(char *script);
|
|
bool ScriptCommand(void);
|
|
uint16_t xFAT_DATE(uint16_t year, uint8_t month, uint8_t day);
|
|
uint16_t xFAT_TIME(uint8_t hour, uint8_t minute, uint8_t second);
|
|
void dateTime(uint16_t* date, uint16_t* time);
|
|
bool ScriptMqttData(void);
|
|
String ScriptSubscribe(const char *data, int data_len);
|
|
String ScriptUnsubscribe(const char * data, int data_len);
|
|
void Script_Check_HTML_Setvars(void);
|
|
void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen);
|
|
void ScriptWebShow(void);
|
|
void ScriptJsonAppend(void);
|
|
bool Xdrv10(uint8_t function);
|
|
void KNX_ADD_GA( uint8_t GAop, uint8_t GA_FNUM, uint8_t GA_AREA, uint8_t GA_FDEF );
|
|
void KNX_DEL_GA( uint8_t GAnum );
|
|
void KNX_ADD_CB( uint8_t CBop, uint8_t CB_FNUM, uint8_t CB_AREA, uint8_t CB_FDEF );
|
|
void KNX_DEL_CB( uint8_t CBnum );
|
|
bool KNX_CONFIG_NOT_MATCH(void);
|
|
void KNXStart(void);
|
|
void KNX_INIT(void);
|
|
void KNX_CB_Action(message_t const &msg, void *arg);
|
|
void KnxUpdatePowerState(uint8_t device, power_t state);
|
|
void KnxSendButtonPower(void);
|
|
void KnxSensor(uint8_t sensor_type, float value);
|
|
void HandleKNXConfiguration(void);
|
|
void KNX_Save_Settings(void);
|
|
void CmndKnxTxCmnd(void);
|
|
void CmndKnxTxVal(void);
|
|
void CmndKnxEnabled(void);
|
|
void CmndKnxEnhanced(void);
|
|
void CmndKnxPa(void);
|
|
void CmndKnxGa(void);
|
|
void CmndKnxCb(void);
|
|
bool Xdrv11(uint8_t function);
|
|
void TryResponseAppend_P(const char *format, ...);
|
|
void HAssAnnounceRelayLight(void);
|
|
void HAssAnnounceButtonSwitch(uint8_t device, char *topic, uint8_t present, uint8_t key, uint8_t toggle);
|
|
void HAssAnnounceSwitches(void);
|
|
void HAssAnnounceButtons(void);
|
|
void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const char *MultiSubName, uint8_t subqty, uint8_t subidx);
|
|
void HAssAnnounceSensors(void);
|
|
void HAssAnnounceStatusSensor(void);
|
|
void HAssPublishStatus(void);
|
|
void HAssDiscovery(void);
|
|
void HAssDiscover(void);
|
|
void HAssAnyKey(void);
|
|
bool Xdrv12(uint8_t function);
|
|
void DisplayInit(uint8_t mode);
|
|
void DisplayClear(void);
|
|
void DisplayDrawHLine(uint16_t x, uint16_t y, int16_t len, uint16_t color);
|
|
void DisplayDrawVLine(uint16_t x, uint16_t y, int16_t len, uint16_t color);
|
|
void DisplayDrawLine(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color);
|
|
void DisplayDrawCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color);
|
|
void DisplayDrawFilledCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color);
|
|
void DisplayDrawRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color);
|
|
void DisplayDrawFilledRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color);
|
|
void DisplayDrawFrame(void);
|
|
void DisplaySetSize(uint8_t size);
|
|
void DisplaySetFont(uint8_t font);
|
|
void DisplaySetRotation(uint8_t rotation);
|
|
void DisplayDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag);
|
|
void DisplayOnOff(uint8_t on);
|
|
uint8_t fatoiv(char *cp,float *res);
|
|
uint8_t atoiv(char *cp, int16_t *res);
|
|
uint8_t atoiV(char *cp, uint16_t *res);
|
|
void alignright(char *string);
|
|
uint32_t decode_te(char *line);
|
|
void DisplayText(void);
|
|
void DisplayClearScreenBuffer(void);
|
|
void DisplayFreeScreenBuffer(void);
|
|
void DisplayAllocScreenBuffer(void);
|
|
void DisplayReAllocScreenBuffer(void);
|
|
void DisplayFillScreen(uint32_t line);
|
|
void DisplayClearLogBuffer(void);
|
|
void DisplayFreeLogBuffer(void);
|
|
void DisplayAllocLogBuffer(void);
|
|
void DisplayReAllocLogBuffer(void);
|
|
void DisplayLogBufferAdd(char* txt);
|
|
char* DisplayLogBuffer(char temp_code);
|
|
void DisplayLogBufferInit(void);
|
|
void DisplayJsonValue(const char* topic, const char* device, const char* mkey, const char* value);
|
|
void DisplayAnalyzeJson(char *topic, char *json);
|
|
void DisplayMqttSubscribe(void);
|
|
bool DisplayMqttData(void);
|
|
void DisplayLocalSensor(void);
|
|
void DisplayInitDriver(void);
|
|
void DisplaySetPower(void);
|
|
void CmndDisplay(void);
|
|
void CmndDisplayModel(void);
|
|
void CmndDisplayWidth(void);
|
|
void CmndDisplayHeight(void);
|
|
void CmndDisplayMode(void);
|
|
void CmndDisplayDimmer(void);
|
|
void CmndDisplaySize(void);
|
|
void CmndDisplayFont(void);
|
|
void CmndDisplayRotate(void);
|
|
void CmndDisplayText(void);
|
|
void CmndDisplayAddress(void);
|
|
void CmndDisplayRefresh(void);
|
|
void CmndDisplayColumns(void);
|
|
void CmndDisplayRows(void);
|
|
void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp);
|
|
void DrawAClock(uint16_t rad);
|
|
void ClrGraph(uint16_t num);
|
|
void DefineGraph(uint16_t num,uint16_t xp,uint16_t yp,int16_t xs,uint16_t ys,int16_t dec,float ymin, float ymax,uint8_t icol);
|
|
void DisplayCheckGraph();
|
|
void Save_graph(uint8_t num, char *path);
|
|
void Restore_graph(uint8_t num, char *path);
|
|
void RedrawGraph(uint8_t num, uint8_t flags);
|
|
void AddGraph(uint8_t num,uint8_t val);
|
|
void AddValue(uint8_t num,float fval);
|
|
bool Xdrv13(uint8_t function);
|
|
uint16_t MP3_Checksum(uint8_t *array);
|
|
void MP3PlayerInit(void);
|
|
void MP3_CMD(uint8_t mp3cmd,uint16_t val);
|
|
bool MP3PlayerCmd(void);
|
|
bool Xdrv14(uint8_t function);
|
|
void PCA9685_Detect(void);
|
|
void PCA9685_Reset(void);
|
|
void PCA9685_SetPWMfreq(double freq);
|
|
void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off);
|
|
void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted);
|
|
bool PCA9685_Command(void);
|
|
void PCA9685_OutputTelemetry(bool telemetry);
|
|
bool Xdrv15(uint8_t function);
|
|
void CmndTuyaSend(void);
|
|
void CmndTuyaMcu(void);
|
|
void TuyaAddMcuFunc(uint8_t fnId, uint8_t dpId);
|
|
void UpdateDevices();
|
|
inline bool TuyaFuncIdValid(uint8_t fnId);
|
|
uint8_t TuyaGetFuncId(uint8_t dpid);
|
|
uint8_t TuyaGetDpId(uint8_t fnId);
|
|
void TuyaSendState(uint8_t id, uint8_t type, uint8_t* value);
|
|
void TuyaSendBool(uint8_t id, bool value);
|
|
void TuyaSendValue(uint8_t id, uint32_t value);
|
|
void TuyaSendEnum(uint8_t id, uint32_t value);
|
|
void TuyaSendString(uint8_t id, char data[]);
|
|
bool TuyaSetPower(void);
|
|
bool TuyaSetChannels(void);
|
|
void LightSerialDuty(uint16_t duty);
|
|
void TuyaRequestState(void);
|
|
void TuyaResetWifi(void);
|
|
void TuyaProcessStatePacket(void);
|
|
void TuyaLowPowerModePacketProcess(void);
|
|
void TuyaHandleProductInfoPacket(void);
|
|
void TuyaSendLowPowerSuccessIfNeeded(void);
|
|
void TuyaNormalPowerModePacketProcess(void);
|
|
bool TuyaModuleSelected(void);
|
|
void TuyaInit(void);
|
|
void TuyaSerialInput(void);
|
|
bool TuyaButtonPressed(void);
|
|
uint8_t TuyaGetTuyaWifiState(void);
|
|
void TuyaSetWifiLed(void);
|
|
bool Xnrg16(uint8_t function);
|
|
bool Xdrv16(uint8_t function);
|
|
void RfReceiveCheck(void);
|
|
void RfInit(void);
|
|
void CmndRfSend(void);
|
|
bool Xdrv17(uint8_t function);
|
|
bool ArmtronixSetChannels(void);
|
|
void LightSerial2Duty(uint8_t duty1, uint8_t duty2);
|
|
void ArmtronixRequestState(void);
|
|
bool ArmtronixModuleSelected(void);
|
|
void ArmtronixInit(void);
|
|
void ArmtronixSerialInput(void);
|
|
void ArmtronixSetWifiLed(void);
|
|
bool Xdrv18(uint8_t function);
|
|
void PS16DZSerialSend(const char *tx_buffer);
|
|
void PS16DZSerialSendOk(void);
|
|
void PS16DZSerialSendUpdateCommand(void);
|
|
void PS16DZSerialInput(void);
|
|
bool PS16DZSerialSendUpdateCommandIfRequired(void);
|
|
void PS16DZInit(void);
|
|
bool PS16DZModuleSelected(void);
|
|
bool Xdrv19(uint8_t function);
|
|
String HueBridgeId(void);
|
|
String HueSerialnumber(void);
|
|
String HueUuid(void);
|
|
void HueRespondToMSearch(void);
|
|
String GetHueDeviceId(uint8_t id);
|
|
String GetHueUserId(void);
|
|
void HandleUpnpSetupHue(void);
|
|
void HueNotImplemented(String *path);
|
|
void HueConfigResponse(String *response);
|
|
void HueConfig(String *path);
|
|
uint8_t getLocalLightSubtype(uint8_t device);
|
|
void HueLightStatus1(uint8_t device, String *response);
|
|
bool HueActive(uint8_t device);
|
|
void HueLightStatus2(uint8_t device, String *response);
|
|
uint32_t EncodeLightId(uint8_t relay_id);
|
|
uint32_t DecodeLightId(uint32_t hue_id);
|
|
uint32_t findEchoGeneration(void);
|
|
void HueGlobalConfig(String *path);
|
|
void HueAuthentication(String *path);
|
|
void HueLights(String *path);
|
|
void HueGroups(String *path);
|
|
void HandleHueApi(String *path);
|
|
bool Xdrv20(uint8_t function);
|
|
String WemoSerialnumber(void);
|
|
String WemoUuid(void);
|
|
void WemoRespondToMSearch(int echo_type);
|
|
void HandleUpnpEvent(void);
|
|
void HandleUpnpService(void);
|
|
void HandleUpnpMetaService(void);
|
|
void HandleUpnpSetupWemo(void);
|
|
bool Xdrv21(uint8_t function);
|
|
bool IsModuleIfan(void);
|
|
uint8_t MaxFanspeed(void);
|
|
uint8_t GetFanspeed(void);
|
|
void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence);
|
|
void SonoffIfanReceived(void);
|
|
bool SonoffIfanSerialInput(void);
|
|
void CmndFanspeed(void);
|
|
bool SonoffIfanInit(void);
|
|
void SonoffIfanUpdate(void);
|
|
bool Xdrv22(uint8_t function);
|
|
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val);
|
|
void CopyJsonArray(JsonArray &to, const JsonArray &arr);
|
|
void CopyJsonObject(JsonObject &to, const JsonObject &from);
|
|
uint16_t fromClusterCode(uint8_t c);
|
|
uint8_t toClusterCode(uint16_t c);
|
|
class SBuffer hibernateDevice(const struct Z_Device &device);
|
|
class SBuffer hibernateDevices(void);
|
|
void hidrateDevices(const SBuffer &buf);
|
|
void loadZigbeeDevices(void);
|
|
void saveZigbeeDevices(void);
|
|
void eraseZigbeeDevices(void);
|
|
uint8_t toPercentageCR2032(uint32_t voltage);
|
|
uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf,
|
|
uint32_t offset, uint32_t len);
|
|
int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value);
|
|
int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value);
|
|
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint);
|
|
const __FlashStringHelper* zigbeeFindCommand(const char *command);
|
|
inline bool isXYZ(char c);
|
|
inline char hexDigit(uint32_t h);
|
|
String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z);
|
|
uint32_t ZigbeeAliasOrNumber(const char *state_text);
|
|
uint8_t ZigbeeGetInstructionSize(uint8_t instr);
|
|
void ZigbeeGotoLabel(uint8_t label);
|
|
void ZigbeeStateMachine_Run(void);
|
|
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf);
|
|
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf);
|
|
int32_t Z_Reboot(int32_t res, class SBuffer &buf);
|
|
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf);
|
|
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match);
|
|
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf);
|
|
void Z_SendActiveEpReq(uint16_t shortaddr);
|
|
void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint);
|
|
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf);
|
|
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf);
|
|
void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid);
|
|
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf);
|
|
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf);
|
|
int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf);
|
|
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json);
|
|
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value);
|
|
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf);
|
|
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf);
|
|
int32_t Z_Load_Devices(uint8_t value);
|
|
int32_t Z_State_Ready(uint8_t value);
|
|
int32_t ZigbeeProcessInput(class SBuffer &buf);
|
|
void ZigbeeInput(void);
|
|
void ZigbeeInit(void);
|
|
uint32_t strToUInt(const JsonVariant &val);
|
|
void CmndZbReset(void);
|
|
void CmndZbZNPSendOrReceive(bool send);
|
|
void CmndZbZNPReceive(void);
|
|
void CmndZbZNPSend(void);
|
|
void ZigbeeZNPSend(const uint8_t *msg, size_t len);
|
|
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId);
|
|
inline int8_t hexValue(char c);
|
|
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data);
|
|
void CmndZbSend(void);
|
|
void CmndZbBind(void);
|
|
void CmndZbProbe(void);
|
|
void CmndZbName(void);
|
|
void CmndZbForget(void);
|
|
void CmndZbSave(void);
|
|
void CmndZbRead(void);
|
|
void CmndZbPermitJoin(void);
|
|
void CmndZbStatus(void);
|
|
bool Xdrv23(uint8_t function);
|
|
void BuzzerOff(void);
|
|
void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32_t mode);
|
|
void BuzzerSetStateToLed(uint32_t state);
|
|
void BuzzerBeep(uint32_t count);
|
|
void BuzzerEnabledBeep(uint32_t count, uint32_t duration);
|
|
bool BuzzerPinState(void);
|
|
void BuzzerInit(void);
|
|
void BuzzerEvery100mSec(void);
|
|
void CmndBuzzer(void);
|
|
bool Xdrv24(uint8_t function);
|
|
void A4988Init(void);
|
|
void CmndDoMove(void);
|
|
void CmndDoRotate(void);
|
|
void CmndDoTurn(void);
|
|
void CmndSetMIS(void);
|
|
void CmndSetSPR(void);
|
|
void CmndSetRPM(void);
|
|
bool Xdrv25(uint8_t function);
|
|
void AriluxRfInterrupt(void);
|
|
void AriluxRfHandler(void);
|
|
void AriluxRfInit(void);
|
|
void AriluxRfDisable(void);
|
|
bool Xdrv26(uint8_t function);
|
|
void ShutterLogPos(uint32_t i);
|
|
void ShutterRtc50mS(void);
|
|
int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index);
|
|
uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index);
|
|
void ShutterInit(void);
|
|
void ShutterReportPosition(bool always);
|
|
void ShutterLimitRealAndTargetPositions(uint32_t i);
|
|
void ShutterUpdatePosition(void);
|
|
bool ShutterState(uint32_t device);
|
|
void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos);
|
|
void ShutterWaitForMotorStop(uint32_t i);
|
|
int32_t ShutterCounterBasedPosition(uint32_t i);
|
|
void ShutterRelayChanged(void);
|
|
bool ShutterButtonIsSimultaneousHold(uint32_t button_index, uint32_t shutter_index);
|
|
void ShutterButtonHandler(void);
|
|
void ShutterSetPosition(uint32_t device, uint32_t position);
|
|
void CmndShutterOpen(void);
|
|
void CmndShutterClose(void);
|
|
void CmndShutterStop(void);
|
|
void CmndShutterPosition(void);
|
|
void CmndShutterOpenTime(void);
|
|
void CmndShutterCloseTime(void);
|
|
void CmndShutterMotorDelay(void);
|
|
void CmndShutterRelay(void);
|
|
void CmndShutterButton(void);
|
|
void CmndShutterSetHalfway(void);
|
|
void CmndShutterFrequency(void);
|
|
void CmndShutterSetClose(void);
|
|
void CmndShutterInvert(void);
|
|
void CmndShutterCalibration(void);
|
|
void CmndShutterLock(void);
|
|
void CmndShutterEnableEndStopTime(void);
|
|
bool Xdrv27(uint8_t function);
|
|
void Pcf8574SwitchRelay(void);
|
|
void Pcf8574Init(void);
|
|
void HandlePcf8574(void);
|
|
void Pcf8574SaveSettings(void);
|
|
bool Xdrv28(uint8_t function);
|
|
bool DeepSleepEnabled(void);
|
|
void DeepSleepReInit(void);
|
|
void DeepSleepPrepare(void);
|
|
void DeepSleepStart(void);
|
|
void DeepSleepEverySecond(void);
|
|
void CmndDeepsleepTime(void);
|
|
bool Xdrv29(uint8_t function);
|
|
uint8_t crc8(const uint8_t *p, uint8_t len);
|
|
void ExsSendCmd(uint8_t cmd, uint8_t value);
|
|
uint8_t ExsSetPower(uint8_t device, uint8_t power);
|
|
uint8_t ExsSetBri(uint8_t device, uint8_t bri);
|
|
uint8_t ExsSyncState(uint8_t device);
|
|
bool ExsSyncState();
|
|
void ExsDebugState();
|
|
void ExsPacketProcess(void);
|
|
bool ExsModuleSelected(void);
|
|
bool ExsSetChannels(void);
|
|
bool ExsSetPower(void);
|
|
void EsxMcuStart(void);
|
|
void ExsInit(void);
|
|
void ExsSerialInput(void);
|
|
void CmndExsDimm(void);
|
|
void CmndExsDimmTbl(void);
|
|
void CmndExsDimmVal(void);
|
|
void CmndExsDimms(void);
|
|
void CmndExsChLock(void);
|
|
void CmndExsState(void);
|
|
bool Xdrv30(uint8_t function);
|
|
uint32_t TasmotaSlave_FlashStart(void);
|
|
uint8_t TasmotaSlave_UpdateInit(void);
|
|
void TasmotaSlave_Reset(void);
|
|
uint8_t TasmotaSlave_waitForSerialData(int dataCount, int timeout);
|
|
uint8_t TasmotaSlave_sendBytes(uint8_t* bytes, int count);
|
|
uint8_t TasmotaSlave_execCmd(uint8_t cmd);
|
|
uint8_t TasmotaSlave_execParam(uint8_t cmd, uint8_t* params, int count);
|
|
uint8_t TasmotaSlave_exitProgMode(void);
|
|
uint8_t TasmotaSlave_SetupFlash(void);
|
|
uint8_t TasmotaSlave_loadAddress(uint8_t adrHi, uint8_t adrLo);
|
|
void TasmotaSlave_FlashPage(uint8_t addr_h, uint8_t addr_l, uint8_t* data);
|
|
void TasmotaSlave_Flash(void);
|
|
void TasmotaSlave_SetFlagFlashing(bool value);
|
|
bool TasmotaSlave_GetFlagFlashing(void);
|
|
void TasmotaSlave_WriteBuffer(uint8_t *buf, size_t size);
|
|
void TasmotaSlave_Init(void);
|
|
void TasmotaSlave_Show(void);
|
|
void TasmotaSlave_sendCmnd(uint8_t cmnd, uint8_t param);
|
|
void CmndTasmotaSlaveReset(void);
|
|
void CmndTasmotaSlaveSend(void);
|
|
void TasmotaSlave_ProcessIn(void);
|
|
bool Xdrv31(uint8_t function);
|
|
void HotPlugInit(void);
|
|
void HotPlugEverySecond(void);
|
|
void CmndHotPlugTime(void);
|
|
bool Xdrv32(uint8_t function);
|
|
bool NRF24initRadio();
|
|
bool NRF24Detect(void);
|
|
bool Xdrv33(uint8_t function);
|
|
void ExceptionTest(uint8_t type);
|
|
void CpuLoadLoop(void);
|
|
void DebugFreeMem(void);
|
|
void DebugFreeMem(void);
|
|
void DebugRtcDump(char* parms);
|
|
void DebugCfgDump(char* parms);
|
|
void DebugCfgPeek(char* parms);
|
|
void DebugCfgPoke(char* parms);
|
|
void SetFlashMode(uint8_t mode);
|
|
void CmndHelp(void);
|
|
void CmndRtcDump(void);
|
|
void CmndCfgDump(void);
|
|
void CmndCfgPeek(void);
|
|
void CmndCfgPoke(void);
|
|
void CmndCfgXor(void);
|
|
void CmndException(void);
|
|
void CmndCpuCheck(void);
|
|
void CmndFreemem(void);
|
|
void CmndSetSensor(void);
|
|
void CmndFlashMode(void);
|
|
uint32_t DebugSwap32(uint32_t x);
|
|
void CmndFlashDump(void);
|
|
void CmndI2cWrite(void);
|
|
void CmndI2cRead(void);
|
|
void CmndI2cStretch(void);
|
|
void CmndI2cClock(void);
|
|
bool Xdrv99(uint8_t function);
|
|
void XsnsDriverState(void);
|
|
bool XdrvRulesProcess(void);
|
|
void ShowFreeMem(const char *where);
|
|
bool XdrvCallDriver(uint32_t driver, uint8_t Function);
|
|
bool XdrvCall(uint8_t Function);
|
|
void LcdInitMode(void);
|
|
void LcdInit(uint8_t mode);
|
|
void LcdInitDriver(void);
|
|
void LcdDrawStringAt(void);
|
|
void LcdDisplayOnOff(uint8_t on);
|
|
void LcdCenter(uint8_t row, char* txt);
|
|
bool LcdPrintLog(void);
|
|
void LcdTime(void);
|
|
void LcdRefresh(void);
|
|
bool Xdsp01(uint8_t function);
|
|
void SSD1306InitDriver(void);
|
|
void Ssd1306PrintLog(void);
|
|
void Ssd1306Time(void);
|
|
void Ssd1306Refresh(void);
|
|
bool Xdsp02(byte function);
|
|
void MatrixWrite(void);
|
|
void MatrixClear(void);
|
|
void MatrixFixed(char* txt);
|
|
void MatrixCenter(char* txt);
|
|
void MatrixScrollLeft(char* txt, int loop);
|
|
void MatrixScrollUp(char* txt, int loop);
|
|
void MatrixInitMode(void);
|
|
void MatrixInit(uint8_t mode);
|
|
void MatrixInitDriver(void);
|
|
void MatrixOnOff(void);
|
|
void MatrixDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag);
|
|
void MatrixPrintLog(uint8_t direction);
|
|
void MatrixRefresh(void);
|
|
bool Xdsp03(uint8_t function);
|
|
void Ili9341InitMode(void);
|
|
void Ili9341Init(uint8_t mode);
|
|
void Ili9341InitDriver(void);
|
|
void Ili9341Clear(void);
|
|
void Ili9341DrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag);
|
|
void Ili9341DisplayOnOff(uint8_t on);
|
|
void Ili9341OnOff(void);
|
|
void Ili9341PrintLog(void);
|
|
void Ili9341Refresh(void);
|
|
bool Xdsp04(uint8_t function);
|
|
void EpdInitDriver29();
|
|
void EpdPrintLog29(void);
|
|
void EpdRefresh29(void);
|
|
bool Xdsp05(uint8_t function);
|
|
void EpdInitDriver42();
|
|
void EpdRefresh42();
|
|
bool Xdsp06(uint8_t function);
|
|
void SH1106InitDriver();
|
|
void SH1106PrintLog(void);
|
|
void SH1106Time(void);
|
|
void SH1106Refresh(void);
|
|
bool Xdsp07(uint8_t function);
|
|
void ILI9488_InitDriver();
|
|
void ILI9488_MQTT(uint8_t count,const char *cp);
|
|
void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr);
|
|
void FT6236Check();
|
|
bool Xdsp08(uint8_t function);
|
|
void SSD1351_InitDriver();
|
|
void SSD1351PrintLog(void);
|
|
void SSD1351Time(void);
|
|
void SSD1351Refresh(void);
|
|
bool Xdsp09(uint8_t function);
|
|
void RA8876_InitDriver();
|
|
void RA8876_MQTT(uint8_t count,const char *cp);
|
|
void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr);
|
|
void FT5316Check();
|
|
bool Xdsp10(uint8_t function);
|
|
uint8_t XdspPresent(void);
|
|
bool XdspCall(uint8_t Function);
|
|
void Ws2812StripShow(void);
|
|
int mod(int a, int b);
|
|
void Ws2812UpdatePixelColor(int position, struct WsColor hand_color, float offset);
|
|
void Ws2812UpdateHand(int position, uint32_t index);
|
|
void Ws2812Clock(void);
|
|
void Ws2812GradientColor(uint32_t schemenr, struct WsColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i);
|
|
void Ws2812Gradient(uint32_t schemenr);
|
|
void Ws2812Bars(uint32_t schemenr);
|
|
void Ws2812Clear(void);
|
|
void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white);
|
|
char* Ws2812GetColor(uint32_t led, char* scolor);
|
|
void Ws2812ForceSuspend (void);
|
|
void Ws2812ForceUpdate (void);
|
|
bool Ws2812SetChannels(void);
|
|
void Ws2812ShowScheme(void);
|
|
void Ws2812ModuleSelected(void);
|
|
void CmndLed(void);
|
|
void CmndPixels(void);
|
|
void CmndRotation(void);
|
|
void CmndWidth(void);
|
|
bool Xlgt01(uint8_t function);
|
|
void LightDiPulse(uint8_t times);
|
|
void LightDckiPulse(uint8_t times);
|
|
void LightMy92x1Write(uint8_t data);
|
|
void LightMy92x1Init(void);
|
|
void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c);
|
|
bool My92x1SetChannels(void);
|
|
void My92x1ModuleSelected(void);
|
|
bool Xlgt02(uint8_t function);
|
|
void SM16716_SendBit(uint8_t v);
|
|
void SM16716_SendByte(uint8_t v);
|
|
void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b);
|
|
void SM16716_Init(void);
|
|
bool Sm16716SetChannels(void);
|
|
void Sm16716ModuleSelected(void);
|
|
bool Xlgt03(uint8_t function);
|
|
uint8_t Sm2135Write(uint8_t data);
|
|
void Sm2135Send(uint8_t *buffer, uint8_t size);
|
|
bool Sm2135SetChannels(void);
|
|
void Sm2135ModuleSelected(void);
|
|
bool Xlgt04(uint8_t function);
|
|
void SnfL1Send(const char *buffer);
|
|
void SnfL1SerialSendOk(void);
|
|
bool SnfL1SerialInput(void);
|
|
bool SnfL1SetChannels(void);
|
|
void SnfL1ModuleSelected(void);
|
|
bool Xlgt05(uint8_t function);
|
|
bool XlgtCall(uint8_t function);
|
|
void HlwCfInterrupt(void);
|
|
void HlwCf1Interrupt(void);
|
|
void HlwEvery200ms(void);
|
|
void HlwEverySecond(void);
|
|
void HlwSnsInit(void);
|
|
void HlwDrvInit(void);
|
|
bool HlwCommand(void);
|
|
bool Xnrg01(uint8_t function);
|
|
void CseReceived(void);
|
|
bool CseSerialInput(void);
|
|
void CseEverySecond(void);
|
|
void CseSnsInit(void);
|
|
void CseDrvInit(void);
|
|
bool CseCommand(void);
|
|
bool Xnrg02(uint8_t function);
|
|
uint8_t PzemCrc(uint8_t *data);
|
|
void PzemSend(uint8_t cmd);
|
|
bool PzemReceiveReady(void);
|
|
bool PzemRecieve(uint8_t resp, float *data);
|
|
void PzemEvery250ms(void);
|
|
void PzemSnsInit(void);
|
|
void PzemDrvInit(void);
|
|
bool PzemCommand(void);
|
|
bool Xnrg03(uint8_t function);
|
|
uint8_t McpChecksum(uint8_t *data);
|
|
unsigned long McpExtractInt(char *data, uint8_t offset, uint8_t size);
|
|
void McpSetInt(unsigned long value, uint8_t *data, uint8_t offset, size_t size);
|
|
void McpSend(uint8_t *data);
|
|
void McpGetAddress(void);
|
|
void McpAddressReceive(void);
|
|
void McpGetCalibration(void);
|
|
void McpParseCalibration(void);
|
|
bool McpCalibrationCalc(struct mcp_cal_registers_type *cal_registers, uint8_t range_shift);
|
|
void McpSetCalibration(struct mcp_cal_registers_type *cal_registers);
|
|
void McpSetSystemConfiguration(uint16 interval);
|
|
void McpGetFrequency(void);
|
|
void McpParseFrequency(void);
|
|
void McpSetFrequency(uint16_t line_frequency_ref, uint16_t gain_line_frequency);
|
|
void McpGetData(void);
|
|
void McpParseData(void);
|
|
void McpSerialInput(void);
|
|
void McpEverySecond(void);
|
|
void McpSnsInit(void);
|
|
void McpDrvInit(void);
|
|
bool McpCommand(void);
|
|
bool Xnrg04(uint8_t function);
|
|
void PzemAcEverySecond(void);
|
|
void PzemAcSnsInit(void);
|
|
void PzemAcDrvInit(void);
|
|
bool PzemAcCommand(void);
|
|
bool Xnrg05(uint8_t function);
|
|
void PzemDcEverySecond(void);
|
|
void PzemDcSnsInit(void);
|
|
void PzemDcDrvInit(void);
|
|
bool PzemDcCommand(void);
|
|
bool Xnrg06(uint8_t function);
|
|
int Ade7953RegSize(uint16_t reg);
|
|
void Ade7953Write(uint16_t reg, uint32_t val);
|
|
int32_t Ade7953Read(uint16_t reg);
|
|
void Ade7953Init(void);
|
|
void Ade7953GetData(void);
|
|
void Ade7953EnergyEverySecond(void);
|
|
void Ade7953DrvInit(void);
|
|
bool Ade7953Command(void);
|
|
bool Xnrg07(uint8_t function);
|
|
void SDM120Every250ms(void);
|
|
void Sdm120SnsInit(void);
|
|
void Sdm120DrvInit(void);
|
|
void Sdm220Reset(void);
|
|
void Sdm220Show(bool json);
|
|
bool Xnrg08(uint8_t function);
|
|
void Dds2382EverySecond(void);
|
|
void Dds2382SnsInit(void);
|
|
void Dds2382DrvInit(void);
|
|
bool Xnrg09(uint8_t function);
|
|
void SDM630Every250ms(void);
|
|
void Sdm630SnsInit(void);
|
|
void Sdm630DrvInit(void);
|
|
bool Xnrg10(uint8_t function);
|
|
void DDSU666Every250ms(void);
|
|
void Ddsu666SnsInit(void);
|
|
void Ddsu666DrvInit(void);
|
|
bool Xnrg11(uint8_t function);
|
|
bool solaxX1_RS485ReceiveReady(void);
|
|
void solaxX1_RS485Send(uint16_t msgLen);
|
|
uint8_t solaxX1_RS485Receive(uint8_t *value);
|
|
uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen);
|
|
void solaxX1_SendInverterAddress(void);
|
|
void solaxX1_QueryLiveData(void);
|
|
uint8_t solaxX1_ParseErrorCode(uint32_t code);
|
|
void solaxX1250MSecond(void);
|
|
void solaxX1SnsInit(void);
|
|
void solaxX1DrvInit(void);
|
|
void solaxX1Show(bool json);
|
|
bool Xnrg12(uint8_t function);
|
|
void FifLEEvery250ms(void);
|
|
void FifLESnsInit(void);
|
|
void FifLEDrvInit(void);
|
|
void FifLEReset(void);
|
|
void FifLEShow(bool json);
|
|
bool Xnrg13(uint8_t function);
|
|
bool XnrgCall(uint8_t function);
|
|
void CounterUpdate(uint8_t index);
|
|
void CounterUpdate1(void);
|
|
void CounterUpdate2(void);
|
|
void CounterUpdate3(void);
|
|
void CounterUpdate4(void);
|
|
bool CounterPinState(void);
|
|
void CounterInit(void);
|
|
void CounterEverySecond(void);
|
|
void CounterSaveState(void);
|
|
void CounterShow(bool json);
|
|
void CmndCounter(void);
|
|
void CmndCounterType(void);
|
|
void CmndCounterDebounce(void);
|
|
bool Xsns01(uint8_t function);
|
|
void AdcInit(void);
|
|
uint16_t AdcRead(uint8_t factor);
|
|
void AdcEvery250ms(void);
|
|
uint16_t AdcGetLux(void);
|
|
uint16_t AdcGetRange(void);
|
|
void AdcGetCurrentPower(uint8_t factor);
|
|
void AdcEverySecond(void);
|
|
void AdcShow(bool json);
|
|
void CmndAdc(void);
|
|
void CmndAdcs(void);
|
|
void CmndAdcParam(void);
|
|
bool Xsns02(uint8_t function);
|
|
void SonoffScSend(const char *data);
|
|
void SonoffScInit(void);
|
|
void SonoffScSerialInput(char *rcvstat);
|
|
void SonoffScShow(bool json);
|
|
bool Xsns04(uint8_t function);
|
|
uint8_t OneWireReset(void);
|
|
void OneWireWriteBit(uint8_t v);
|
|
uint8_t OneWireReadBit(void);
|
|
void OneWireWrite(uint8_t v);
|
|
uint8_t OneWireRead(void);
|
|
void OneWireSelect(const uint8_t rom[8]);
|
|
void OneWireResetSearch(void);
|
|
uint8_t OneWireSearch(uint8_t *newAddr);
|
|
bool OneWireCrc8(uint8_t *addr);
|
|
void Ds18x20Init(void);
|
|
void Ds18x20Convert(void);
|
|
bool Ds18x20Read(uint8_t sensor);
|
|
void Ds18x20Name(uint8_t sensor);
|
|
void Ds18x20EverySecond(void);
|
|
void Ds18x20Show(bool json);
|
|
bool Xsns05(uint8_t function);
|
|
void DhtReadPrep(void);
|
|
int32_t DhtExpectPulse(uint8_t sensor, bool level);
|
|
bool DhtRead(uint8_t sensor);
|
|
void DhtReadTempHum(uint8_t sensor);
|
|
bool DhtPinState();
|
|
void DhtInit(void);
|
|
void DhtEverySecond(void);
|
|
void DhtShow(bool json);
|
|
bool Xsns06(uint8_t function);
|
|
void DhtReadPrep(void);
|
|
int32_t DhtExpectPulse(uint8_t sensor, bool level);
|
|
bool DhtRead(uint8_t sensor);
|
|
void DhtReadTempHum(uint8_t sensor);
|
|
bool DhtPinState();
|
|
void DhtInit(void);
|
|
void DhtEverySecond(void);
|
|
void DhtShow(bool json);
|
|
bool Xsns06(uint8_t function);
|
|
bool DhtExpectPulse(uint8_t sensor, int level);
|
|
int DhtReadDat(uint8_t sensor);
|
|
bool DhtRead(uint8_t sensor);
|
|
void DhtReadTempHum(uint8_t sensor);
|
|
bool DhtPinState();
|
|
void DhtInit(void);
|
|
void DhtEverySecond(void);
|
|
void DhtShow(bool json);
|
|
bool Xsns06(uint8_t function);
|
|
bool DhtExpectPulse(uint32_t sensor, uint32_t level);
|
|
bool DhtRead(uint32_t sensor);
|
|
void DhtReadTempHum(uint32_t sensor);
|
|
bool DhtPinState();
|
|
void DhtInit(void);
|
|
void DhtEverySecond(void);
|
|
void DhtShow(bool json);
|
|
bool Xsns06(uint8_t function);
|
|
bool DhtWaitState(uint32_t sensor, uint32_t level);
|
|
bool DhtRead(uint32_t sensor);
|
|
bool DhtPinState();
|
|
void DhtInit(void);
|
|
void DhtEverySecond(void);
|
|
void DhtShow(bool json);
|
|
bool Xsns06(uint8_t function);
|
|
bool ShtReset(void);
|
|
bool ShtSendCommand(const uint8_t cmd);
|
|
bool ShtAwaitResult(void);
|
|
int ShtReadData(void);
|
|
bool ShtRead(void);
|
|
void ShtDetect(void);
|
|
void ShtEverySecond(void);
|
|
void ShtShow(bool json);
|
|
bool Xsns07(uint8_t function);
|
|
uint8_t HtuCheckCrc8(uint16_t data);
|
|
uint8_t HtuReadDeviceId(void);
|
|
void HtuSetResolution(uint8_t resolution);
|
|
void HtuReset(void);
|
|
void HtuHeater(uint8_t heater);
|
|
void HtuInit(void);
|
|
bool HtuRead(void);
|
|
void HtuDetect(void);
|
|
void HtuEverySecond(void);
|
|
void HtuShow(bool json);
|
|
bool Xsns08(uint8_t function);
|
|
bool Bmp180Calibration(uint8_t bmp_idx);
|
|
void Bmp180Read(uint8_t bmp_idx);
|
|
bool Bmx280Calibrate(uint8_t bmp_idx);
|
|
void Bme280Read(uint8_t bmp_idx);
|
|
static void BmeDelayMs(uint32_t ms);
|
|
bool Bme680Init(uint8_t bmp_idx);
|
|
void Bme680Read(uint8_t bmp_idx);
|
|
void BmpDetect(void);
|
|
void BmpRead(void);
|
|
void BmpShow(bool json);
|
|
void BMP_EnterSleep(void);
|
|
bool Xsns09(uint8_t function);
|
|
bool Bh1750Read(void);
|
|
void Bh1750Detect(void);
|
|
void Bh1750EverySecond(void);
|
|
void Bh1750Show(bool json);
|
|
bool Xsns10(uint8_t function);
|
|
void Veml6070Detect(void);
|
|
void Veml6070UvTableInit(void);
|
|
void Veml6070EverySecond(void);
|
|
void Veml6070ModeCmd(bool mode_cmd);
|
|
uint16_t Veml6070ReadUv(void);
|
|
double Veml6070UvRiskLevel(uint16_t uv_level);
|
|
double Veml6070UvPower(double uvrisk);
|
|
void Veml6070Show(bool json);
|
|
bool Xsns11(uint8_t function);
|
|
void Ads1115StartComparator(uint8_t channel, uint16_t mode);
|
|
int16_t Ads1115GetConversion(uint8_t channel);
|
|
void Ads1115Detect(void);
|
|
void Ads1115Show(bool json);
|
|
bool Xsns12(uint8_t function);
|
|
bool Ina219SetCalibration(uint8_t mode, uint16_t addr);
|
|
float Ina219GetShuntVoltage_mV(uint16_t addr);
|
|
float Ina219GetBusVoltage_V(uint16_t addr);
|
|
float Ina219GetCurrent_mA(uint16_t addr);
|
|
bool Ina219Read(void);
|
|
bool Ina219CommandSensor(void);
|
|
void Ina219Detect(void);
|
|
void Ina219EverySecond(void);
|
|
void Ina219Show(bool json);
|
|
bool Xsns13(uint8_t function);
|
|
bool Sht3xRead(float &t, float &h, uint8_t sht3x_address);
|
|
void Sht3xDetect(void);
|
|
void Sht3xShow(bool json);
|
|
bool Xsns14(uint8_t function);
|
|
uint8_t MhzCalculateChecksum(uint8_t *array);
|
|
size_t MhzSendCmd(uint8_t command_id);
|
|
bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s);
|
|
void MhzEverySecond(void);
|
|
bool MhzCommandSensor(void);
|
|
void MhzInit(void);
|
|
void MhzShow(bool json);
|
|
bool Xsns15(uint8_t function);
|
|
bool Tsl2561Read(void);
|
|
void Tsl2561Detect(void);
|
|
void Tsl2561EverySecond(void);
|
|
void Tsl2561Show(bool json);
|
|
bool Xsns16(uint8_t function);
|
|
void Senseair250ms(void);
|
|
void SenseairInit(void);
|
|
void SenseairShow(bool json);
|
|
bool Xsns17(uint8_t function);
|
|
bool PmsReadData(void);
|
|
void PmsSecond(void);
|
|
void PmsInit(void);
|
|
void PmsShow(bool json);
|
|
bool Xsns18(uint8_t function);
|
|
void MGSInit(void);
|
|
void MGSPrepare(void);
|
|
char* measure_gas(int gas_type, char* buffer);
|
|
void MGSShow(bool json);
|
|
bool Xsns19(uint8_t function);
|
|
bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, uint8_t *buffer);
|
|
void NovaSdsSetWorkPeriod(void);
|
|
bool NovaSdsReadData(void);
|
|
void NovaSdsSecond(void);
|
|
bool NovaSdsCommandSensor(void);
|
|
void NovaSdsInit(void);
|
|
void NovaSdsShow(bool json);
|
|
bool Xsns20(uint8_t function);
|
|
void sgp30_Init(void);
|
|
float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit);
|
|
void Sgp30Update(void);
|
|
void Sgp30Show(bool json);
|
|
bool Xsns21(uint8_t function);
|
|
uint8_t Sr04TModeDetect(void);
|
|
uint16_t Sr04TMiddleValue(uint16_t first, uint16_t second, uint16_t third);
|
|
uint16_t Sr04TMode3Distance();
|
|
uint16_t Sr04TMode2Distance(void);
|
|
void Sr04TReading(void);
|
|
void Sr04Show(bool json);
|
|
bool Xsns22(uint8_t function);
|
|
uint8_t Si1145ReadByte(uint8_t reg);
|
|
uint16_t Si1145ReadHalfWord(uint8_t reg);
|
|
bool Si1145WriteByte(uint8_t reg, uint16_t val);
|
|
uint8_t Si1145WriteParamData(uint8_t p, uint8_t v);
|
|
bool Si1145Present(void);
|
|
void Si1145Reset(void);
|
|
void Si1145DeInit(void);
|
|
bool Si1145Begin(void);
|
|
uint16_t Si1145ReadUV(void);
|
|
uint16_t Si1145ReadVisible(void);
|
|
uint16_t Si1145ReadIR(void);
|
|
bool Si1145Read(void);
|
|
void Si1145Detect(void);
|
|
void Si1145Update(void);
|
|
void Si1145Show(bool json);
|
|
bool Xsns24(uint8_t function);
|
|
void LM75ADDetect(void);
|
|
float LM75ADGetTemp(void);
|
|
void LM75ADShow(bool json);
|
|
bool Xsns26(uint8_t function);
|
|
int8_t wireReadDataBlock( uint8_t reg,
|
|
uint8_t *val,
|
|
uint16_t len);
|
|
void calculateColorTemperature(void);
|
|
bool APDS9960_init(void);
|
|
uint8_t getMode(void);
|
|
void setMode(uint8_t mode, uint8_t enable);
|
|
void enableLightSensor(void);
|
|
void disableLightSensor(void);
|
|
void enableProximitySensor(void);
|
|
void disableProximitySensor(void);
|
|
void enableGestureSensor(void);
|
|
void disableGestureSensor(void);
|
|
bool isGestureAvailable(void);
|
|
int16_t readGesture(void);
|
|
void enablePower(void);
|
|
void disablePower(void);
|
|
void readAllColorAndProximityData(void);
|
|
void resetGestureParameters(void);
|
|
bool processGestureData(void);
|
|
bool decodeGesture(void);
|
|
void handleGesture(void);
|
|
void APDS9960_adjustATime(void);
|
|
void APDS9960_loop(void);
|
|
void APDS9960_detect(void);
|
|
void APDS9960_show(bool json);
|
|
bool APDS9960CommandSensor(void);
|
|
bool Xsns27(uint8_t function);
|
|
void Tm16XXSend(uint8_t data);
|
|
void Tm16XXSendCommand(uint8_t cmd);
|
|
void TM16XXSendData(uint8_t address, uint8_t data);
|
|
uint8_t Tm16XXReceive(void);
|
|
void Tm16XXClearDisplay(void);
|
|
void Tm1638SetLED(uint8_t color, uint8_t pos);
|
|
void Tm1638SetLEDs(word leds);
|
|
uint8_t Tm1638GetButtons(void);
|
|
void TmInit(void);
|
|
void TmLoop(void);
|
|
bool Xsns28(uint8_t function);
|
|
void MCP230xx_CheckForIntCounter(void);
|
|
void MCP230xx_CheckForIntRetainer(void);
|
|
const char* IntModeTxt(uint8_t intmo);
|
|
uint8_t MCP230xx_readGPIO(uint8_t port);
|
|
void MCP230xx_ApplySettings(void);
|
|
void MCP230xx_Detect(void);
|
|
void MCP230xx_CheckForInterrupt(void);
|
|
void MCP230xx_Show(bool json);
|
|
void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate);
|
|
void MCP230xx_Reset(uint8_t pinmode);
|
|
bool MCP230xx_Command(void);
|
|
void MCP230xx_UpdateWebData(void);
|
|
void MCP230xx_OutputTelemetry(void);
|
|
void MCP230xx_Interrupt_Counter_Report(void);
|
|
void MCP230xx_Interrupt_Retain_Report(void);
|
|
bool Xsns29(uint8_t function);
|
|
void Mpr121Init(struct mpr121 *pS, bool initial);
|
|
void Mpr121Show(struct mpr121 *pS, uint8_t function);
|
|
bool Xsns30(uint8_t function);
|
|
void CCS811Detect(void);
|
|
void CCS811Update(void);
|
|
void CCS811Show(bool json);
|
|
bool Xsns31(uint8_t function);
|
|
void MPU_6050PerformReading(void);
|
|
void MPU_6050Detect(void);
|
|
void MPU_6050Show(bool json);
|
|
bool Xsns32(uint8_t function);
|
|
void DS3231Detect(void);
|
|
uint8_t bcd2dec(uint8_t n);
|
|
uint8_t dec2bcd(uint8_t n);
|
|
uint32_t ReadFromDS3231(void);
|
|
void SetDS3231Time (uint32_t epoch_time);
|
|
void DS3231EverySecond(void);
|
|
bool Xsns33(uint8_t function);
|
|
bool HxIsReady(uint16_t timeout);
|
|
long HxRead(void);
|
|
void HxResetPart(void);
|
|
void HxReset(void);
|
|
void HxCalibrationStateTextJson(uint8_t msg_id);
|
|
void SetWeightDelta();
|
|
bool HxCommand(void);
|
|
long HxWeight(void);
|
|
void HxInit(void);
|
|
void HxEvery100mSecond(void);
|
|
void HxSaveBeforeRestart(void);
|
|
void HxShow(bool json);
|
|
void HandleHxAction(void);
|
|
void HxSaveSettings(void);
|
|
void HxLogUpdates(void);
|
|
bool Xsns34(uint8_t function);
|
|
void Tx20StartRead(void);
|
|
void Tx20Read(void);
|
|
void Tx20Init(void);
|
|
void Tx20Show(bool json);
|
|
bool Xsns35(uint8_t function);
|
|
void MGC3130_handleSensorData();
|
|
void MGC3130_sendMessage(uint8_t data[], uint8_t length);
|
|
void MGC3130_handleGesture();
|
|
bool MGC3130_handleTouch();
|
|
void MGC3130_handleAirWheel();
|
|
void MGC3130_handleSystemStatus();
|
|
bool MGC3130_receiveMessage();
|
|
bool MGC3130_readData();
|
|
void MGC3130_nextMode();
|
|
void MGC3130_loop();
|
|
void MGC3130_detect(void);
|
|
void MGC3130_show(bool json);
|
|
bool MGC3130CommandSensor();
|
|
bool Xsns36(uint8_t function);
|
|
bool RfSnsFetchSignal(uint8_t DataPin, bool StateSignal);
|
|
void RfSnsInitTheoV2(void);
|
|
void RfSnsAnalyzeTheov2(void);
|
|
void RfSnsTheoV2Show(bool json);
|
|
void RfSnsInitAlectoV2(void);
|
|
void RfSnsAnalyzeAlectov2();
|
|
void RfSnsAlectoResetRain(void);
|
|
uint8_t RfSnsAlectoCRC8(uint8_t *addr, uint8_t len);
|
|
void RfSnsAlectoV2Show(bool json);
|
|
void RfSnsInit(void);
|
|
void RfSnsAnalyzeRawSignal(void);
|
|
void RfSnsEverySecond(void);
|
|
void RfSnsShow(bool json);
|
|
bool Xsns37(uint8_t function);
|
|
void AzEverySecond(void);
|
|
void AzInit(void);
|
|
void AzShow(bool json);
|
|
bool Xsns38(uint8_t function);
|
|
void MAX31855_Init(void);
|
|
void MAX31855_GetResult(void);
|
|
float MAX31855_GetProbeTemperature(int32_t RawData);
|
|
float MAX31855_GetReferenceTemperature(int32_t RawData);
|
|
int32_t MAX31855_ShiftIn(uint8_t Length);
|
|
void MAX31855_Show(bool Json);
|
|
bool Xsns39(uint8_t function);
|
|
void PN532_Init(void);
|
|
int8_t PN532_receive(uint8_t *buf, int len, uint16_t timeout);
|
|
int8_t PN532_readAckFrame(void);
|
|
uint32_t PN532_getFirmwareVersion(void);
|
|
void PN532_wakeup(void);
|
|
bool PN532_setPassiveActivationRetries(uint8_t maxRetries);
|
|
bool PN532_SAMConfig(void);
|
|
uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData);
|
|
uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data);
|
|
uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data);
|
|
void PN532_ScanForTag(void);
|
|
bool PN532_Command(void);
|
|
bool Xsns40(uint8_t function);
|
|
bool Max4409Read_lum(void);
|
|
void Max4409Detect(void);
|
|
void Max4409EverySecond(void);
|
|
void Max4409Show(bool json);
|
|
bool Xsns41(uint8_t function);
|
|
void Scd30Detect(void);
|
|
void Scd30Update(void);
|
|
int Scd30GetCommand(int command_code, uint16_t *pvalue);
|
|
int Scd30SetCommand(int command_code, uint16_t value);
|
|
bool Scd30CommandSensor();
|
|
void Scd30Show(bool json);
|
|
bool Xsns42(byte function);
|
|
int hreReadBit();
|
|
char hreReadChar(int &parity_errors);
|
|
void hreInit(void);
|
|
void hreEvery50ms(void);
|
|
void hreShow(boolean json);
|
|
bool Xsns43(byte function);
|
|
uint8_t sps30_calc_CRC(uint8_t *data);
|
|
void sps30_get_data(uint16_t cmd, uint8_t *data, uint8_t dlen);
|
|
void sps30_cmd(uint16_t cmd);
|
|
void SPS30_Detect(void);
|
|
void SPS30_Every_Second();
|
|
void SPS30_Show(bool json);
|
|
bool SPS30_cmd(void);
|
|
bool Xsns44(byte function);
|
|
void Vl53l0Detect(void);
|
|
void Vl53l0Every_250MSecond(void);
|
|
void Vl53l0Show(boolean json);
|
|
bool Xsns45(byte function);
|
|
void MLX90614_Init(void);
|
|
void MLX90614_Every_Second(void);
|
|
void MLX90614_Show(uint8_t json);
|
|
bool Xsns46(byte function);
|
|
void MAX31865_Init(void);
|
|
void MAX31865_GetResult(void);
|
|
void MAX31865_Show(bool Json);
|
|
bool Xsns47(uint8_t function);
|
|
void ChirpWriteI2CRegister(uint8_t addr, uint8_t reg);
|
|
uint16_t ChirpFinishReadI2CRegister16bit(uint8_t addr);
|
|
void ChirpReset(uint8_t addr);
|
|
void ChirpResetAll(void);
|
|
void ChirpClockSet();
|
|
void ChirpSleep(uint8_t addr);
|
|
void ChirpSelect(uint8_t sensor);
|
|
uint8_t ChirpReadVersion(uint8_t addr);
|
|
bool ChirpSet(uint8_t addr);
|
|
bool ChirpScan();
|
|
void ChirpDetect(void);
|
|
void ChirpServiceAllSensors(uint8_t job);
|
|
void ChirpEvery100MSecond(void);
|
|
void ChirpShow(bool json);
|
|
bool ChirpCmd(void);
|
|
bool Xsns48(uint8_t function);
|
|
void PAJ7620SelectBank(uint8_t bank);
|
|
void PAJ7620DecodeGesture(void);
|
|
void PAJ7620ReadGesture(void);
|
|
void PAJ7620Detect(void);
|
|
void PAJ7620Init(void);
|
|
void PAJ7620Loop(void);
|
|
void PAJ7620Show(bool json);
|
|
bool PAJ7620CommandSensor(void);
|
|
bool Xsns50(uint8_t function);
|
|
void RDM6300_Init();
|
|
void RDM6300_ScanForTag();
|
|
uint8_t rm6300_hexnibble(char chr);
|
|
void rm6300_hstring_to_array(uint8_t array[], uint8_t len, char buffer[]);
|
|
void RDM6300_Show(void);
|
|
bool Xsns51(byte function);
|
|
void IBEACON_Init();
|
|
void hm17_every_second(void);
|
|
void hm17_sbclr(void);
|
|
void hm17_sendcmd(uint8_t cmd);
|
|
uint32_t ibeacon_add(struct IBEACON *ib);
|
|
void hm17_decode(void);
|
|
void IBEACON_loop();
|
|
void IBEACON_Show(void);
|
|
bool xsns52_cmd(void);
|
|
bool ibeacon_cmd(void);
|
|
void ib_sendbeep(void);
|
|
void ibeacon_mqtt(const char *mac,const char *rssi);
|
|
bool Xsns52(byte function);
|
|
double sml_median_array(double *array,uint8_t len);
|
|
double sml_median(struct SML_MEDIAN_FILTER* mf, double in);
|
|
void ADS1115_init(void);
|
|
bool Serial_available();
|
|
uint8_t Serial_read();
|
|
uint8_t Serial_peek();
|
|
void Dump2log(void);
|
|
double sml_getvalue(unsigned char *cp,uint8_t index);
|
|
uint8_t hexnibble(char chr);
|
|
double CharToDouble(const char *str);
|
|
void ebus_esc(uint8_t *ebus_buffer, unsigned char len);
|
|
uint8_t ebus_crc8(uint8_t data, uint8_t crc_init);
|
|
uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen );
|
|
void sml_empty_receiver(uint32_t meters);
|
|
void sml_shift_in(uint32_t meters,uint32_t shard);
|
|
void SML_Poll(void);
|
|
void SML_Decode(uint8_t index);
|
|
void SML_Immediate_MQTT(const char *mp,uint8_t index,uint8_t mindex);
|
|
void SML_Show(boolean json);
|
|
void SML_CounterUpd(uint8_t index);
|
|
void SML_CounterUpd1(void);
|
|
void SML_CounterUpd2(void);
|
|
void SML_CounterUpd3(void);
|
|
void SML_CounterUpd4(void);
|
|
bool Gpio_used(uint8_t gpiopin);
|
|
void SML_Init(void);
|
|
uint32_t SML_SetBaud(uint32_t meter, uint32_t br);
|
|
uint32_t SML_Write(uint32_t meter,char *hstr);
|
|
void SetDBGLed(uint8_t srcpin, uint8_t ledpin);
|
|
void SML_Counter_Poll(void);
|
|
void SML_Check_Send(void);
|
|
uint8_t sml_hexnibble(char chr);
|
|
void SML_Send_Seq(uint32_t meter,char *seq);
|
|
uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num);
|
|
uint8_t SML_PzemCrc(uint8_t *data, uint8_t len);
|
|
uint8_t CalcEvenParity(uint8_t data);
|
|
bool XSNS_53_cmd(void);
|
|
void InjektCounterValue(uint8_t meter,uint32_t counter);
|
|
void SML_CounterSaveState(void);
|
|
bool Xsns53(byte function);
|
|
static uint32_t _expand_r_shunt(uint16_t compact_r_shunt);
|
|
void Ina226SetCalibration(uint8_t slaveIndex);
|
|
bool Ina226TestPresence(uint8_t device);
|
|
void Ina226ResetActive(void);
|
|
void Ina226Init();
|
|
float Ina226ReadBus_v(uint8_t device);
|
|
float Ina226ReadShunt_i(uint8_t device);
|
|
float Ina226ReadPower_w(uint8_t device);
|
|
void Ina226Read(uint8_t device);
|
|
void Ina226EverySecond();
|
|
bool Ina226CommandSensor();
|
|
void Ina226Show(bool json);
|
|
bool Xsns54(byte callback_id);
|
|
bool Hih6Read(void);
|
|
void Hih6Detect(void);
|
|
void Hih6EverySecond(void);
|
|
void Hih6Show(bool json);
|
|
bool Xsns55(uint8_t function);
|
|
void HpmaSecond(void);
|
|
void HpmaInit(void);
|
|
void HpmaShow(bool json);
|
|
bool Xsns56(uint8_t function);
|
|
void Tsl2591Init(void);
|
|
bool Tsl2591Read(void);
|
|
void Tsl2591EverySecond(void);
|
|
void Tsl2591Show(bool json);
|
|
bool Xsns57(uint8_t function);
|
|
bool Dht12Read(void);
|
|
void Dht12Detect(void);
|
|
void Dht12EverySecond(void);
|
|
void Dht12Show(bool json);
|
|
bool Xsns58(uint8_t function);
|
|
uint32_t DS1624_Idx2Addr(uint32_t idx);
|
|
int DS1624_Restart(uint8_t config, uint32_t idx);
|
|
void DS1624_HotPlugUp(uint32_t idx);
|
|
void DS1624_HotPlugDown(int idx);
|
|
bool DS1624GetTemp(float *value, int idx);
|
|
void DS1624HotPlugScan(void);
|
|
void DS1624EverySecond(void);
|
|
void DS1624Show(bool json);
|
|
bool Xsns59(uint8_t function);
|
|
void UBXcalcChecksum(char* CK, size_t msgSize);
|
|
bool UBXcompareMsgHeader(const char* msgHeader);
|
|
void UBXinitCFG(void);
|
|
void UBXTriggerTele(void);
|
|
void UBXDetect(void);
|
|
uint32_t UBXprocessGPS();
|
|
void UBXsendHeader(void);
|
|
void UBXsendRecord(uint8_t *buf);
|
|
void UBXsendFooter(void);
|
|
void UBXsendFile(void);
|
|
void UBXSetRate(uint16_t interval);
|
|
void UBXSelectMode(uint16_t mode);
|
|
bool UBXHandlePOSLLH();
|
|
void UBXHandleSTATUS();
|
|
void UBXHandleTIME();
|
|
void UBXHandleOther(void);
|
|
void UBXLoop50msec(void);
|
|
void UBXLoop(void);
|
|
void UBXShow(bool json);
|
|
bool UBXCmd(void);
|
|
bool Xsns60(uint8_t function);
|
|
bool MINRFinitBLE(uint8_t _mode);
|
|
void MINRFhopChannel();
|
|
bool MINRFreceivePacket(void);
|
|
void MINRFswapbuf(uint8_t len);
|
|
void MINRFwhiten(uint8_t *buf, uint8_t len, uint8_t lfsr);
|
|
void MINRFreverseMAC(uint8_t _mac[]);
|
|
void MINRFchangePacketModeTo(uint8_t _mode);
|
|
void MINRFpurgeFakeSensors(void);
|
|
void MINRFhandleFloraPacket(void);
|
|
void MINRFhandleMJ_HT_V1Packet(void);
|
|
void MINRFhandleLYWSD02Packet(void);
|
|
void MINRFhandleLYWSD03Packet(void);
|
|
void MINRF_EVERY_50_MSECOND();
|
|
void MINRFShow(bool json);
|
|
bool Xsns61(uint8_t function);
|
|
void HM10_Launchtask(uint8_t task, uint8_t slot, uint8_t delay);
|
|
void HM10_TaskReplaceInSlot(uint8_t task, uint8_t slot);
|
|
void HM10_Reset(void);
|
|
void HM10_Discovery_Scan(void);
|
|
void HM10_Read_LYWSD03(void);
|
|
void HM10_Read_LYWSD02(void);
|
|
void HM10_Time_LYWSD02(void);
|
|
void HM10SerialInit(void);
|
|
void HM10MACStringToBytes(const char* string, uint8_t _mac[]);
|
|
void HM10ParseResponse(char *buf);
|
|
void HM10readTempHum(char *_buf);
|
|
bool HM10readBat(char *_buf);
|
|
bool HM10SerialHandleFeedback();
|
|
void HM10_TaskEvery100ms();
|
|
void HM10EverySecond();
|
|
bool HM10Cmd(void);
|
|
void HM10Show(bool json);
|
|
bool Xsns62(uint8_t function);
|
|
bool begin();
|
|
bool AHT10Read(void);
|
|
unsigned char readStatus(void);
|
|
void AHT10Detect(void);
|
|
void AHT10EverySecond(void);
|
|
void AHT10Show(bool json);
|
|
bool Xsns64(uint8_t function);
|
|
void HandleMetrics(void);
|
|
bool Xsns91(uint8_t function);
|
|
bool XsnsEnabled(uint32_t sns_index);
|
|
void XsnsSensorState(void);
|
|
bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index);
|
|
bool XsnsCall(uint8_t Function);
|
|
bool I2cEnabled(uint32_t i2c_index);
|
|
void I2cDriverState(void);
|
|
#line 184 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota.ino"
|
|
void setup(void)
|
|
{
|
|
global_state.data = 3;
|
|
|
|
RtcRebootLoad();
|
|
if (!RtcRebootValid()) {
|
|
RtcReboot.fast_reboot_count = 0;
|
|
}
|
|
RtcReboot.fast_reboot_count++;
|
|
RtcRebootSave();
|
|
|
|
Serial.begin(APP_BAUDRATE);
|
|
seriallog_level = LOG_LEVEL_INFO;
|
|
|
|
snprintf_P(my_version, sizeof(my_version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff);
|
|
if (VERSION & 0xff) {
|
|
snprintf_P(my_version, sizeof(my_version), PSTR("%s.%d"), my_version, VERSION & 0xff);
|
|
}
|
|
|
|
snprintf_P(my_image, sizeof(my_image), PSTR("(%s)"), CODE_IMAGE_STR);
|
|
|
|
SettingsLoad();
|
|
SettingsDelta();
|
|
|
|
OsWatchInit();
|
|
|
|
GetFeatures();
|
|
|
|
if (1 == RtcReboot.fast_reboot_count) {
|
|
UpdateQuickPowerCycle(true);
|
|
XdrvCall(FUNC_SETTINGS_OVERRIDE);
|
|
}
|
|
|
|
|
|
seriallog_level = Settings.seriallog_level;
|
|
seriallog_timer = SERIALLOG_TIMER;
|
|
syslog_level = Settings.syslog_level;
|
|
stop_flash_rotate = Settings.flag.stop_flash_rotate;
|
|
save_data_counter = Settings.save_data;
|
|
sleep = Settings.sleep;
|
|
#ifndef USE_EMULATION
|
|
Settings.flag2.emulation = 0;
|
|
#else
|
|
#ifndef USE_EMULATION_WEMO
|
|
if (EMUL_WEMO == Settings.flag2.emulation) { Settings.flag2.emulation = 0; }
|
|
#endif
|
|
#ifndef USE_EMULATION_HUE
|
|
if (EMUL_HUE == Settings.flag2.emulation) { Settings.flag2.emulation = 0; }
|
|
#endif
|
|
#endif
|
|
|
|
if (Settings.param[P_BOOT_LOOP_OFFSET]) {
|
|
|
|
if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET]) {
|
|
Settings.flag3.user_esp8285_enable = 0;
|
|
if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +1) {
|
|
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) {
|
|
if (bitRead(Settings.rule_stop, i)) {
|
|
bitWrite(Settings.rule_enabled, i, 0);
|
|
}
|
|
}
|
|
}
|
|
if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +2) {
|
|
Settings.rule_enabled = 0;
|
|
}
|
|
if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +3) {
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
Settings.my_gp.io[i] = GPIO_NONE;
|
|
}
|
|
Settings.my_adc0 = ADC0_NONE;
|
|
}
|
|
if (RtcReboot.fast_reboot_count > Settings.param[P_BOOT_LOOP_OFFSET] +4) {
|
|
Settings.module = SONOFF_BASIC;
|
|
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count);
|
|
}
|
|
}
|
|
|
|
Format(mqtt_client, SettingsText(SET_MQTT_CLIENT), sizeof(mqtt_client));
|
|
Format(mqtt_topic, SettingsText(SET_MQTT_TOPIC), sizeof(mqtt_topic));
|
|
if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) {
|
|
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
|
|
snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME), mqtt_topic, ESP.getChipId() & 0x1FFF);
|
|
} else {
|
|
snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME));
|
|
}
|
|
|
|
GetEspHardwareType();
|
|
GpioInit();
|
|
|
|
|
|
|
|
WifiConnect();
|
|
|
|
SetPowerOnState();
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_ESP8266_RELEASE), PROJECT, SettingsText(SET_FRIENDLYNAME1), my_version, my_image);
|
|
#ifdef FIRMWARE_MINIMAL
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION));
|
|
#endif
|
|
|
|
memcpy_P(log_data, VERSION_MARKER, 1);
|
|
|
|
RtcInit();
|
|
|
|
#ifdef USE_ARDUINO_OTA
|
|
ArduinoOTAInit();
|
|
#endif
|
|
|
|
XdrvCall(FUNC_INIT);
|
|
XsnsCall(FUNC_INIT);
|
|
}
|
|
|
|
void BacklogLoop(void)
|
|
{
|
|
if (TimeReached(backlog_delay)) {
|
|
if (!BACKLOG_EMPTY && !backlog_mutex) {
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
backlog_mutex = true;
|
|
String cmd = backlog.shift();
|
|
backlog_mutex = false;
|
|
ExecuteCommand((char*)cmd.c_str(), SRC_BACKLOG);
|
|
#else
|
|
backlog_mutex = true;
|
|
ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG);
|
|
backlog_pointer++;
|
|
if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; }
|
|
backlog_mutex = false;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void loop(void)
|
|
{
|
|
uint32_t my_sleep = millis();
|
|
|
|
XdrvCall(FUNC_LOOP);
|
|
XsnsCall(FUNC_LOOP);
|
|
|
|
OsWatchLoop();
|
|
|
|
ButtonLoop();
|
|
SwitchLoop();
|
|
#ifdef ROTARY_V1
|
|
RotaryLoop();
|
|
#endif
|
|
BacklogLoop();
|
|
|
|
if (TimeReached(state_50msecond)) {
|
|
SetNextTimeInterval(state_50msecond, 50);
|
|
XdrvCall(FUNC_EVERY_50_MSECOND);
|
|
XsnsCall(FUNC_EVERY_50_MSECOND);
|
|
}
|
|
if (TimeReached(state_100msecond)) {
|
|
SetNextTimeInterval(state_100msecond, 100);
|
|
Every100mSeconds();
|
|
XdrvCall(FUNC_EVERY_100_MSECOND);
|
|
XsnsCall(FUNC_EVERY_100_MSECOND);
|
|
}
|
|
if (TimeReached(state_250msecond)) {
|
|
SetNextTimeInterval(state_250msecond, 250);
|
|
Every250mSeconds();
|
|
XdrvCall(FUNC_EVERY_250_MSECOND);
|
|
XsnsCall(FUNC_EVERY_250_MSECOND);
|
|
}
|
|
if (TimeReached(state_second)) {
|
|
SetNextTimeInterval(state_second, 1000);
|
|
PerformEverySecond();
|
|
XdrvCall(FUNC_EVERY_SECOND);
|
|
XsnsCall(FUNC_EVERY_SECOND);
|
|
}
|
|
|
|
if (!serial_local) { SerialInput(); }
|
|
|
|
#ifdef USE_ARDUINO_OTA
|
|
ArduinoOtaLoop();
|
|
#endif
|
|
|
|
uint32_t my_activity = millis() - my_sleep;
|
|
|
|
if (Settings.flag3.sleep_normal) {
|
|
|
|
delay(sleep);
|
|
} else {
|
|
if (my_activity < (uint32_t)sleep) {
|
|
delay((uint32_t)sleep - my_activity);
|
|
} else {
|
|
if (global_state.wifi_down) {
|
|
delay(my_activity /2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!my_activity) { my_activity++; }
|
|
uint32_t loop_delay = sleep;
|
|
if (!loop_delay) { loop_delay++; }
|
|
uint32_t loops_per_second = 1000 / loop_delay;
|
|
uint32_t this_cycle_ratio = 100 * my_activity / loop_delay;
|
|
loop_load_avg = loop_load_avg - (loop_load_avg / loops_per_second) + (this_cycle_ratio / loops_per_second);
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/sendemail.ino"
|
|
#ifdef USE_SENDMAIL
|
|
|
|
#include "sendemail.h"
|
|
# 25 "C:/shared/sonoff/Git/Tasmota/tasmota/sendemail.ino"
|
|
#ifndef SEND_MAIL_MINRAM
|
|
#define SEND_MAIL_MINRAM 12*1024
|
|
#endif
|
|
|
|
#define xPSTR(a) a
|
|
|
|
uint16_t SendMail(char *buffer) {
|
|
char *params,*oparams;
|
|
const char *mserv;
|
|
uint16_t port;
|
|
const char *user;
|
|
const char *pstr;
|
|
const char *passwd;
|
|
const char *from;
|
|
const char *to;
|
|
const char *subject;
|
|
const char *cmd;
|
|
char auth=0;
|
|
uint16_t status=1;
|
|
SendEmail *mail=0;
|
|
uint16_t blen;
|
|
char *endcmd;
|
|
|
|
|
|
|
|
uint16_t mem=ESP.getFreeHeap();
|
|
if (mem<SEND_MAIL_MINRAM) {
|
|
return 4;
|
|
}
|
|
|
|
while (*buffer==' ') buffer++;
|
|
|
|
if (*buffer!='[') {
|
|
goto exit;
|
|
}
|
|
|
|
buffer++;
|
|
|
|
endcmd=strchr(buffer,']');
|
|
if (!endcmd) {
|
|
goto exit;
|
|
}
|
|
|
|
|
|
blen=(uint32_t)endcmd-(uint32_t)buffer;
|
|
oparams=(char*)calloc(blen+2,1);
|
|
if (!oparams) return 4;
|
|
params=oparams;
|
|
strncpy(oparams,buffer,blen+2);
|
|
oparams[blen]=0;
|
|
|
|
cmd=endcmd+1;
|
|
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("mailsize: %d"),blen);
|
|
#endif
|
|
|
|
mserv=strtok(params,":");
|
|
if (!mserv) {
|
|
goto exit;
|
|
}
|
|
|
|
|
|
pstr=strtok(NULL,":");
|
|
if (!pstr) {
|
|
goto exit;
|
|
}
|
|
|
|
#ifdef EMAIL_PORT
|
|
if (*pstr=='*') {
|
|
port=EMAIL_PORT;
|
|
} else {
|
|
port=atoi(pstr);
|
|
}
|
|
#else
|
|
port=atoi(pstr);
|
|
#endif
|
|
|
|
user=strtok(NULL,":");
|
|
if (!user) {
|
|
goto exit;
|
|
}
|
|
|
|
passwd=strtok(NULL,":");
|
|
if (!passwd) {
|
|
goto exit;
|
|
}
|
|
|
|
from=strtok(NULL,":");
|
|
if (!from) {
|
|
goto exit;
|
|
}
|
|
|
|
to=strtok(NULL,":");
|
|
if (!to) {
|
|
goto exit;
|
|
}
|
|
|
|
subject=strtok(NULL,"]");
|
|
if (!subject) {
|
|
goto exit;
|
|
}
|
|
|
|
|
|
#ifdef EMAIL_USER
|
|
if (*user=='*') {
|
|
user=xPSTR(EMAIL_USER);
|
|
}
|
|
#endif
|
|
#ifdef EMAIL_PASSWORD
|
|
if (*passwd=='*') {
|
|
passwd=xPSTR(EMAIL_PASSWORD);
|
|
}
|
|
#endif
|
|
#ifdef EMAIL_SERVER
|
|
if (*mserv=='*') {
|
|
mserv=xPSTR(EMAIL_SERVER);
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s - %d - %s - %s"),mserv,port,user,passwd);
|
|
#endif
|
|
|
|
|
|
#ifndef MAIL_TIMEOUT
|
|
#define MAIL_TIMEOUT 2000
|
|
#endif
|
|
mail = new SendEmail(mserv,port,user,passwd, MAIL_TIMEOUT, auth);
|
|
|
|
#ifdef EMAIL_FROM
|
|
if (*from=='*') {
|
|
from=xPSTR(EMAIL_FROM);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s - %s - %s - %s"),from,to,subject,cmd);
|
|
#endif
|
|
|
|
if (mail) {
|
|
bool result=mail->send(from,to,subject,cmd);
|
|
delete mail;
|
|
if (result==true) status=0;
|
|
}
|
|
|
|
exit:
|
|
if (oparams) free(oparams);
|
|
return status;
|
|
}
|
|
|
|
void script_send_email_body(BearSSL::WiFiClientSecure_light *client);
|
|
|
|
|
|
SendEmail::SendEmail(const String& host, const int port, const String& user, const String& passwd, const int timeout, const int auth_used) :
|
|
host(host), port(port), user(user), passwd(passwd), timeout(timeout), ssl(ssl), auth_used(auth_used), client(new BearSSL::WiFiClientSecure_light(1024,1024)) {
|
|
}
|
|
|
|
String SendEmail::readClient() {
|
|
delay(0);
|
|
String r = client->readStringUntil('\n');
|
|
|
|
r.trim();
|
|
while (client->available()) {
|
|
delay(0);
|
|
r += client->readString();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
bool SendEmail::send(const String& from, const String& to, const String& subject, const char *msg) {
|
|
bool status=false;
|
|
String buffer;
|
|
|
|
if (!host.length()) {
|
|
return status;
|
|
}
|
|
|
|
client->setTimeout(timeout);
|
|
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Connecting: %s on port %d"),host.c_str(),port);
|
|
#endif
|
|
|
|
if (!client->connect(host.c_str(), port)) {
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("Connection failed"));
|
|
#endif
|
|
goto exit;
|
|
}
|
|
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("220"))) {
|
|
goto exit;
|
|
}
|
|
|
|
buffer = F("EHLO ");
|
|
buffer += client->localIP().toString();
|
|
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("250"))) {
|
|
goto exit;
|
|
}
|
|
if (user.length()>0 && passwd.length()>0 ) {
|
|
|
|
buffer = F("AUTH LOGIN");
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("334")))
|
|
{
|
|
goto exit;
|
|
}
|
|
base64 b;
|
|
buffer = b.encode(user);
|
|
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("334"))) {
|
|
goto exit;
|
|
}
|
|
buffer = b.encode(passwd);
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("235"))) {
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
|
|
buffer = F("MAIL FROM:");
|
|
buffer += from;
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("250"))) {
|
|
goto exit;
|
|
}
|
|
buffer = F("RCPT TO:");
|
|
buffer += to;
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("250"))) {
|
|
goto exit;
|
|
}
|
|
|
|
buffer = F("DATA");
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = readClient();
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
if (!buffer.startsWith(F("354"))) {
|
|
goto exit;
|
|
}
|
|
buffer = F("From: ");
|
|
buffer += from;
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = F("To: ");
|
|
buffer += to;
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
buffer = F("Subject: ");
|
|
buffer += subject;
|
|
buffer += F("\r\n");
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
|
|
#ifdef USE_SCRIPT
|
|
if (*msg=='*' && *(msg+1)==0) {
|
|
script_send_email_body(client);
|
|
} else {
|
|
client->println(msg);
|
|
}
|
|
#else
|
|
client->println(msg);
|
|
#endif
|
|
client->println('.');
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
|
|
buffer = F("QUIT");
|
|
client->println(buffer);
|
|
#ifdef DEBUG_EMAIL_PORT
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),buffer.c_str());
|
|
#endif
|
|
|
|
status=true;
|
|
exit:
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
#ifndef DOMOTICZ_UPDATE_TIMER
|
|
#define DOMOTICZ_UPDATE_TIMER 0
|
|
#endif
|
|
|
|
#ifndef EMULATION
|
|
#define EMULATION EMUL_NONE
|
|
#endif
|
|
|
|
#ifndef MTX_ADDRESS1
|
|
#define MTX_ADDRESS1 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS2
|
|
#define MTX_ADDRESS2 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS3
|
|
#define MTX_ADDRESS3 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS4
|
|
#define MTX_ADDRESS4 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS5
|
|
#define MTX_ADDRESS5 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS6
|
|
#define MTX_ADDRESS6 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS7
|
|
#define MTX_ADDRESS7 0
|
|
#endif
|
|
#ifndef MTX_ADDRESS8
|
|
#define MTX_ADDRESS8 0
|
|
#endif
|
|
|
|
#ifndef HOME_ASSISTANT_DISCOVERY_ENABLE
|
|
#define HOME_ASSISTANT_DISCOVERY_ENABLE 0
|
|
#endif
|
|
|
|
#ifndef LATITUDE
|
|
#define LATITUDE 48.858360
|
|
#endif
|
|
#ifndef LONGITUDE
|
|
#define LONGITUDE 2.294442
|
|
#endif
|
|
|
|
#ifndef WORKING_PERIOD
|
|
#define WORKING_PERIOD 5
|
|
#endif
|
|
|
|
#ifndef COLOR_TEXT
|
|
#define COLOR_TEXT "#000"
|
|
#endif
|
|
#ifndef COLOR_BACKGROUND
|
|
#define COLOR_BACKGROUND "#fff"
|
|
#endif
|
|
#ifndef COLOR_FORM
|
|
#define COLOR_FORM "#f2f2f2"
|
|
#endif
|
|
#ifndef COLOR_INPUT_TEXT
|
|
#define COLOR_INPUT_TEXT "#000"
|
|
#endif
|
|
#ifndef COLOR_INPUT
|
|
#define COLOR_INPUT "#fff"
|
|
#endif
|
|
#ifndef COLOR_CONSOLE_TEXT
|
|
#define COLOR_CONSOLE_TEXT "#000"
|
|
#endif
|
|
#ifndef COLOR_CONSOLE
|
|
#define COLOR_CONSOLE "#fff"
|
|
#endif
|
|
#ifndef COLOR_TEXT_WARNING
|
|
#define COLOR_TEXT_WARNING "#f00"
|
|
#endif
|
|
#ifndef COLOR_TEXT_SUCCESS
|
|
#define COLOR_TEXT_SUCCESS "#008000"
|
|
#endif
|
|
#ifndef COLOR_BUTTON_TEXT
|
|
#define COLOR_BUTTON_TEXT "#fff"
|
|
#endif
|
|
#ifndef COLOR_BUTTON
|
|
#define COLOR_BUTTON "#1fa3ec"
|
|
#endif
|
|
#ifndef COLOR_BUTTON_HOVER
|
|
#define COLOR_BUTTON_HOVER "#0e70a4"
|
|
#endif
|
|
#ifndef COLOR_BUTTON_RESET
|
|
#define COLOR_BUTTON_RESET "#d43535"
|
|
#endif
|
|
#ifndef COLOR_BUTTON_RESET_HOVER
|
|
#define COLOR_BUTTON_RESET_HOVER "#931f1f"
|
|
#endif
|
|
#ifndef COLOR_BUTTON_SAVE
|
|
#define COLOR_BUTTON_SAVE "#47c266"
|
|
#endif
|
|
#ifndef COLOR_BUTTON_SAVE_HOVER
|
|
#define COLOR_BUTTON_SAVE_HOVER "#5aaf6f"
|
|
#endif
|
|
#ifndef COLOR_TIMER_TAB_TEXT
|
|
#define COLOR_TIMER_TAB_TEXT "#fff"
|
|
#endif
|
|
#ifndef COLOR_TIMER_TAB_BACKGROUND
|
|
#define COLOR_TIMER_TAB_BACKGROUND "#999"
|
|
#endif
|
|
#ifndef COLOR_TITLE_TEXT
|
|
#define COLOR_TITLE_TEXT COLOR_TEXT
|
|
#endif
|
|
#ifndef IR_RCV_MIN_UNKNOWN_SIZE
|
|
#define IR_RCV_MIN_UNKNOWN_SIZE 6
|
|
#endif
|
|
#ifndef ENERGY_OVERTEMP
|
|
#define ENERGY_OVERTEMP 90
|
|
#endif
|
|
#ifndef DEFAULT_DIMMER_MAX
|
|
#define DEFAULT_DIMMER_MAX 100
|
|
#endif
|
|
#ifndef DEFAULT_DIMMER_MIN
|
|
#define DEFAULT_DIMMER_MIN 0
|
|
#endif
|
|
#ifndef DEFAULT_LIGHT_DIMMER
|
|
#define DEFAULT_LIGHT_DIMMER 10
|
|
#endif
|
|
#ifndef DEFAULT_LIGHT_COMPONENT
|
|
#define DEFAULT_LIGHT_COMPONENT 255
|
|
#endif
|
|
#ifndef CORS_ENABLED_ALL
|
|
#define CORS_ENABLED_ALL "*"
|
|
#endif
|
|
|
|
|
|
enum WebColors {
|
|
COL_TEXT, COL_BACKGROUND, COL_FORM,
|
|
COL_INPUT_TEXT, COL_INPUT, COL_CONSOLE_TEXT, COL_CONSOLE,
|
|
COL_TEXT_WARNING, COL_TEXT_SUCCESS,
|
|
COL_BUTTON_TEXT, COL_BUTTON, COL_BUTTON_HOVER, COL_BUTTON_RESET, COL_BUTTON_RESET_HOVER, COL_BUTTON_SAVE, COL_BUTTON_SAVE_HOVER,
|
|
COL_TIMER_TAB_TEXT, COL_TIMER_TAB_BACKGROUND, COL_TITLE,
|
|
COL_LAST };
|
|
|
|
const char kWebColors[] PROGMEM =
|
|
COLOR_TEXT "|" COLOR_BACKGROUND "|" COLOR_FORM "|"
|
|
COLOR_INPUT_TEXT "|" COLOR_INPUT "|" COLOR_CONSOLE_TEXT "|" COLOR_CONSOLE "|"
|
|
COLOR_TEXT_WARNING "|" COLOR_TEXT_SUCCESS "|"
|
|
COLOR_BUTTON_TEXT "|" COLOR_BUTTON "|" COLOR_BUTTON_HOVER "|" COLOR_BUTTON_RESET "|" COLOR_BUTTON_RESET_HOVER "|" COLOR_BUTTON_SAVE "|" COLOR_BUTTON_SAVE_HOVER "|"
|
|
COLOR_TIMER_TAB_TEXT "|" COLOR_TIMER_TAB_BACKGROUND "|" COLOR_TITLE_TEXT;
|
|
|
|
enum TasmotaSerialConfig {
|
|
TS_SERIAL_5N1, TS_SERIAL_6N1, TS_SERIAL_7N1, TS_SERIAL_8N1,
|
|
TS_SERIAL_5N2, TS_SERIAL_6N2, TS_SERIAL_7N2, TS_SERIAL_8N2,
|
|
TS_SERIAL_5E1, TS_SERIAL_6E1, TS_SERIAL_7E1, TS_SERIAL_8E1,
|
|
TS_SERIAL_5E2, TS_SERIAL_6E2, TS_SERIAL_7E2, TS_SERIAL_8E2,
|
|
TS_SERIAL_5O1, TS_SERIAL_6O1, TS_SERIAL_7O1, TS_SERIAL_8O1,
|
|
TS_SERIAL_5O2, TS_SERIAL_6O2, TS_SERIAL_7O2, TS_SERIAL_8O2 };
|
|
|
|
const uint8_t kTasmotaSerialConfig[] PROGMEM = {
|
|
SERIAL_5N1, SERIAL_6N1, SERIAL_7N1, SERIAL_8N1,
|
|
SERIAL_5N2, SERIAL_6N2, SERIAL_7N2, SERIAL_8N2,
|
|
SERIAL_5E1, SERIAL_6E1, SERIAL_7E1, SERIAL_8E1,
|
|
SERIAL_5E2, SERIAL_6E2, SERIAL_7E2, SERIAL_8E2,
|
|
SERIAL_5O1, SERIAL_6O1, SERIAL_7O1, SERIAL_8O1,
|
|
SERIAL_5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const uint16_t RTC_MEM_VALID = 0xA55A;
|
|
|
|
uint32_t rtc_settings_crc = 0;
|
|
|
|
uint32_t GetRtcSettingsCrc(void)
|
|
{
|
|
uint32_t crc = 0;
|
|
uint8_t *bytes = (uint8_t*)&RtcSettings;
|
|
|
|
for (uint32_t i = 0; i < sizeof(RTCMEM); i++) {
|
|
crc += bytes[i]*(i+1);
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
void RtcSettingsSave(void)
|
|
{
|
|
if (GetRtcSettingsCrc() != rtc_settings_crc) {
|
|
RtcSettings.valid = RTC_MEM_VALID;
|
|
ESP.rtcUserMemoryWrite(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM));
|
|
rtc_settings_crc = GetRtcSettingsCrc();
|
|
}
|
|
}
|
|
|
|
void RtcSettingsLoad(void)
|
|
{
|
|
ESP.rtcUserMemoryRead(100, (uint32_t*)&RtcSettings, sizeof(RTCMEM));
|
|
if (RtcSettings.valid != RTC_MEM_VALID) {
|
|
memset(&RtcSettings, 0, sizeof(RTCMEM));
|
|
RtcSettings.valid = RTC_MEM_VALID;
|
|
RtcSettings.energy_kWhtoday = Settings.energy_kWhtoday;
|
|
RtcSettings.energy_kWhtotal = Settings.energy_kWhtotal;
|
|
RtcSettings.energy_usage = Settings.energy_usage;
|
|
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
|
|
RtcSettings.pulse_counter[i] = Settings.pulse_counter[i];
|
|
}
|
|
RtcSettings.power = Settings.power;
|
|
RtcSettingsSave();
|
|
}
|
|
rtc_settings_crc = GetRtcSettingsCrc();
|
|
}
|
|
|
|
bool RtcSettingsValid(void)
|
|
{
|
|
return (RTC_MEM_VALID == RtcSettings.valid);
|
|
}
|
|
|
|
|
|
|
|
uint32_t rtc_reboot_crc = 0;
|
|
|
|
uint32_t GetRtcRebootCrc(void)
|
|
{
|
|
uint32_t crc = 0;
|
|
uint8_t *bytes = (uint8_t*)&RtcReboot;
|
|
|
|
for (uint32_t i = 0; i < sizeof(RTCRBT); i++) {
|
|
crc += bytes[i]*(i+1);
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
void RtcRebootSave(void)
|
|
{
|
|
if (GetRtcRebootCrc() != rtc_reboot_crc) {
|
|
RtcReboot.valid = RTC_MEM_VALID;
|
|
ESP.rtcUserMemoryWrite(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT));
|
|
rtc_reboot_crc = GetRtcRebootCrc();
|
|
}
|
|
}
|
|
|
|
void RtcRebootReset(void)
|
|
{
|
|
RtcReboot.fast_reboot_count = 0;
|
|
RtcRebootSave();
|
|
}
|
|
|
|
void RtcRebootLoad(void)
|
|
{
|
|
ESP.rtcUserMemoryRead(100 - sizeof(RTCRBT), (uint32_t*)&RtcReboot, sizeof(RTCRBT));
|
|
if (RtcReboot.valid != RTC_MEM_VALID) {
|
|
memset(&RtcReboot, 0, sizeof(RTCRBT));
|
|
RtcReboot.valid = RTC_MEM_VALID;
|
|
|
|
RtcRebootSave();
|
|
}
|
|
rtc_reboot_crc = GetRtcRebootCrc();
|
|
}
|
|
|
|
bool RtcRebootValid(void)
|
|
{
|
|
return (RTC_MEM_VALID == RtcReboot.valid);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
#include "spi_flash.h"
|
|
}
|
|
#include "eboot_command.h"
|
|
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2)
|
|
|
|
extern "C" uint32_t _SPIFFS_end;
|
|
|
|
const uint32_t SPIFFS_END = ((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;
|
|
|
|
#else
|
|
|
|
#if AUTOFLASHSIZE
|
|
|
|
#include "flash_hal.h"
|
|
|
|
|
|
const uint32_t SPIFFS_END = (FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;
|
|
|
|
#else
|
|
|
|
extern "C" uint32_t _FS_end;
|
|
|
|
const uint32_t SPIFFS_END = ((uint32_t)&_FS_end - 0x40200000) / SPI_FLASH_SEC_SIZE;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
const uint32_t SETTINGS_LOCATION = SPIFFS_END;
|
|
|
|
const uint8_t CFG_ROTATES = 8;
|
|
|
|
uint32_t settings_location = SETTINGS_LOCATION;
|
|
uint32_t settings_crc32 = 0;
|
|
uint8_t *settings_buffer = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
void SetFlashModeDout(void)
|
|
{
|
|
uint8_t *_buffer;
|
|
uint32_t address;
|
|
|
|
eboot_command ebcmd;
|
|
eboot_command_read(&ebcmd);
|
|
address = ebcmd.args[0];
|
|
_buffer = new uint8_t[FLASH_SECTOR_SIZE];
|
|
|
|
if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) {
|
|
if (_buffer[2] != 3) {
|
|
_buffer[2] = 3;
|
|
if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) {
|
|
ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE);
|
|
}
|
|
}
|
|
}
|
|
delete[] _buffer;
|
|
}
|
|
|
|
bool VersionCompatible(void)
|
|
{
|
|
if (Settings.flag3.compatibility_check) {
|
|
return true;
|
|
}
|
|
|
|
eboot_command ebcmd;
|
|
eboot_command_read(&ebcmd);
|
|
uint32_t start_address = ebcmd.args[0];
|
|
uint32_t end_address = start_address + (ebcmd.args[2] & 0xFFFFF000) + FLASH_SECTOR_SIZE;
|
|
uint32_t* buffer = new uint32_t[FLASH_SECTOR_SIZE / 4];
|
|
|
|
uint32_t version[3] = { 0 };
|
|
bool found = false;
|
|
for (uint32_t address = start_address; address < end_address; address = address + FLASH_SECTOR_SIZE) {
|
|
ESP.flashRead(address, (uint32_t*)buffer, FLASH_SECTOR_SIZE);
|
|
if ((address == start_address) && (0x1F == (buffer[0] & 0xFF))) {
|
|
version[1] = 0xFFFFFFFF;
|
|
found = true;
|
|
} else {
|
|
for (uint32_t i = 0; i < (FLASH_SECTOR_SIZE / 4); i++) {
|
|
version[0] = version[1];
|
|
version[1] = version[2];
|
|
version[2] = buffer[i];
|
|
if ((MARKER_START == version[0]) && (MARKER_END == version[2])) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found) { break; }
|
|
}
|
|
delete[] buffer;
|
|
|
|
if (!found) { version[1] = 0; }
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("OTA: Version 0x%08X, Compatible 0x%08X"), version[1], VERSION_COMPATIBLE);
|
|
|
|
if (version[1] < VERSION_COMPATIBLE) {
|
|
uint32_t eboot_magic = 0;
|
|
ESP.rtcUserMemoryWrite(0, (uint32_t*)&eboot_magic, sizeof(eboot_magic));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SettingsBufferFree(void)
|
|
{
|
|
if (settings_buffer != nullptr) {
|
|
free(settings_buffer);
|
|
settings_buffer = nullptr;
|
|
}
|
|
}
|
|
|
|
bool SettingsBufferAlloc(void)
|
|
{
|
|
SettingsBufferFree();
|
|
if (!(settings_buffer = (uint8_t *)malloc(sizeof(Settings)))) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_UPLOAD_ERR_2));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint16_t GetCfgCrc16(uint8_t *bytes, uint32_t size)
|
|
{
|
|
uint16_t crc = 0;
|
|
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
if ((i < 14) || (i > 15)) { crc += bytes[i]*(i+1); }
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
uint16_t GetSettingsCrc(void)
|
|
{
|
|
|
|
uint32_t size = ((Settings.version < 0x06060007) || (Settings.version > 0x0606000A)) ? 3584 : sizeof(SYSCFG);
|
|
return GetCfgCrc16((uint8_t*)&Settings, size);
|
|
}
|
|
|
|
uint32_t GetCfgCrc32(uint8_t *bytes, uint32_t size)
|
|
{
|
|
|
|
uint32_t crc = 0;
|
|
|
|
while (size--) {
|
|
crc ^= *bytes++;
|
|
for (uint32_t j = 0; j < 8; j++) {
|
|
crc = (crc >> 1) ^ (-int(crc & 1) & 0xEDB88320);
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
uint32_t GetSettingsCrc32(void)
|
|
{
|
|
return GetCfgCrc32((uint8_t*)&Settings, sizeof(SYSCFG) -4);
|
|
}
|
|
|
|
void SettingsSaveAll(void)
|
|
{
|
|
if (Settings.flag.save_state) {
|
|
Settings.power = power;
|
|
} else {
|
|
Settings.power = 0;
|
|
}
|
|
XsnsCall(FUNC_SAVE_BEFORE_RESTART);
|
|
XdrvCall(FUNC_SAVE_BEFORE_RESTART);
|
|
SettingsSave(0);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void UpdateQuickPowerCycle(bool update)
|
|
{
|
|
if (Settings.flag3.fast_power_cycle_disable) { return; }
|
|
|
|
uint32_t pc_register;
|
|
uint32_t pc_location = SETTINGS_LOCATION - CFG_ROTATES;
|
|
|
|
ESP.flashRead(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register));
|
|
if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) {
|
|
uint32_t counter = ((pc_register & 0xF) << 1) & 0xF;
|
|
if (0 == counter) {
|
|
SettingsErase(3);
|
|
EspRestart();
|
|
} else {
|
|
pc_register = 0xFFA55AB0 | counter;
|
|
ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register));
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Flag %02X"), counter);
|
|
}
|
|
}
|
|
else if (pc_register != 0xFFA55ABF) {
|
|
pc_register = 0xFFA55ABF;
|
|
|
|
if (ESP.flashEraseSector(pc_location)) {
|
|
ESP.flashWrite(pc_location * SPI_FLASH_SEC_SIZE, (uint32*)&pc_register, sizeof(pc_register));
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("QPC: Reset"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t GetSettingsTextLen(void)
|
|
{
|
|
char* position = Settings.text_pool;
|
|
for (uint32_t size = 0; size < SET_MAX; size++) {
|
|
while (*position++ != '\0') { }
|
|
}
|
|
return position - Settings.text_pool;
|
|
}
|
|
|
|
bool SettingsUpdateText(uint32_t index, const char* replace_me)
|
|
{
|
|
if (index >= SET_MAX) {
|
|
return false;
|
|
}
|
|
|
|
|
|
uint32_t replace_len = strlen(replace_me);
|
|
char replace[replace_len +1];
|
|
memcpy(replace, replace_me, sizeof(replace));
|
|
|
|
uint32_t start_pos = 0;
|
|
uint32_t end_pos = 0;
|
|
char* position = Settings.text_pool;
|
|
for (uint32_t size = 0; size < SET_MAX; size++) {
|
|
while (*position++ != '\0') { }
|
|
if (1 == index) {
|
|
start_pos = position - Settings.text_pool;
|
|
}
|
|
else if (0 == index) {
|
|
end_pos = position - Settings.text_pool -1;
|
|
}
|
|
index--;
|
|
}
|
|
uint32_t char_len = position - Settings.text_pool;
|
|
|
|
uint32_t current_len = end_pos - start_pos;
|
|
int diff = replace_len - current_len;
|
|
|
|
|
|
|
|
|
|
int too_long = (char_len + diff) - settings_text_size;
|
|
if (too_long > 0) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_CONFIG "Text overflow by %d char(s)"), too_long);
|
|
return false;
|
|
}
|
|
|
|
if (diff != 0) {
|
|
|
|
memmove_P(Settings.text_pool + start_pos + replace_len, Settings.text_pool + end_pos, char_len - end_pos);
|
|
}
|
|
|
|
memmove_P(Settings.text_pool + start_pos, replace, replace_len);
|
|
|
|
memset(Settings.text_pool + char_len + diff, 0x00, settings_text_size - char_len - diff);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG "CR %d/%d"), GetSettingsTextLen(), settings_text_size);
|
|
|
|
return true;
|
|
}
|
|
|
|
char* SettingsText(uint32_t index)
|
|
{
|
|
char* position = Settings.text_pool;
|
|
|
|
if (index >= SET_MAX) {
|
|
position += settings_text_size -1;
|
|
} else {
|
|
for (;index > 0; index--) {
|
|
while (*position++ != '\0') { }
|
|
}
|
|
}
|
|
return position;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t GetSettingsAddress(void)
|
|
{
|
|
return settings_location * SPI_FLASH_SEC_SIZE;
|
|
}
|
|
|
|
void SettingsSave(uint8_t rotate)
|
|
{
|
|
# 590 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
#ifndef FIRMWARE_MINIMAL
|
|
if ((GetSettingsCrc32() != settings_crc32) || rotate) {
|
|
if (1 == rotate) {
|
|
stop_flash_rotate = 1;
|
|
}
|
|
if (2 == rotate) {
|
|
settings_location = SETTINGS_LOCATION +1;
|
|
}
|
|
if (stop_flash_rotate) {
|
|
settings_location = SETTINGS_LOCATION;
|
|
} else {
|
|
settings_location--;
|
|
if (settings_location <= (SETTINGS_LOCATION - CFG_ROTATES)) {
|
|
settings_location = SETTINGS_LOCATION;
|
|
}
|
|
}
|
|
|
|
Settings.save_flag++;
|
|
if (UtcTime() > START_VALID_TIME) {
|
|
Settings.cfg_timestamp = UtcTime();
|
|
} else {
|
|
Settings.cfg_timestamp++;
|
|
}
|
|
Settings.cfg_size = sizeof(SYSCFG);
|
|
Settings.cfg_crc = GetSettingsCrc();
|
|
Settings.cfg_crc32 = GetSettingsCrc32();
|
|
|
|
if (ESP.flashEraseSector(settings_location)) {
|
|
ESP.flashWrite(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG));
|
|
}
|
|
|
|
if (!stop_flash_rotate && rotate) {
|
|
for (uint32_t i = 1; i < CFG_ROTATES; i++) {
|
|
ESP.flashEraseSector(settings_location -i);
|
|
delay(1);
|
|
}
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG D_SAVED_TO_FLASH_AT " %X, " D_COUNT " %d, " D_BYTES " %d"), settings_location, Settings.save_flag, sizeof(SYSCFG));
|
|
|
|
settings_crc32 = Settings.cfg_crc32;
|
|
}
|
|
#endif
|
|
RtcSettingsSave();
|
|
}
|
|
|
|
void SettingsLoad(void)
|
|
{
|
|
|
|
struct SYSCFGH {
|
|
uint16_t cfg_holder;
|
|
uint16_t cfg_size;
|
|
unsigned long save_flag;
|
|
} _SettingsH;
|
|
unsigned long save_flag = 0;
|
|
|
|
settings_location = 0;
|
|
uint32_t flash_location = SETTINGS_LOCATION +1;
|
|
uint16_t cfg_holder = 0;
|
|
for (uint32_t i = 0; i < CFG_ROTATES; i++) {
|
|
flash_location--;
|
|
ESP.flashRead(flash_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG));
|
|
|
|
bool valid = false;
|
|
if (Settings.version > 0x06000000) {
|
|
bool almost_valid = (Settings.cfg_crc32 == GetSettingsCrc32());
|
|
if (Settings.version < 0x0606000B) {
|
|
almost_valid = (Settings.cfg_crc == GetSettingsCrc());
|
|
}
|
|
|
|
if (almost_valid && (0 == cfg_holder)) { cfg_holder = Settings.cfg_holder; }
|
|
valid = (cfg_holder == Settings.cfg_holder);
|
|
} else {
|
|
ESP.flashRead((flash_location -1) * SPI_FLASH_SEC_SIZE, (uint32*)&_SettingsH, sizeof(SYSCFGH));
|
|
valid = (Settings.cfg_holder == _SettingsH.cfg_holder);
|
|
}
|
|
if (valid) {
|
|
if (Settings.save_flag > save_flag) {
|
|
save_flag = Settings.save_flag;
|
|
settings_location = flash_location;
|
|
if (Settings.flag.stop_flash_rotate && (0 == i)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
delay(1);
|
|
}
|
|
if (settings_location > 0) {
|
|
ESP.flashRead(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(SYSCFG));
|
|
AddLog_P2(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_LOADED_FROM_FLASH_AT " %X, " D_COUNT " %lu"), settings_location, Settings.save_flag);
|
|
}
|
|
|
|
#ifndef FIRMWARE_MINIMAL
|
|
if (!settings_location || (Settings.cfg_holder != (uint16_t)CFG_HOLDER)) {
|
|
SettingsDefault();
|
|
}
|
|
settings_crc32 = GetSettingsCrc32();
|
|
#endif
|
|
|
|
RtcSettingsLoad();
|
|
}
|
|
|
|
void EspErase(uint32_t start_sector, uint32_t end_sector)
|
|
{
|
|
bool serial_output = (LOG_LEVEL_DEBUG_MORE <= seriallog_level);
|
|
for (uint32_t sector = start_sector; sector < end_sector; sector++) {
|
|
|
|
bool result = ESP.flashEraseSector(sector);
|
|
|
|
|
|
|
|
if (serial_output) {
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
Serial.printf(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n", sector, (result) ? D_OK : D_ERROR);
|
|
#else
|
|
Serial.printf_P(PSTR(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n"), sector, (result) ? D_OK : D_ERROR);
|
|
#endif
|
|
delay(10);
|
|
} else {
|
|
yield();
|
|
}
|
|
OsWatchLoop();
|
|
}
|
|
}
|
|
|
|
void SettingsErase(uint8_t type)
|
|
{
|
|
# 733 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
#ifndef FIRMWARE_MINIMAL
|
|
uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1;
|
|
uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE;
|
|
if (1 == type) {
|
|
|
|
_sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4;
|
|
}
|
|
else if (2 == type) {
|
|
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES;
|
|
_sectorEnd = SETTINGS_LOCATION +1;
|
|
}
|
|
else if (3 == type) {
|
|
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES;
|
|
_sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart);
|
|
|
|
|
|
EsptoolErase(_sectorStart, _sectorEnd);
|
|
#endif
|
|
}
|
|
|
|
void SettingsSdkErase(void)
|
|
{
|
|
WiFi.disconnect(true);
|
|
SettingsErase(1);
|
|
delay(1000);
|
|
}
|
|
|
|
|
|
|
|
void SettingsDefault(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_USE_DEFAULTS));
|
|
SettingsDefaultSet1();
|
|
SettingsDefaultSet2();
|
|
SettingsSave(2);
|
|
}
|
|
|
|
void SettingsDefaultSet1(void)
|
|
{
|
|
memset(&Settings, 0x00, sizeof(SYSCFG));
|
|
|
|
Settings.cfg_holder = (uint16_t)CFG_HOLDER;
|
|
Settings.cfg_size = sizeof(SYSCFG);
|
|
|
|
Settings.version = VERSION;
|
|
|
|
|
|
}
|
|
|
|
void SettingsDefaultSet2(void)
|
|
{
|
|
memset((char*)&Settings +16, 0x00, sizeof(SYSCFG) -16);
|
|
|
|
Settings.flag.stop_flash_rotate = APP_FLASH_CYCLE;
|
|
Settings.flag.global_state = APP_ENABLE_LEDLINK;
|
|
Settings.flag3.sleep_normal = APP_NORMAL_SLEEP;
|
|
Settings.flag3.no_power_feedback = APP_NO_RELAY_SCAN;
|
|
Settings.flag3.fast_power_cycle_disable = APP_DISABLE_POWERCYCLE;
|
|
Settings.flag3.bootcount_update = DEEPSLEEP_BOOTCOUNT;
|
|
Settings.flag3.compatibility_check = OTA_COMPATIBILITY;
|
|
Settings.save_data = SAVE_DATA;
|
|
Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY;
|
|
Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET;
|
|
Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW;
|
|
Settings.sleep = APP_SLEEP;
|
|
if (Settings.sleep < 50) {
|
|
Settings.sleep = 50;
|
|
}
|
|
|
|
|
|
|
|
Settings.interlock[0] = 0xFF;
|
|
Settings.module = MODULE;
|
|
ModuleDefault(WEMOS);
|
|
|
|
SettingsUpdateText(SET_FRIENDLYNAME1, FRIENDLY_NAME);
|
|
SettingsUpdateText(SET_FRIENDLYNAME2, FRIENDLY_NAME"2");
|
|
SettingsUpdateText(SET_FRIENDLYNAME3, FRIENDLY_NAME"3");
|
|
SettingsUpdateText(SET_FRIENDLYNAME4, FRIENDLY_NAME"4");
|
|
SettingsUpdateText(SET_OTAURL, OTA_URL);
|
|
|
|
|
|
Settings.flag.save_state = SAVE_STATE;
|
|
Settings.power = APP_POWER;
|
|
Settings.poweronstate = APP_POWERON_STATE;
|
|
Settings.blinktime = APP_BLINKTIME;
|
|
Settings.blinkcount = APP_BLINKCOUNT;
|
|
Settings.ledstate = APP_LEDSTATE;
|
|
Settings.ledmask = APP_LEDMASK;
|
|
Settings.pulse_timer[0] = APP_PULSETIME;
|
|
|
|
|
|
|
|
Settings.serial_config = TS_SERIAL_8N1;
|
|
Settings.baudrate = APP_BAUDRATE / 300;
|
|
Settings.sbaudrate = SOFT_BAUDRATE / 300;
|
|
Settings.serial_delimiter = 0xff;
|
|
Settings.seriallog_level = SERIAL_LOG_LEVEL;
|
|
|
|
|
|
Settings.flag3.use_wifi_scan = WIFI_SCAN_AT_RESTART;
|
|
Settings.flag3.use_wifi_rescan = WIFI_SCAN_REGULARLY;
|
|
Settings.wifi_output_power = 170;
|
|
ParseIp(&Settings.ip_address[0], WIFI_IP_ADDRESS);
|
|
ParseIp(&Settings.ip_address[1], WIFI_GATEWAY);
|
|
ParseIp(&Settings.ip_address[2], WIFI_SUBNETMASK);
|
|
ParseIp(&Settings.ip_address[3], WIFI_DNS);
|
|
Settings.sta_config = WIFI_CONFIG_TOOL;
|
|
|
|
SettingsUpdateText(SET_STASSID1, STA_SSID1);
|
|
SettingsUpdateText(SET_STASSID2, STA_SSID2);
|
|
SettingsUpdateText(SET_STAPWD1, STA_PASS1);
|
|
SettingsUpdateText(SET_STAPWD2, STA_PASS2);
|
|
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
|
|
|
|
|
|
SettingsUpdateText(SET_SYSLOG_HOST, SYS_LOG_HOST);
|
|
Settings.syslog_port = SYS_LOG_PORT;
|
|
Settings.syslog_level = SYS_LOG_LEVEL;
|
|
|
|
|
|
Settings.flag2.emulation = EMULATION;
|
|
Settings.flag3.gui_hostname_ip = GUI_SHOW_HOSTNAME;
|
|
Settings.flag3.mdns_enabled = MDNS_ENABLED;
|
|
Settings.webserver = WEB_SERVER;
|
|
Settings.weblog_level = WEB_LOG_LEVEL;
|
|
SettingsUpdateText(SET_WEBPWD, WEB_PASSWORD);
|
|
SettingsUpdateText(SET_CORS, CORS_DOMAIN);
|
|
|
|
|
|
Settings.flag.button_restrict = KEY_DISABLE_MULTIPRESS;
|
|
Settings.flag.button_swap = KEY_SWAP_DOUBLE_PRESS;
|
|
Settings.flag.button_single = KEY_ONLY_SINGLE_PRESS;
|
|
Settings.param[P_HOLD_TIME] = KEY_HOLD_TIME;
|
|
|
|
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) { Settings.switchmode[i] = SWITCH_MODE; }
|
|
|
|
|
|
Settings.flag.mqtt_enabled = MQTT_USE;
|
|
Settings.flag.mqtt_response = MQTT_RESULT_COMMAND;
|
|
Settings.flag.mqtt_offline = MQTT_LWT_MESSAGE;
|
|
Settings.flag.mqtt_power_retain = MQTT_POWER_RETAIN;
|
|
Settings.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN;
|
|
Settings.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN;
|
|
Settings.flag.mqtt_sensor_retain = MQTT_SENSOR_RETAIN;
|
|
|
|
Settings.flag.device_index_enable = MQTT_POWER_FORMAT;
|
|
Settings.flag3.time_append_timezone = MQTT_APPEND_TIMEZONE;
|
|
Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL;
|
|
Settings.flag3.no_hold_retain = MQTT_NO_HOLD_RETAIN;
|
|
Settings.flag3.use_underscore = MQTT_INDEX_SEPARATOR;
|
|
Settings.flag3.grouptopic_mode = MQTT_GROUPTOPIC_FORMAT;
|
|
SettingsUpdateText(SET_MQTT_HOST, MQTT_HOST);
|
|
Settings.mqtt_port = MQTT_PORT;
|
|
SettingsUpdateText(SET_MQTT_CLIENT, MQTT_CLIENT_ID);
|
|
SettingsUpdateText(SET_MQTT_USER, MQTT_USER);
|
|
SettingsUpdateText(SET_MQTT_PWD, MQTT_PASS);
|
|
SettingsUpdateText(SET_MQTT_TOPIC, MQTT_TOPIC);
|
|
SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC);
|
|
SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC);
|
|
SettingsUpdateText(SET_MQTT_GRP_TOPIC, MQTT_GRPTOPIC);
|
|
SettingsUpdateText(SET_MQTT_FULLTOPIC, MQTT_FULLTOPIC);
|
|
Settings.mqtt_retry = MQTT_RETRY_SECS;
|
|
SettingsUpdateText(SET_MQTTPREFIX1, SUB_PREFIX);
|
|
SettingsUpdateText(SET_MQTTPREFIX2, PUB_PREFIX);
|
|
SettingsUpdateText(SET_MQTTPREFIX3, PUB_PREFIX2);
|
|
SettingsUpdateText(SET_STATE_TXT1, MQTT_STATUS_OFF);
|
|
SettingsUpdateText(SET_STATE_TXT2, MQTT_STATUS_ON);
|
|
SettingsUpdateText(SET_STATE_TXT3, MQTT_CMND_TOGGLE);
|
|
SettingsUpdateText(SET_STATE_TXT4, MQTT_CMND_HOLD);
|
|
char fingerprint[60];
|
|
strlcpy(fingerprint, MQTT_FINGERPRINT1, sizeof(fingerprint));
|
|
char *p = fingerprint;
|
|
for (uint32_t i = 0; i < 20; i++) {
|
|
Settings.mqtt_fingerprint[0][i] = strtol(p, &p, 16);
|
|
}
|
|
strlcpy(fingerprint, MQTT_FINGERPRINT2, sizeof(fingerprint));
|
|
p = fingerprint;
|
|
for (uint32_t i = 0; i < 20; i++) {
|
|
Settings.mqtt_fingerprint[1][i] = strtol(p, &p, 16);
|
|
}
|
|
Settings.tele_period = TELE_PERIOD;
|
|
Settings.mqttlog_level = MQTT_LOG_LEVEL;
|
|
|
|
|
|
Settings.flag.no_power_on_check = ENERGY_VOLTAGE_ALWAYS;
|
|
Settings.flag2.current_resolution = 3;
|
|
|
|
|
|
Settings.flag2.energy_resolution = ENERGY_RESOLUTION;
|
|
Settings.flag3.dds2382_model = ENERGY_DDS2382_MODE;
|
|
Settings.flag3.hardware_energy_total = ENERGY_HARDWARE_TOTALS;
|
|
Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY;
|
|
|
|
Settings.energy_power_calibration = HLW_PREF_PULSE;
|
|
Settings.energy_voltage_calibration = HLW_UREF_PULSE;
|
|
Settings.energy_current_calibration = HLW_IREF_PULSE;
|
|
# 944 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
Settings.energy_max_power_limit_hold = MAX_POWER_HOLD;
|
|
Settings.energy_max_power_limit_window = MAX_POWER_WINDOW;
|
|
|
|
Settings.energy_max_power_safe_limit_hold = SAFE_POWER_HOLD;
|
|
Settings.energy_max_power_safe_limit_window = SAFE_POWER_WINDOW;
|
|
|
|
|
|
|
|
RtcSettings.energy_kWhtotal = 0;
|
|
|
|
memset((char*)&RtcSettings.energy_usage, 0x00, sizeof(RtcSettings.energy_usage));
|
|
Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP;
|
|
|
|
|
|
Settings.flag.ir_receive_decimal = IR_DATA_RADIX;
|
|
Settings.flag3.receive_raw = IR_ADD_RAW_DATA;
|
|
Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE;
|
|
|
|
|
|
Settings.flag.rf_receive_decimal = RF_DATA_RADIX;
|
|
|
|
memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9);
|
|
|
|
|
|
Settings.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER;
|
|
# 979 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
Settings.flag.temperature_conversion = TEMP_CONVERSION;
|
|
Settings.flag.pressure_conversion = PRESSURE_CONVERSION;
|
|
Settings.flag2.pressure_resolution = PRESSURE_RESOLUTION;
|
|
Settings.flag2.humidity_resolution = HUMIDITY_RESOLUTION;
|
|
Settings.flag2.temperature_resolution = TEMP_RESOLUTION;
|
|
Settings.flag3.ds18x20_internal_pullup = DS18X20_PULL_UP;
|
|
Settings.flag3.counter_reset_on_tele = COUNTER_RESET;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Settings.flag2.calc_resolution = CALC_RESOLUTION;
|
|
|
|
|
|
Settings.flag3.timers_enable = TIMERS_ENABLED;
|
|
|
|
|
|
Settings.flag.hass_light = HASS_AS_LIGHT;
|
|
Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE;
|
|
Settings.flag3.hass_tele_on_power = TELE_ON_POWER;
|
|
|
|
|
|
Settings.flag.knx_enabled = KNX_ENABLED;
|
|
Settings.flag.knx_enable_enhancement = KNX_ENHANCED;
|
|
|
|
|
|
Settings.flag.pwm_control = LIGHT_MODE;
|
|
Settings.flag.ws_clock_reverse = LIGHT_CLOCK_DIRECTION;
|
|
Settings.flag.light_signal = LIGHT_PAIRS_CO2;
|
|
Settings.flag.not_power_linked = LIGHT_POWER_CONTROL;
|
|
Settings.flag.decimal_text = LIGHT_COLOR_RADIX;
|
|
Settings.flag3.pwm_multi_channels = LIGHT_CHANNEL_MODE;
|
|
Settings.flag3.slider_dimmer_stay_on = LIGHT_SLIDER_POWER;
|
|
Settings.flag4.alexa_ct_range = LIGHT_ALEXA_CT_RANGE;
|
|
|
|
Settings.pwm_frequency = PWM_FREQ;
|
|
Settings.pwm_range = PWM_RANGE;
|
|
for (uint32_t i = 0; i < MAX_PWMS; i++) {
|
|
Settings.light_color[i] = DEFAULT_LIGHT_COMPONENT;
|
|
|
|
}
|
|
Settings.light_correction = 1;
|
|
Settings.light_dimmer = DEFAULT_LIGHT_DIMMER;
|
|
|
|
Settings.light_speed = 1;
|
|
|
|
Settings.light_width = 1;
|
|
|
|
Settings.light_pixels = WS2812_LEDS;
|
|
|
|
Settings.ws_width[WS_SECOND] = 1;
|
|
Settings.ws_color[WS_SECOND][WS_RED] = 255;
|
|
|
|
Settings.ws_color[WS_SECOND][WS_BLUE] = 255;
|
|
Settings.ws_width[WS_MINUTE] = 3;
|
|
|
|
Settings.ws_color[WS_MINUTE][WS_GREEN] = 255;
|
|
|
|
Settings.ws_width[WS_HOUR] = 5;
|
|
Settings.ws_color[WS_HOUR][WS_RED] = 255;
|
|
|
|
|
|
|
|
Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX;
|
|
Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN;
|
|
|
|
|
|
|
|
Settings.display_mode = 1;
|
|
Settings.display_refresh = 2;
|
|
Settings.display_rows = 2;
|
|
Settings.display_cols[0] = 16;
|
|
Settings.display_cols[1] = 8;
|
|
Settings.display_dimmer = 1;
|
|
Settings.display_size = 1;
|
|
Settings.display_font = 1;
|
|
|
|
Settings.display_address[0] = MTX_ADDRESS1;
|
|
Settings.display_address[1] = MTX_ADDRESS2;
|
|
Settings.display_address[2] = MTX_ADDRESS3;
|
|
Settings.display_address[3] = MTX_ADDRESS4;
|
|
Settings.display_address[4] = MTX_ADDRESS5;
|
|
Settings.display_address[5] = MTX_ADDRESS6;
|
|
Settings.display_address[6] = MTX_ADDRESS7;
|
|
Settings.display_address[7] = MTX_ADDRESS8;
|
|
|
|
|
|
if (((APP_TIMEZONE > -14) && (APP_TIMEZONE < 15)) || (99 == APP_TIMEZONE)) {
|
|
Settings.timezone = APP_TIMEZONE;
|
|
Settings.timezone_minutes = 0;
|
|
} else {
|
|
Settings.timezone = APP_TIMEZONE / 60;
|
|
Settings.timezone_minutes = abs(APP_TIMEZONE % 60);
|
|
}
|
|
SettingsUpdateText(SET_NTPSERVER1, NTP_SERVER1);
|
|
SettingsUpdateText(SET_NTPSERVER2, NTP_SERVER2);
|
|
SettingsUpdateText(SET_NTPSERVER3, NTP_SERVER3);
|
|
for (uint32_t i = 0; i < MAX_NTP_SERVERS; i++) {
|
|
SettingsUpdateText(SET_NTPSERVER1 +i, ReplaceCommaWithDot(SettingsText(SET_NTPSERVER1 +i)));
|
|
}
|
|
Settings.latitude = (int)((double)LATITUDE * 1000000);
|
|
Settings.longitude = (int)((double)LONGITUDE * 1000000);
|
|
SettingsResetStd();
|
|
SettingsResetDst();
|
|
|
|
Settings.button_debounce = KEY_DEBOUNCE_TIME;
|
|
Settings.switch_debounce = SWITCH_DEBOUNCE_TIME;
|
|
|
|
for (uint32_t j = 0; j < 5; j++) {
|
|
Settings.rgbwwTable[j] = 255;
|
|
}
|
|
|
|
Settings.novasds_startingoffset = STARTING_OFFSET;
|
|
|
|
SettingsDefaultWebColor();
|
|
|
|
memset(&Settings.monitors, 0xFF, 20);
|
|
SettingsEnableAllI2cDrivers();
|
|
|
|
|
|
Settings.flag3.tuya_apply_o20 = TUYA_SETOPTION_20;
|
|
Settings.flag3.tuya_serial_mqtt_publish = MQTT_TUYA_RECEIVED;
|
|
|
|
Settings.flag3.buzzer_enable = BUZZER_ENABLE;
|
|
Settings.flag3.shutter_mode = SHUTTER_SUPPORT;
|
|
Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS;
|
|
Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES;
|
|
}
|
|
|
|
|
|
|
|
void SettingsResetStd(void)
|
|
{
|
|
Settings.tflag[0].hemis = TIME_STD_HEMISPHERE;
|
|
Settings.tflag[0].week = TIME_STD_WEEK;
|
|
Settings.tflag[0].dow = TIME_STD_DAY;
|
|
Settings.tflag[0].month = TIME_STD_MONTH;
|
|
Settings.tflag[0].hour = TIME_STD_HOUR;
|
|
Settings.toffset[0] = TIME_STD_OFFSET;
|
|
}
|
|
|
|
void SettingsResetDst(void)
|
|
{
|
|
Settings.tflag[1].hemis = TIME_DST_HEMISPHERE;
|
|
Settings.tflag[1].week = TIME_DST_WEEK;
|
|
Settings.tflag[1].dow = TIME_DST_DAY;
|
|
Settings.tflag[1].month = TIME_DST_MONTH;
|
|
Settings.tflag[1].hour = TIME_DST_HOUR;
|
|
Settings.toffset[1] = TIME_DST_OFFSET;
|
|
}
|
|
|
|
void SettingsDefaultWebColor(void)
|
|
{
|
|
char scolor[10];
|
|
for (uint32_t i = 0; i < COL_LAST; i++) {
|
|
WebHexCode(i, GetTextIndexed(scolor, sizeof(scolor), i, kWebColors));
|
|
}
|
|
}
|
|
|
|
void SettingsEnableAllI2cDrivers(void)
|
|
{
|
|
Settings.i2c_drivers[0] = 0xFFFFFFFF;
|
|
Settings.i2c_drivers[1] = 0xFFFFFFFF;
|
|
Settings.i2c_drivers[2] = 0xFFFFFFFF;
|
|
}
|
|
|
|
|
|
|
|
void SettingsDelta(void)
|
|
{
|
|
if (Settings.version != VERSION) {
|
|
if (Settings.version < 0x06000000) {
|
|
Settings.cfg_size = sizeof(SYSCFG);
|
|
Settings.cfg_crc = GetSettingsCrc();
|
|
}
|
|
if (Settings.version < 0x06000002) {
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
if (i < 4) {
|
|
Settings.switchmode[i] = Settings.interlock[i];
|
|
} else {
|
|
Settings.switchmode[i] = SWITCH_MODE;
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
if (Settings.my_gp.io[i] >= GPIO_SWT5) {
|
|
Settings.my_gp.io[i] += 4;
|
|
}
|
|
}
|
|
}
|
|
if (Settings.version < 0x06000003) {
|
|
Settings.flag.mqtt_serial_raw = 0;
|
|
Settings.flag.pressure_conversion = 0;
|
|
Settings.flag3.data = 0;
|
|
}
|
|
if (Settings.version < 0x06010103) {
|
|
Settings.flag3.timers_enable = 1;
|
|
}
|
|
if (Settings.version < 0x0601010C) {
|
|
Settings.button_debounce = KEY_DEBOUNCE_TIME;
|
|
Settings.switch_debounce = SWITCH_DEBOUNCE_TIME;
|
|
}
|
|
if (Settings.version < 0x0602010A) {
|
|
for (uint32_t j = 0; j < 5; j++) {
|
|
Settings.rgbwwTable[j] = 255;
|
|
}
|
|
}
|
|
if (Settings.version < 0x06030002) {
|
|
Settings.timezone_minutes = 0;
|
|
}
|
|
if (Settings.version < 0x06030004) {
|
|
memset(&Settings.monitors, 0xFF, 20);
|
|
}
|
|
if (Settings.version < 0x0603000E) {
|
|
Settings.flag2.calc_resolution = CALC_RESOLUTION;
|
|
}
|
|
if (Settings.version < 0x0603000F) {
|
|
if (Settings.sleep < 50) {
|
|
Settings.sleep = 50;
|
|
}
|
|
}
|
|
if (Settings.version < 0x06040105) {
|
|
Settings.flag3.mdns_enabled = MDNS_ENABLED;
|
|
Settings.param[P_MDNS_DELAYED_START] = 0;
|
|
}
|
|
if (Settings.version < 0x0604010B) {
|
|
Settings.interlock[0] = 0xFF;
|
|
for (uint32_t i = 1; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; }
|
|
}
|
|
if (Settings.version < 0x0604010D) {
|
|
Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET;
|
|
}
|
|
if (Settings.version < 0x06040110) {
|
|
ModuleDefault(WEMOS);
|
|
}
|
|
if (Settings.version < 0x06040113) {
|
|
Settings.param[P_RGB_REMAP] = RGB_REMAP_RGBW;
|
|
}
|
|
if (Settings.version < 0x06050003) {
|
|
Settings.novasds_startingoffset = STARTING_OFFSET;
|
|
}
|
|
if (Settings.version < 0x06050006) {
|
|
SettingsDefaultWebColor();
|
|
}
|
|
if (Settings.version < 0x06050007) {
|
|
Settings.ledmask = APP_LEDMASK;
|
|
}
|
|
if (Settings.version < 0x0605000A) {
|
|
Settings.my_adc0 = ADC0_NONE;
|
|
}
|
|
if (Settings.version < 0x0605000D) {
|
|
Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE;
|
|
}
|
|
if (Settings.version < 0x06060001) {
|
|
Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP;
|
|
}
|
|
if (Settings.version < 0x06060007) {
|
|
memset((char*)&Settings +0xE00, 0x00, sizeof(SYSCFG) -0xE00);
|
|
}
|
|
if (Settings.version < 0x06060008) {
|
|
|
|
if (Settings.flag3.tuya_serial_mqtt_publish) {
|
|
Settings.param[P_ex_DIMMER_MAX] = 100;
|
|
} else {
|
|
Settings.param[P_ex_DIMMER_MAX] = 255;
|
|
}
|
|
}
|
|
if (Settings.version < 0x06060009) {
|
|
Settings.baudrate = APP_BAUDRATE / 300;
|
|
Settings.sbaudrate = SOFT_BAUDRATE / 300;
|
|
}
|
|
if (Settings.version < 0x0606000A) {
|
|
uint8_t tuyaindex = 0;
|
|
if (Settings.param[P_BACKLOG_DELAY] > 0) {
|
|
Settings.tuya_fnid_map[tuyaindex].fnid = 21;
|
|
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_BACKLOG_DELAY];
|
|
tuyaindex++;
|
|
} else if (Settings.flag3.fast_power_cycle_disable == 1) {
|
|
Settings.tuya_fnid_map[tuyaindex].fnid = 11;
|
|
Settings.tuya_fnid_map[tuyaindex].dpid = 1;
|
|
tuyaindex++;
|
|
}
|
|
if (Settings.param[P_ex_TUYA_RELAYS] > 0) {
|
|
for (uint8_t i = 0 ; i < Settings.param[P_ex_TUYA_RELAYS]; i++) {
|
|
Settings.tuya_fnid_map[tuyaindex].fnid = 12 + i;
|
|
Settings.tuya_fnid_map[tuyaindex].dpid = i + 2;
|
|
tuyaindex++;
|
|
}
|
|
}
|
|
if (Settings.param[P_ex_TUYA_POWER_ID] > 0) {
|
|
Settings.tuya_fnid_map[tuyaindex].fnid = 31;
|
|
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_POWER_ID];
|
|
tuyaindex++;
|
|
}
|
|
if (Settings.param[P_ex_TUYA_VOLTAGE_ID] > 0) {
|
|
Settings.tuya_fnid_map[tuyaindex].fnid = 33;
|
|
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_VOLTAGE_ID];
|
|
tuyaindex++;
|
|
}
|
|
if (Settings.param[P_ex_TUYA_CURRENT_ID] > 0) {
|
|
Settings.tuya_fnid_map[tuyaindex].fnid = 32;
|
|
Settings.tuya_fnid_map[tuyaindex].dpid = Settings.param[P_ex_TUYA_CURRENT_ID];
|
|
}
|
|
}
|
|
if (Settings.version < 0x0606000C) {
|
|
memset((char*)&Settings +0x1D6, 0x00, 16);
|
|
}
|
|
if (Settings.version < 0x0606000F) {
|
|
Settings.ex_shutter_accuracy = 0;
|
|
Settings.ex_mqttlog_level = MQTT_LOG_LEVEL;
|
|
}
|
|
if (Settings.version < 0x06060011) {
|
|
Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY;
|
|
}
|
|
if (Settings.version < 0x06060012) {
|
|
Settings.dimmer_hw_min = DEFAULT_DIMMER_MIN;
|
|
Settings.dimmer_hw_max = DEFAULT_DIMMER_MAX;
|
|
if (TUYA_DIMMER == Settings.module) {
|
|
if (Settings.flag3.ex_tuya_dimmer_min_limit) {
|
|
Settings.dimmer_hw_min = 25;
|
|
} else {
|
|
Settings.dimmer_hw_min = 1;
|
|
}
|
|
Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX];
|
|
}
|
|
else if (PS_16_DZ == Settings.module) {
|
|
Settings.dimmer_hw_min = 10;
|
|
Settings.dimmer_hw_max = Settings.param[P_ex_DIMMER_MAX];
|
|
}
|
|
}
|
|
if (Settings.version < 0x06060014) {
|
|
# 1323 "C:/shared/sonoff/Git/Tasmota/tasmota/settings.ino"
|
|
Settings.flag3.fast_power_cycle_disable = 0;
|
|
Settings.energy_power_delta = Settings.ex_energy_power_delta;
|
|
Settings.ex_energy_power_delta = 0;
|
|
}
|
|
if (Settings.version < 0x06060015) {
|
|
if ((EX_WIFI_SMARTCONFIG == Settings.ex_sta_config) || (EX_WIFI_WPSCONFIG == Settings.ex_sta_config)) {
|
|
Settings.ex_sta_config = WIFI_MANAGER;
|
|
}
|
|
}
|
|
|
|
if (Settings.version < 0x07000002) {
|
|
Settings.web_color2[0][0] = Settings.web_color[0][0];
|
|
Settings.web_color2[0][1] = Settings.web_color[0][1];
|
|
Settings.web_color2[0][2] = Settings.web_color[0][2];
|
|
}
|
|
if (Settings.version < 0x07000003) {
|
|
SettingsEnableAllI2cDrivers();
|
|
}
|
|
if (Settings.version < 0x07000004) {
|
|
Settings.ex_wifi_output_power = 170;
|
|
}
|
|
if (Settings.version < 0x07010202) {
|
|
Settings.ex_serial_config = TS_SERIAL_8N1;
|
|
}
|
|
if (Settings.version < 0x07010204) {
|
|
if (Settings.flag3.ex_cors_enabled == 1) {
|
|
strlcpy(Settings.ex_cors_domain, CORS_ENABLED_ALL, sizeof(Settings.ex_cors_domain));
|
|
} else {
|
|
Settings.ex_cors_domain[0] = 0;
|
|
}
|
|
}
|
|
if (Settings.version < 0x07010205) {
|
|
Settings.seriallog_level = Settings.ex_seriallog_level;
|
|
Settings.sta_config = Settings.ex_sta_config;
|
|
Settings.sta_active = Settings.ex_sta_active;
|
|
memcpy((char*)&Settings.rule_stop, (char*)&Settings.ex_rule_stop, 47);
|
|
}
|
|
if (Settings.version < 0x07010206) {
|
|
Settings.flag4 = Settings.ex_flag4;
|
|
Settings.mqtt_port = Settings.ex_mqtt_port;
|
|
memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5);
|
|
}
|
|
|
|
if (Settings.version < 0x08000000) {
|
|
char temp[strlen(Settings.text_pool) +1]; strncpy(temp, Settings.text_pool, sizeof(temp));
|
|
char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21));
|
|
char temp22[strlen(Settings.ex_mqtt_prefix[1]) +1]; strncpy(temp22, Settings.ex_mqtt_prefix[1], sizeof(temp22));
|
|
char temp23[strlen(Settings.ex_mqtt_prefix[2]) +1]; strncpy(temp23, Settings.ex_mqtt_prefix[2], sizeof(temp23));
|
|
char temp31[strlen(Settings.ex_sta_ssid[0]) +1]; strncpy(temp31, Settings.ex_sta_ssid[0], sizeof(temp31));
|
|
char temp32[strlen(Settings.ex_sta_ssid[1]) +1]; strncpy(temp32, Settings.ex_sta_ssid[1], sizeof(temp32));
|
|
char temp41[strlen(Settings.ex_sta_pwd[0]) +1]; strncpy(temp41, Settings.ex_sta_pwd[0], sizeof(temp41));
|
|
char temp42[strlen(Settings.ex_sta_pwd[1]) +1]; strncpy(temp42, Settings.ex_sta_pwd[1], sizeof(temp42));
|
|
char temp5[strlen(Settings.ex_hostname) +1]; strncpy(temp5, Settings.ex_hostname, sizeof(temp5));
|
|
char temp6[strlen(Settings.ex_syslog_host) +1]; strncpy(temp6, Settings.ex_syslog_host, sizeof(temp6));
|
|
char temp7[strlen(Settings.ex_mqtt_host) +1]; strncpy(temp7, Settings.ex_mqtt_host, sizeof(temp7));
|
|
char temp8[strlen(Settings.ex_mqtt_client) +1]; strncpy(temp8, Settings.ex_mqtt_client, sizeof(temp8));
|
|
char temp9[strlen(Settings.ex_mqtt_user) +1]; strncpy(temp9, Settings.ex_mqtt_user, sizeof(temp9));
|
|
char temp10[strlen(Settings.ex_mqtt_pwd) +1]; strncpy(temp10, Settings.ex_mqtt_pwd, sizeof(temp10));
|
|
char temp11[strlen(Settings.ex_mqtt_topic) +1]; strncpy(temp11, Settings.ex_mqtt_topic, sizeof(temp11));
|
|
char temp12[strlen(Settings.ex_button_topic) +1]; strncpy(temp12, Settings.ex_button_topic, sizeof(temp12));
|
|
char temp13[strlen(Settings.ex_mqtt_grptopic) +1]; strncpy(temp13, Settings.ex_mqtt_grptopic, sizeof(temp13));
|
|
|
|
memset(Settings.text_pool, 0x00, settings_text_size);
|
|
SettingsUpdateText(SET_OTAURL, temp);
|
|
SettingsUpdateText(SET_MQTTPREFIX1, temp21);
|
|
SettingsUpdateText(SET_MQTTPREFIX2, temp22);
|
|
SettingsUpdateText(SET_MQTTPREFIX3, temp23);
|
|
SettingsUpdateText(SET_STASSID1, temp31);
|
|
SettingsUpdateText(SET_STASSID2, temp32);
|
|
SettingsUpdateText(SET_STAPWD1, temp41);
|
|
SettingsUpdateText(SET_STAPWD2, temp42);
|
|
SettingsUpdateText(SET_HOSTNAME, temp5);
|
|
SettingsUpdateText(SET_SYSLOG_HOST, temp6);
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
if (!strlen(Settings.ex_mqtt_user)) {
|
|
SettingsUpdateText(SET_MQTT_HOST, temp7);
|
|
SettingsUpdateText(SET_MQTT_USER, temp9);
|
|
} else {
|
|
char aws_mqtt_host[66];
|
|
snprintf_P(aws_mqtt_host, sizeof(aws_mqtt_host), PSTR("%s%s"), temp9, temp7);
|
|
SettingsUpdateText(SET_MQTT_HOST, aws_mqtt_host);
|
|
SettingsUpdateText(SET_MQTT_USER, "");
|
|
}
|
|
#else
|
|
SettingsUpdateText(SET_MQTT_HOST, temp7);
|
|
SettingsUpdateText(SET_MQTT_USER, temp9);
|
|
#endif
|
|
SettingsUpdateText(SET_MQTT_CLIENT, temp8);
|
|
SettingsUpdateText(SET_MQTT_PWD, temp10);
|
|
SettingsUpdateText(SET_MQTT_TOPIC, temp11);
|
|
SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12);
|
|
SettingsUpdateText(SET_MQTT_GRP_TOPIC, temp13);
|
|
|
|
SettingsUpdateText(SET_WEBPWD, Settings.ex_web_password);
|
|
SettingsUpdateText(SET_CORS, Settings.ex_cors_domain);
|
|
SettingsUpdateText(SET_MQTT_FULLTOPIC, Settings.ex_mqtt_fulltopic);
|
|
SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, Settings.ex_switch_topic);
|
|
SettingsUpdateText(SET_STATE_TXT1, Settings.ex_state_text[0]);
|
|
SettingsUpdateText(SET_STATE_TXT2, Settings.ex_state_text[1]);
|
|
SettingsUpdateText(SET_STATE_TXT3, Settings.ex_state_text[2]);
|
|
SettingsUpdateText(SET_STATE_TXT4, Settings.ex_state_text[3]);
|
|
SettingsUpdateText(SET_NTPSERVER1, Settings.ex_ntp_server[0]);
|
|
SettingsUpdateText(SET_NTPSERVER2, Settings.ex_ntp_server[1]);
|
|
SettingsUpdateText(SET_NTPSERVER3, Settings.ex_ntp_server[2]);
|
|
SettingsUpdateText(SET_MEM1, Settings.script_pram[0]);
|
|
SettingsUpdateText(SET_MEM2, Settings.script_pram[1]);
|
|
SettingsUpdateText(SET_MEM3, Settings.script_pram[2]);
|
|
SettingsUpdateText(SET_MEM4, Settings.script_pram[3]);
|
|
SettingsUpdateText(SET_MEM5, Settings.script_pram[4]);
|
|
SettingsUpdateText(SET_FRIENDLYNAME1, Settings.ex_friendlyname[0]);
|
|
SettingsUpdateText(SET_FRIENDLYNAME2, Settings.ex_friendlyname[1]);
|
|
SettingsUpdateText(SET_FRIENDLYNAME3, Settings.ex_friendlyname[2]);
|
|
SettingsUpdateText(SET_FRIENDLYNAME4, Settings.ex_friendlyname[3]);
|
|
}
|
|
|
|
Settings.version = VERSION;
|
|
SettingsSave(1);
|
|
}
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
|
|
IPAddress syslog_host_addr;
|
|
uint32_t syslog_host_hash = 0;
|
|
|
|
extern "C" {
|
|
extern struct rst_info resetInfo;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include <Ticker.h>
|
|
|
|
Ticker tickerOSWatch;
|
|
|
|
const uint32_t OSWATCH_RESET_TIME = 120;
|
|
|
|
static unsigned long oswatch_last_loop_time;
|
|
uint8_t oswatch_blocked_loop = 0;
|
|
|
|
#ifndef USE_WS2812_DMA
|
|
|
|
#endif
|
|
|
|
#ifdef USE_KNX
|
|
bool knx_started = false;
|
|
#endif
|
|
|
|
void OsWatchTicker(void)
|
|
{
|
|
uint32_t t = millis();
|
|
uint32_t last_run = abs(t - oswatch_last_loop_time);
|
|
|
|
#ifdef DEBUG_THEO
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d %% (%d dBm), last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI(), last_run);
|
|
#endif
|
|
if (last_run >= (OSWATCH_RESET_TIME * 1000)) {
|
|
|
|
RtcSettings.oswatch_blocked_loop = 1;
|
|
RtcSettingsSave();
|
|
|
|
|
|
|
|
|
|
|
|
volatile uint32_t dummy;
|
|
dummy = *((uint32_t*) 0x00000000);
|
|
}
|
|
}
|
|
|
|
void OsWatchInit(void)
|
|
{
|
|
oswatch_blocked_loop = RtcSettings.oswatch_blocked_loop;
|
|
RtcSettings.oswatch_blocked_loop = 0;
|
|
oswatch_last_loop_time = millis();
|
|
tickerOSWatch.attach_ms(((OSWATCH_RESET_TIME / 3) * 1000), OsWatchTicker);
|
|
}
|
|
|
|
void OsWatchLoop(void)
|
|
{
|
|
oswatch_last_loop_time = millis();
|
|
|
|
}
|
|
|
|
bool OsWatchBlockedLoop(void)
|
|
{
|
|
return oswatch_blocked_loop;
|
|
}
|
|
|
|
uint32_t ResetReason(void)
|
|
{
|
|
# 101 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
|
|
return resetInfo.reason;
|
|
}
|
|
|
|
String GetResetReason(void)
|
|
{
|
|
if (oswatch_blocked_loop) {
|
|
char buff[32];
|
|
strncpy_P(buff, PSTR(D_JSON_BLOCKED_LOOP), sizeof(buff));
|
|
return String(buff);
|
|
} else {
|
|
return ESP.getResetReason();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
size_t strchrspn(const char *str1, int character)
|
|
{
|
|
size_t ret = 0;
|
|
char *start = (char*)str1;
|
|
char *end = strchr(str1, character);
|
|
if (end) ret = end - start;
|
|
return ret;
|
|
}
|
|
|
|
|
|
char* subStr(char* dest, char* str, const char *delim, int index)
|
|
{
|
|
char *act;
|
|
char *sub = nullptr;
|
|
char *ptr;
|
|
int i;
|
|
|
|
|
|
strncpy(dest, str, strlen(str)+1);
|
|
for (i = 1, act = dest; i <= index; i++, act = nullptr) {
|
|
sub = strtok_r(act, delim, &ptr);
|
|
if (sub == nullptr) break;
|
|
}
|
|
sub = Trim(sub);
|
|
return sub;
|
|
}
|
|
|
|
float CharToFloat(const char *str)
|
|
{
|
|
|
|
char strbuf[24];
|
|
|
|
strlcpy(strbuf, str, sizeof(strbuf));
|
|
char *pt = strbuf;
|
|
while ((*pt != '\0') && isblank(*pt)) { pt++; }
|
|
|
|
signed char sign = 1;
|
|
if (*pt == '-') { sign = -1; }
|
|
if (*pt == '-' || *pt=='+') { pt++; }
|
|
|
|
float left = 0;
|
|
if (*pt != '.') {
|
|
left = atoi(pt);
|
|
while (isdigit(*pt)) { pt++; }
|
|
}
|
|
|
|
float right = 0;
|
|
if (*pt == '.') {
|
|
pt++;
|
|
right = atoi(pt);
|
|
while (isdigit(*pt)) {
|
|
pt++;
|
|
right /= 10.0f;
|
|
}
|
|
}
|
|
|
|
float result = left + right;
|
|
if (sign < 0) {
|
|
return -result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int TextToInt(char *str)
|
|
{
|
|
char *p;
|
|
uint8_t radix = 10;
|
|
if ('#' == str[0]) {
|
|
radix = 16;
|
|
str++;
|
|
}
|
|
return strtol(str, &p, radix);
|
|
}
|
|
|
|
char* ulltoa(unsigned long long value, char *str, int radix)
|
|
{
|
|
char digits[64];
|
|
char *dst = str;
|
|
int i = 0;
|
|
|
|
|
|
|
|
do {
|
|
int n = value % radix;
|
|
digits[i++] = (n < 10) ? (char)n+'0' : (char)n-10+'A';
|
|
value /= radix;
|
|
} while (value != 0);
|
|
|
|
while (i > 0) { *dst++ = digits[--i]; }
|
|
|
|
*dst = 0;
|
|
return str;
|
|
}
|
|
|
|
|
|
|
|
char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween)
|
|
{
|
|
|
|
|
|
|
|
static const char * hex = "0123456789ABCDEF";
|
|
int between = (inbetween) ? 3 : 2;
|
|
const unsigned char * pin = in;
|
|
char * pout = out;
|
|
for (; pin < in+insz; pout += between, pin++) {
|
|
pout[0] = hex[(pgm_read_byte(pin)>>4) & 0xF];
|
|
pout[1] = hex[ pgm_read_byte(pin) & 0xF];
|
|
if (inbetween) { pout[2] = inbetween; }
|
|
if (pout + 3 - out > outsz) { break; }
|
|
}
|
|
pout[(inbetween && insz) ? -1 : 0] = 0;
|
|
return out;
|
|
}
|
|
|
|
char* Uint64toHex(uint64_t value, char *str, uint16_t bits)
|
|
{
|
|
ulltoa(value, str, 16);
|
|
|
|
int fill = 8;
|
|
if ((bits > 3) && (bits < 65)) {
|
|
fill = bits / 4;
|
|
if (bits % 4) { fill++; }
|
|
}
|
|
int len = strlen(str);
|
|
fill -= len;
|
|
if (fill > 0) {
|
|
memmove(str + fill, str, len +1);
|
|
memset(str, '0', fill);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
char* dtostrfd(double number, unsigned char prec, char *s)
|
|
{
|
|
if ((isnan(number)) || (isinf(number))) {
|
|
strcpy(s, "null");
|
|
return s;
|
|
} else {
|
|
return dtostrf(number, 1, prec, s);
|
|
}
|
|
}
|
|
|
|
char* Unescape(char* buffer, uint32_t* size)
|
|
{
|
|
uint8_t* read = (uint8_t*)buffer;
|
|
uint8_t* write = (uint8_t*)buffer;
|
|
int32_t start_size = *size;
|
|
int32_t end_size = *size;
|
|
uint8_t che = 0;
|
|
|
|
|
|
|
|
while (start_size > 0) {
|
|
uint8_t ch = *read++;
|
|
start_size--;
|
|
if (ch != '\\') {
|
|
*write++ = ch;
|
|
} else {
|
|
if (start_size > 0) {
|
|
uint8_t chi = *read++;
|
|
start_size--;
|
|
end_size--;
|
|
switch (chi) {
|
|
case '\\': che = '\\'; break;
|
|
case 'a': che = '\a'; break;
|
|
case 'b': che = '\b'; break;
|
|
case 'e': che = '\e'; break;
|
|
case 'f': che = '\f'; break;
|
|
case 'n': che = '\n'; break;
|
|
case 'r': che = '\r'; break;
|
|
case 's': che = ' '; break;
|
|
case 't': che = '\t'; break;
|
|
case 'v': che = '\v'; break;
|
|
case 'x': {
|
|
uint8_t* start = read;
|
|
che = (uint8_t)strtol((const char*)read, (char**)&read, 16);
|
|
start_size -= (uint16_t)(read - start);
|
|
end_size -= (uint16_t)(read - start);
|
|
break;
|
|
}
|
|
case '"': che = '\"'; break;
|
|
|
|
default : {
|
|
che = chi;
|
|
*write++ = ch;
|
|
end_size++;
|
|
}
|
|
}
|
|
*write++ = che;
|
|
}
|
|
}
|
|
}
|
|
*size = end_size;
|
|
*write++ = 0;
|
|
|
|
|
|
return buffer;
|
|
}
|
|
|
|
char* RemoveSpace(char* p)
|
|
{
|
|
char* write = p;
|
|
char* read = p;
|
|
char ch = '.';
|
|
|
|
while (ch != '\0') {
|
|
ch = *read++;
|
|
if (!isspace(ch)) {
|
|
*write++ = ch;
|
|
}
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
char* ReplaceCommaWithDot(char* p)
|
|
{
|
|
char* write = (char*)p;
|
|
char* read = (char*)p;
|
|
char ch = '.';
|
|
|
|
while (ch != '\0') {
|
|
ch = *read++;
|
|
if (ch == ',') {
|
|
ch = '.';
|
|
}
|
|
*write++ = ch;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
char* LowerCase(char* dest, const char* source)
|
|
{
|
|
char* write = dest;
|
|
const char* read = source;
|
|
char ch = '.';
|
|
|
|
while (ch != '\0') {
|
|
ch = *read++;
|
|
*write++ = tolower(ch);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
char* UpperCase(char* dest, const char* source)
|
|
{
|
|
char* write = dest;
|
|
const char* read = source;
|
|
char ch = '.';
|
|
|
|
while (ch != '\0') {
|
|
ch = *read++;
|
|
*write++ = toupper(ch);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
char* UpperCase_P(char* dest, const char* source)
|
|
{
|
|
char* write = dest;
|
|
const char* read = source;
|
|
char ch = '.';
|
|
|
|
while (ch != '\0') {
|
|
ch = pgm_read_byte(read++);
|
|
*write++ = toupper(ch);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
char* Trim(char* p)
|
|
{
|
|
while ((*p != '\0') && isblank(*p)) { p++; }
|
|
char* q = p + strlen(p) -1;
|
|
while ((q >= p) && isblank(*q)) { q--; }
|
|
q++;
|
|
*q = '\0';
|
|
return p;
|
|
}
|
|
|
|
char* RemoveAllSpaces(char* p)
|
|
{
|
|
|
|
char *cursor = p;
|
|
uint32_t offset = 0;
|
|
while (1) {
|
|
*cursor = *(cursor + offset);
|
|
if ((' ' == *cursor) || ('\t' == *cursor) || ('\n' == *cursor)) {
|
|
offset++;
|
|
} else {
|
|
if (0 == *cursor) { break; }
|
|
cursor++;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
char* NoAlNumToUnderscore(char* dest, const char* source)
|
|
{
|
|
char* write = dest;
|
|
const char* read = source;
|
|
char ch = '.';
|
|
|
|
while (ch != '\0') {
|
|
ch = *read++;
|
|
*write++ = (isalnum(ch) || ('\0' == ch)) ? ch : '_';
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
char IndexSeparator(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Settings.flag3.use_underscore) {
|
|
return '_';
|
|
} else {
|
|
return '-';
|
|
}
|
|
}
|
|
|
|
void SetShortcutDefault(void)
|
|
{
|
|
if ('\0' != XdrvMailbox.data[0]) {
|
|
XdrvMailbox.data[0] = '0' + SC_DEFAULT;
|
|
XdrvMailbox.data[1] = '\0';
|
|
}
|
|
}
|
|
|
|
uint8_t Shortcut(void)
|
|
{
|
|
uint8_t result = 10;
|
|
|
|
if ('\0' == XdrvMailbox.data[1]) {
|
|
if (('"' == XdrvMailbox.data[0]) || ('0' == XdrvMailbox.data[0])) {
|
|
result = SC_CLEAR;
|
|
} else {
|
|
result = atoi(XdrvMailbox.data);
|
|
if (0 == result) {
|
|
result = 10;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool ValidIpAddress(const char* str)
|
|
{
|
|
const char* p = str;
|
|
|
|
while (*p && ((*p == '.') || ((*p >= '0') && (*p <= '9')))) { p++; }
|
|
return (*p == '\0');
|
|
}
|
|
|
|
bool ParseIp(uint32_t* addr, const char* str)
|
|
{
|
|
uint8_t *part = (uint8_t*)addr;
|
|
uint8_t i;
|
|
|
|
*addr = 0;
|
|
for (i = 0; i < 4; i++) {
|
|
part[i] = strtoul(str, nullptr, 10);
|
|
str = strchr(str, '.');
|
|
if (str == nullptr || *str == '\0') {
|
|
break;
|
|
}
|
|
str++;
|
|
}
|
|
return (3 == i);
|
|
}
|
|
|
|
uint32_t ParseParameters(uint32_t count, uint32_t *params)
|
|
{
|
|
char *p;
|
|
uint32_t i = 0;
|
|
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < count; str = strtok_r(nullptr, ", ", &p), i++) {
|
|
params[i] = strtoul(str, nullptr, 0);
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
bool NewerVersion(char* version_str)
|
|
{
|
|
uint32_t version = 0;
|
|
uint32_t i = 0;
|
|
char *str_ptr;
|
|
|
|
char version_dup[strlen(version_str) +1];
|
|
strncpy(version_dup, version_str, sizeof(version_dup));
|
|
|
|
for (char *str = strtok_r(version_dup, ".", &str_ptr); str && i < sizeof(VERSION); str = strtok_r(nullptr, ".", &str_ptr), i++) {
|
|
int field = atoi(str);
|
|
|
|
if ((field < 0) || (field > 255)) {
|
|
return false;
|
|
}
|
|
|
|
version = (version << 8) + field;
|
|
|
|
if ((2 == i) && isalpha(str[strlen(str)-1])) {
|
|
field = str[strlen(str)-1] & 0x1f;
|
|
version = (version << 8) + field;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
|
|
if ((i < 2) || (i > sizeof(VERSION))) {
|
|
return false;
|
|
}
|
|
|
|
|
|
while (i < sizeof(VERSION)) {
|
|
version <<= 8;
|
|
i++;
|
|
}
|
|
|
|
return (version > VERSION);
|
|
}
|
|
|
|
char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option)
|
|
{
|
|
strncpy_P(dest, S_RSLT_POWER, size);
|
|
if ((devices_present + option) > 1) {
|
|
char sidx[8];
|
|
snprintf_P(sidx, sizeof(sidx), PSTR("%d"), idx);
|
|
strncat(dest, sidx, size - strlen(dest) -1);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
char* GetPowerDevice(char* dest, uint32_t idx, size_t size)
|
|
{
|
|
return GetPowerDevice(dest, idx, size, 0);
|
|
}
|
|
|
|
void GetEspHardwareType(void)
|
|
{
|
|
|
|
uint32_t efuse1 = *(uint32_t*)(0x3FF00050);
|
|
uint32_t efuse2 = *(uint32_t*)(0x3FF00054);
|
|
|
|
|
|
|
|
is_8285 = ( (efuse1 & (1 << 4)) || (efuse2 & (1 << 16)) );
|
|
if (is_8285 && (ESP.getFlashChipRealSize() > 1048576)) {
|
|
is_8285 = false;
|
|
}
|
|
}
|
|
|
|
String GetDeviceHardware(void)
|
|
{
|
|
char buff[10];
|
|
if (is_8285) {
|
|
strcpy_P(buff, PSTR("ESP8285"));
|
|
} else {
|
|
strcpy_P(buff, PSTR("ESP8266EX"));
|
|
}
|
|
return String(buff);
|
|
}
|
|
|
|
float ConvertTemp(float c)
|
|
{
|
|
float result = c;
|
|
|
|
global_update = uptime;
|
|
global_temperature = c;
|
|
|
|
if (!isnan(c) && Settings.flag.temperature_conversion) {
|
|
result = c * 1.8 + 32;
|
|
}
|
|
result = result + (0.1 * Settings.temp_comp);
|
|
return result;
|
|
}
|
|
|
|
float ConvertTempToCelsius(float c)
|
|
{
|
|
float result = c;
|
|
|
|
if (!isnan(c) && Settings.flag.temperature_conversion) {
|
|
result = (c - 32) / 1.8;
|
|
}
|
|
result = result + (0.1 * Settings.temp_comp);
|
|
return result;
|
|
}
|
|
|
|
char TempUnit(void)
|
|
{
|
|
return (Settings.flag.temperature_conversion) ? 'F' : 'C';
|
|
}
|
|
|
|
float ConvertHumidity(float h)
|
|
{
|
|
global_update = uptime;
|
|
global_humidity = h;
|
|
|
|
return h;
|
|
}
|
|
|
|
float ConvertPressure(float p)
|
|
{
|
|
float result = p;
|
|
|
|
global_update = uptime;
|
|
global_pressure = p;
|
|
|
|
if (!isnan(p) && Settings.flag.pressure_conversion) {
|
|
result = p * 0.75006375541921;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
String PressureUnit(void)
|
|
{
|
|
return (Settings.flag.pressure_conversion) ? String(D_UNIT_MILLIMETER_MERCURY) : String(D_UNIT_PRESSURE);
|
|
}
|
|
|
|
void ResetGlobalValues(void)
|
|
{
|
|
if ((uptime - global_update) > GLOBAL_VALUES_VALID) {
|
|
global_update = 0;
|
|
global_temperature = 9999;
|
|
global_humidity = 0;
|
|
global_pressure = 0;
|
|
}
|
|
}
|
|
|
|
uint32_t SqrtInt(uint32_t num)
|
|
{
|
|
if (num <= 1) {
|
|
return num;
|
|
}
|
|
|
|
uint32_t x = num / 2;
|
|
uint32_t y;
|
|
do {
|
|
y = (x + num / x) / 2;
|
|
if (y >= x) {
|
|
return x;
|
|
}
|
|
x = y;
|
|
} while (true);
|
|
}
|
|
|
|
uint32_t RoundSqrtInt(uint32_t num)
|
|
{
|
|
uint32_t s = SqrtInt(4 * num);
|
|
if (s & 1) {
|
|
s++;
|
|
}
|
|
return s / 2;
|
|
}
|
|
|
|
char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack)
|
|
{
|
|
|
|
|
|
char* write = destination;
|
|
const char* read = haystack;
|
|
|
|
index++;
|
|
while (index--) {
|
|
size_t size = destination_size -1;
|
|
write = destination;
|
|
char ch = '.';
|
|
while ((ch != '\0') && (ch != '|')) {
|
|
ch = pgm_read_byte(read++);
|
|
if (size && (ch != '|')) {
|
|
*write++ = ch;
|
|
size--;
|
|
}
|
|
}
|
|
if (0 == ch) {
|
|
if (index) {
|
|
write = destination;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
*write = '\0';
|
|
return destination;
|
|
}
|
|
|
|
int GetCommandCode(char* destination, size_t destination_size, const char* needle, const char* haystack)
|
|
{
|
|
|
|
|
|
int result = -1;
|
|
const char* read = haystack;
|
|
char* write = destination;
|
|
|
|
while (true) {
|
|
result++;
|
|
size_t size = destination_size -1;
|
|
write = destination;
|
|
char ch = '.';
|
|
while ((ch != '\0') && (ch != '|')) {
|
|
ch = pgm_read_byte(read++);
|
|
if (size && (ch != '|')) {
|
|
*write++ = ch;
|
|
size--;
|
|
}
|
|
}
|
|
*write = '\0';
|
|
if (!strcasecmp(needle, destination)) {
|
|
break;
|
|
}
|
|
if (0 == ch) {
|
|
result = -1;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void))
|
|
{
|
|
GetTextIndexed(XdrvMailbox.command, CMDSZ, 0, haystack);
|
|
int prefix_length = strlen(XdrvMailbox.command);
|
|
if (prefix_length) {
|
|
char prefix[prefix_length +1];
|
|
snprintf_P(prefix, sizeof(prefix), XdrvMailbox.topic);
|
|
if (strcasecmp(prefix, XdrvMailbox.command)) {
|
|
return false;
|
|
}
|
|
}
|
|
int command_code = GetCommandCode(XdrvMailbox.command + prefix_length, CMDSZ, XdrvMailbox.topic + prefix_length, haystack);
|
|
if (command_code > 0) {
|
|
XdrvMailbox.command_code = command_code -1;
|
|
MyCommand[XdrvMailbox.command_code]();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const char kOptions[] PROGMEM = "OFF|" D_OFF "|FALSE|" D_FALSE "|STOP|" D_STOP "|" D_CELSIUS "|"
|
|
"ON|" D_ON "|TRUE|" D_TRUE "|START|" D_START "|" D_FAHRENHEIT "|" D_USER "|"
|
|
"TOGGLE|" D_TOGGLE "|" D_ADMIN "|"
|
|
"BLINK|" D_BLINK "|"
|
|
"BLINKOFF|" D_BLINKOFF "|"
|
|
"ALL" ;
|
|
|
|
const uint8_t sNumbers[] PROGMEM = { 0,0,0,0,0,0,0,
|
|
1,1,1,1,1,1,1,1,
|
|
2,2,2,
|
|
3,3,
|
|
4,4,
|
|
255 };
|
|
|
|
int GetStateNumber(char *state_text)
|
|
{
|
|
char command[CMDSZ];
|
|
int state_number = GetCommandCode(command, sizeof(command), state_text, kOptions);
|
|
if (state_number >= 0) {
|
|
state_number = pgm_read_byte(sNumbers + state_number);
|
|
}
|
|
return state_number;
|
|
}
|
|
|
|
String GetSerialConfig(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
const char kParity[] = "NEOI";
|
|
|
|
char config[4];
|
|
config[0] = '5' + (Settings.serial_config & 0x3);
|
|
config[1] = kParity[(Settings.serial_config >> 3) & 0x3];
|
|
config[2] = '1' + ((Settings.serial_config >> 2) & 0x1);
|
|
config[3] = '\0';
|
|
return String(config);
|
|
}
|
|
|
|
void SetSerialBegin()
|
|
{
|
|
uint32_t baudrate = Settings.baudrate * 300;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_SERIAL "Set to %s %d bit/s"), GetSerialConfig().c_str(), baudrate);
|
|
Serial.flush();
|
|
Serial.begin(baudrate, (SerialConfig)pgm_read_byte(kTasmotaSerialConfig + Settings.serial_config));
|
|
}
|
|
|
|
void SetSerialConfig(uint32_t serial_config)
|
|
{
|
|
if (serial_config > TS_SERIAL_8O2) {
|
|
serial_config = TS_SERIAL_8N1;
|
|
}
|
|
if (serial_config != Settings.serial_config) {
|
|
Settings.serial_config = serial_config;
|
|
SetSerialBegin();
|
|
}
|
|
}
|
|
|
|
void SetSerialBaudrate(uint32_t baudrate)
|
|
{
|
|
Settings.baudrate = baudrate / 300;
|
|
if (Serial.baudRate() != baudrate) {
|
|
SetSerialBegin();
|
|
}
|
|
}
|
|
|
|
void SetSerial(uint32_t baudrate, uint32_t serial_config)
|
|
{
|
|
Settings.flag.mqtt_serial = 0;
|
|
Settings.serial_config = serial_config;
|
|
Settings.baudrate = baudrate / 300;
|
|
SetSeriallog(LOG_LEVEL_NONE);
|
|
SetSerialBegin();
|
|
}
|
|
|
|
void ClaimSerial(void)
|
|
{
|
|
serial_local = true;
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("SNS: Hardware Serial"));
|
|
SetSeriallog(LOG_LEVEL_NONE);
|
|
Settings.baudrate = Serial.baudRate() / 300;
|
|
}
|
|
|
|
void SerialSendRaw(char *codes)
|
|
{
|
|
char *p;
|
|
char stemp[3];
|
|
uint8_t code;
|
|
|
|
int size = strlen(codes);
|
|
|
|
while (size > 1) {
|
|
strlcpy(stemp, codes, sizeof(stemp));
|
|
code = strtol(stemp, &p, 16);
|
|
Serial.write(code);
|
|
size -= 2;
|
|
codes += 2;
|
|
}
|
|
}
|
|
|
|
uint32_t GetHash(const char *buffer, size_t size)
|
|
{
|
|
uint32_t hash = 0;
|
|
for (uint32_t i = 0; i <= size; i++) {
|
|
hash += (uint8_t)*buffer++ * (i +1);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
void ShowSource(uint32_t source)
|
|
{
|
|
if ((source > 0) && (source < SRC_MAX)) {
|
|
char stemp1[20];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SRC: %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource));
|
|
}
|
|
}
|
|
|
|
void WebHexCode(uint32_t i, const char* code)
|
|
{
|
|
char scolor[10];
|
|
|
|
strlcpy(scolor, code, sizeof(scolor));
|
|
char* p = scolor;
|
|
if ('#' == p[0]) { p++; }
|
|
|
|
if (3 == strlen(p)) {
|
|
p[6] = p[3];
|
|
p[5] = p[2];
|
|
p[4] = p[2];
|
|
p[3] = p[1];
|
|
p[2] = p[1];
|
|
p[1] = p[0];
|
|
}
|
|
|
|
uint32_t color = strtol(p, nullptr, 16);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t j = sizeof(Settings.web_color) / 3;
|
|
# 916 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
|
|
if (i >= j) {
|
|
|
|
i += ((((uint8_t*)&Settings.web_color2 - (uint8_t*)&Settings.web_color) / 3) - j);
|
|
}
|
|
Settings.web_color[i][0] = (color >> 16) & 0xFF;
|
|
Settings.web_color[i][1] = (color >> 8) & 0xFF;
|
|
Settings.web_color[i][2] = color & 0xFF;
|
|
}
|
|
|
|
uint32_t WebColor(uint32_t i)
|
|
{
|
|
uint32_t j = sizeof(Settings.web_color) / 3;
|
|
|
|
|
|
|
|
|
|
if (i >= j) {
|
|
|
|
i += ((((uint8_t*)&Settings.web_color2 - (uint8_t*)&Settings.web_color) / 3) - j);
|
|
}
|
|
uint32_t tcolor = (Settings.web_color[i][0] << 16) | (Settings.web_color[i][1] << 8) | Settings.web_color[i][2];
|
|
|
|
return tcolor;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const uint16_t TIMESZ = 100;
|
|
|
|
char* ResponseGetTime(uint32_t format, char* time_str)
|
|
{
|
|
switch (format) {
|
|
case 1:
|
|
snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%u"), GetDateAndTime(DT_LOCAL).c_str(), UtcTime());
|
|
break;
|
|
case 2:
|
|
snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":%u"), UtcTime());
|
|
break;
|
|
default:
|
|
snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str());
|
|
}
|
|
return time_str;
|
|
}
|
|
|
|
int Response_P(const char* format, ...)
|
|
{
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), format, args);
|
|
va_end(args);
|
|
return len;
|
|
}
|
|
|
|
int ResponseTime_P(const char* format, ...)
|
|
{
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
|
|
ResponseGetTime(Settings.flag2.time_format, mqtt_data);
|
|
|
|
int mlen = strlen(mqtt_data);
|
|
int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args);
|
|
va_end(args);
|
|
return len + mlen;
|
|
}
|
|
|
|
int ResponseAppend_P(const char* format, ...)
|
|
{
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
int mlen = strlen(mqtt_data);
|
|
int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args);
|
|
va_end(args);
|
|
return len + mlen;
|
|
}
|
|
|
|
int ResponseAppendTimeFormat(uint32_t format)
|
|
{
|
|
char time_str[TIMESZ];
|
|
return ResponseAppend_P(ResponseGetTime(format, time_str));
|
|
}
|
|
|
|
int ResponseAppendTime(void)
|
|
{
|
|
return ResponseAppendTimeFormat(Settings.flag2.time_format);
|
|
}
|
|
|
|
int ResponseJsonEnd(void)
|
|
{
|
|
return ResponseAppend_P(PSTR("}"));
|
|
}
|
|
|
|
int ResponseJsonEndEnd(void)
|
|
{
|
|
return ResponseAppend_P(PSTR("}}"));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void DigitalWrite(uint32_t gpio_pin, uint32_t state)
|
|
{
|
|
if (pin[gpio_pin] < 99) {
|
|
digitalWrite(pin[gpio_pin], state &1);
|
|
}
|
|
}
|
|
|
|
uint8_t ModuleNr(void)
|
|
{
|
|
|
|
|
|
return (USER_MODULE == Settings.module) ? 0 : Settings.module +1;
|
|
}
|
|
|
|
bool ValidTemplateModule(uint32_t index)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(kModuleNiceList); i++) {
|
|
if (index == pgm_read_byte(kModuleNiceList + i)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ValidModule(uint32_t index)
|
|
{
|
|
if (index == USER_MODULE) { return true; }
|
|
return ValidTemplateModule(index);
|
|
}
|
|
|
|
String AnyModuleName(uint32_t index)
|
|
{
|
|
if (USER_MODULE == index) {
|
|
return String(Settings.user_template.name);
|
|
} else {
|
|
return FPSTR(kModules[index].name);
|
|
}
|
|
}
|
|
|
|
String ModuleName(void)
|
|
{
|
|
return AnyModuleName(Settings.module);
|
|
}
|
|
|
|
void ModuleGpios(myio *gp)
|
|
{
|
|
uint8_t *dest = (uint8_t *)gp;
|
|
memset(dest, GPIO_NONE, sizeof(myio));
|
|
|
|
uint8_t src[sizeof(mycfgio)];
|
|
if (USER_MODULE == Settings.module) {
|
|
memcpy(&src, &Settings.user_template.gp, sizeof(mycfgio));
|
|
} else {
|
|
memcpy_P(&src, &kModules[Settings.module].gp, sizeof(mycfgio));
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t j = 0;
|
|
for (uint32_t i = 0; i < sizeof(mycfgio); i++) {
|
|
if (6 == i) { j = 9; }
|
|
if (8 == i) { j = 12; }
|
|
dest[j] = src[i];
|
|
j++;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
gpio_flag ModuleFlag(void)
|
|
{
|
|
gpio_flag flag;
|
|
|
|
if (USER_MODULE == Settings.module) {
|
|
flag = Settings.user_template.flag;
|
|
} else {
|
|
memcpy_P(&flag, &kModules[Settings.module].flag, sizeof(gpio_flag));
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
|
|
void ModuleDefault(uint32_t module)
|
|
{
|
|
if (USER_MODULE == module) { module = WEMOS; }
|
|
Settings.user_template_base = module;
|
|
memcpy_P(&Settings.user_template, &kModules[module], sizeof(mytmplt));
|
|
}
|
|
|
|
void SetModuleType(void)
|
|
{
|
|
my_module_type = (USER_MODULE == Settings.module) ? Settings.user_template_base : Settings.module;
|
|
}
|
|
|
|
bool FlashPin(uint32_t pin)
|
|
{
|
|
return (((pin > 5) && (pin < 9)) || (11 == pin));
|
|
}
|
|
|
|
uint8_t ValidPin(uint32_t pin, uint32_t gpio)
|
|
{
|
|
if (FlashPin(pin)) {
|
|
return GPIO_NONE;
|
|
}
|
|
|
|
|
|
if ((WEMOS == Settings.module) && !Settings.flag3.user_esp8285_enable) {
|
|
if ((pin == 9) || (pin == 10)) {
|
|
return GPIO_NONE;
|
|
}
|
|
}
|
|
|
|
return gpio;
|
|
}
|
|
|
|
bool ValidGPIO(uint32_t pin, uint32_t gpio)
|
|
{
|
|
return (GPIO_USER == ValidPin(pin, gpio));
|
|
}
|
|
|
|
bool ValidAdc(void)
|
|
{
|
|
gpio_flag flag = ModuleFlag();
|
|
uint32_t template_adc0 = flag.data &15;
|
|
return (ADC0_USER == template_adc0);
|
|
}
|
|
|
|
bool GetUsedInModule(uint32_t val, uint8_t *arr)
|
|
{
|
|
int offset = 0;
|
|
|
|
if (!val) { return false; }
|
|
|
|
if ((val >= GPIO_KEY1) && (val < GPIO_KEY1 + MAX_KEYS)) {
|
|
offset = (GPIO_KEY1_NP - GPIO_KEY1);
|
|
}
|
|
if ((val >= GPIO_KEY1_NP) && (val < GPIO_KEY1_NP + MAX_KEYS)) {
|
|
offset = -(GPIO_KEY1_NP - GPIO_KEY1);
|
|
}
|
|
if ((val >= GPIO_KEY1_INV) && (val < GPIO_KEY1_INV + MAX_KEYS)) {
|
|
offset = -(GPIO_KEY1_INV - GPIO_KEY1);
|
|
}
|
|
if ((val >= GPIO_KEY1_INV_NP) && (val < GPIO_KEY1_INV_NP + MAX_KEYS)) {
|
|
offset = -(GPIO_KEY1_INV_NP - GPIO_KEY1);
|
|
}
|
|
|
|
if ((val >= GPIO_SWT1) && (val < GPIO_SWT1 + MAX_SWITCHES)) {
|
|
offset = (GPIO_SWT1_NP - GPIO_SWT1);
|
|
}
|
|
if ((val >= GPIO_SWT1_NP) && (val < GPIO_SWT1_NP + MAX_SWITCHES)) {
|
|
offset = -(GPIO_SWT1_NP - GPIO_SWT1);
|
|
}
|
|
|
|
if ((val >= GPIO_REL1) && (val < GPIO_REL1 + MAX_RELAYS)) {
|
|
offset = (GPIO_REL1_INV - GPIO_REL1);
|
|
}
|
|
if ((val >= GPIO_REL1_INV) && (val < GPIO_REL1_INV + MAX_RELAYS)) {
|
|
offset = -(GPIO_REL1_INV - GPIO_REL1);
|
|
}
|
|
|
|
if ((val >= GPIO_LED1) && (val < GPIO_LED1 + MAX_LEDS)) {
|
|
offset = (GPIO_LED1_INV - GPIO_LED1);
|
|
}
|
|
if ((val >= GPIO_LED1_INV) && (val < GPIO_LED1_INV + MAX_LEDS)) {
|
|
offset = -(GPIO_LED1_INV - GPIO_LED1);
|
|
}
|
|
|
|
if ((val >= GPIO_PWM1) && (val < GPIO_PWM1 + MAX_PWMS)) {
|
|
offset = (GPIO_PWM1_INV - GPIO_PWM1);
|
|
}
|
|
if ((val >= GPIO_PWM1_INV) && (val < GPIO_PWM1_INV + MAX_PWMS)) {
|
|
offset = -(GPIO_PWM1_INV - GPIO_PWM1);
|
|
}
|
|
|
|
if ((val >= GPIO_CNTR1) && (val < GPIO_CNTR1 + MAX_COUNTERS)) {
|
|
offset = (GPIO_CNTR1_NP - GPIO_CNTR1);
|
|
}
|
|
if ((val >= GPIO_CNTR1_NP) && (val < GPIO_CNTR1_NP + MAX_COUNTERS)) {
|
|
offset = -(GPIO_CNTR1_NP - GPIO_CNTR1);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_GPIO_PIN; i++) {
|
|
if (arr[i] == val) { return true; }
|
|
if (arr[i] == val + offset) { return true; }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JsonTemplate(const char* dataBuf)
|
|
{
|
|
|
|
|
|
if (strlen(dataBuf) < 9) { return false; }
|
|
|
|
StaticJsonBuffer<350> jb;
|
|
JsonObject& obj = jb.parseObject(dataBuf);
|
|
if (!obj.success()) { return false; }
|
|
|
|
|
|
const char* name = obj[D_JSON_NAME];
|
|
if (name != nullptr) {
|
|
strlcpy(Settings.user_template.name, name, sizeof(Settings.user_template.name));
|
|
}
|
|
if (obj[D_JSON_GPIO].success()) {
|
|
for (uint32_t i = 0; i < sizeof(mycfgio); i++) {
|
|
Settings.user_template.gp.io[i] = obj[D_JSON_GPIO][i] | 0;
|
|
}
|
|
}
|
|
if (obj[D_JSON_FLAG].success()) {
|
|
uint8_t flag = obj[D_JSON_FLAG] | 0;
|
|
memcpy(&Settings.user_template.flag, &flag, sizeof(gpio_flag));
|
|
}
|
|
if (obj[D_JSON_BASE].success()) {
|
|
uint8_t base = obj[D_JSON_BASE];
|
|
if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; }
|
|
Settings.user_template_base = base -1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void TemplateJson(void)
|
|
{
|
|
Response_P(PSTR("{\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), Settings.user_template.name);
|
|
for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) {
|
|
ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Settings.user_template.gp.io[i]);
|
|
}
|
|
ResponseAppend_P(PSTR("],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), Settings.user_template.flag, Settings.user_template_base +1);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
inline int32_t TimeDifference(uint32_t prev, uint32_t next)
|
|
{
|
|
return ((int32_t) (next - prev));
|
|
}
|
|
|
|
int32_t TimePassedSince(uint32_t timestamp)
|
|
{
|
|
|
|
|
|
return TimeDifference(timestamp, millis());
|
|
}
|
|
|
|
bool TimeReached(uint32_t timer)
|
|
{
|
|
|
|
const long passed = TimePassedSince(timer);
|
|
return (passed >= 0);
|
|
}
|
|
|
|
void SetNextTimeInterval(unsigned long& timer, const unsigned long step)
|
|
{
|
|
timer += step;
|
|
const long passed = TimePassedSince(timer);
|
|
if (passed < 0) { return; }
|
|
if (static_cast<unsigned long>(passed) > step) {
|
|
|
|
timer = millis() + step;
|
|
return;
|
|
}
|
|
|
|
timer = millis() + (step - passed);
|
|
}
|
|
|
|
int32_t TimePassedSinceUsec(uint32_t timestamp)
|
|
{
|
|
return TimeDifference(timestamp, micros());
|
|
}
|
|
|
|
bool TimeReachedUsec(uint32_t timer)
|
|
{
|
|
|
|
const long passed = TimePassedSinceUsec(timer);
|
|
return (passed >= 0);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_I2C
|
|
const uint8_t I2C_RETRY_COUNTER = 3;
|
|
|
|
uint32_t i2c_active[4] = { 0 };
|
|
uint32_t i2c_buffer = 0;
|
|
|
|
bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size)
|
|
{
|
|
uint8_t retry = I2C_RETRY_COUNTER;
|
|
bool status = false;
|
|
|
|
i2c_buffer = 0;
|
|
while (!status && retry) {
|
|
Wire.beginTransmission(addr);
|
|
Wire.write(reg);
|
|
if (0 == Wire.endTransmission(false)) {
|
|
Wire.requestFrom((int)addr, (int)size);
|
|
if (Wire.available() == size) {
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
i2c_buffer = i2c_buffer << 8 | Wire.read();
|
|
}
|
|
status = true;
|
|
}
|
|
}
|
|
retry--;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg)
|
|
{
|
|
bool status = I2cValidRead(addr, reg, 1);
|
|
*data = (uint8_t)i2c_buffer;
|
|
return status;
|
|
}
|
|
|
|
bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg)
|
|
{
|
|
bool status = I2cValidRead(addr, reg, 2);
|
|
*data = (uint16_t)i2c_buffer;
|
|
return status;
|
|
}
|
|
|
|
bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg)
|
|
{
|
|
bool status = I2cValidRead(addr, reg, 2);
|
|
*data = (int16_t)i2c_buffer;
|
|
return status;
|
|
}
|
|
|
|
bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg)
|
|
{
|
|
uint16_t ldata;
|
|
bool status = I2cValidRead16(&ldata, addr, reg);
|
|
*data = (ldata >> 8) | (ldata << 8);
|
|
return status;
|
|
}
|
|
|
|
bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg)
|
|
{
|
|
uint16_t ldata;
|
|
bool status = I2cValidRead16LE(&ldata, addr, reg);
|
|
*data = (int16_t)ldata;
|
|
return status;
|
|
}
|
|
|
|
bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg)
|
|
{
|
|
bool status = I2cValidRead(addr, reg, 3);
|
|
*data = i2c_buffer;
|
|
return status;
|
|
}
|
|
|
|
uint8_t I2cRead8(uint8_t addr, uint8_t reg)
|
|
{
|
|
I2cValidRead(addr, reg, 1);
|
|
return (uint8_t)i2c_buffer;
|
|
}
|
|
|
|
uint16_t I2cRead16(uint8_t addr, uint8_t reg)
|
|
{
|
|
I2cValidRead(addr, reg, 2);
|
|
return (uint16_t)i2c_buffer;
|
|
}
|
|
|
|
int16_t I2cReadS16(uint8_t addr, uint8_t reg)
|
|
{
|
|
I2cValidRead(addr, reg, 2);
|
|
return (int16_t)i2c_buffer;
|
|
}
|
|
|
|
uint16_t I2cRead16LE(uint8_t addr, uint8_t reg)
|
|
{
|
|
I2cValidRead(addr, reg, 2);
|
|
uint16_t temp = (uint16_t)i2c_buffer;
|
|
return (temp >> 8) | (temp << 8);
|
|
}
|
|
|
|
int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg)
|
|
{
|
|
return (int16_t)I2cRead16LE(addr, reg);
|
|
}
|
|
|
|
int32_t I2cRead24(uint8_t addr, uint8_t reg)
|
|
{
|
|
I2cValidRead(addr, reg, 3);
|
|
return i2c_buffer;
|
|
}
|
|
|
|
bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size)
|
|
{
|
|
uint8_t x = I2C_RETRY_COUNTER;
|
|
|
|
do {
|
|
Wire.beginTransmission((uint8_t)addr);
|
|
Wire.write(reg);
|
|
uint8_t bytes = size;
|
|
while (bytes--) {
|
|
Wire.write((val >> (8 * bytes)) & 0xFF);
|
|
}
|
|
x--;
|
|
} while (Wire.endTransmission(true) != 0 && x != 0);
|
|
return (x);
|
|
}
|
|
|
|
bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val)
|
|
{
|
|
return I2cWrite(addr, reg, val, 1);
|
|
}
|
|
|
|
bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val)
|
|
{
|
|
return I2cWrite(addr, reg, val, 2);
|
|
}
|
|
|
|
int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len)
|
|
{
|
|
Wire.beginTransmission((uint8_t)addr);
|
|
Wire.write((uint8_t)reg);
|
|
Wire.endTransmission();
|
|
if (len != Wire.requestFrom((uint8_t)addr, (uint8_t)len)) {
|
|
return 1;
|
|
}
|
|
while (len--) {
|
|
*reg_data = (uint8_t)Wire.read();
|
|
reg_data++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len)
|
|
{
|
|
Wire.beginTransmission((uint8_t)addr);
|
|
Wire.write((uint8_t)reg);
|
|
while (len--) {
|
|
Wire.write(*reg_data);
|
|
reg_data++;
|
|
}
|
|
Wire.endTransmission();
|
|
return 0;
|
|
}
|
|
|
|
void I2cScan(char *devs, unsigned int devs_len)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t error = 0;
|
|
uint8_t address = 0;
|
|
uint8_t any = 0;
|
|
|
|
snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_DEVICES_FOUND_AT));
|
|
for (address = 1; address <= 127; address++) {
|
|
Wire.beginTransmission(address);
|
|
error = Wire.endTransmission();
|
|
if (0 == error) {
|
|
any = 1;
|
|
snprintf_P(devs, devs_len, PSTR("%s 0x%02x"), devs, address);
|
|
}
|
|
else if (error != 2) {
|
|
any = 2;
|
|
snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"Error %d at 0x%02x"), error, address);
|
|
break;
|
|
}
|
|
}
|
|
if (any) {
|
|
strncat(devs, "\"}", devs_len - strlen(devs) -1);
|
|
}
|
|
else {
|
|
snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_NO_DEVICES_FOUND "\"}"));
|
|
}
|
|
}
|
|
|
|
void I2cResetActive(uint32_t addr, uint32_t count = 1)
|
|
{
|
|
addr &= 0x7F;
|
|
count &= 0x7F;
|
|
while (count-- && (addr < 128)) {
|
|
i2c_active[addr / 32] &= ~(1 << (addr % 32));
|
|
addr++;
|
|
}
|
|
|
|
}
|
|
|
|
void I2cSetActive(uint32_t addr, uint32_t count = 1)
|
|
{
|
|
addr &= 0x7F;
|
|
count &= 0x7F;
|
|
while (count-- && (addr < 128)) {
|
|
i2c_active[addr / 32] |= (1 << (addr % 32));
|
|
addr++;
|
|
}
|
|
|
|
}
|
|
|
|
void I2cSetActiveFound(uint32_t addr, const char *types)
|
|
{
|
|
I2cSetActive(addr);
|
|
AddLog_P2(LOG_LEVEL_INFO, S_LOG_I2C_FOUND_AT, types, addr);
|
|
}
|
|
|
|
bool I2cActive(uint32_t addr)
|
|
{
|
|
addr &= 0x7F;
|
|
if (i2c_active[addr / 32] & (1 << (addr % 32))) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool I2cSetDevice(uint32_t addr)
|
|
{
|
|
addr &= 0x7F;
|
|
if (I2cActive(addr)) {
|
|
return false;
|
|
}
|
|
Wire.beginTransmission((uint8_t)addr);
|
|
return (0 == Wire.endTransmission());
|
|
}
|
|
#endif
|
|
# 1559 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
|
|
void SetSeriallog(uint32_t loglevel)
|
|
{
|
|
Settings.seriallog_level = loglevel;
|
|
seriallog_level = loglevel;
|
|
seriallog_timer = 0;
|
|
}
|
|
|
|
void SetSyslog(uint32_t loglevel)
|
|
{
|
|
Settings.syslog_level = loglevel;
|
|
syslog_level = loglevel;
|
|
syslog_timer = 0;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
void GetLog(uint32_t idx, char** entry_pp, size_t* len_p)
|
|
{
|
|
char* entry_p = nullptr;
|
|
size_t len = 0;
|
|
|
|
if (idx) {
|
|
char* it = web_log;
|
|
do {
|
|
uint32_t cur_idx = *it;
|
|
it++;
|
|
size_t tmp = strchrspn(it, '\1');
|
|
tmp++;
|
|
if (cur_idx == idx) {
|
|
len = tmp;
|
|
entry_p = it;
|
|
break;
|
|
}
|
|
it += tmp;
|
|
} while (it < web_log + WEB_LOG_SIZE && *it != '\0');
|
|
}
|
|
*entry_pp = entry_p;
|
|
*len_p = len;
|
|
}
|
|
#endif
|
|
|
|
void Syslog(void)
|
|
{
|
|
|
|
|
|
uint32_t current_hash = GetHash(SettingsText(SET_SYSLOG_HOST), strlen(SettingsText(SET_SYSLOG_HOST)));
|
|
if (syslog_host_hash != current_hash) {
|
|
syslog_host_hash = current_hash;
|
|
WiFi.hostByName(SettingsText(SET_SYSLOG_HOST), syslog_host_addr);
|
|
}
|
|
if (PortUdp.beginPacket(syslog_host_addr, Settings.syslog_port)) {
|
|
char syslog_preamble[64];
|
|
snprintf_P(syslog_preamble, sizeof(syslog_preamble), PSTR("%s ESP-"), my_hostname);
|
|
memmove(log_data + strlen(syslog_preamble), log_data, sizeof(log_data) - strlen(syslog_preamble));
|
|
log_data[sizeof(log_data) -1] = '\0';
|
|
memcpy(log_data, syslog_preamble, strlen(syslog_preamble));
|
|
PortUdp.write(log_data, strlen(log_data));
|
|
PortUdp.endPacket();
|
|
delay(1);
|
|
} else {
|
|
syslog_level = 0;
|
|
syslog_timer = SYSLOG_TIMER;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_HOST_NOT_FOUND ". " D_RETRY_IN " %d " D_UNIT_SECOND), SYSLOG_TIMER);
|
|
}
|
|
}
|
|
|
|
void AddLog(uint32_t loglevel)
|
|
{
|
|
char mxtime[10];
|
|
|
|
snprintf_P(mxtime, sizeof(mxtime), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d "), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
|
|
if (loglevel <= seriallog_level) {
|
|
Serial.printf("%s%s\r\n", mxtime, log_data);
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
if (Settings.webserver && (loglevel <= Settings.weblog_level)) {
|
|
|
|
|
|
web_log_index &= 0xFF;
|
|
if (!web_log_index) web_log_index++;
|
|
while (web_log_index == web_log[0] ||
|
|
strlen(web_log) + strlen(log_data) + 13 > WEB_LOG_SIZE)
|
|
{
|
|
char* it = web_log;
|
|
it++;
|
|
it += strchrspn(it, '\1');
|
|
it++;
|
|
memmove(web_log, it, WEB_LOG_SIZE -(it-web_log));
|
|
}
|
|
snprintf_P(web_log, sizeof(web_log), PSTR("%s%c%s%s\1"), web_log, web_log_index++, mxtime, log_data);
|
|
web_log_index &= 0xFF;
|
|
if (!web_log_index) web_log_index++;
|
|
}
|
|
#endif
|
|
if (Settings.flag.mqtt_enabled &&
|
|
!global_state.mqtt_down &&
|
|
(loglevel <= Settings.mqttlog_level)) { MqttPublishLogging(mxtime); }
|
|
|
|
if (!global_state.wifi_down &&
|
|
(loglevel <= syslog_level)) { Syslog(); }
|
|
}
|
|
|
|
void AddLog_P(uint32_t loglevel, const char *formatP)
|
|
{
|
|
snprintf_P(log_data, sizeof(log_data), formatP);
|
|
AddLog(loglevel);
|
|
}
|
|
|
|
void AddLog_P(uint32_t loglevel, const char *formatP, const char *formatP2)
|
|
{
|
|
char message[sizeof(log_data)];
|
|
|
|
snprintf_P(log_data, sizeof(log_data), formatP);
|
|
snprintf_P(message, sizeof(message), formatP2);
|
|
strncat(log_data, message, sizeof(log_data) - strlen(log_data) -1);
|
|
AddLog(loglevel);
|
|
}
|
|
|
|
void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, formatP);
|
|
vsnprintf_P(log_data, sizeof(log_data), formatP, arg);
|
|
va_end(arg);
|
|
|
|
prepped_loglevel = loglevel;
|
|
}
|
|
|
|
void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, formatP);
|
|
vsnprintf_P(log_data, sizeof(log_data), formatP, arg);
|
|
va_end(arg);
|
|
|
|
AddLog(loglevel);
|
|
}
|
|
|
|
void AddLog_Debug(PGM_P formatP, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, formatP);
|
|
vsnprintf_P(log_data, sizeof(log_data), formatP, arg);
|
|
va_end(arg);
|
|
|
|
AddLog(LOG_LEVEL_DEBUG);
|
|
}
|
|
|
|
void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count)
|
|
{
|
|
# 1721 "C:/shared/sonoff/Git/Tasmota/tasmota/support.ino"
|
|
char hex_char[(count * 3) + 2];
|
|
AddLog_P2(loglevel, PSTR("DMP: %s"), ToHex_P(buffer, count, hex_char, sizeof(hex_char), ' '));
|
|
}
|
|
|
|
void AddLogSerial(uint32_t loglevel)
|
|
{
|
|
AddLogBuffer(loglevel, (uint8_t*)serial_in_buffer, serial_in_byte_counter);
|
|
}
|
|
|
|
void AddLogMissed(char *sensor, uint32_t misses)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SNS: %s missed %d"), sensor, SENSOR_MAX_MISS - misses);
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino"
|
|
#define BUTTON_V1
|
|
#ifdef BUTTON_V1
|
|
|
|
|
|
|
|
|
|
#define MAX_BUTTON_COMMANDS 5
|
|
const char kCommands[] PROGMEM =
|
|
D_CMND_WIFICONFIG " 2|" D_CMND_WIFICONFIG " 2|" D_CMND_WIFICONFIG " 2|" D_CMND_RESTART " 1|" D_CMND_UPGRADE " 1";
|
|
|
|
struct BUTTON {
|
|
unsigned long debounce = 0;
|
|
uint16_t hold_timer[MAX_KEYS] = { 0 };
|
|
uint16_t dual_code = 0;
|
|
|
|
uint8_t last_state[MAX_KEYS] = { NOT_PRESSED, NOT_PRESSED, NOT_PRESSED, NOT_PRESSED };
|
|
uint8_t window_timer[MAX_KEYS] = { 0 };
|
|
uint8_t press_counter[MAX_KEYS] = { 0 };
|
|
|
|
uint8_t dual_receive_count = 0;
|
|
uint8_t no_pullup_mask = 0;
|
|
uint8_t inverted_mask = 0;
|
|
uint8_t present = 0;
|
|
uint8_t adc = 99;
|
|
} Button;
|
|
|
|
|
|
|
|
void ButtonPullupFlag(uint8 button_bit)
|
|
{
|
|
bitSet(Button.no_pullup_mask, button_bit);
|
|
}
|
|
|
|
void ButtonInvertFlag(uint8 button_bit)
|
|
{
|
|
bitSet(Button.inverted_mask, button_bit);
|
|
}
|
|
|
|
void ButtonInit(void)
|
|
{
|
|
Button.present = 0;
|
|
for (uint32_t i = 0; i < MAX_KEYS; i++) {
|
|
if (pin[GPIO_KEY1 +i] < 99) {
|
|
Button.present++;
|
|
pinMode(pin[GPIO_KEY1 +i], bitRead(Button.no_pullup_mask, i) ? INPUT : ((16 == pin[GPIO_KEY1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP));
|
|
}
|
|
#ifndef USE_ADC_VCC
|
|
else if ((99 == Button.adc) && ((ADC0_BUTTON == my_adc0) || (ADC0_BUTTON_INV == my_adc0))) {
|
|
Button.present++;
|
|
Button.adc = i;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
uint8_t ButtonSerial(uint8_t serial_in_byte)
|
|
{
|
|
if (Button.dual_receive_count) {
|
|
Button.dual_receive_count--;
|
|
if (Button.dual_receive_count) {
|
|
Button.dual_code = (Button.dual_code << 8) | serial_in_byte;
|
|
serial_in_byte = 0;
|
|
} else {
|
|
if (serial_in_byte != 0xA1) {
|
|
Button.dual_code = 0;
|
|
}
|
|
}
|
|
}
|
|
if (0xA0 == serial_in_byte) {
|
|
serial_in_byte = 0;
|
|
Button.dual_code = 0;
|
|
Button.dual_receive_count = 3;
|
|
}
|
|
|
|
return serial_in_byte;
|
|
}
|
|
# 108 "C:/shared/sonoff/Git/Tasmota/tasmota/support_button.ino"
|
|
void ButtonHandler(void)
|
|
{
|
|
if (uptime < 4) { return; }
|
|
|
|
uint8_t hold_time_extent = IMMINENT_RESET_FACTOR;
|
|
uint16_t loops_per_second = 1000 / Settings.button_debounce;
|
|
char scmnd[20];
|
|
|
|
|
|
|
|
for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) {
|
|
uint8_t button = NOT_PRESSED;
|
|
uint8_t button_present = 0;
|
|
|
|
if (!button_index && ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type))) {
|
|
button_present = 1;
|
|
if (Button.dual_code) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON " " D_CODE " %04X"), Button.dual_code);
|
|
button = PRESSED;
|
|
if (0xF500 == Button.dual_code) {
|
|
Button.hold_timer[button_index] = (loops_per_second * Settings.param[P_HOLD_TIME] / 10) -1;
|
|
hold_time_extent = 1;
|
|
}
|
|
Button.dual_code = 0;
|
|
}
|
|
}
|
|
else if (pin[GPIO_KEY1 +button_index] < 99) {
|
|
button_present = 1;
|
|
button = (digitalRead(pin[GPIO_KEY1 +button_index]) != bitRead(Button.inverted_mask, button_index));
|
|
}
|
|
#ifndef USE_ADC_VCC
|
|
if (Button.adc == button_index) {
|
|
button_present = 1;
|
|
if (ADC0_BUTTON_INV == my_adc0) {
|
|
button = (AdcRead(1) < 128);
|
|
}
|
|
else if (ADC0_BUTTON == my_adc0) {
|
|
button = (AdcRead(1) > 128);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (button_present) {
|
|
XdrvMailbox.index = button_index;
|
|
XdrvMailbox.payload = button;
|
|
if (XdrvCall(FUNC_BUTTON_PRESSED)) {
|
|
|
|
}
|
|
else if (SONOFF_4CHPRO == my_module_type) {
|
|
if (Button.hold_timer[button_index]) { Button.hold_timer[button_index]--; }
|
|
|
|
bool button_pressed = false;
|
|
if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_LEVEL_10), button_index +1);
|
|
Button.hold_timer[button_index] = loops_per_second;
|
|
button_pressed = true;
|
|
}
|
|
if ((NOT_PRESSED == button) && (PRESSED == Button.last_state[button_index])) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_LEVEL_01), button_index +1);
|
|
if (!Button.hold_timer[button_index]) { button_pressed = true; }
|
|
}
|
|
if (button_pressed) {
|
|
if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) {
|
|
ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) {
|
|
if (Settings.flag.button_single) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_IMMEDIATE), button_index +1);
|
|
if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) {
|
|
ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON);
|
|
}
|
|
} else {
|
|
Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BUTTON "%d " D_MULTI_PRESS " %d"), button_index +1, Button.press_counter[button_index]);
|
|
Button.window_timer[button_index] = loops_per_second / 2;
|
|
}
|
|
blinks = 201;
|
|
}
|
|
|
|
if (NOT_PRESSED == button) {
|
|
Button.hold_timer[button_index] = 0;
|
|
} else {
|
|
Button.hold_timer[button_index]++;
|
|
if (Settings.flag.button_single) {
|
|
if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) {
|
|
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_SETOPTION "13 0"));
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
}
|
|
} else {
|
|
if (Settings.flag.button_restrict) {
|
|
if (Settings.param[P_HOLD_IGNORE] > 0) {
|
|
if (Button.hold_timer[button_index] > loops_per_second * Settings.param[P_HOLD_IGNORE] / 10) {
|
|
Button.hold_timer[button_index] = 0;
|
|
Button.press_counter[button_index] = 0;
|
|
DEBUG_CORE_LOG(PSTR("BTN: " D_BUTTON "%d cancel by " D_CMND_SETOPTION "40 %d"), button_index +1, Settings.param[P_HOLD_IGNORE]);
|
|
}
|
|
}
|
|
if (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10) {
|
|
Button.press_counter[button_index] = 0;
|
|
SendKey(KEY_BUTTON, button_index +1, POWER_HOLD);
|
|
}
|
|
} else {
|
|
if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) {
|
|
Button.press_counter[button_index] = 0;
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1"));
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Settings.flag.button_single) {
|
|
if (Button.window_timer[button_index]) {
|
|
Button.window_timer[button_index]--;
|
|
} else {
|
|
if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0) && (Button.press_counter[button_index] < MAX_BUTTON_COMMANDS +3)) {
|
|
bool single_press = false;
|
|
if (Button.press_counter[button_index] < 3) {
|
|
if ((SONOFF_DUAL_R2 == my_module_type) || (SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) {
|
|
single_press = true;
|
|
} else {
|
|
single_press = (Settings.flag.button_swap +1 == Button.press_counter[button_index]);
|
|
if ((1 == Button.present) && (2 == devices_present)) {
|
|
if (Settings.flag.button_swap) {
|
|
Button.press_counter[button_index] = (single_press) ? 1 : 2;
|
|
}
|
|
} else {
|
|
Button.press_counter[button_index] = 1;
|
|
}
|
|
}
|
|
}
|
|
#if defined(USE_LIGHT) && defined(ROTARY_V1)
|
|
if (!((0 == button_index) && RotaryButtonPressed())) {
|
|
#endif
|
|
if (single_press && SendKey(KEY_BUTTON, button_index + Button.press_counter[button_index], POWER_TOGGLE)) {
|
|
|
|
} else {
|
|
if (Button.press_counter[button_index] < 3) {
|
|
if (WifiState() > WIFI_RESTART) {
|
|
restart_flag = 1;
|
|
} else {
|
|
ExecuteCommandPower(button_index + Button.press_counter[button_index], POWER_TOGGLE, SRC_BUTTON);
|
|
}
|
|
} else {
|
|
if (!Settings.flag.button_restrict) {
|
|
GetTextIndexed(scmnd, sizeof(scmnd), Button.press_counter[button_index] -3, kCommands);
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
}
|
|
}
|
|
}
|
|
#if defined(USE_LIGHT) && defined(ROTARY_V1)
|
|
}
|
|
#endif
|
|
Button.press_counter[button_index] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Button.last_state[button_index] = button;
|
|
}
|
|
}
|
|
|
|
void ButtonLoop(void)
|
|
{
|
|
if (Button.present) {
|
|
if (TimeReached(Button.debounce)) {
|
|
SetNextTimeInterval(Button.debounce, Settings.button_debounce);
|
|
ButtonHandler();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino"
|
|
const char kTasmotaCommands[] PROGMEM = "|"
|
|
D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_SLEEP "|" D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|"
|
|
D_CMND_SERIALLOG "|" D_CMND_RESTART "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" D_CMND_BLINKTIME "|" D_CMND_BLINKCOUNT "|" D_CMND_SAVEDATA "|"
|
|
D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|"
|
|
D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|"
|
|
D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|"
|
|
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|"
|
|
D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|"
|
|
D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
|
|
D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|"
|
|
#ifdef USE_I2C
|
|
D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|"
|
|
#endif
|
|
D_CMND_SENSOR "|" D_CMND_DRIVER;
|
|
|
|
void (* const TasmotaCommand[])(void) PROGMEM = {
|
|
&CmndBacklog, &CmndDelay, &CmndPower, &CmndStatus, &CmndState, &CmndSleep, &CmndUpgrade, &CmndUpgrade, &CmndOtaUrl,
|
|
&CmndSeriallog, &CmndRestart, &CmndPowerOnState, &CmndPulsetime, &CmndBlinktime, &CmndBlinkcount, &CmndSavedata,
|
|
&CmndSetoption, &CmndTemperatureResolution, &CmndHumidityResolution, &CmndPressureResolution, &CmndPowerResolution,
|
|
&CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution,
|
|
&CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange,
|
|
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig,
|
|
&CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig,
|
|
&CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
|
|
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset,
|
|
#ifdef USE_I2C
|
|
&CmndI2cScan, CmndI2cDriver,
|
|
#endif
|
|
&CmndSensor, &CmndDriver };
|
|
|
|
const char kWifiConfig[] PROGMEM =
|
|
D_WCFG_0_RESTART "||" D_WCFG_2_WIFIMANAGER "||" D_WCFG_4_RETRY "|" D_WCFG_5_WAIT "|" D_WCFG_6_SERIAL "|" D_WCFG_7_WIFIMANAGER_RESET_ONLY;
|
|
|
|
|
|
|
|
void ResponseCmndNumber(int value)
|
|
{
|
|
Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, value);
|
|
}
|
|
|
|
void ResponseCmndFloat(float value, uint32_t decimals)
|
|
{
|
|
char stemp1[TOPSZ];
|
|
dtostrfd(value, decimals, stemp1);
|
|
Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp1);
|
|
}
|
|
|
|
void ResponseCmndIdxNumber(int value)
|
|
{
|
|
Response_P(S_JSON_COMMAND_INDEX_NVALUE, XdrvMailbox.command, XdrvMailbox.index, value);
|
|
}
|
|
|
|
void ResponseCmndChar(const char* value)
|
|
{
|
|
Response_P(S_JSON_COMMAND_SVALUE, XdrvMailbox.command, value);
|
|
}
|
|
|
|
void ResponseCmndStateText(uint32_t value)
|
|
{
|
|
ResponseCmndChar(GetStateText(value));
|
|
}
|
|
|
|
void ResponseCmndDone(void)
|
|
{
|
|
ResponseCmndChar(D_JSON_DONE);
|
|
}
|
|
|
|
void ResponseCmndIdxChar(const char* value)
|
|
{
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, XdrvMailbox.command, XdrvMailbox.index, value);
|
|
}
|
|
|
|
void ResponseCmndAll(uint32_t text_index, uint32_t count)
|
|
{
|
|
mqtt_data[0] = '\0';
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
ResponseAppend_P(PSTR("%c\"%s%d\":\"%s\""), (i) ? ',' : '{', XdrvMailbox.command, i +1, SettingsText(text_index +i));
|
|
}
|
|
ResponseJsonEnd();
|
|
}
|
|
|
|
|
|
|
|
void ExecuteCommand(const char *cmnd, uint32_t source)
|
|
{
|
|
|
|
|
|
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("ExecuteCommand"));
|
|
#endif
|
|
ShowSource(source);
|
|
|
|
const char *pos = cmnd;
|
|
while (*pos && isspace(*pos)) {
|
|
pos++;
|
|
}
|
|
|
|
const char *start = pos;
|
|
|
|
while (*pos && (isalpha(*pos) || isdigit(*pos) || '_' == *pos || '/' == *pos)) {
|
|
if ('/' == *pos) {
|
|
start = pos + 1;
|
|
}
|
|
pos++;
|
|
}
|
|
if ('\0' == *start || pos <= start) {
|
|
return;
|
|
}
|
|
|
|
uint32_t size = pos - start;
|
|
char stopic[size + 2];
|
|
stopic[0] = '/';
|
|
memcpy(stopic+1, start, size);
|
|
stopic[size+1] = '\0';
|
|
|
|
char svalue[strlen(pos) +1];
|
|
strlcpy(svalue, pos, sizeof(svalue));
|
|
CommandHandler(stopic, svalue, strlen(svalue));
|
|
}
|
|
# 148 "C:/shared/sonoff/Git/Tasmota/tasmota/support_command.ino"
|
|
void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len)
|
|
{
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("CommandHandler"));
|
|
#endif
|
|
|
|
while (*dataBuf && isspace(*dataBuf)) {
|
|
dataBuf++;
|
|
data_len--;
|
|
}
|
|
|
|
bool grpflg = (strstr(topicBuf, SettingsText(SET_MQTT_GRP_TOPIC)) != nullptr);
|
|
|
|
char stemp1[TOPSZ];
|
|
GetFallbackTopic_P(stemp1, "");
|
|
fallback_topic_flag = (!strncmp(topicBuf, stemp1, strlen(stemp1)));
|
|
|
|
char *type = strrchr(topicBuf, '/');
|
|
|
|
uint32_t index = 1;
|
|
bool user_index = false;
|
|
if (type != nullptr) {
|
|
type++;
|
|
uint32_t i;
|
|
for (i = 0; i < strlen(type); i++) {
|
|
type[i] = toupper(type[i]);
|
|
}
|
|
while (isdigit(type[i-1])) {
|
|
i--;
|
|
}
|
|
if (i < strlen(type)) {
|
|
index = atoi(type +i);
|
|
user_index = true;
|
|
}
|
|
type[i] = '\0';
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CMD: " D_GROUP " %d, " D_INDEX " %d, " D_COMMAND " \"%s\", " D_DATA " \"%s\""), grpflg, index, type, dataBuf);
|
|
|
|
if (type != nullptr) {
|
|
Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_ERROR "\"}"));
|
|
|
|
if (Settings.ledstate &0x02) { blinks++; }
|
|
|
|
if (!strcmp(dataBuf,"?")) { data_len = 0; }
|
|
|
|
char *p;
|
|
int32_t payload = strtol(dataBuf, &p, 0);
|
|
if (p == dataBuf) { payload = -99; }
|
|
int temp_payload = GetStateNumber(dataBuf);
|
|
if (temp_payload > -1) { payload = temp_payload; }
|
|
|
|
DEBUG_CORE_LOG(PSTR("CMD: Payload %d"), payload);
|
|
|
|
|
|
backlog_delay = millis() + Settings.param[P_BACKLOG_DELAY];
|
|
|
|
char command[CMDSZ] = { 0 };
|
|
XdrvMailbox.command = command;
|
|
XdrvMailbox.index = index;
|
|
XdrvMailbox.data_len = data_len;
|
|
XdrvMailbox.payload = payload;
|
|
XdrvMailbox.grpflg = grpflg;
|
|
XdrvMailbox.usridx = user_index;
|
|
XdrvMailbox.topic = type;
|
|
XdrvMailbox.data = dataBuf;
|
|
|
|
#ifdef USE_SCRIPT_SUB_COMMAND
|
|
|
|
if (!Script_SubCmd()) {
|
|
if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) {
|
|
if (!XdrvCall(FUNC_COMMAND)) {
|
|
if (!XsnsCall(FUNC_COMMAND)) {
|
|
type = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
if (!DecodeCommand(kTasmotaCommands, TasmotaCommand)) {
|
|
if (!XdrvCall(FUNC_COMMAND)) {
|
|
if (!XsnsCall(FUNC_COMMAND)) {
|
|
type = nullptr;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
if (type == nullptr) {
|
|
blinks = 201;
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR(D_JSON_COMMAND));
|
|
Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_UNKNOWN "\"}"));
|
|
type = (char*)stemp1;
|
|
}
|
|
|
|
if (mqtt_data[0] != '\0') {
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, type);
|
|
XdrvRulesProcess();
|
|
}
|
|
fallback_topic_flag = false;
|
|
}
|
|
|
|
|
|
|
|
void CmndBacklog(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
char *blcommand = strtok(XdrvMailbox.data, ";");
|
|
while ((blcommand != nullptr) && (backlog.size() < MAX_BACKLOG))
|
|
#else
|
|
uint32_t bl_pointer = (!backlog_pointer) ? MAX_BACKLOG -1 : backlog_pointer;
|
|
bl_pointer--;
|
|
char *blcommand = strtok(XdrvMailbox.data, ";");
|
|
while ((blcommand != nullptr) && (backlog_index != bl_pointer))
|
|
#endif
|
|
{
|
|
while(true) {
|
|
blcommand = Trim(blcommand);
|
|
if (!strncasecmp_P(blcommand, PSTR(D_CMND_BACKLOG), strlen(D_CMND_BACKLOG))) {
|
|
blcommand += strlen(D_CMND_BACKLOG);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (*blcommand != '\0') {
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
if (backlog.size() < MAX_BACKLOG) {
|
|
backlog.add(blcommand);
|
|
}
|
|
#else
|
|
backlog[backlog_index] = String(blcommand);
|
|
backlog_index++;
|
|
if (backlog_index >= MAX_BACKLOG) backlog_index = 0;
|
|
#endif
|
|
}
|
|
blcommand = strtok(nullptr, ";");
|
|
}
|
|
|
|
mqtt_data[0] = '\0';
|
|
} else {
|
|
bool blflag = BACKLOG_EMPTY;
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
backlog.clear();
|
|
#else
|
|
backlog_pointer = backlog_index;
|
|
#endif
|
|
ResponseCmndChar(blflag ? D_JSON_EMPTY : D_JSON_ABORTED);
|
|
}
|
|
}
|
|
|
|
void CmndDelay(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= (MIN_BACKLOG_DELAY / 100)) && (XdrvMailbox.payload <= 3600)) {
|
|
backlog_delay = millis() + (100 * XdrvMailbox.payload);
|
|
}
|
|
uint32_t bl_delay = 0;
|
|
long bl_delta = TimePassedSince(backlog_delay);
|
|
if (bl_delta < 0) { bl_delay = (bl_delta *-1) / 100; }
|
|
ResponseCmndNumber(bl_delay);
|
|
}
|
|
|
|
void CmndPower(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= devices_present)) {
|
|
if ((XdrvMailbox.payload < POWER_OFF) || (XdrvMailbox.payload > POWER_BLINK_STOP)) {
|
|
XdrvMailbox.payload = POWER_SHOW_STATE;
|
|
}
|
|
|
|
ExecuteCommandPower(XdrvMailbox.index, XdrvMailbox.payload, SRC_IGNORE);
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
else if (0 == XdrvMailbox.index) {
|
|
if ((XdrvMailbox.payload < POWER_OFF) || (XdrvMailbox.payload > POWER_TOGGLE)) {
|
|
XdrvMailbox.payload = POWER_SHOW_STATE;
|
|
}
|
|
SetAllPower(XdrvMailbox.payload, SRC_IGNORE);
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
}
|
|
|
|
void CmndStatus(void)
|
|
{
|
|
uint32_t payload = ((XdrvMailbox.payload < 0) || (XdrvMailbox.payload > MAX_STATUS)) ? 99 : XdrvMailbox.payload;
|
|
|
|
uint32_t option = STAT;
|
|
char stemp[200];
|
|
char stemp2[TOPSZ];
|
|
|
|
|
|
|
|
|
|
|
|
if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; }
|
|
if (!energy_flg && (9 == payload)) { payload = 99; }
|
|
if (!CrashFlag() && (12 == payload)) { payload = 99; }
|
|
|
|
if ((0 == payload) || (99 == payload)) {
|
|
uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present;
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) { maxfn = 1; }
|
|
#endif
|
|
stemp[0] = '\0';
|
|
for (uint32_t i = 0; i < maxfn; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%s%s\"%s\"" ), stemp, (i > 0 ? "," : ""), SettingsText(SET_FRIENDLYNAME1 +i));
|
|
}
|
|
stemp2[0] = '\0';
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
snprintf_P(stemp2, sizeof(stemp2), PSTR("%s%s%d" ), stemp2, (i > 0 ? "," : ""), Settings.switchmode[i]);
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_FRIENDLYNAME "\":[%s],\"" D_CMND_TOPIC "\":\"%s\",\""
|
|
D_CMND_BUTTONTOPIC "\":\"%s\",\"" D_CMND_POWER "\":%d,\"" D_CMND_POWERONSTATE "\":%d,\"" D_CMND_LEDSTATE "\":%d,\""
|
|
D_CMND_LEDMASK "\":\"%04X\",\"" D_CMND_SAVEDATA "\":%d,\"" D_JSON_SAVESTATE "\":%d,\"" D_CMND_SWITCHTOPIC "\":\"%s\",\""
|
|
D_CMND_SWITCHMODE "\":[%s],\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_SWITCHRETAIN "\":%d,\"" D_CMND_SENSORRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"),
|
|
ModuleNr(), stemp, mqtt_topic,
|
|
SettingsText(SET_MQTT_BUTTON_TOPIC), power, Settings.poweronstate, Settings.ledstate,
|
|
Settings.ledmask, Settings.save_data,
|
|
Settings.flag.save_state,
|
|
SettingsText(SET_MQTT_SWITCH_TOPIC),
|
|
stemp2,
|
|
Settings.flag.mqtt_button_retain,
|
|
Settings.flag.mqtt_switch_retain,
|
|
Settings.flag.mqtt_sensor_retain,
|
|
Settings.flag.mqtt_power_retain);
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS));
|
|
}
|
|
|
|
if ((0 == payload) || (1 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS1_PARAMETER "\":{\"" D_JSON_BAUDRATE "\":%d,\"" D_CMND_SERIALCONFIG "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\",\"" D_CMND_OTAURL "\":\"%s\",\""
|
|
D_JSON_RESTARTREASON "\":\"%s\",\"" D_JSON_UPTIME "\":\"%s\",\"" D_JSON_STARTUPUTC "\":\"%s\",\"" D_CMND_SLEEP "\":%d,\""
|
|
D_JSON_CONFIG_HOLDER "\":%d,\"" D_JSON_BOOTCOUNT "\":%d,\"BCResetTime\":\"%s\",\"" D_JSON_SAVECOUNT "\":%d,\"" D_JSON_SAVEADDRESS "\":\"%X\"}}"),
|
|
Settings.baudrate * 300, GetSerialConfig().c_str(), SettingsText(SET_MQTT_GRP_TOPIC), SettingsText(SET_OTAURL),
|
|
GetResetReason().c_str(), GetUptime().c_str(), GetDateAndTime(DT_RESTART).c_str(), Settings.sleep,
|
|
Settings.cfg_holder, Settings.bootcount, GetDateAndTime(DT_BOOTCOUNT).c_str(), Settings.save_flag, GetSettingsAddress());
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "1"));
|
|
}
|
|
|
|
if ((0 == payload) || (2 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS2_FIRMWARE "\":{\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_BUILDDATETIME "\":\"%s\",\""
|
|
D_JSON_BOOTVERSION "\":%d,\"" D_JSON_COREVERSION "\":\"" ARDUINO_ESP8266_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\","
|
|
"\"Hardware\":\"%s\""
|
|
"%s}}"),
|
|
my_version, my_image, GetBuildDateAndTime().c_str(),
|
|
ESP.getBootVersion(), ESP.getSdkVersion(),
|
|
GetDeviceHardware().c_str(),
|
|
GetStatistics().c_str());
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "2"));
|
|
}
|
|
|
|
if ((0 == payload) || (3 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS3_LOGGING "\":{\"" D_CMND_SERIALLOG "\":%d,\"" D_CMND_WEBLOG "\":%d,\"" D_CMND_MQTTLOG "\":%d,\"" D_CMND_SYSLOG "\":%d,\""
|
|
D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" D_CMND_SSID "\":[\"%s\",\"%s\"],\"" D_CMND_TELEPERIOD "\":%d,\""
|
|
D_JSON_RESOLUTION "\":\"%08X\",\"" D_CMND_SETOPTION "\":[\"%08X\",\"%s\",\"%08X\",\"%08X\"]}}"),
|
|
Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level,
|
|
SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), Settings.tele_period,
|
|
Settings.flag2.data, Settings.flag.data, ToHex_P((unsigned char*)Settings.param, PARAM8_SIZE, stemp2, sizeof(stemp2)),
|
|
Settings.flag3.data, Settings.flag4.data);
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "3"));
|
|
}
|
|
|
|
if ((0 == payload) || (4 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS4_MEMORY "\":{\"" D_JSON_PROGRAMSIZE "\":%d,\"" D_JSON_FREEMEMORY "\":%d,\"" D_JSON_HEAPSIZE "\":%d,\""
|
|
D_JSON_PROGRAMFLASHSIZE "\":%d,\"" D_JSON_FLASHSIZE "\":%d,\"" D_JSON_FLASHCHIPID "\":\"%06X\",\"" D_JSON_FLASHMODE "\":%d,\""
|
|
D_JSON_FEATURES "\":[\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\"]"),
|
|
ESP.getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP.getFreeHeap()/1024,
|
|
ESP.getFlashChipSize()/1024, ESP.getFlashChipRealSize()/1024, ESP.getFlashChipId(), ESP.getFlashChipMode(),
|
|
LANGUAGE_LCID, feature_drv1, feature_drv2, feature_sns1, feature_sns2, feature5, feature6);
|
|
XsnsDriverState();
|
|
ResponseAppend_P(PSTR(",\"Sensors\":"));
|
|
XsnsSensorState();
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "4"));
|
|
}
|
|
|
|
if ((0 == payload) || (5 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"" D_JSON_GATEWAY "\":\"%s\",\""
|
|
D_JSON_SUBNETMASK "\":\"%s\",\"" D_JSON_DNSSERVER "\":\"%s\",\"" D_JSON_MAC "\":\"%s\",\""
|
|
D_CMND_WEBSERVER "\":%d,\"" D_CMND_WIFICONFIG "\":%d,\"" D_CMND_WIFIPOWER "\":%s}}"),
|
|
my_hostname, WiFi.localIP().toString().c_str(), IPAddress(Settings.ip_address[1]).toString().c_str(),
|
|
IPAddress(Settings.ip_address[2]).toString().c_str(), IPAddress(Settings.ip_address[3]).toString().c_str(), WiFi.macAddress().c_str(),
|
|
Settings.webserver, Settings.sta_config, WifiGetOutputPower().c_str());
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "5"));
|
|
}
|
|
|
|
if (((0 == payload) || (6 == payload)) && Settings.flag.mqtt_enabled) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_JSON_MASK "\":\"%s\",\""
|
|
D_CMND_MQTTCLIENT "\":\"%s\",\"" D_CMND_MQTTUSER "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"),
|
|
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT),
|
|
mqtt_client, SettingsText(SET_MQTT_USER), MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE);
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "6"));
|
|
}
|
|
|
|
if ((0 == payload) || (7 == payload)) {
|
|
if (99 == Settings.timezone) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%d" ), Settings.timezone);
|
|
} else {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("\"%s\"" ), GetTimeZone().c_str());
|
|
}
|
|
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\""
|
|
D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%s,\"" D_JSON_SUNRISE "\":\"%s\",\"" D_JSON_SUNSET "\":\"%s\"}}"),
|
|
GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(),
|
|
GetTime(3).c_str(), stemp, GetSun(0).c_str(), GetSun(1).c_str());
|
|
#else
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\""
|
|
D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%s}}"),
|
|
GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(),
|
|
GetTime(3).c_str(), stemp);
|
|
#endif
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "7"));
|
|
}
|
|
|
|
#if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION)
|
|
if (energy_flg) {
|
|
if ((0 == payload) || (9 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERDELTA "\":%d,\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\""
|
|
D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"),
|
|
Settings.energy_power_delta, Settings.energy_min_power, Settings.energy_max_power,
|
|
Settings.energy_min_voltage, Settings.energy_max_voltage, Settings.energy_min_current, Settings.energy_max_current);
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9"));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if ((0 == payload) || (8 == payload) || (10 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS10_SENSOR "\":"));
|
|
MqttShowSensor();
|
|
ResponseJsonEnd();
|
|
if (8 == payload) {
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "8"));
|
|
} else {
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "10"));
|
|
}
|
|
}
|
|
|
|
if ((0 == payload) || (11 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS11_STATUS "\":"));
|
|
MqttShowState();
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "11"));
|
|
}
|
|
|
|
if (CrashFlag()) {
|
|
if ((0 == payload) || (12 == payload)) {
|
|
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS12_STATUS "\":"));
|
|
CrashDump();
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "12"));
|
|
}
|
|
}
|
|
|
|
#ifdef USE_SCRIPT_STATUS
|
|
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data);
|
|
#endif
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
|
|
void CmndState(void)
|
|
{
|
|
mqtt_data[0] = '\0';
|
|
MqttShowState();
|
|
if (Settings.flag3.hass_tele_on_power) {
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
|
|
}
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if (Settings.flag.hass_discovery) {
|
|
HAssPublishStatus();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CmndTempOffset(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
int value = (int)(CharToFloat(XdrvMailbox.data) * 10);
|
|
if ((value > -127) && (value < 127)) {
|
|
Settings.temp_comp = value;
|
|
}
|
|
}
|
|
ResponseCmndFloat((float)(Settings.temp_comp) / 10, 1);
|
|
}
|
|
|
|
void CmndSleep(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 251)) {
|
|
Settings.sleep = XdrvMailbox.payload;
|
|
sleep = XdrvMailbox.payload;
|
|
WiFiSetSleepMode();
|
|
}
|
|
Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.sleep, sleep);
|
|
|
|
}
|
|
|
|
void CmndUpgrade(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if (((1 == XdrvMailbox.data_len) && (1 == XdrvMailbox.payload)) || ((XdrvMailbox.data_len >= 3) && NewerVersion(XdrvMailbox.data))) {
|
|
ota_state_flag = 3;
|
|
char stemp1[TOPSZ];
|
|
Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, my_version, GetOtaUrl(stemp1, sizeof(stemp1)));
|
|
} else {
|
|
Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, my_version);
|
|
}
|
|
}
|
|
|
|
void CmndOtaUrl(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_OTAURL, (SC_DEFAULT == Shortcut()) ? OTA_URL : XdrvMailbox.data);
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_OTAURL));
|
|
}
|
|
|
|
void CmndSeriallog(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
|
|
Settings.flag.mqtt_serial = 0;
|
|
SetSeriallog(XdrvMailbox.payload);
|
|
}
|
|
Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.seriallog_level, seriallog_level);
|
|
}
|
|
|
|
void CmndRestart(void)
|
|
{
|
|
switch (XdrvMailbox.payload) {
|
|
case 1:
|
|
restart_flag = 2;
|
|
ResponseCmndChar(D_JSON_RESTARTING);
|
|
break;
|
|
case -1:
|
|
CmndCrash();
|
|
break;
|
|
case -2:
|
|
CmndWDT();
|
|
break;
|
|
case -3:
|
|
CmndBlockedLoop();
|
|
break;
|
|
case 99:
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING));
|
|
EspRestart();
|
|
break;
|
|
default:
|
|
ResponseCmndChar(D_JSON_ONE_TO_RESTART);
|
|
}
|
|
}
|
|
|
|
void CmndPowerOnState(void)
|
|
{
|
|
if (my_module_type != MOTOR) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((XdrvMailbox.payload >= POWER_ALL_OFF) && (XdrvMailbox.payload <= POWER_ALL_OFF_PULSETIME_ON)) {
|
|
Settings.poweronstate = XdrvMailbox.payload;
|
|
if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) {
|
|
for (uint32_t i = 1; i <= devices_present; i++) {
|
|
ExecuteCommandPower(i, POWER_ON, SRC_IGNORE);
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.poweronstate);
|
|
}
|
|
}
|
|
|
|
void CmndPulsetime(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PULSETIMERS)) {
|
|
uint32_t items = 1;
|
|
if (!XdrvMailbox.usridx && !XdrvMailbox.data_len) {
|
|
items = MAX_PULSETIMERS;
|
|
} else {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) {
|
|
Settings.pulse_timer[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
SetPulseTimer(XdrvMailbox.index -1, XdrvMailbox.payload);
|
|
}
|
|
}
|
|
mqtt_data[0] = '\0';
|
|
for (uint32_t i = 0; i < items; i++) {
|
|
uint32_t index = (1 == items) ? XdrvMailbox.index : i +1;
|
|
ResponseAppend_P(PSTR("%c\"%s%d\":{\"" D_JSON_SET "\":%d,\"" D_JSON_REMAINING "\":%d}"),
|
|
(i) ? ',' : '{',
|
|
XdrvMailbox.command, index,
|
|
Settings.pulse_timer[index -1], GetPulseTimer(index -1));
|
|
}
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
void CmndBlinktime(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 1) && (XdrvMailbox.payload <= 3600)) {
|
|
Settings.blinktime = XdrvMailbox.payload;
|
|
if (blink_timer > 0) { blink_timer = millis() + (100 * XdrvMailbox.payload); }
|
|
}
|
|
ResponseCmndNumber(Settings.blinktime);
|
|
}
|
|
|
|
void CmndBlinkcount(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 65536)) {
|
|
Settings.blinkcount = XdrvMailbox.payload;
|
|
if (blink_counter) { blink_counter = Settings.blinkcount *2; }
|
|
}
|
|
ResponseCmndNumber(Settings.blinkcount);
|
|
}
|
|
|
|
void CmndSavedata(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3600)) {
|
|
Settings.save_data = XdrvMailbox.payload;
|
|
save_data_counter = Settings.save_data;
|
|
}
|
|
SettingsSaveAll();
|
|
char stemp1[TOPSZ];
|
|
if (Settings.save_data > 1) {
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR(D_JSON_EVERY " %d " D_UNIT_SECOND), Settings.save_data);
|
|
}
|
|
ResponseCmndChar((Settings.save_data > 1) ? stemp1 : GetStateText(Settings.save_data));
|
|
}
|
|
|
|
void CmndSetoption(void)
|
|
{
|
|
if (XdrvMailbox.index < 114) {
|
|
uint32_t ptype;
|
|
uint32_t pindex;
|
|
if (XdrvMailbox.index <= 31) {
|
|
ptype = 2;
|
|
pindex = XdrvMailbox.index;
|
|
}
|
|
else if (XdrvMailbox.index <= 49) {
|
|
ptype = 1;
|
|
pindex = XdrvMailbox.index -32;
|
|
}
|
|
else if (XdrvMailbox.index <= 81) {
|
|
ptype = 3;
|
|
pindex = XdrvMailbox.index -50;
|
|
}
|
|
else {
|
|
ptype = 4;
|
|
pindex = XdrvMailbox.index -82;
|
|
}
|
|
|
|
if (XdrvMailbox.payload >= 0) {
|
|
if (1 == ptype) {
|
|
uint32_t param_low = 0;
|
|
uint32_t param_high = 255;
|
|
switch (pindex) {
|
|
case P_HOLD_TIME:
|
|
case P_MAX_POWER_RETRY:
|
|
param_low = 1;
|
|
param_high = 250;
|
|
break;
|
|
}
|
|
if ((XdrvMailbox.payload >= param_low) && (XdrvMailbox.payload <= param_high)) {
|
|
Settings.param[pindex] = XdrvMailbox.payload;
|
|
#ifdef USE_LIGHT
|
|
if (P_RGB_REMAP == pindex) {
|
|
LightUpdateColorMapping();
|
|
restart_flag = 2;
|
|
}
|
|
#endif
|
|
#if (defined(USE_IR_REMOTE) && defined(USE_IR_RECEIVE)) || defined(USE_IR_REMOTE_FULL)
|
|
if (P_IR_UNKNOW_THRESHOLD == pindex) {
|
|
IrReceiveUpdateThreshold();
|
|
}
|
|
#endif
|
|
} else {
|
|
ptype = 99;
|
|
}
|
|
} else {
|
|
if (XdrvMailbox.payload <= 1) {
|
|
if (2 == ptype) {
|
|
switch (pindex) {
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 9:
|
|
case 14:
|
|
case 22:
|
|
case 23:
|
|
case 25:
|
|
case 27:
|
|
ptype = 99;
|
|
break;
|
|
case 3:
|
|
case 15:
|
|
restart_flag = 2;
|
|
default:
|
|
bitWrite(Settings.flag.data, pindex, XdrvMailbox.payload);
|
|
}
|
|
if (12 == pindex) {
|
|
stop_flash_rotate = XdrvMailbox.payload;
|
|
SettingsSave(2);
|
|
}
|
|
#ifdef USE_HOME_ASSISTANT
|
|
if ((19 == pindex) || (30 == pindex)) {
|
|
HAssDiscover();
|
|
}
|
|
#endif
|
|
}
|
|
else if (3 == ptype) {
|
|
bitWrite(Settings.flag3.data, pindex, XdrvMailbox.payload);
|
|
switch (pindex) {
|
|
case 5:
|
|
if (0 == XdrvMailbox.payload) {
|
|
restart_flag = 2;
|
|
}
|
|
break;
|
|
case 10:
|
|
WiFiSetSleepMode();
|
|
break;
|
|
case 18:
|
|
case 25:
|
|
restart_flag = 2;
|
|
break;
|
|
}
|
|
}
|
|
else if (4 == ptype) {
|
|
bitWrite(Settings.flag4.data, pindex, XdrvMailbox.payload);
|
|
}
|
|
} else {
|
|
ptype = 99;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ptype < 99) {
|
|
if (1 == ptype) {
|
|
ResponseCmndIdxNumber(Settings.param[pindex]);
|
|
} else {
|
|
uint32_t flag = Settings.flag.data;
|
|
if (3 == ptype) {
|
|
flag = Settings.flag3.data;
|
|
}
|
|
else if (4 == ptype) {
|
|
flag = Settings.flag4.data;
|
|
}
|
|
ResponseCmndIdxChar(GetStateText(bitRead(flag, pindex)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndTemperatureResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.temperature_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.temperature_resolution);
|
|
}
|
|
|
|
void CmndHumidityResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.humidity_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.humidity_resolution);
|
|
}
|
|
|
|
void CmndPressureResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.pressure_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.pressure_resolution);
|
|
}
|
|
|
|
void CmndPowerResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.wattage_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.wattage_resolution);
|
|
}
|
|
|
|
void CmndVoltageResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.voltage_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.voltage_resolution);
|
|
}
|
|
|
|
void CmndFrequencyResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.frequency_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.frequency_resolution);
|
|
}
|
|
|
|
void CmndCurrentResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.current_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.current_resolution);
|
|
}
|
|
|
|
void CmndEnergyResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) {
|
|
Settings.flag2.energy_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.energy_resolution);
|
|
}
|
|
|
|
void CmndWeightResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
Settings.flag2.weight_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.weight_resolution);
|
|
}
|
|
|
|
void CmndModule(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= MAXMODULE)) {
|
|
bool present = false;
|
|
if (0 == XdrvMailbox.payload) {
|
|
XdrvMailbox.payload = USER_MODULE;
|
|
present = true;
|
|
} else {
|
|
XdrvMailbox.payload--;
|
|
present = ValidTemplateModule(XdrvMailbox.payload);
|
|
}
|
|
if (present) {
|
|
Settings.last_module = Settings.module;
|
|
Settings.module = XdrvMailbox.payload;
|
|
SetModuleType();
|
|
if (Settings.last_module != XdrvMailbox.payload) {
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
Settings.my_gp.io[i] = GPIO_NONE;
|
|
}
|
|
}
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, ModuleNr(), ModuleName().c_str());
|
|
}
|
|
|
|
void CmndModules(void)
|
|
{
|
|
uint32_t midx = USER_MODULE;
|
|
uint32_t lines = 1;
|
|
bool jsflg = false;
|
|
for (uint32_t i = 0; i <= sizeof(kModuleNiceList); i++) {
|
|
if (i > 0) { midx = pgm_read_byte(kModuleNiceList + i -1); }
|
|
if (!jsflg) {
|
|
Response_P(PSTR("{\"" D_CMND_MODULES "%d\":{"), lines);
|
|
} else {
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
jsflg = true;
|
|
uint32_t j = i ? midx +1 : 0;
|
|
if ((ResponseAppend_P(PSTR("\"%d\":\"%s\""), j, AnyModuleName(midx).c_str()) > (LOGSZ - TOPSZ)) || (i == sizeof(kModuleNiceList))) {
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, UpperCase(XdrvMailbox.command, XdrvMailbox.command));
|
|
jsflg = false;
|
|
lines++;
|
|
}
|
|
}
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
|
|
void CmndGpio(void)
|
|
{
|
|
if (XdrvMailbox.index < sizeof(Settings.my_gp)) {
|
|
myio cmodule;
|
|
ModuleGpios(&cmodule);
|
|
if (ValidGPIO(XdrvMailbox.index, cmodule.io[XdrvMailbox.index]) && (XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < GPIO_SENSOR_END)) {
|
|
bool present = false;
|
|
for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) {
|
|
uint32_t midx = pgm_read_byte(kGpioNiceList + i);
|
|
if (midx == XdrvMailbox.payload) { present = true; }
|
|
}
|
|
if (present) {
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
if (ValidGPIO(i, cmodule.io[i]) && (Settings.my_gp.io[i] == XdrvMailbox.payload)) {
|
|
Settings.my_gp.io[i] = GPIO_NONE;
|
|
}
|
|
}
|
|
Settings.my_gp.io[XdrvMailbox.index] = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
Response_P(PSTR("{"));
|
|
bool jsflg = false;
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
if (ValidGPIO(i, cmodule.io[i]) || ((GPIO_USER == XdrvMailbox.payload) && !FlashPin(i))) {
|
|
if (jsflg) { ResponseAppend_P(PSTR(",")); }
|
|
jsflg = true;
|
|
uint8_t sensor_type = Settings.my_gp.io[i];
|
|
if (!ValidGPIO(i, cmodule.io[i])) {
|
|
sensor_type = cmodule.io[i];
|
|
if (GPIO_USER == sensor_type) {
|
|
sensor_type = GPIO_NONE;
|
|
}
|
|
}
|
|
uint8_t sensor_name_idx = sensor_type;
|
|
const char *sensor_names = kSensorNames;
|
|
if (sensor_type > GPIO_FIX_START) {
|
|
sensor_name_idx = sensor_type - GPIO_FIX_START -1;
|
|
sensor_names = kSensorNamesFixed;
|
|
}
|
|
char stemp1[TOPSZ];
|
|
ResponseAppend_P(PSTR("\"" D_CMND_GPIO "%d\":{\"%d\":\"%s\"}"),
|
|
i, sensor_type, GetTextIndexed(stemp1, sizeof(stemp1), sensor_name_idx, sensor_names));
|
|
}
|
|
}
|
|
if (jsflg) {
|
|
ResponseJsonEnd();
|
|
} else {
|
|
ResponseCmndChar(D_JSON_NOT_SUPPORTED);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndGpios(void)
|
|
{
|
|
myio cmodule;
|
|
ModuleGpios(&cmodule);
|
|
uint32_t lines = 1;
|
|
bool jsflg = false;
|
|
for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) {
|
|
uint32_t midx = pgm_read_byte(kGpioNiceList + i);
|
|
if ((XdrvMailbox.payload != 255) && GetUsedInModule(midx, cmodule.io)) { continue; }
|
|
if (!jsflg) {
|
|
Response_P(PSTR("{\"" D_CMND_GPIOS "%d\":{"), lines);
|
|
} else {
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
jsflg = true;
|
|
char stemp1[TOPSZ];
|
|
if ((ResponseAppend_P(PSTR("\"%d\":\"%s\""), midx, GetTextIndexed(stemp1, sizeof(stemp1), midx, kSensorNames)) > (LOGSZ - TOPSZ)) || (i == sizeof(kGpioNiceList) -1)) {
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, UpperCase(XdrvMailbox.command, XdrvMailbox.command));
|
|
jsflg = false;
|
|
lines++;
|
|
}
|
|
}
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
|
|
void CmndTemplate(void)
|
|
{
|
|
|
|
bool error = false;
|
|
|
|
if (strstr(XdrvMailbox.data, "{") == nullptr) {
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= MAXMODULE)) {
|
|
XdrvMailbox.payload--;
|
|
if (ValidTemplateModule(XdrvMailbox.payload)) {
|
|
ModuleDefault(XdrvMailbox.payload);
|
|
if (USER_MODULE == Settings.module) { restart_flag = 2; }
|
|
}
|
|
}
|
|
else if (0 == XdrvMailbox.payload) {
|
|
if (Settings.module != USER_MODULE) {
|
|
ModuleDefault(Settings.module);
|
|
}
|
|
}
|
|
else if (255 == XdrvMailbox.payload) {
|
|
if (Settings.module != USER_MODULE) {
|
|
ModuleDefault(Settings.module);
|
|
}
|
|
snprintf_P(Settings.user_template.name, sizeof(Settings.user_template.name), PSTR("Merged"));
|
|
uint32_t j = 0;
|
|
for (uint32_t i = 0; i < sizeof(mycfgio); i++) {
|
|
if (6 == i) { j = 9; }
|
|
if (8 == i) { j = 12; }
|
|
if (my_module.io[j] > GPIO_NONE) {
|
|
Settings.user_template.gp.io[i] = my_module.io[j];
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (JsonTemplate(XdrvMailbox.data)) {
|
|
if (USER_MODULE == Settings.module) { restart_flag = 2; }
|
|
} else {
|
|
ResponseCmndChar(D_JSON_INVALID_JSON);
|
|
error = true;
|
|
}
|
|
}
|
|
if (!error) { TemplateJson(); }
|
|
}
|
|
|
|
void CmndPwm(void)
|
|
{
|
|
if (pwm_present && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_PWMS)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= Settings.pwm_range) && (pin[GPIO_PWM1 + XdrvMailbox.index -1] < 99)) {
|
|
Settings.pwm_value[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
analogWrite(pin[GPIO_PWM1 + XdrvMailbox.index -1], bitRead(pwm_inverted, XdrvMailbox.index -1) ? Settings.pwm_range - XdrvMailbox.payload : XdrvMailbox.payload);
|
|
}
|
|
Response_P(PSTR("{"));
|
|
MqttShowPWMState();
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
void CmndPwmfrequency(void)
|
|
{
|
|
if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= PWM_MIN) && (XdrvMailbox.payload <= PWM_MAX))) {
|
|
Settings.pwm_frequency = (1 == XdrvMailbox.payload) ? PWM_FREQ : XdrvMailbox.payload;
|
|
analogWriteFreq(Settings.pwm_frequency);
|
|
}
|
|
ResponseCmndNumber(Settings.pwm_frequency);
|
|
}
|
|
|
|
void CmndPwmrange(void)
|
|
{
|
|
if ((1 == XdrvMailbox.payload) || ((XdrvMailbox.payload > 254) && (XdrvMailbox.payload < 1024))) {
|
|
Settings.pwm_range = (1 == XdrvMailbox.payload) ? PWM_RANGE : XdrvMailbox.payload;
|
|
for (uint32_t i = 0; i < MAX_PWMS; i++) {
|
|
if (Settings.pwm_value[i] > Settings.pwm_range) {
|
|
Settings.pwm_value[i] = Settings.pwm_range;
|
|
}
|
|
}
|
|
analogWriteRange(Settings.pwm_range);
|
|
}
|
|
ResponseCmndNumber(Settings.pwm_range);
|
|
}
|
|
|
|
void CmndButtonDebounce(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) {
|
|
Settings.button_debounce = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.button_debounce);
|
|
}
|
|
|
|
void CmndSwitchDebounce(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 39) && (XdrvMailbox.payload < 1001)) {
|
|
Settings.switch_debounce = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.switch_debounce);
|
|
}
|
|
|
|
void CmndBaudrate(void)
|
|
{
|
|
if (XdrvMailbox.payload >= 300) {
|
|
XdrvMailbox.payload /= 300;
|
|
uint32_t baudrate = (XdrvMailbox.payload & 0xFFFF) * 300;
|
|
SetSerialBaudrate(baudrate);
|
|
}
|
|
ResponseCmndNumber(Settings.baudrate * 300);
|
|
}
|
|
|
|
void CmndSerialConfig(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (XdrvMailbox.data_len < 3) {
|
|
if ((XdrvMailbox.payload >= TS_SERIAL_5N1) && (XdrvMailbox.payload <= TS_SERIAL_8O2)) {
|
|
SetSerialConfig(XdrvMailbox.payload);
|
|
}
|
|
}
|
|
else if ((XdrvMailbox.payload >= 5) && (XdrvMailbox.payload <= 8)) {
|
|
uint8_t serial_config = XdrvMailbox.payload -5;
|
|
|
|
bool valid = true;
|
|
char parity = (XdrvMailbox.data[1] & 0xdf);
|
|
if ('E' == parity) {
|
|
serial_config += 0x08;
|
|
}
|
|
else if ('O' == parity) {
|
|
serial_config += 0x10;
|
|
}
|
|
else if ('N' != parity) {
|
|
valid = false;
|
|
}
|
|
|
|
if ('2' == XdrvMailbox.data[2]) {
|
|
serial_config += 0x04;
|
|
}
|
|
else if ('1' != XdrvMailbox.data[2]) {
|
|
valid = false;
|
|
}
|
|
|
|
if (valid) {
|
|
SetSerialConfig(serial_config);
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndChar(GetSerialConfig().c_str());
|
|
}
|
|
|
|
void CmndSerialSend(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) {
|
|
SetSeriallog(LOG_LEVEL_NONE);
|
|
Settings.flag.mqtt_serial = 1;
|
|
Settings.flag.mqtt_serial_raw = (XdrvMailbox.index > 3) ? 1 : 0;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (1 == XdrvMailbox.index) {
|
|
Serial.printf("%s\n", XdrvMailbox.data);
|
|
}
|
|
else if (2 == XdrvMailbox.index || 4 == XdrvMailbox.index) {
|
|
for (uint32_t i = 0; i < XdrvMailbox.data_len; i++) {
|
|
Serial.write(XdrvMailbox.data[i]);
|
|
}
|
|
}
|
|
else if (3 == XdrvMailbox.index) {
|
|
uint32_t dat_len = XdrvMailbox.data_len;
|
|
Serial.printf("%s", Unescape(XdrvMailbox.data, &dat_len));
|
|
}
|
|
else if (5 == XdrvMailbox.index) {
|
|
SerialSendRaw(RemoveSpace(XdrvMailbox.data));
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndSerialDelimiter(void)
|
|
{
|
|
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.payload < 256)) {
|
|
if (XdrvMailbox.payload > 0) {
|
|
Settings.serial_delimiter = XdrvMailbox.payload;
|
|
} else {
|
|
uint32_t dat_len = XdrvMailbox.data_len;
|
|
Unescape(XdrvMailbox.data, &dat_len);
|
|
Settings.serial_delimiter = XdrvMailbox.data[0];
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.serial_delimiter);
|
|
}
|
|
|
|
void CmndSyslog(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
|
|
SetSyslog(XdrvMailbox.payload);
|
|
}
|
|
Response_P(S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, XdrvMailbox.command, Settings.syslog_level, syslog_level);
|
|
}
|
|
|
|
void CmndLoghost(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_SYSLOG_HOST, (SC_DEFAULT == Shortcut()) ? SYS_LOG_HOST : XdrvMailbox.data);
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_SYSLOG_HOST));
|
|
}
|
|
|
|
void CmndLogport(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) {
|
|
Settings.syslog_port = (1 == XdrvMailbox.payload) ? SYS_LOG_PORT : XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.syslog_port);
|
|
}
|
|
|
|
void CmndIpAddress(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
|
|
uint32_t address;
|
|
if (ParseIp(&address, XdrvMailbox.data)) {
|
|
Settings.ip_address[XdrvMailbox.index -1] = address;
|
|
|
|
}
|
|
char stemp1[TOPSZ];
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR(" (%s)"), WiFi.localIP().toString().c_str());
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE_SVALUE, XdrvMailbox.command, XdrvMailbox.index, IPAddress(Settings.ip_address[XdrvMailbox.index -1]).toString().c_str(), (1 == XdrvMailbox.index) ? stemp1:"");
|
|
}
|
|
}
|
|
|
|
void CmndNtpServer(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_NTP_SERVERS)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_NTPSERVER1, MAX_NTP_SERVERS);
|
|
} else {
|
|
uint32_t ntp_server = SET_NTPSERVER1 + XdrvMailbox.index -1;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(ntp_server,
|
|
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? NTP_SERVER1 : (2 == XdrvMailbox.index) ? NTP_SERVER2 : NTP_SERVER3 : XdrvMailbox.data);
|
|
SettingsUpdateText(ntp_server, ReplaceCommaWithDot(SettingsText(ntp_server)));
|
|
|
|
ntp_force_sync = true;
|
|
}
|
|
ResponseCmndIdxChar(SettingsText(ntp_server));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndAp(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
Settings.sta_active ^= 1;
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
Settings.sta_active = XdrvMailbox.payload -1;
|
|
}
|
|
restart_flag = 2;
|
|
}
|
|
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active));
|
|
}
|
|
|
|
void CmndSsid(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SSIDS)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_STASSID1, MAX_SSIDS);
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_STASSID1 + XdrvMailbox.index -1,
|
|
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_SSID1 : STA_SSID2 : XdrvMailbox.data);
|
|
Settings.sta_active = XdrvMailbox.index -1;
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndIdxChar(SettingsText(SET_STASSID1 + XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndPassword(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
|
|
if ((XdrvMailbox.data_len > 4) || (SC_CLEAR == Shortcut()) || (SC_DEFAULT == Shortcut())) {
|
|
SettingsUpdateText(SET_STAPWD1 + XdrvMailbox.index -1,
|
|
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_PASS1 : STA_PASS2 : XdrvMailbox.data);
|
|
Settings.sta_active = XdrvMailbox.index -1;
|
|
restart_flag = 2;
|
|
ResponseCmndIdxChar(SettingsText(SET_STAPWD1 + XdrvMailbox.index -1));
|
|
} else {
|
|
Response_P(S_JSON_COMMAND_INDEX_ASTERISK, XdrvMailbox.command, XdrvMailbox.index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndHostname(void)
|
|
{
|
|
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
|
|
SettingsUpdateText(SET_HOSTNAME, (SC_DEFAULT == Shortcut()) ? WIFI_HOSTNAME : XdrvMailbox.data);
|
|
if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) {
|
|
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
|
|
}
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_HOSTNAME));
|
|
}
|
|
|
|
void CmndWifiConfig(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= WIFI_RESTART) && (XdrvMailbox.payload < MAX_WIFI_OPTION)) {
|
|
if ((EX_WIFI_SMARTCONFIG == XdrvMailbox.payload) || (EX_WIFI_WPSCONFIG == XdrvMailbox.payload)) {
|
|
XdrvMailbox.payload = WIFI_MANAGER;
|
|
}
|
|
Settings.sta_config = XdrvMailbox.payload;
|
|
wifi_state_flag = Settings.sta_config;
|
|
if (WifiState() > WIFI_RESTART) {
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
char stemp1[TOPSZ];
|
|
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_config, GetTextIndexed(stemp1, sizeof(stemp1), Settings.sta_config, kWifiConfig));
|
|
}
|
|
|
|
void CmndFriendlyname(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_FRIENDLYNAME1, MAX_FRIENDLYNAMES);
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char stemp1[TOPSZ];
|
|
if (1 == XdrvMailbox.index) {
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME));
|
|
} else {
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), XdrvMailbox.index);
|
|
}
|
|
SettingsUpdateText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data);
|
|
}
|
|
ResponseCmndIdxChar(SettingsText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndSwitchMode(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SWITCHES)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_SWITCH_OPTION)) {
|
|
Settings.switchmode[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.switchmode[XdrvMailbox.index-1]);
|
|
}
|
|
}
|
|
|
|
void CmndInterlock(void)
|
|
{
|
|
|
|
uint32_t max_relays = devices_present;
|
|
if (light_type) { max_relays--; }
|
|
if (max_relays > sizeof(Settings.interlock[0]) * 8) { max_relays = sizeof(Settings.interlock[0]) * 8; }
|
|
if (max_relays > 1) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) { Settings.interlock[i] = 0; }
|
|
char *group;
|
|
char *q;
|
|
uint32_t group_index = 0;
|
|
power_t relay_mask = 0;
|
|
for (group = strtok_r(XdrvMailbox.data, " ", &q); group && group_index < MAX_INTERLOCKS; group = strtok_r(nullptr, " ", &q)) {
|
|
char *str;
|
|
char *p;
|
|
for (str = strtok_r(group, ",", &p); str; str = strtok_r(nullptr, ",", &p)) {
|
|
int pbit = atoi(str);
|
|
if ((pbit > 0) && (pbit <= max_relays)) {
|
|
pbit--;
|
|
if (!bitRead(relay_mask, pbit)) {
|
|
bitSet(relay_mask, pbit);
|
|
bitSet(Settings.interlock[group_index], pbit);
|
|
}
|
|
}
|
|
}
|
|
group_index++;
|
|
}
|
|
for (uint32_t i = 0; i < group_index; i++) {
|
|
uint32_t minimal_bits = 0;
|
|
for (uint32_t j = 0; j < max_relays; j++) {
|
|
if (bitRead(Settings.interlock[i], j)) { minimal_bits++; }
|
|
}
|
|
if (minimal_bits < 2) { Settings.interlock[i] = 0; }
|
|
}
|
|
} else {
|
|
Settings.flag.interlock = XdrvMailbox.payload &1;
|
|
if (Settings.flag.interlock) {
|
|
SetDevicePower(power, SRC_IGNORE);
|
|
}
|
|
}
|
|
#ifdef USE_SHUTTER
|
|
if (Settings.flag3.shutter_mode) {
|
|
ShutterInit();
|
|
}
|
|
#endif
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_INTERLOCK "\":\"%s\",\"" D_JSON_GROUPS "\":\""), GetStateText(Settings.flag.interlock));
|
|
uint32_t anygroup = 0;
|
|
for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) {
|
|
if (Settings.interlock[i]) {
|
|
anygroup++;
|
|
ResponseAppend_P(PSTR("%s"), (anygroup > 1) ? " " : "");
|
|
uint32_t anybit = 0;
|
|
power_t mask = 1;
|
|
for (uint32_t j = 0; j < max_relays; j++) {
|
|
if (Settings.interlock[i] & mask) {
|
|
anybit++;
|
|
ResponseAppend_P(PSTR("%s%d"), (anybit > 1) ? "," : "", j +1);
|
|
}
|
|
mask <<= 1;
|
|
}
|
|
}
|
|
}
|
|
if (!anygroup) {
|
|
for (uint32_t j = 1; j <= max_relays; j++) {
|
|
ResponseAppend_P(PSTR("%s%d"), (j > 1) ? "," : "", j);
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("\"}"));
|
|
} else {
|
|
Settings.flag.interlock = 0;
|
|
ResponseCmndStateText(Settings.flag.interlock);
|
|
}
|
|
}
|
|
|
|
void CmndTeleperiod(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.tele_period = (1 == XdrvMailbox.payload) ? TELE_PERIOD : XdrvMailbox.payload;
|
|
if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) Settings.tele_period = 10;
|
|
tele_period = Settings.tele_period;
|
|
}
|
|
ResponseCmndNumber(Settings.tele_period);
|
|
}
|
|
|
|
void CmndReset(void)
|
|
{
|
|
switch (XdrvMailbox.payload) {
|
|
case 1:
|
|
restart_flag = 211;
|
|
ResponseCmndChar(D_JSON_RESET_AND_RESTARTING);
|
|
break;
|
|
case 2 ... 6:
|
|
restart_flag = 210 + XdrvMailbox.payload;
|
|
Response_P(PSTR("{\"" D_CMND_RESET "\":\"" D_JSON_ERASE ", " D_JSON_RESET_AND_RESTARTING "\"}"));
|
|
break;
|
|
case 99:
|
|
Settings.bootcount = 0;
|
|
Settings.bootcount_reset_time = 0;
|
|
ResponseCmndDone();
|
|
break;
|
|
default:
|
|
ResponseCmndChar(D_JSON_ONE_TO_RESET);
|
|
}
|
|
}
|
|
|
|
void CmndTime(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t format = Settings.flag2.time_format;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4)) {
|
|
Settings.flag2.time_format = XdrvMailbox.payload -1;
|
|
format = Settings.flag2.time_format;
|
|
} else {
|
|
format = 1;
|
|
RtcSetTime(XdrvMailbox.payload);
|
|
}
|
|
}
|
|
mqtt_data[0] = '\0';
|
|
ResponseAppendTimeFormat(format);
|
|
ResponseJsonEnd();
|
|
}
|
|
|
|
void CmndTimezone(void)
|
|
{
|
|
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.payload >= -13)) {
|
|
Settings.timezone = XdrvMailbox.payload;
|
|
Settings.timezone_minutes = 0;
|
|
if (XdrvMailbox.payload < 15) {
|
|
char *p = strtok (XdrvMailbox.data, ":");
|
|
if (p) {
|
|
p = strtok (nullptr, ":");
|
|
if (p) {
|
|
Settings.timezone_minutes = strtol(p, nullptr, 10);
|
|
if (Settings.timezone_minutes > 59) { Settings.timezone_minutes = 59; }
|
|
}
|
|
}
|
|
} else {
|
|
Settings.timezone = 99;
|
|
}
|
|
ntp_force_sync = true;
|
|
}
|
|
if (99 == Settings.timezone) {
|
|
ResponseCmndNumber(Settings.timezone);
|
|
} else {
|
|
char stemp1[TOPSZ];
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR("%+03d:%02d"), Settings.timezone, Settings.timezone_minutes);
|
|
ResponseCmndChar(stemp1);
|
|
}
|
|
}
|
|
|
|
void CmndTimeStdDst(uint32_t ts)
|
|
{
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
uint32_t tpos = 0;
|
|
int value = 0;
|
|
char *p = XdrvMailbox.data;
|
|
char *q = p;
|
|
while (p && (tpos < 7)) {
|
|
if (p > q) {
|
|
if (1 == tpos) { Settings.tflag[ts].hemis = value &1; }
|
|
if (2 == tpos) { Settings.tflag[ts].week = (value < 0) ? 0 : (value > 4) ? 4 : value; }
|
|
if (3 == tpos) { Settings.tflag[ts].month = (value < 1) ? 1 : (value > 12) ? 12 : value; }
|
|
if (4 == tpos) { Settings.tflag[ts].dow = (value < 1) ? 1 : (value > 7) ? 7 : value; }
|
|
if (5 == tpos) { Settings.tflag[ts].hour = (value < 0) ? 0 : (value > 23) ? 23 : value; }
|
|
if (6 == tpos) { Settings.toffset[ts] = (value < -900) ? -900 : (value > 900) ? 900 : value; }
|
|
}
|
|
p = Trim(p);
|
|
if (tpos && (*p == ',')) { p++; }
|
|
p = Trim(p);
|
|
q = p;
|
|
value = strtol(p, &p, 10);
|
|
tpos++;
|
|
}
|
|
ntp_force_sync = true;
|
|
} else {
|
|
if (0 == XdrvMailbox.payload) {
|
|
if (0 == ts) {
|
|
SettingsResetStd();
|
|
} else {
|
|
SettingsResetDst();
|
|
}
|
|
}
|
|
ntp_force_sync = true;
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"%s\":{\"Hemisphere\":%d,\"Week\":%d,\"Month\":%d,\"Day\":%d,\"Hour\":%d,\"Offset\":%d}}"),
|
|
XdrvMailbox.command, Settings.tflag[ts].hemis, Settings.tflag[ts].week, Settings.tflag[ts].month, Settings.tflag[ts].dow, Settings.tflag[ts].hour, Settings.toffset[ts]);
|
|
}
|
|
|
|
void CmndTimeStd(void)
|
|
{
|
|
CmndTimeStdDst(0);
|
|
}
|
|
|
|
void CmndTimeDst(void)
|
|
{
|
|
CmndTimeStdDst(1);
|
|
}
|
|
|
|
void CmndAltitude(void)
|
|
{
|
|
if ((XdrvMailbox.data_len > 0) && ((XdrvMailbox.payload >= -30000) && (XdrvMailbox.payload <= 30000))) {
|
|
Settings.altitude = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.altitude);
|
|
}
|
|
|
|
void CmndLedPower(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_LEDS)) {
|
|
if (99 == pin[GPIO_LEDLNK]) { XdrvMailbox.index = 1; }
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
Settings.ledstate &= 8;
|
|
uint32_t mask = 1 << (XdrvMailbox.index -1);
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
led_power &= (0xFF ^ mask);
|
|
Settings.ledstate = 0;
|
|
break;
|
|
case 1:
|
|
led_power |= mask;
|
|
Settings.ledstate = 8;
|
|
break;
|
|
case 2:
|
|
led_power ^= mask;
|
|
Settings.ledstate ^= 8;
|
|
break;
|
|
}
|
|
blinks = 0;
|
|
if (99 == pin[GPIO_LEDLNK]) {
|
|
SetLedPower(Settings.ledstate &8);
|
|
} else {
|
|
SetLedPowerIdx(XdrvMailbox.index -1, (led_power & mask));
|
|
}
|
|
}
|
|
bool state = bitRead(led_power, XdrvMailbox.index -1);
|
|
if (99 == pin[GPIO_LEDLNK]) {
|
|
state = bitRead(Settings.ledstate, 3);
|
|
}
|
|
ResponseCmndIdxChar(GetStateText(state));
|
|
}
|
|
}
|
|
|
|
void CmndLedState(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_LED_OPTION)) {
|
|
Settings.ledstate = XdrvMailbox.payload;
|
|
if (!Settings.ledstate) {
|
|
SetLedPowerAll(0);
|
|
SetLedLink(0);
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.ledstate);
|
|
}
|
|
|
|
void CmndLedMask(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
Settings.ledmask = XdrvMailbox.payload;
|
|
}
|
|
char stemp1[TOPSZ];
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR("%d (0x%04X)"), Settings.ledmask, Settings.ledmask);
|
|
ResponseCmndChar(stemp1);
|
|
}
|
|
|
|
void CmndWifiPower(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
Settings.wifi_output_power = (uint8_t)(CharToFloat(XdrvMailbox.data) * 10);
|
|
if (Settings.wifi_output_power > 205) {
|
|
Settings.wifi_output_power = 205;
|
|
}
|
|
WifiSetOutputPower();
|
|
}
|
|
ResponseCmndChar(WifiGetOutputPower().c_str());
|
|
}
|
|
|
|
#ifdef USE_I2C
|
|
void CmndI2cScan(void)
|
|
{
|
|
if (i2c_flg) {
|
|
I2cScan(mqtt_data, sizeof(mqtt_data));
|
|
}
|
|
}
|
|
|
|
void CmndI2cDriver(void)
|
|
{
|
|
if (XdrvMailbox.index < MAX_I2C_DRIVERS) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
bitWrite(Settings.i2c_drivers[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1);
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_I2CDRIVER "\":"));
|
|
I2cDriverState();
|
|
ResponseJsonEnd();
|
|
}
|
|
#endif
|
|
|
|
void CmndSensor(void)
|
|
{
|
|
XsnsCall(FUNC_COMMAND_SENSOR);
|
|
}
|
|
|
|
void CmndDriver(void)
|
|
{
|
|
XdrvCall(FUNC_COMMAND_DRIVER);
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_crash_recorder.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_crash_recorder.ino"
|
|
const uint32_t crash_magic = 0x53415400;
|
|
const uint32_t crash_rtc_offset = 32;
|
|
const uint32_t crash_dump_max_len = 31;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end )
|
|
{
|
|
uint32_t addr_written = 0;
|
|
uint32_t value;
|
|
|
|
for (uint32_t i = stack; i < stack_end; i += 4) {
|
|
value = *((uint32_t*) i);
|
|
if ((value >= 0x40000000) && (value < 0x40300000)) {
|
|
ESP.rtcUserMemoryWrite(crash_rtc_offset + addr_written, (uint32_t*)&value, sizeof(value));
|
|
addr_written++;
|
|
if (addr_written >= crash_dump_max_len) { break; }
|
|
}
|
|
}
|
|
value = crash_magic + addr_written;
|
|
ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
|
|
}
|
|
|
|
|
|
void CmndCrash(void)
|
|
{
|
|
volatile uint32_t dummy;
|
|
dummy = *((uint32_t*) 0x00000000);
|
|
}
|
|
|
|
|
|
void CmndWDT(void)
|
|
{
|
|
volatile uint32_t dummy = 0;
|
|
while (1) {
|
|
dummy++;
|
|
}
|
|
}
|
|
|
|
|
|
void CmndBlockedLoop(void)
|
|
{
|
|
while (1) {
|
|
delay(1000);
|
|
}
|
|
}
|
|
|
|
|
|
void CrashDumpClear(void)
|
|
{
|
|
uint32_t value = 0;
|
|
ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CrashFlag(void)
|
|
{
|
|
return ((ResetReason() == REASON_EXCEPTION_RST) || (ResetReason() == REASON_SOFT_WDT_RST) || oswatch_blocked_loop);
|
|
}
|
|
|
|
void CrashDump(void)
|
|
{
|
|
ResponseAppend_P(PSTR("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":[\"%08x\",\"%08x\",\"%08x\"],\"EXCVADDR\":\"%08x\",\"DEPC\":\"%08x\""),
|
|
resetInfo.exccause,
|
|
GetResetReason().c_str(),
|
|
resetInfo.epc1,
|
|
resetInfo.epc2,
|
|
resetInfo.epc3,
|
|
resetInfo.excvaddr,
|
|
resetInfo.depc);
|
|
|
|
uint32_t value;
|
|
ESP.rtcUserMemoryRead(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
|
|
if (crash_magic == (value & 0xFFFFFF00)) {
|
|
ResponseAppend_P(PSTR(",\"CallChain\":["));
|
|
uint32_t count = value & 0x3F;
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
ESP.rtcUserMemoryRead(crash_rtc_offset +i, (uint32_t*)&value, sizeof(value));
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(PSTR("\"%08x\""), value);
|
|
}
|
|
ResponseAppend_P(PSTR("]"));
|
|
}
|
|
|
|
ResponseJsonEnd();
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_esptool.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_esptool.ino"
|
|
#define USE_ESPTOOL
|
|
#ifdef USE_ESPTOOL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define READ_REG(REG) (*((volatile uint32_t *)(REG)))
|
|
#define WRITE_REG(REG,VAL) *((volatile uint32_t *)(REG)) = (VAL)
|
|
#define REG_SET_MASK(reg,mask) WRITE_REG((reg), (READ_REG(reg)|(mask)))
|
|
|
|
#define SPI_BASE_REG 0x60000200
|
|
|
|
#define SPI_CMD_REG (SPI_BASE_REG + 0x00)
|
|
#define SPI_FLASH_RDSR (1<<27)
|
|
#define SPI_FLASH_SE (1<<24)
|
|
#define SPI_FLASH_BE (1<<23)
|
|
#define SPI_FLASH_WREN (1<<30)
|
|
|
|
#define SPI_ADDR_REG (SPI_BASE_REG + 0x04)
|
|
#define SPI_CTRL_REG (SPI_BASE_REG + 0x08)
|
|
#define SPI_RD_STATUS_REG (SPI_BASE_REG + 0x10)
|
|
#define SPI_W0_REG (SPI_BASE_REG + 0x40)
|
|
#define SPI_EXT2_REG (SPI_BASE_REG + 0xF8)
|
|
|
|
#define SPI_ST 0x7
|
|
|
|
|
|
#define SECTORS_PER_BLOCK (FLASH_BLOCK_SIZE / SPI_FLASH_SEC_SIZE)
|
|
|
|
|
|
static const uint32_t STATUS_WIP_BIT = (1 << 0);
|
|
|
|
|
|
inline static void spi_wait_ready(void)
|
|
{
|
|
while((READ_REG(SPI_EXT2_REG) & SPI_ST)) { }
|
|
}
|
|
|
|
|
|
|
|
static bool spiflash_is_ready(void)
|
|
{
|
|
spi_wait_ready();
|
|
WRITE_REG(SPI_RD_STATUS_REG, 0);
|
|
WRITE_REG(SPI_CMD_REG, SPI_FLASH_RDSR);
|
|
while(READ_REG(SPI_CMD_REG) != 0) { }
|
|
uint32_t status_value = READ_REG(SPI_RD_STATUS_REG);
|
|
return (status_value & STATUS_WIP_BIT) == 0;
|
|
}
|
|
|
|
static void spi_write_enable(void)
|
|
{
|
|
while(!spiflash_is_ready()) { }
|
|
WRITE_REG(SPI_CMD_REG, SPI_FLASH_WREN);
|
|
while(READ_REG(SPI_CMD_REG) != 0) { }
|
|
}
|
|
|
|
bool EsptoolEraseSector(uint32_t sector)
|
|
{
|
|
spi_write_enable();
|
|
spi_wait_ready();
|
|
|
|
WRITE_REG(SPI_ADDR_REG, (sector * SPI_FLASH_SEC_SIZE) & 0xffffff);
|
|
WRITE_REG(SPI_CMD_REG, SPI_FLASH_SE);
|
|
while(READ_REG(SPI_CMD_REG) != 0) { }
|
|
while(!spiflash_is_ready()) { }
|
|
|
|
return true;
|
|
}
|
|
|
|
void EsptoolErase(uint32_t start_sector, uint32_t end_sector)
|
|
{
|
|
int next_erase_sector = start_sector;
|
|
int remaining_erase_sector = end_sector - start_sector;
|
|
|
|
while (remaining_erase_sector > 0) {
|
|
spi_write_enable();
|
|
|
|
uint32_t command = SPI_FLASH_SE;
|
|
uint32_t sectors_to_erase = 1;
|
|
if (remaining_erase_sector >= SECTORS_PER_BLOCK &&
|
|
next_erase_sector % SECTORS_PER_BLOCK == 0) {
|
|
command = SPI_FLASH_BE;
|
|
sectors_to_erase = SECTORS_PER_BLOCK;
|
|
}
|
|
uint32_t addr = next_erase_sector * SPI_FLASH_SEC_SIZE;
|
|
|
|
spi_wait_ready();
|
|
WRITE_REG(SPI_ADDR_REG, addr & 0xffffff);
|
|
WRITE_REG(SPI_CMD_REG, command);
|
|
while(READ_REG(SPI_CMD_REG) != 0) { }
|
|
remaining_erase_sector -= sectors_to_erase;
|
|
next_erase_sector += sectors_to_erase;
|
|
|
|
while (!spiflash_is_ready()) { }
|
|
yield();
|
|
OsWatchLoop();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino"
|
|
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino"
|
|
void GetFeatures(void)
|
|
{
|
|
feature_drv1 = 0x00000000;
|
|
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
feature_drv1 |= 0x00000001;
|
|
#endif
|
|
#ifdef USE_LIGHT
|
|
feature_drv1 |= 0x00000002;
|
|
#endif
|
|
#ifdef USE_I2C
|
|
feature_drv1 |= 0x00000004;
|
|
#endif
|
|
#ifdef USE_SPI
|
|
feature_drv1 |= 0x00000008;
|
|
#endif
|
|
#ifdef USE_DISCOVERY
|
|
feature_drv1 |= 0x00000010;
|
|
#endif
|
|
#ifdef USE_ARDUINO_OTA
|
|
feature_drv1 |= 0x00000020;
|
|
#endif
|
|
#ifdef USE_MQTT_TLS
|
|
feature_drv1 |= 0x00000040;
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
feature_drv1 |= 0x00000080;
|
|
#endif
|
|
#ifdef WEBSERVER_ADVERTISE
|
|
feature_drv1 |= 0x00000100;
|
|
#endif
|
|
#ifdef USE_EMULATION_HUE
|
|
feature_drv1 |= 0x00000200;
|
|
#endif
|
|
#if (MQTT_LIBRARY_TYPE == MQTT_PUBSUBCLIENT)
|
|
feature_drv1 |= 0x00000400;
|
|
#endif
|
|
#if (MQTT_LIBRARY_TYPE == MQTT_TASMOTAMQTT)
|
|
|
|
#endif
|
|
#if (MQTT_LIBRARY_TYPE == MQTT_ESPMQTTARDUINO)
|
|
|
|
#endif
|
|
#ifdef MQTT_HOST_DISCOVERY
|
|
feature_drv1 |= 0x00002000;
|
|
#endif
|
|
#ifdef USE_ARILUX_RF
|
|
feature_drv1 |= 0x00004000;
|
|
#endif
|
|
#if defined(USE_LIGHT) && defined(USE_WS2812)
|
|
feature_drv1 |= 0x00008000;
|
|
#endif
|
|
#ifdef USE_WS2812_DMA
|
|
feature_drv1 |= 0x00010000;
|
|
#endif
|
|
#if defined(USE_IR_REMOTE) || defined(USE_IR_REMOTE_FULL)
|
|
feature_drv1 |= 0x00020000;
|
|
#endif
|
|
#ifdef USE_IR_HVAC
|
|
feature_drv1 |= 0x00040000;
|
|
#endif
|
|
#ifdef USE_IR_RECEIVE
|
|
feature_drv1 |= 0x00080000;
|
|
#endif
|
|
#ifdef USE_DOMOTICZ
|
|
feature_drv1 |= 0x00100000;
|
|
#endif
|
|
#ifdef USE_DISPLAY
|
|
feature_drv1 |= 0x00200000;
|
|
#endif
|
|
#ifdef USE_HOME_ASSISTANT
|
|
feature_drv1 |= 0x00400000;
|
|
#endif
|
|
#ifdef USE_SERIAL_BRIDGE
|
|
feature_drv1 |= 0x00800000;
|
|
#endif
|
|
#ifdef USE_TIMERS
|
|
feature_drv1 |= 0x01000000;
|
|
#endif
|
|
#ifdef USE_SUNRISE
|
|
feature_drv1 |= 0x02000000;
|
|
#endif
|
|
#ifdef USE_TIMERS_WEB
|
|
feature_drv1 |= 0x04000000;
|
|
#endif
|
|
#ifdef USE_RULES
|
|
feature_drv1 |= 0x08000000;
|
|
#endif
|
|
#ifdef USE_KNX
|
|
feature_drv1 |= 0x10000000;
|
|
#endif
|
|
#ifdef USE_WPS
|
|
feature_drv1 |= 0x20000000;
|
|
#endif
|
|
#ifdef USE_SMARTCONFIG
|
|
feature_drv1 |= 0x40000000;
|
|
#endif
|
|
#ifdef USE_ENERGY_POWER_LIMIT
|
|
feature_drv1 |= 0x80000000;
|
|
#endif
|
|
|
|
|
|
|
|
feature_drv2 = 0x00000000;
|
|
|
|
#ifdef USE_CONFIG_OVERRIDE
|
|
feature_drv2 |= 0x00000001;
|
|
#endif
|
|
#ifdef FIRMWARE_MINIMAL
|
|
feature_drv2 |= 0x00000002;
|
|
#endif
|
|
#ifdef FIRMWARE_SENSORS
|
|
feature_drv2 |= 0x00000004;
|
|
#endif
|
|
#ifdef FIRMWARE_CLASSIC
|
|
feature_drv2 |= 0x00000008;
|
|
#endif
|
|
#ifdef FIRMWARE_KNX_NO_EMULATION
|
|
feature_drv2 |= 0x00000010;
|
|
#endif
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
feature_drv2 |= 0x00000020;
|
|
#endif
|
|
#ifdef USE_DISPLAY_GRAPH
|
|
feature_drv2 |= 0x00000040;
|
|
#endif
|
|
#ifdef USE_DISPLAY_LCD
|
|
feature_drv2 |= 0x00000080;
|
|
#endif
|
|
#ifdef USE_DISPLAY_SSD1306
|
|
feature_drv2 |= 0x00000100;
|
|
#endif
|
|
#ifdef USE_DISPLAY_MATRIX
|
|
feature_drv2 |= 0x00000200;
|
|
#endif
|
|
#ifdef USE_DISPLAY_ILI9341
|
|
feature_drv2 |= 0x00000400;
|
|
#endif
|
|
#ifdef USE_DISPLAY_EPAPER_29
|
|
feature_drv2 |= 0x00000800;
|
|
#endif
|
|
#ifdef USE_DISPLAY_SH1106
|
|
feature_drv2 |= 0x00001000;
|
|
#endif
|
|
#ifdef USE_MP3_PLAYER
|
|
feature_drv2 |= 0x00002000;
|
|
#endif
|
|
#ifdef USE_PCA9685
|
|
feature_drv2 |= 0x00004000;
|
|
#endif
|
|
#if defined(USE_LIGHT) && defined(USE_TUYA_MCU)
|
|
feature_drv2 |= 0x00008000;
|
|
#endif
|
|
#ifdef USE_RC_SWITCH
|
|
feature_drv2 |= 0x00010000;
|
|
#endif
|
|
#if defined(USE_LIGHT) && defined(USE_ARMTRONIX_DIMMERS)
|
|
feature_drv2 |= 0x00020000;
|
|
#endif
|
|
#if defined(USE_LIGHT) && defined(USE_SM16716)
|
|
feature_drv2 |= 0x00040000;
|
|
#endif
|
|
#ifdef USE_SCRIPT
|
|
feature_drv2 |= 0x00080000;
|
|
#endif
|
|
#ifdef USE_EMULATION_WEMO
|
|
feature_drv2 |= 0x00100000;
|
|
#endif
|
|
#ifdef USE_SONOFF_IFAN
|
|
feature_drv2 |= 0x00200000;
|
|
#endif
|
|
#ifdef USE_ZIGBEE
|
|
feature_drv2 |= 0x00400000;
|
|
#endif
|
|
#ifdef NO_EXTRA_4K_HEAP
|
|
feature_drv2 |= 0x00800000;
|
|
#endif
|
|
#ifdef VTABLES_IN_IRAM
|
|
feature_drv2 |= 0x01000000;
|
|
#endif
|
|
#ifdef VTABLES_IN_DRAM
|
|
feature_drv2 |= 0x02000000;
|
|
#endif
|
|
#ifdef VTABLES_IN_FLASH
|
|
feature_drv2 |= 0x04000000;
|
|
#endif
|
|
#ifdef PIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
|
|
feature_drv2 |= 0x08000000;
|
|
#endif
|
|
#ifdef PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
|
|
feature_drv2 |= 0x10000000;
|
|
#endif
|
|
#ifdef PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
|
|
feature_drv2 |= 0x20000000;
|
|
#endif
|
|
#ifdef DEBUG_THEO
|
|
feature_drv2 |= 0x40000000;
|
|
#endif
|
|
#ifdef USE_DEBUG_DRIVER
|
|
feature_drv2 |= 0x80000000;
|
|
#endif
|
|
|
|
|
|
|
|
feature_sns1 = 0x00000000;
|
|
|
|
#ifdef USE_COUNTER
|
|
feature_sns1 |= 0x00000001;
|
|
#endif
|
|
#ifdef USE_ADC_VCC
|
|
feature_sns1 |= 0x00000002;
|
|
#endif
|
|
#ifdef USE_ENERGY_SENSOR
|
|
feature_sns1 |= 0x00000004;
|
|
#endif
|
|
#ifdef USE_PZEM004T
|
|
feature_sns1 |= 0x00000008;
|
|
#endif
|
|
#ifdef USE_DS18B20
|
|
feature_sns1 |= 0x00000010;
|
|
#endif
|
|
#ifdef USE_DS18x20_LEGACY
|
|
feature_sns1 |= 0x00000020;
|
|
#endif
|
|
#ifdef USE_DS18x20
|
|
feature_sns1 |= 0x00000040;
|
|
#endif
|
|
#ifdef USE_DHT
|
|
feature_sns1 |= 0x00000080;
|
|
#endif
|
|
#ifdef USE_SHT
|
|
feature_sns1 |= 0x00000100;
|
|
#endif
|
|
#ifdef USE_HTU
|
|
feature_sns1 |= 0x00000200;
|
|
#endif
|
|
#ifdef USE_BMP
|
|
feature_sns1 |= 0x00000400;
|
|
#endif
|
|
#ifdef USE_BME680
|
|
feature_sns1 |= 0x00000800;
|
|
#endif
|
|
#ifdef USE_BH1750
|
|
feature_sns1 |= 0x00001000;
|
|
#endif
|
|
#ifdef USE_VEML6070
|
|
feature_sns1 |= 0x00002000;
|
|
#endif
|
|
#ifdef USE_ADS1115_I2CDEV
|
|
feature_sns1 |= 0x00004000;
|
|
#endif
|
|
#ifdef USE_ADS1115
|
|
feature_sns1 |= 0x00008000;
|
|
#endif
|
|
#ifdef USE_INA219
|
|
feature_sns1 |= 0x00010000;
|
|
#endif
|
|
#ifdef USE_SHT3X
|
|
feature_sns1 |= 0x00020000;
|
|
#endif
|
|
#ifdef USE_MHZ19
|
|
feature_sns1 |= 0x00040000;
|
|
#endif
|
|
#ifdef USE_TSL2561
|
|
feature_sns1 |= 0x00080000;
|
|
#endif
|
|
#ifdef USE_SENSEAIR
|
|
feature_sns1 |= 0x00100000;
|
|
#endif
|
|
#ifdef USE_PMS5003
|
|
feature_sns1 |= 0x00200000;
|
|
#endif
|
|
#ifdef USE_MGS
|
|
feature_sns1 |= 0x00400000;
|
|
#endif
|
|
#ifdef USE_NOVA_SDS
|
|
feature_sns1 |= 0x00800000;
|
|
#endif
|
|
#ifdef USE_SGP30
|
|
feature_sns1 |= 0x01000000;
|
|
#endif
|
|
#ifdef USE_SR04
|
|
feature_sns1 |= 0x02000000;
|
|
#endif
|
|
#ifdef USE_SDM120
|
|
feature_sns1 |= 0x04000000;
|
|
#endif
|
|
#ifdef USE_SI1145
|
|
feature_sns1 |= 0x08000000;
|
|
#endif
|
|
#ifdef USE_SDM630
|
|
feature_sns1 |= 0x10000000;
|
|
#endif
|
|
#ifdef USE_LM75AD
|
|
feature_sns1 |= 0x20000000;
|
|
#endif
|
|
#ifdef USE_APDS9960
|
|
feature_sns1 |= 0x40000000;
|
|
#endif
|
|
#ifdef USE_TM1638
|
|
feature_sns1 |= 0x80000000;
|
|
#endif
|
|
|
|
|
|
|
|
feature_sns2 = 0x00000000;
|
|
|
|
#ifdef USE_MCP230xx
|
|
feature_sns2 |= 0x00000001;
|
|
#endif
|
|
#ifdef USE_MPR121
|
|
feature_sns2 |= 0x00000002;
|
|
#endif
|
|
#ifdef USE_CCS811
|
|
feature_sns2 |= 0x00000004;
|
|
#endif
|
|
#ifdef USE_MPU6050
|
|
feature_sns2 |= 0x00000008;
|
|
#endif
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
feature_sns2 |= 0x00000010;
|
|
#endif
|
|
#ifdef USE_MCP230xx_DISPLAYOUTPUT
|
|
feature_sns2 |= 0x00000020;
|
|
#endif
|
|
#ifdef USE_HLW8012
|
|
feature_sns2 |= 0x00000040;
|
|
#endif
|
|
#ifdef USE_CSE7766
|
|
feature_sns2 |= 0x00000080;
|
|
#endif
|
|
#ifdef USE_MCP39F501
|
|
feature_sns2 |= 0x00000100;
|
|
#endif
|
|
#ifdef USE_PZEM_AC
|
|
feature_sns2 |= 0x00000200;
|
|
#endif
|
|
#ifdef USE_DS3231
|
|
feature_sns2 |= 0x00000400;
|
|
#endif
|
|
#ifdef USE_HX711
|
|
feature_sns2 |= 0x00000800;
|
|
#endif
|
|
#ifdef USE_PZEM_DC
|
|
feature_sns2 |= 0x00001000;
|
|
#endif
|
|
#ifdef USE_TX20_WIND_SENSOR
|
|
feature_sns2 |= 0x00002000;
|
|
#endif
|
|
#ifdef USE_MGC3130
|
|
feature_sns2 |= 0x00004000;
|
|
#endif
|
|
#ifdef USE_RF_SENSOR
|
|
feature_sns2 |= 0x00008000;
|
|
#endif
|
|
#ifdef USE_THEO_V2
|
|
feature_sns2 |= 0x00010000;
|
|
#endif
|
|
#ifdef USE_ALECTO_V2
|
|
feature_sns2 |= 0x00020000;
|
|
#endif
|
|
#ifdef USE_AZ7798
|
|
feature_sns2 |= 0x00040000;
|
|
#endif
|
|
#ifdef USE_MAX31855
|
|
feature_sns2 |= 0x00080000;
|
|
#endif
|
|
#ifdef USE_PN532_HSU
|
|
feature_sns2 |= 0x00100000;
|
|
#endif
|
|
#ifdef USE_MAX44009
|
|
feature_sns2 |= 0x00200000;
|
|
#endif
|
|
#ifdef USE_SCD30
|
|
feature_sns2 |= 0x00400000;
|
|
#endif
|
|
#ifdef USE_HRE
|
|
feature_sns2 |= 0x00800000;
|
|
#endif
|
|
#ifdef USE_ADE7953
|
|
feature_sns2 |= 0x01000000;
|
|
#endif
|
|
#ifdef USE_SPS30
|
|
feature_sns2 |= 0x02000000;
|
|
#endif
|
|
#ifdef USE_VL53L0X
|
|
feature_sns2 |= 0x04000000;
|
|
#endif
|
|
#ifdef USE_MLX90614
|
|
feature_sns2 |= 0x08000000;
|
|
#endif
|
|
#ifdef USE_MAX31865
|
|
feature_sns2 |= 0x10000000;
|
|
#endif
|
|
#ifdef USE_CHIRP
|
|
feature_sns2 |= 0x20000000;
|
|
#endif
|
|
#ifdef USE_SOLAX_X1
|
|
feature_sns2 |= 0x40000000;
|
|
#endif
|
|
#ifdef USE_PAJ7620
|
|
feature_sns2 |= 0x80000000;
|
|
#endif
|
|
|
|
|
|
|
|
feature5 = 0x00000000;
|
|
|
|
#ifdef USE_BUZZER
|
|
feature5 |= 0x00000001;
|
|
#endif
|
|
#ifdef USE_RDM6300
|
|
feature5 |= 0x00000002;
|
|
#endif
|
|
#ifdef USE_IBEACON
|
|
feature5 |= 0x00000004;
|
|
#endif
|
|
#ifdef USE_SML_M
|
|
feature5 |= 0x00000008;
|
|
#endif
|
|
#ifdef USE_INA226
|
|
feature5 |= 0x00000010;
|
|
#endif
|
|
#ifdef USE_A4988_STEPPER
|
|
feature5 |= 0x00000020;
|
|
#endif
|
|
#ifdef USE_DDS2382
|
|
feature5 |= 0x00000040;
|
|
#endif
|
|
#ifdef USE_SM2135
|
|
feature5 |= 0x00000080;
|
|
#endif
|
|
#ifdef USE_SHUTTER
|
|
feature5 |= 0x00000100;
|
|
#endif
|
|
#ifdef USE_PCF8574
|
|
feature5 |= 0x00000200;
|
|
#endif
|
|
#ifdef USE_DDSU666
|
|
feature5 |= 0x00000400;
|
|
#endif
|
|
#ifdef USE_DEEPSLEEP
|
|
feature5 |= 0x00000800;
|
|
#endif
|
|
#ifdef USE_SONOFF_SC
|
|
feature5 |= 0x00001000;
|
|
#endif
|
|
#ifdef USE_SONOFF_RF
|
|
feature5 |= 0x00002000;
|
|
#endif
|
|
#ifdef USE_SONOFF_L1
|
|
feature5 |= 0x00004000;
|
|
#endif
|
|
#ifdef USE_EXS_DIMMER
|
|
feature5 |= 0x00008000;
|
|
#endif
|
|
#ifdef USE_ARDUINO_SLAVE
|
|
feature5 |= 0x00010000;
|
|
#endif
|
|
#ifdef USE_HIH6
|
|
feature5 |= 0x00020000;
|
|
#endif
|
|
#ifdef USE_HPMA
|
|
feature5 |= 0x00040000;
|
|
#endif
|
|
#ifdef USE_TSL2591
|
|
feature5 |= 0x00080000;
|
|
#endif
|
|
#ifdef USE_DHT12
|
|
feature5 |= 0x00100000;
|
|
#endif
|
|
#ifdef USE_DS1624
|
|
feature5 |= 0x00200000;
|
|
#endif
|
|
#ifdef USE_GPS
|
|
feature5 |= 0x00400000;
|
|
#endif
|
|
#ifdef USE_HOTPLUG
|
|
feature5 |= 0x00800000;
|
|
#endif
|
|
#ifdef USE_NRF24
|
|
feature5 |= 0x01000000;
|
|
#endif
|
|
#ifdef USE_MIBLE
|
|
feature5 |= 0x02000000;
|
|
#endif
|
|
#ifdef USE_HM10
|
|
feature5 |= 0x04000000;
|
|
#endif
|
|
#ifdef USE_LE01MR
|
|
feature5 |= 0x08000000;
|
|
#endif
|
|
#ifdef USE_AHT10
|
|
feature5 |= 0x10000000;
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
feature6 = 0x00000000;
|
|
# 568 "C:/shared/sonoff/Git/Tasmota/tasmota/support_features.ino"
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino"
|
|
# 39 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino"
|
|
#ifdef USE_FLOG
|
|
|
|
class FLOG
|
|
|
|
#define MAGIC_WORD_FL 0x464c
|
|
|
|
{
|
|
|
|
struct header_t{
|
|
uint16_t magic_word;
|
|
uint16_t padding;
|
|
uint32_t physical_start_sector:10;
|
|
uint32_t number:10;
|
|
uint32_t buf_pointer:12;
|
|
};
|
|
|
|
private:
|
|
void _readSector(uint8_t one_sector);
|
|
void _eraseSector(uint8_t one_sector);
|
|
void _writeSector(uint8_t one_sector);
|
|
void _clearBuffer(void);
|
|
void _searchSaves(void);
|
|
void _findFirstErasedSector(void);
|
|
void _showBuffer(void);
|
|
void _initBuffer(void);
|
|
void _saveBufferToSector(void);
|
|
header_t _saved_header;
|
|
|
|
public:
|
|
uint32_t size;
|
|
uint32_t start;
|
|
uint32_t end;
|
|
uint16_t num_sectors;
|
|
|
|
uint16_t first_erased_sector;
|
|
uint16_t current_sector;
|
|
|
|
uint16_t bytes_left;
|
|
uint16_t sectors_left;
|
|
|
|
uint8_t mode = 0;
|
|
bool found_saved_data = false;
|
|
bool ready = false;
|
|
bool running_download = false;
|
|
bool recording = false;
|
|
|
|
union sector_t{
|
|
uint32_t dword_buffer[FLASH_SECTOR_SIZE/4];
|
|
uint8_t byte_buffer[FLASH_SECTOR_SIZE];
|
|
header_t header;
|
|
} sector;
|
|
|
|
void init(void);
|
|
void addToBuffer(uint8_t src[], uint32_t size);
|
|
void startRecording(bool append);
|
|
void stopRecording(void);
|
|
|
|
typedef void (*CallbackNoArgs) ();
|
|
typedef void (*CallbackWithArgs) (uint8_t *_record);
|
|
|
|
void startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter);
|
|
};
|
|
|
|
extern "C" uint32_t _SPIFFS_start;
|
|
extern "C" uint32_t _FS_start;
|
|
|
|
|
|
|
|
|
|
void FLOG::init(void)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: init ..."));
|
|
size = ESP.getSketchSize();
|
|
|
|
start = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2)
|
|
end = (uint32_t)&_SPIFFS_start - 0x40200000;
|
|
#else
|
|
end = (uint32_t)&_FS_start - 0x40200000;
|
|
#endif
|
|
num_sectors = (end - start)/FLASH_SECTOR_SIZE;
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: size: 0x%lx, start: 0x%lx, end: 0x%lx, num_sectors(dec): %lu"), size, start, end, num_sectors );
|
|
_findFirstErasedSector();
|
|
if(first_erased_sector == 0xffff){
|
|
_eraseSector(0);
|
|
first_erased_sector = 0;
|
|
}
|
|
_searchSaves();
|
|
_initBuffer();
|
|
ready = true;
|
|
}
|
|
# 142 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino"
|
|
void FLOG::_readSector(uint8_t one_sector){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: read sector number: %u" ), one_sector);
|
|
ESP.flashRead(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§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; i<num_sectors; i++){
|
|
bool success = true;
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: read sector: %u"), i);
|
|
_readSector(i);
|
|
for (uint32_t j = 0; j<(sizeof(sector.dword_buffer)/4); j++){
|
|
if(sector.dword_buffer[j]!=0xffffffff){
|
|
|
|
success = false;
|
|
}
|
|
}
|
|
if(success){
|
|
first_erased_sector = i;
|
|
sector.header.physical_start_sector = i;
|
|
current_sector = i;
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: first erased sector: %u, now init ..."), first_erased_sector);
|
|
return;
|
|
}
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: no erased sector found"));
|
|
first_erased_sector = 0xffff;
|
|
}
|
|
|
|
|
|
|
|
|
|
void FLOG::_searchSaves(void){
|
|
|
|
found_saved_data = false;
|
|
uint32_t s;
|
|
if(first_erased_sector==0){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: sector 0 was erased before, examine sector: %u"), num_sectors);
|
|
s = num_sectors;
|
|
}
|
|
else{
|
|
s = first_erased_sector-1;
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: examine sector: %u"), s);
|
|
}
|
|
_readSector(s);
|
|
if(sector.header.magic_word!=MAGIC_WORD_FL){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
|
|
return;
|
|
}
|
|
sectors_left = sector.header.number + 1;
|
|
_saved_header = sector.header;
|
|
s = sector.header.physical_start_sector;
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: will check pysical start sector: %u"), s);
|
|
_readSector(s);
|
|
_showBuffer();
|
|
if(sector.header.magic_word!=MAGIC_WORD_FL){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
|
|
sectors_left = 0;
|
|
return;
|
|
}
|
|
if(sector.header.number==0){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: possible saved data found"));
|
|
found_saved_data = true;
|
|
}
|
|
else{
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: number: %u should be 0"), sector.header.number);
|
|
sectors_left = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void FLOG::_initBuffer(void){
|
|
if(!found_saved_data){
|
|
sector.header.physical_start_sector = (uint16_t)first_erased_sector;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: init header"));
|
|
sector.header.magic_word = MAGIC_WORD_FL;
|
|
sector.header.number = 0;
|
|
sector.header.buf_pointer = (uint16_t)sizeof(sector.header);
|
|
current_sector = first_erased_sector;
|
|
ready = true;
|
|
_clearBuffer();
|
|
}
|
|
|
|
|
|
|
|
|
|
void FLOG::_showBuffer(void){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: Header: %c %c"), sector.byte_buffer[0],sector.byte_buffer[1]);
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: V_Start_sector: %u, sector number: %u, pointer: %u "), sector.header.physical_start_sector, sector.header.number, sector.header.buf_pointer);
|
|
uint32_t j = 0;
|
|
for (uint32_t i = sector.header.buf_pointer-16; i<(sizeof(sector.byte_buffer)); i+=8){
|
|
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: buffer: %u %u %u %u %u %u %u %u "), sector.byte_buffer[i], sector.byte_buffer[i+1], sector.byte_buffer[i+2], sector.byte_buffer[i+3], sector.byte_buffer[i+4], sector.byte_buffer[i+5], sector.byte_buffer[i+6], sector.byte_buffer[i+7]);
|
|
j++;
|
|
if(j>3){
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void FLOG::addToBuffer(uint8_t src[], uint32_t size){
|
|
if(mode == 0){
|
|
if(sector.header.number == num_sectors && !ready){
|
|
return;
|
|
}
|
|
}
|
|
if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){
|
|
|
|
|
|
|
|
memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size);
|
|
sector.header.buf_pointer+=size;
|
|
|
|
}
|
|
else{
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: save buffer to flash sector: %u"), current_sector);
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer);
|
|
_saveBufferToSector();
|
|
sectors_left++;
|
|
|
|
if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){
|
|
memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size);
|
|
sector.header.buf_pointer+=size;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void FLOG::startRecording(bool append){
|
|
if(recording){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: already recording"));
|
|
return;
|
|
}
|
|
recording = true;
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: start recording"));
|
|
_initBuffer();
|
|
if(!found_saved_data) {
|
|
append = false;
|
|
}
|
|
if(append){
|
|
sector.header.number = _saved_header.number+1;
|
|
sector.header.physical_start_sector = _saved_header.physical_start_sector;
|
|
}
|
|
else{
|
|
sector.header.physical_start_sector = (uint16_t)first_erased_sector;
|
|
found_saved_data = false;
|
|
sectors_left = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FLOG::stopRecording(void){
|
|
_saveBufferToSector();
|
|
_findFirstErasedSector();
|
|
_searchSaves();
|
|
_initBuffer();
|
|
recording = false;
|
|
found_saved_data = true;
|
|
}
|
|
# 381 "C:/shared/sonoff/Git/Tasmota/tasmota/support_flash_log.ino"
|
|
void FLOG::startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter){
|
|
|
|
_readSector(sector.header.physical_start_sector);
|
|
uint32_t next_sector = sector.header.physical_start_sector;
|
|
bytes_left = sector.header.buf_pointer - sizeof(sector.header);
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: create file for download, will process %u bytes"), bytes_left);
|
|
running_download = true;
|
|
|
|
sendHeader();
|
|
|
|
while(sectors_left){
|
|
DEBUG_SENSOR_LOG(PSTR("FLOG: next sector: %u"), next_sector);
|
|
|
|
uint32_t k = sizeof(sector.header);
|
|
while(bytes_left){
|
|
|
|
uint8_t *_record_start = (uint8_t*)§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.h>
|
|
|
|
Ticker TickerRtc;
|
|
|
|
static const uint8_t kDaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
|
static const char kMonthNamesEnglish[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
|
|
|
|
struct RTC {
|
|
uint32_t utc_time = 0;
|
|
uint32_t local_time = 0;
|
|
uint32_t daylight_saving_time = 0;
|
|
uint32_t standard_time = 0;
|
|
uint32_t ntp_time = 0;
|
|
uint32_t midnight = 0;
|
|
uint32_t restart_time = 0;
|
|
int32_t drift_time = 0;
|
|
int32_t time_timezone = 0;
|
|
uint8_t ntp_sync_minute = 0;
|
|
bool midnight_now = false;
|
|
bool user_time_entry = false;
|
|
} Rtc;
|
|
|
|
uint32_t UtcTime(void)
|
|
{
|
|
return Rtc.utc_time;
|
|
}
|
|
|
|
uint32_t LocalTime(void)
|
|
{
|
|
return Rtc.local_time;
|
|
}
|
|
|
|
int32_t DriftTime(void)
|
|
{
|
|
return Rtc.drift_time;
|
|
}
|
|
|
|
uint32_t Midnight(void)
|
|
{
|
|
return Rtc.midnight;
|
|
}
|
|
|
|
bool MidnightNow(void)
|
|
{
|
|
if (Rtc.midnight_now) {
|
|
Rtc.midnight_now = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsDst(void)
|
|
{
|
|
if (Rtc.time_timezone == Settings.toffset[1]) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String GetBuildDateAndTime(void)
|
|
{
|
|
|
|
char bdt[21];
|
|
char *p;
|
|
char mdate[] = __DATE__;
|
|
char *smonth = mdate;
|
|
int day = 0;
|
|
int year = 0;
|
|
|
|
|
|
uint8_t i = 0;
|
|
for (char *str = strtok_r(mdate, " ", &p); str && i < 3; str = strtok_r(nullptr, " ", &p)) {
|
|
switch (i++) {
|
|
case 0:
|
|
smonth = str;
|
|
break;
|
|
case 1:
|
|
day = atoi(str);
|
|
break;
|
|
case 2:
|
|
year = atoi(str);
|
|
}
|
|
}
|
|
int month = (strstr(kMonthNamesEnglish, smonth) -kMonthNamesEnglish) /3 +1;
|
|
snprintf_P(bdt, sizeof(bdt), PSTR("%d" D_YEAR_MONTH_SEPARATOR "%02d" D_MONTH_DAY_SEPARATOR "%02d" D_DATE_TIME_SEPARATOR "%s"), year, month, day, __TIME__);
|
|
return String(bdt);
|
|
}
|
|
|
|
String GetMinuteTime(uint32_t minutes)
|
|
{
|
|
char tm[6];
|
|
snprintf_P(tm, sizeof(tm), PSTR("%02d:%02d"), minutes / 60, minutes % 60);
|
|
|
|
return String(tm);
|
|
}
|
|
|
|
String GetTimeZone(void)
|
|
{
|
|
char tz[7];
|
|
snprintf_P(tz, sizeof(tz), PSTR("%+03d:%02d"), Rtc.time_timezone / 60, abs(Rtc.time_timezone % 60));
|
|
|
|
return String(tz);
|
|
}
|
|
|
|
String GetDuration(uint32_t time)
|
|
{
|
|
char dt[16];
|
|
|
|
TIME_T ut;
|
|
BreakTime(time, ut);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
snprintf_P(dt, sizeof(dt), PSTR("%dT%02d:%02d:%02d"), ut.days, ut.hour, ut.minute, ut.second);
|
|
|
|
return String(dt);
|
|
}
|
|
|
|
String GetDT(uint32_t time)
|
|
{
|
|
|
|
|
|
char dt[20];
|
|
TIME_T tmpTime;
|
|
|
|
BreakTime(time, tmpTime);
|
|
snprintf_P(dt, sizeof(dt), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"),
|
|
tmpTime.year +1970, tmpTime.month, tmpTime.day_of_month, tmpTime.hour, tmpTime.minute, tmpTime.second);
|
|
|
|
return String(dt);
|
|
}
|
|
# 181 "C:/shared/sonoff/Git/Tasmota/tasmota/support_rtc.ino"
|
|
String GetDateAndTime(uint8_t time_type)
|
|
{
|
|
|
|
uint32_t time = Rtc.local_time;
|
|
|
|
switch (time_type) {
|
|
case DT_BOOTCOUNT:
|
|
time = Settings.bootcount_reset_time;
|
|
break;
|
|
case DT_ENERGY:
|
|
time = Settings.energy_kWhtotal_time;
|
|
break;
|
|
case DT_UTC:
|
|
time = Rtc.utc_time;
|
|
break;
|
|
case DT_RESTART:
|
|
if (Rtc.restart_time == 0) {
|
|
return "";
|
|
}
|
|
time = Rtc.restart_time;
|
|
break;
|
|
}
|
|
String dt = GetDT(time);
|
|
if (Settings.flag3.time_append_timezone && (DT_LOCAL == time_type)) {
|
|
dt += GetTimeZone();
|
|
}
|
|
return dt;
|
|
}
|
|
|
|
String GetTime(int type)
|
|
{
|
|
|
|
|
|
|
|
|
|
char stime[25];
|
|
|
|
uint32_t time = Rtc.utc_time;
|
|
if (1 == type) time = Rtc.local_time;
|
|
if (2 == type) time = Rtc.daylight_saving_time;
|
|
if (3 == type) time = Rtc.standard_time;
|
|
snprintf_P(stime, sizeof(stime), sntp_get_real_time(time));
|
|
|
|
return String(stime);
|
|
}
|
|
|
|
uint32_t UpTime(void)
|
|
{
|
|
if (Rtc.restart_time) {
|
|
return Rtc.utc_time - Rtc.restart_time;
|
|
} else {
|
|
return uptime;
|
|
}
|
|
}
|
|
|
|
uint32_t MinutesUptime(void)
|
|
{
|
|
return (UpTime() / 60);
|
|
}
|
|
|
|
String GetUptime(void)
|
|
{
|
|
return GetDuration(UpTime());
|
|
}
|
|
|
|
uint32_t MinutesPastMidnight(void)
|
|
{
|
|
uint32_t minutes = 0;
|
|
|
|
if (RtcTime.valid) {
|
|
minutes = (RtcTime.hour *60) + RtcTime.minute;
|
|
}
|
|
return minutes;
|
|
}
|
|
|
|
void BreakTime(uint32_t time_input, TIME_T &tm)
|
|
{
|
|
|
|
|
|
|
|
|
|
uint8_t year;
|
|
uint8_t month;
|
|
uint8_t month_length;
|
|
uint32_t time;
|
|
unsigned long days;
|
|
|
|
time = time_input;
|
|
tm.second = time % 60;
|
|
time /= 60;
|
|
tm.minute = time % 60;
|
|
time /= 60;
|
|
tm.hour = time % 24;
|
|
time /= 24;
|
|
tm.days = time;
|
|
tm.day_of_week = ((time + 4) % 7) + 1;
|
|
|
|
year = 0;
|
|
days = 0;
|
|
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
|
|
year++;
|
|
}
|
|
tm.year = year;
|
|
|
|
days -= LEAP_YEAR(year) ? 366 : 365;
|
|
time -= days;
|
|
tm.day_of_year = time;
|
|
|
|
for (month = 0; month < 12; month++) {
|
|
if (1 == month) {
|
|
if (LEAP_YEAR(year)) {
|
|
month_length = 29;
|
|
} else {
|
|
month_length = 28;
|
|
}
|
|
} else {
|
|
month_length = kDaysInMonth[month];
|
|
}
|
|
|
|
if (time >= month_length) {
|
|
time -= month_length;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
strlcpy(tm.name_of_month, kMonthNames + (month *3), 4);
|
|
tm.month = month + 1;
|
|
tm.day_of_month = time + 1;
|
|
tm.valid = (time_input > START_VALID_TIME);
|
|
}
|
|
|
|
uint32_t MakeTime(TIME_T &tm)
|
|
{
|
|
|
|
|
|
|
|
int i;
|
|
uint32_t seconds;
|
|
|
|
|
|
seconds = tm.year * (SECS_PER_DAY * 365);
|
|
for (i = 0; i < tm.year; i++) {
|
|
if (LEAP_YEAR(i)) {
|
|
seconds += SECS_PER_DAY;
|
|
}
|
|
}
|
|
|
|
|
|
for (i = 1; i < tm.month; i++) {
|
|
if ((2 == i) && LEAP_YEAR(tm.year)) {
|
|
seconds += SECS_PER_DAY * 29;
|
|
} else {
|
|
seconds += SECS_PER_DAY * kDaysInMonth[i-1];
|
|
}
|
|
}
|
|
seconds+= (tm.day_of_month - 1) * SECS_PER_DAY;
|
|
seconds+= tm.hour * SECS_PER_HOUR;
|
|
seconds+= tm.minute * SECS_PER_MIN;
|
|
seconds+= tm.second;
|
|
return seconds;
|
|
}
|
|
|
|
uint32_t RuleToTime(TimeRule r, int yr)
|
|
{
|
|
TIME_T tm;
|
|
uint32_t t;
|
|
uint8_t m;
|
|
uint8_t w;
|
|
|
|
m = r.month;
|
|
w = r.week;
|
|
if (0 == w) {
|
|
if (++m > 12) {
|
|
m = 1;
|
|
yr++;
|
|
}
|
|
w = 1;
|
|
}
|
|
|
|
tm.hour = r.hour;
|
|
tm.minute = 0;
|
|
tm.second = 0;
|
|
tm.day_of_month = 1;
|
|
tm.month = m;
|
|
tm.year = yr - 1970;
|
|
t = MakeTime(tm);
|
|
BreakTime(t, tm);
|
|
t += (7 * (w - 1) + (r.dow - tm.day_of_week + 7) % 7) * SECS_PER_DAY;
|
|
if (0 == r.week) {
|
|
t -= 7 * SECS_PER_DAY;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
void RtcSecond(void)
|
|
{
|
|
TIME_T tmpTime;
|
|
|
|
if (!Rtc.user_time_entry && !global_state.wifi_down) {
|
|
uint8_t uptime_minute = (uptime / 60) % 60;
|
|
if ((Rtc.ntp_sync_minute > 59) && (uptime_minute > 2)) {
|
|
Rtc.ntp_sync_minute = 1;
|
|
}
|
|
uint8_t offset = (uptime < 30) ? RtcTime.second : (((ESP.getChipId() & 0xF) * 3) + 3) ;
|
|
if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) ||
|
|
(Rtc.ntp_sync_minute == uptime_minute))) ||
|
|
ntp_force_sync ) ) {
|
|
Rtc.ntp_time = sntp_get_current_timestamp();
|
|
if (Rtc.ntp_time > START_VALID_TIME) {
|
|
ntp_force_sync = false;
|
|
if (Rtc.utc_time > START_VALID_TIME) { Rtc.drift_time = Rtc.ntp_time - Rtc.utc_time; }
|
|
Rtc.utc_time = Rtc.ntp_time;
|
|
Rtc.ntp_sync_minute = 60;
|
|
if (Rtc.restart_time == 0) {
|
|
Rtc.restart_time = Rtc.utc_time - uptime;
|
|
}
|
|
BreakTime(Rtc.utc_time, tmpTime);
|
|
RtcTime.year = tmpTime.year + 1970;
|
|
Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year);
|
|
Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year);
|
|
|
|
|
|
PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: Drift %d, (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
|
|
DriftTime(), GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str());
|
|
|
|
if (Rtc.local_time < START_VALID_TIME) {
|
|
rules_flag.time_init = 1;
|
|
} else {
|
|
rules_flag.time_set = 1;
|
|
}
|
|
} else {
|
|
Rtc.ntp_sync_minute++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Rtc.utc_time++;
|
|
Rtc.local_time = Rtc.utc_time;
|
|
if (Rtc.local_time > START_VALID_TIME) {
|
|
int16_t timezone_minutes = Settings.timezone_minutes;
|
|
if (Settings.timezone < 0) { timezone_minutes *= -1; }
|
|
Rtc.time_timezone = (Settings.timezone * SECS_PER_HOUR) + (timezone_minutes * SECS_PER_MIN);
|
|
if (99 == Settings.timezone) {
|
|
int32_t dstoffset = Settings.toffset[1] * SECS_PER_MIN;
|
|
int32_t stdoffset = Settings.toffset[0] * SECS_PER_MIN;
|
|
if (Settings.tflag[1].hemis) {
|
|
|
|
if ((Rtc.utc_time >= (Rtc.standard_time - dstoffset)) && (Rtc.utc_time < (Rtc.daylight_saving_time - stdoffset))) {
|
|
Rtc.time_timezone = stdoffset;
|
|
} else {
|
|
Rtc.time_timezone = dstoffset;
|
|
}
|
|
} else {
|
|
|
|
if ((Rtc.utc_time >= (Rtc.daylight_saving_time - stdoffset)) && (Rtc.utc_time < (Rtc.standard_time - dstoffset))) {
|
|
Rtc.time_timezone = dstoffset;
|
|
} else {
|
|
Rtc.time_timezone = stdoffset;
|
|
}
|
|
}
|
|
}
|
|
Rtc.local_time += Rtc.time_timezone;
|
|
Rtc.time_timezone /= 60;
|
|
if (!Settings.energy_kWhtotal_time) {
|
|
Settings.energy_kWhtotal_time = Rtc.local_time;
|
|
}
|
|
if (Settings.bootcount_reset_time < START_VALID_TIME) {
|
|
Settings.bootcount_reset_time = Rtc.local_time;
|
|
}
|
|
}
|
|
|
|
BreakTime(Rtc.local_time, RtcTime);
|
|
if (RtcTime.valid) {
|
|
if (!Rtc.midnight) {
|
|
Rtc.midnight = Rtc.local_time - (RtcTime.hour * 3600) - (RtcTime.minute * 60) - RtcTime.second;
|
|
}
|
|
if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) {
|
|
Rtc.midnight = Rtc.local_time;
|
|
Rtc.midnight_now = true;
|
|
}
|
|
}
|
|
|
|
RtcTime.year += 1970;
|
|
}
|
|
|
|
void RtcSetTime(uint32_t epoch)
|
|
{
|
|
if (epoch < START_VALID_TIME) {
|
|
Rtc.user_time_entry = false;
|
|
ntp_force_sync = true;
|
|
sntp_init();
|
|
} else {
|
|
sntp_stop();
|
|
Rtc.user_time_entry = true;
|
|
Rtc.utc_time = epoch -1;
|
|
}
|
|
RtcSecond();
|
|
}
|
|
|
|
void RtcInit(void)
|
|
{
|
|
sntp_setservername(0, SettingsText(SET_NTPSERVER1));
|
|
sntp_setservername(1, SettingsText(SET_NTPSERVER2));
|
|
sntp_setservername(2, SettingsText(SET_NTPSERVER3));
|
|
sntp_stop();
|
|
sntp_set_timezone(0);
|
|
sntp_init();
|
|
Rtc.utc_time = 0;
|
|
BreakTime(Rtc.utc_time, RtcTime);
|
|
TickerRtc.attach(1, RtcSecond);
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_static_buffer.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_static_buffer.ino"
|
|
typedef struct SBuffer_impl {
|
|
uint16_t size;
|
|
uint16_t len;
|
|
uint8_t buf[];
|
|
} SBuffer_impl;
|
|
|
|
|
|
|
|
typedef class SBuffer {
|
|
|
|
protected:
|
|
SBuffer(void) {
|
|
|
|
}
|
|
|
|
public:
|
|
SBuffer(const size_t size) {
|
|
_buf = (SBuffer_impl*) new char[size+4];
|
|
_buf->size = size;
|
|
_buf->len = 0;
|
|
|
|
}
|
|
|
|
inline size_t getSize(void) const { return _buf->size; }
|
|
inline size_t size(void) const { return _buf->size; }
|
|
inline size_t getLen(void) const { return _buf->len; }
|
|
inline size_t len(void) const { return _buf->len; }
|
|
inline uint8_t *getBuffer(void) const { return _buf->buf; }
|
|
inline uint8_t *buf(size_t i = 0) const { return &_buf->buf[i]; }
|
|
inline char *charptr(size_t i = 0) const { return (char*) &_buf->buf[i]; }
|
|
|
|
virtual ~SBuffer(void) {
|
|
delete[] _buf;
|
|
}
|
|
|
|
inline void setLen(const size_t len) {
|
|
uint16_t old_len = _buf->len;
|
|
_buf->len = (len <= _buf->size) ? len : _buf->size;
|
|
if (old_len < _buf->len) {
|
|
memset((void*) &_buf->buf[old_len], 0, _buf->len - old_len);
|
|
}
|
|
}
|
|
|
|
void set8(const size_t offset, const uint8_t data) {
|
|
if (offset < _buf->len) {
|
|
_buf->buf[offset] = data;
|
|
}
|
|
}
|
|
|
|
size_t add8(const uint8_t data) {
|
|
if (_buf->len < _buf->size) {
|
|
_buf->buf[_buf->len++] = data;
|
|
}
|
|
return _buf->len;
|
|
}
|
|
size_t add16(const uint16_t data) {
|
|
if (_buf->len < _buf->size - 1) {
|
|
_buf->buf[_buf->len++] = data;
|
|
_buf->buf[_buf->len++] = data >> 8;
|
|
}
|
|
return _buf->len;
|
|
}
|
|
size_t add32(const uint32_t data) {
|
|
if (_buf->len < _buf->size - 3) {
|
|
_buf->buf[_buf->len++] = data;
|
|
_buf->buf[_buf->len++] = data >> 8;
|
|
_buf->buf[_buf->len++] = data >> 16;
|
|
_buf->buf[_buf->len++] = data >> 24;
|
|
}
|
|
return _buf->len;
|
|
}
|
|
size_t add64(const uint64_t data) {
|
|
if (_buf->len < _buf->size - 7) {
|
|
_buf->buf[_buf->len++] = data;
|
|
_buf->buf[_buf->len++] = data >> 8;
|
|
_buf->buf[_buf->len++] = data >> 16;
|
|
_buf->buf[_buf->len++] = data >> 24;
|
|
_buf->buf[_buf->len++] = data >> 32;
|
|
_buf->buf[_buf->len++] = data >> 40;
|
|
_buf->buf[_buf->len++] = data >> 48;
|
|
_buf->buf[_buf->len++] = data >> 56;
|
|
}
|
|
return _buf->len;
|
|
}
|
|
|
|
size_t addBuffer(const SBuffer &buf2) {
|
|
if (len() + buf2.len() <= size()) {
|
|
for (uint32_t i = 0; i < buf2.len(); i++) {
|
|
_buf->buf[_buf->len++] = buf2.buf()[i];
|
|
}
|
|
}
|
|
return _buf->len;
|
|
}
|
|
|
|
size_t addBuffer(const uint8_t *buf2, size_t len2) {
|
|
if (len() + len2 <= size()) {
|
|
for (uint32_t i = 0; i < len2; i++) {
|
|
_buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]);
|
|
}
|
|
}
|
|
return _buf->len;
|
|
}
|
|
|
|
size_t addBuffer(const char *buf2, size_t len2) {
|
|
if (len() + len2 <= size()) {
|
|
for (uint32_t i = 0; i < len2; i++) {
|
|
_buf->buf[_buf->len++] = pgm_read_byte(&buf2[i]);
|
|
}
|
|
}
|
|
return _buf->len;
|
|
}
|
|
|
|
uint8_t get8(size_t offset) const {
|
|
if (offset < _buf->len) {
|
|
return _buf->buf[offset];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
uint8_t read8(const size_t offset) const {
|
|
if (offset < len()) {
|
|
return _buf->buf[offset];
|
|
}
|
|
return 0;
|
|
}
|
|
uint16_t get16(const size_t offset) const {
|
|
if (offset < len() - 1) {
|
|
return _buf->buf[offset] | (_buf->buf[offset+1] << 8);
|
|
}
|
|
return 0;
|
|
}
|
|
uint32_t get32(const size_t offset) const {
|
|
if (offset < len() - 3) {
|
|
return _buf->buf[offset] | (_buf->buf[offset+1] << 8) |
|
|
(_buf->buf[offset+2] << 16) | (_buf->buf[offset+3] << 24);
|
|
}
|
|
return 0;
|
|
}
|
|
uint64_t get64(const size_t offset) const {
|
|
if (offset < len() - 7) {
|
|
return (uint64_t)_buf->buf[offset] | ((uint64_t)_buf->buf[offset+1] << 8) |
|
|
((uint64_t)_buf->buf[offset+2] << 16) | ((uint64_t)_buf->buf[offset+3] << 24) |
|
|
((uint64_t)_buf->buf[offset+4] << 32) | ((uint64_t)_buf->buf[offset+5] << 40) |
|
|
((uint64_t)_buf->buf[offset+6] << 48) | ((uint64_t)_buf->buf[offset+7] << 56);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
inline size_t strlen(const size_t offset) const {
|
|
return strnlen((const char*) &_buf->buf[offset], len() - offset);
|
|
}
|
|
|
|
size_t strlen_s(const size_t offset) const {
|
|
size_t slen = this->strlen(offset);
|
|
if (slen == len() - offset) {
|
|
return 0;
|
|
} else {
|
|
return slen;
|
|
}
|
|
}
|
|
|
|
SBuffer subBuffer(const size_t start, size_t len) const {
|
|
if (start >= _buf->len) {
|
|
len = 0;
|
|
} else if (start + len > _buf->len) {
|
|
len = _buf->len - start;
|
|
}
|
|
|
|
SBuffer buf2(len);
|
|
memcpy(buf2.buf(), buf()+start, len);
|
|
buf2._buf->len = len;
|
|
return buf2;
|
|
}
|
|
|
|
static SBuffer SBufferFromHex(const char *hex, size_t len) {
|
|
size_t buf_len = (len + 3) / 2;
|
|
SBuffer buf2(buf_len);
|
|
uint8_t val;
|
|
|
|
for (; len > 1; len -= 2) {
|
|
val = asc2byte(*hex++) << 4;
|
|
val |= asc2byte(*hex++);
|
|
buf2.add8(val);
|
|
}
|
|
return buf2;
|
|
}
|
|
|
|
protected:
|
|
|
|
static uint8_t asc2byte(char chr) {
|
|
uint8_t rVal = 0;
|
|
if (isdigit(chr)) { rVal = chr - '0'; }
|
|
else if (chr >= 'A' && chr <= 'F') { rVal = chr + 10 - 'A'; }
|
|
else if (chr >= 'a' && chr <= 'f') { rVal = chr + 10 - 'a'; }
|
|
return rVal;
|
|
}
|
|
|
|
static void unHex(const char* in, uint8_t *out, size_t len) {
|
|
}
|
|
|
|
protected:
|
|
SBuffer_impl * _buf;
|
|
|
|
} SBuffer;
|
|
|
|
typedef class PreAllocatedSBuffer : public SBuffer {
|
|
|
|
public:
|
|
PreAllocatedSBuffer(const size_t size, void * buffer) {
|
|
_buf = (SBuffer_impl*) buffer;
|
|
_buf->size = size - 4;
|
|
_buf->len = 0;
|
|
}
|
|
|
|
~PreAllocatedSBuffer(void) {
|
|
|
|
_buf = nullptr;
|
|
}
|
|
} PreAllocatedSBuffer;
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_statistics.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_statistics.ino"
|
|
#define USE_STATS_CODE
|
|
|
|
#ifdef USE_STATS_CODE
|
|
|
|
|
|
|
|
|
|
String GetStatistics(void)
|
|
{
|
|
char data[40];
|
|
snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size);
|
|
return String(data);
|
|
}
|
|
|
|
#else
|
|
|
|
String GetStatistics(void)
|
|
{
|
|
return String("");
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_switch.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_switch.ino"
|
|
#define SWITCH_V2
|
|
#ifdef SWITCH_V2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t SWITCH_PROBE_INTERVAL = 10;
|
|
|
|
#include <Ticker.h>
|
|
|
|
Ticker TickerSwitch;
|
|
|
|
struct SWITCH {
|
|
unsigned long debounce = 0;
|
|
uint16_t no_pullup_mask = 0;
|
|
uint8_t state[MAX_SWITCHES] = { 0 };
|
|
uint8_t last_state[MAX_SWITCHES];
|
|
uint8_t hold_timer[MAX_SWITCHES] = { 0 };
|
|
uint8_t virtual_state[MAX_SWITCHES];
|
|
uint8_t present = 0;
|
|
} Switch;
|
|
|
|
|
|
|
|
void SwitchPullupFlag(uint16 switch_bit)
|
|
{
|
|
bitSet(Switch.no_pullup_mask, switch_bit);
|
|
}
|
|
|
|
void SwitchSetVirtual(uint32_t index, uint8_t state)
|
|
{
|
|
Switch.virtual_state[index] = state;
|
|
}
|
|
|
|
uint8_t SwitchGetVirtual(uint32_t index)
|
|
{
|
|
return Switch.virtual_state[index];
|
|
}
|
|
|
|
uint8_t SwitchLastState(uint32_t index)
|
|
{
|
|
return Switch.last_state[index];
|
|
}
|
|
|
|
bool SwitchState(uint32_t index)
|
|
{
|
|
uint32_t switchmode = Settings.switchmode[index];
|
|
return ((FOLLOW_INV == switchmode) ||
|
|
(PUSHBUTTON_INV == switchmode) ||
|
|
(PUSHBUTTONHOLD_INV == switchmode) ||
|
|
(FOLLOWMULTI_INV == switchmode) ||
|
|
(PUSHHOLDMULTI_INV == switchmode)
|
|
) ^ Switch.last_state[index];
|
|
}
|
|
|
|
|
|
|
|
void SwitchProbe(void)
|
|
{
|
|
if (uptime < 4) { return; }
|
|
|
|
uint8_t state_filter = Settings.switch_debounce / SWITCH_PROBE_INTERVAL;
|
|
uint8_t force_high = (Settings.switch_debounce % 50) &1;
|
|
uint8_t force_low = (Settings.switch_debounce % 50) &2;
|
|
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
if (pin[GPIO_SWT1 +i] < 99) {
|
|
|
|
if (1 == digitalRead(pin[GPIO_SWT1 +i])) {
|
|
|
|
if (force_high) {
|
|
if (1 == Switch.virtual_state[i]) {
|
|
Switch.state[i] = state_filter;
|
|
}
|
|
}
|
|
|
|
if (Switch.state[i] < state_filter) {
|
|
Switch.state[i]++;
|
|
if (state_filter == Switch.state[i]) {
|
|
Switch.virtual_state[i] = 1;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
if (force_low) {
|
|
if (0 == Switch.virtual_state[i]) {
|
|
Switch.state[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (Switch.state[i] > 0) {
|
|
Switch.state[i]--;
|
|
if (0 == Switch.state[i]) {
|
|
Switch.virtual_state[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe);
|
|
}
|
|
|
|
void SwitchInit(void)
|
|
{
|
|
Switch.present = 0;
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
Switch.last_state[i] = 1;
|
|
if (pin[GPIO_SWT1 +i] < 99) {
|
|
Switch.present++;
|
|
pinMode(pin[GPIO_SWT1 +i], bitRead(Switch.no_pullup_mask, i) ? INPUT : ((16 == pin[GPIO_SWT1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP));
|
|
Switch.last_state[i] = digitalRead(pin[GPIO_SWT1 +i]);
|
|
}
|
|
Switch.virtual_state[i] = Switch.last_state[i];
|
|
}
|
|
if (Switch.present) { TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); }
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SwitchHandler(uint8_t mode)
|
|
{
|
|
if (uptime < 4) { return; }
|
|
|
|
uint16_t loops_per_second = 1000 / Settings.switch_debounce;
|
|
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
if ((pin[GPIO_SWT1 +i] < 99) || (mode)) {
|
|
uint8_t button = Switch.virtual_state[i];
|
|
uint8_t switchflag = POWER_TOGGLE +1;
|
|
|
|
if (Switch.hold_timer[i]) {
|
|
Switch.hold_timer[i]--;
|
|
if (0 == Switch.hold_timer[i]) {
|
|
|
|
switch (Settings.switchmode[i]) {
|
|
case TOGGLEMULTI:
|
|
switchflag = POWER_TOGGLE;
|
|
break;
|
|
case FOLLOWMULTI:
|
|
switchflag = button &1;
|
|
break;
|
|
case FOLLOWMULTI_INV:
|
|
switchflag = ~button &1;
|
|
break;
|
|
case PUSHHOLDMULTI:
|
|
if (NOT_PRESSED == button){
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 25;
|
|
SendKey(KEY_SWITCH, i +1, POWER_INCREMENT);
|
|
}
|
|
else
|
|
SendKey(KEY_SWITCH, i +1, POWER_CLEAR);
|
|
break;
|
|
case PUSHHOLDMULTI_INV:
|
|
if (PRESSED == button){
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 25;
|
|
SendKey(KEY_SWITCH, i +1, POWER_INCREMENT);
|
|
}
|
|
else
|
|
SendKey(KEY_SWITCH, i +1, POWER_CLEAR);
|
|
break;
|
|
default:
|
|
SendKey(KEY_SWITCH, i +1, POWER_HOLD);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (button != Switch.last_state[i]) {
|
|
switch (Settings.switchmode[i]) {
|
|
case TOGGLE:
|
|
case PUSHBUTTON_TOGGLE:
|
|
switchflag = POWER_TOGGLE;
|
|
break;
|
|
case FOLLOW:
|
|
switchflag = button &1;
|
|
break;
|
|
case FOLLOW_INV:
|
|
switchflag = ~button &1;
|
|
break;
|
|
case PUSHBUTTON:
|
|
if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) {
|
|
switchflag = POWER_TOGGLE;
|
|
}
|
|
break;
|
|
case PUSHBUTTON_INV:
|
|
if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) {
|
|
switchflag = POWER_TOGGLE;
|
|
}
|
|
break;
|
|
case PUSHBUTTONHOLD:
|
|
if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) {
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10;
|
|
}
|
|
if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i]) && (Switch.hold_timer[i])) {
|
|
Switch.hold_timer[i] = 0;
|
|
switchflag = POWER_TOGGLE;
|
|
}
|
|
break;
|
|
case PUSHBUTTONHOLD_INV:
|
|
if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) {
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10;
|
|
}
|
|
if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i]) && (Switch.hold_timer[i])) {
|
|
Switch.hold_timer[i] = 0;
|
|
switchflag = POWER_TOGGLE;
|
|
}
|
|
break;
|
|
case TOGGLEMULTI:
|
|
case FOLLOWMULTI:
|
|
case FOLLOWMULTI_INV:
|
|
if (Switch.hold_timer[i]) {
|
|
Switch.hold_timer[i] = 0;
|
|
SendKey(KEY_SWITCH, i +1, POWER_HOLD);
|
|
} else {
|
|
Switch.hold_timer[i] = loops_per_second / 2;
|
|
}
|
|
break;
|
|
case PUSHHOLDMULTI:
|
|
if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) {
|
|
if(Switch.hold_timer[i]!=0)
|
|
SendKey(KEY_SWITCH, i +1, POWER_INV);
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10;
|
|
}
|
|
if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) {
|
|
if(Switch.hold_timer[i] > loops_per_second * Settings.param[P_HOLD_TIME] / 25)
|
|
switchflag = POWER_TOGGLE;
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10;
|
|
}
|
|
break;
|
|
case PUSHHOLDMULTI_INV:
|
|
if ((PRESSED == button) && (NOT_PRESSED == Switch.last_state[i])) {
|
|
if(Switch.hold_timer[i]!=0)
|
|
SendKey(KEY_SWITCH, i +1, POWER_INV);
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10;
|
|
}
|
|
if ((NOT_PRESSED == button) && (PRESSED == Switch.last_state[i])) {
|
|
if(Switch.hold_timer[i] > loops_per_second * Settings.param[P_HOLD_TIME] / 25)
|
|
switchflag = POWER_TOGGLE;
|
|
Switch.hold_timer[i] = loops_per_second * Settings.param[P_HOLD_TIME] / 10;
|
|
}
|
|
break;
|
|
}
|
|
Switch.last_state[i] = button;
|
|
}
|
|
if (switchflag <= POWER_TOGGLE) {
|
|
if (!SendKey(KEY_SWITCH, i +1, switchflag)) {
|
|
ExecuteCommandPower(i +1, switchflag, SRC_SWITCH);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SwitchLoop(void)
|
|
{
|
|
if (Switch.present) {
|
|
if (TimeReached(Switch.debounce)) {
|
|
SetNextTimeInterval(Switch.debounce, Settings.switch_debounce);
|
|
SwitchHandler(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
const char kSleepMode[] PROGMEM = "Dynamic|Normal";
|
|
const char kPrefixes[] PROGMEM = D_CMND "|" D_STAT "|" D_TELE;
|
|
|
|
char* Format(char* output, const char* input, int size)
|
|
{
|
|
char *token;
|
|
uint32_t digits = 0;
|
|
|
|
if (strstr(input, "%") != nullptr) {
|
|
strlcpy(output, input, size);
|
|
token = strtok(output, "%");
|
|
if (strstr(input, "%") == input) {
|
|
output[0] = '\0';
|
|
} else {
|
|
token = strtok(nullptr, "");
|
|
}
|
|
if (token != nullptr) {
|
|
digits = atoi(token);
|
|
if (digits) {
|
|
char tmp[size];
|
|
if (strchr(token, 'd')) {
|
|
snprintf_P(tmp, size, PSTR("%s%c0%dd"), output, '%', digits);
|
|
snprintf_P(output, size, tmp, ESP.getChipId() & 0x1fff);
|
|
} else {
|
|
snprintf_P(tmp, size, PSTR("%s%c0%dX"), output, '%', digits);
|
|
snprintf_P(output, size, tmp, ESP.getChipId());
|
|
}
|
|
} else {
|
|
if (strchr(token, 'd')) {
|
|
snprintf_P(output, size, PSTR("%s%d"), output, ESP.getChipId());
|
|
digits = 8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!digits) {
|
|
strlcpy(output, input, size);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
char* GetOtaUrl(char *otaurl, size_t otaurl_size)
|
|
{
|
|
if (strstr(SettingsText(SET_OTAURL), "%04d") != nullptr) {
|
|
snprintf(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId() & 0x1fff);
|
|
}
|
|
else if (strstr(SettingsText(SET_OTAURL), "%d") != nullptr) {
|
|
snprintf_P(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId());
|
|
}
|
|
else {
|
|
strlcpy(otaurl, SettingsText(SET_OTAURL), otaurl_size);
|
|
}
|
|
|
|
return otaurl;
|
|
}
|
|
|
|
char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopic)
|
|
{
|
|
# 88 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
char romram[CMDSZ];
|
|
String fulltopic;
|
|
|
|
snprintf_P(romram, sizeof(romram), subtopic);
|
|
if (fallback_topic_flag || (prefix > 3)) {
|
|
bool fallback = (prefix < 8);
|
|
prefix &= 3;
|
|
char stemp[11];
|
|
fulltopic = GetTextIndexed(stemp, sizeof(stemp), prefix, kPrefixes);
|
|
fulltopic += F("/");
|
|
if (fallback) {
|
|
fulltopic += mqtt_client;
|
|
fulltopic += F("_fb");
|
|
} else {
|
|
fulltopic += topic;
|
|
}
|
|
} else {
|
|
fulltopic = SettingsText(SET_MQTT_FULLTOPIC);
|
|
if ((0 == prefix) && (-1 == fulltopic.indexOf(FPSTR(MQTT_TOKEN_PREFIX)))) {
|
|
fulltopic += F("/");
|
|
fulltopic += FPSTR(MQTT_TOKEN_PREFIX);
|
|
}
|
|
for (uint32_t i = 0; i < MAX_MQTT_PREFIXES; i++) {
|
|
if (!strlen(SettingsText(SET_MQTTPREFIX1 + i))) {
|
|
char temp[TOPSZ];
|
|
SettingsUpdateText(SET_MQTTPREFIX1 + i, GetTextIndexed(temp, sizeof(temp), i, kPrefixes));
|
|
}
|
|
}
|
|
fulltopic.replace(FPSTR(MQTT_TOKEN_PREFIX), SettingsText(SET_MQTTPREFIX1 + prefix));
|
|
|
|
fulltopic.replace(FPSTR(MQTT_TOKEN_TOPIC), topic);
|
|
fulltopic.replace(F("%hostname%"), my_hostname);
|
|
String token_id = WiFi.macAddress();
|
|
token_id.replace(":", "");
|
|
fulltopic.replace(F("%id%"), token_id);
|
|
}
|
|
fulltopic.replace(F("#"), "");
|
|
fulltopic.replace(F("//"), "/");
|
|
if (!fulltopic.endsWith("/")) {
|
|
fulltopic += "/";
|
|
}
|
|
snprintf_P(stopic, TOPSZ, PSTR("%s%s"), fulltopic.c_str(), romram);
|
|
return stopic;
|
|
}
|
|
|
|
char* GetGroupTopic_P(char *stopic, const char* subtopic)
|
|
{
|
|
|
|
|
|
return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, SettingsText(SET_MQTT_GRP_TOPIC), subtopic);
|
|
}
|
|
|
|
char* GetFallbackTopic_P(char *stopic, const char* subtopic)
|
|
{
|
|
return GetTopic_P(stopic, CMND +4, nullptr, subtopic);
|
|
}
|
|
|
|
char* GetStateText(uint32_t state)
|
|
{
|
|
if (state >= MAX_STATE_TEXT) {
|
|
state = 1;
|
|
}
|
|
return SettingsText(SET_STATE_TXT1 + state);
|
|
}
|
|
|
|
|
|
|
|
void SetLatchingRelay(power_t lpower, uint32_t state)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
if (state && !latching_relay_pulse) {
|
|
latching_power = lpower;
|
|
latching_relay_pulse = 2;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < devices_present; i++) {
|
|
uint32_t port = (i << 1) + ((latching_power >> i) &1);
|
|
DigitalWrite(GPIO_REL1 +port, bitRead(rel_inverted, port) ? !state : state);
|
|
}
|
|
}
|
|
|
|
void SetDevicePower(power_t rpower, uint32_t source)
|
|
{
|
|
ShowSource(source);
|
|
last_source = source;
|
|
|
|
if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) {
|
|
power = (1 << devices_present) -1;
|
|
rpower = power;
|
|
}
|
|
|
|
if (Settings.flag.interlock) {
|
|
for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) {
|
|
power_t mask = 1;
|
|
uint32_t count = 0;
|
|
for (uint32_t j = 0; j < devices_present; j++) {
|
|
if ((Settings.interlock[i] & mask) && (rpower & mask)) {
|
|
count++;
|
|
}
|
|
mask <<= 1;
|
|
}
|
|
if (count > 1) {
|
|
mask = ~Settings.interlock[i];
|
|
power &= mask;
|
|
rpower &= mask;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rpower) {
|
|
last_power = rpower;
|
|
}
|
|
|
|
XdrvMailbox.index = rpower;
|
|
XdrvCall(FUNC_SET_POWER);
|
|
|
|
XdrvMailbox.index = rpower;
|
|
XdrvMailbox.payload = source;
|
|
if (XdrvCall(FUNC_SET_DEVICE_POWER)) {
|
|
|
|
}
|
|
else if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) {
|
|
Serial.write(0xA0);
|
|
Serial.write(0x04);
|
|
Serial.write(rpower &0xFF);
|
|
Serial.write(0xA1);
|
|
Serial.write('\n');
|
|
Serial.flush();
|
|
}
|
|
else if (EXS_RELAY == my_module_type) {
|
|
SetLatchingRelay(rpower, 1);
|
|
}
|
|
else {
|
|
for (uint32_t i = 0; i < devices_present; i++) {
|
|
power_t state = rpower &1;
|
|
if (i < MAX_RELAYS) {
|
|
DigitalWrite(GPIO_REL1 +i, bitRead(rel_inverted, i) ? !state : state);
|
|
}
|
|
rpower >>= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RestorePower(bool publish_power, uint32_t source)
|
|
{
|
|
if (power != last_power) {
|
|
power = last_power;
|
|
SetDevicePower(power, source);
|
|
if (publish_power) {
|
|
MqttPublishAllPowerState();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetAllPower(uint32_t state, uint32_t source)
|
|
{
|
|
# 256 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
bool publish_power = true;
|
|
if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) {
|
|
state &= 3;
|
|
publish_power = false;
|
|
}
|
|
if ((state >= POWER_OFF) && (state <= POWER_TOGGLE)) {
|
|
power_t all_on = (1 << devices_present) -1;
|
|
switch (state) {
|
|
case POWER_OFF:
|
|
power = 0;
|
|
break;
|
|
case POWER_ON:
|
|
power = all_on;
|
|
break;
|
|
case POWER_TOGGLE:
|
|
power ^= all_on;
|
|
}
|
|
SetDevicePower(power, source);
|
|
}
|
|
if (publish_power) {
|
|
MqttPublishAllPowerState();
|
|
}
|
|
}
|
|
|
|
void SetPowerOnState(void)
|
|
{
|
|
if (MOTOR == my_module_type) {
|
|
Settings.poweronstate = POWER_ALL_ON;
|
|
}
|
|
if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) {
|
|
SetDevicePower(1, SRC_RESTART);
|
|
} else {
|
|
if ((ResetReason() == REASON_DEFAULT_RST) || (ResetReason() == REASON_EXT_SYS_RST)) {
|
|
switch (Settings.poweronstate) {
|
|
case POWER_ALL_OFF:
|
|
case POWER_ALL_OFF_PULSETIME_ON:
|
|
power = 0;
|
|
SetDevicePower(power, SRC_RESTART);
|
|
break;
|
|
case POWER_ALL_ON:
|
|
power = (1 << devices_present) -1;
|
|
SetDevicePower(power, SRC_RESTART);
|
|
break;
|
|
case POWER_ALL_SAVED_TOGGLE:
|
|
power = (Settings.power & ((1 << devices_present) -1)) ^ POWER_MASK;
|
|
if (Settings.flag.save_state) {
|
|
SetDevicePower(power, SRC_RESTART);
|
|
}
|
|
break;
|
|
case POWER_ALL_SAVED:
|
|
power = Settings.power & ((1 << devices_present) -1);
|
|
if (Settings.flag.save_state) {
|
|
SetDevicePower(power, SRC_RESTART);
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
power = Settings.power & ((1 << devices_present) -1);
|
|
if (Settings.flag.save_state) {
|
|
SetDevicePower(power, SRC_RESTART);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (uint32_t i = 0; i < devices_present; i++) {
|
|
if (!Settings.flag3.no_power_feedback) {
|
|
if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) {
|
|
bitWrite(power, i, digitalRead(pin[GPIO_REL1 +i]) ^ bitRead(rel_inverted, i));
|
|
}
|
|
}
|
|
if ((i < MAX_PULSETIMERS) && (bitRead(power, i) || (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate))) {
|
|
SetPulseTimer(i, Settings.pulse_timer[i]);
|
|
}
|
|
}
|
|
blink_powersave = power;
|
|
}
|
|
|
|
void SetLedPowerIdx(uint32_t led, uint32_t state)
|
|
{
|
|
if ((99 == pin[GPIO_LEDLNK]) && (0 == led)) {
|
|
if (pin[GPIO_LED2] < 99) {
|
|
led = 1;
|
|
}
|
|
}
|
|
if (pin[GPIO_LED1 + led] < 99) {
|
|
uint32_t mask = 1 << led;
|
|
if (state) {
|
|
state = 1;
|
|
led_power |= mask;
|
|
} else {
|
|
led_power &= (0xFF ^ mask);
|
|
}
|
|
DigitalWrite(GPIO_LED1 + led, bitRead(led_inverted, led) ? !state : state);
|
|
}
|
|
#ifdef USE_BUZZER
|
|
if (led == 0) {
|
|
BuzzerSetStateToLed(state);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SetLedPower(uint32_t state)
|
|
{
|
|
if (99 == pin[GPIO_LEDLNK]) {
|
|
SetLedPowerIdx(0, state);
|
|
} else {
|
|
power_t mask = 1;
|
|
for (uint32_t i = 0; i < leds_present; i++) {
|
|
bool tstate = (power & mask);
|
|
SetLedPowerIdx(i, tstate);
|
|
mask <<= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetLedPowerAll(uint32_t state)
|
|
{
|
|
for (uint32_t i = 0; i < leds_present; i++) {
|
|
SetLedPowerIdx(i, state);
|
|
}
|
|
}
|
|
|
|
void SetLedLink(uint32_t state)
|
|
{
|
|
uint32_t led_pin = pin[GPIO_LEDLNK];
|
|
uint32_t led_inv = ledlnk_inverted;
|
|
if (99 == led_pin) {
|
|
led_pin = pin[GPIO_LED1];
|
|
led_inv = bitRead(led_inverted, 0);
|
|
}
|
|
if (led_pin < 99) {
|
|
if (state) { state = 1; }
|
|
digitalWrite(led_pin, (led_inv) ? !state : state);
|
|
}
|
|
#ifdef USE_BUZZER
|
|
BuzzerSetStateToLed(state);
|
|
#endif
|
|
}
|
|
|
|
void SetPulseTimer(uint32_t index, uint32_t time)
|
|
{
|
|
pulse_timer[index] = (time > 111) ? millis() + (1000 * (time - 100)) : (time > 0) ? millis() + (100 * time) : 0L;
|
|
}
|
|
|
|
uint32_t GetPulseTimer(uint32_t index)
|
|
{
|
|
long time = TimePassedSince(pulse_timer[index]);
|
|
if (time < 0) {
|
|
time *= -1;
|
|
return (time > 11100) ? (time / 1000) + 100 : (time > 0) ? time / 100 : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
bool SendKey(uint32_t key, uint32_t device, uint32_t state)
|
|
{
|
|
# 423 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
char stopic[TOPSZ];
|
|
char scommand[CMDSZ];
|
|
char key_topic[TOPSZ];
|
|
bool result = false;
|
|
|
|
char *tmp = (key) ? SettingsText(SET_MQTT_SWITCH_TOPIC) : SettingsText(SET_MQTT_BUTTON_TOPIC);
|
|
Format(key_topic, tmp, sizeof(key_topic));
|
|
if (Settings.flag.mqtt_enabled && MqttIsConnected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) {
|
|
if (!key && (device > devices_present)) {
|
|
device = 1;
|
|
}
|
|
GetTopic_P(stopic, CMND, key_topic,
|
|
GetPowerDevice(scommand, device, sizeof(scommand), (key + Settings.flag.device_index_enable)));
|
|
if (CLEAR_RETAIN == state) {
|
|
mqtt_data[0] = '\0';
|
|
} else {
|
|
if ((Settings.flag3.button_switch_force_local ||
|
|
!strcmp(mqtt_topic, key_topic) ||
|
|
!strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic)) &&
|
|
(POWER_TOGGLE == state)) {
|
|
state = ~(power >> (device -1)) &1;
|
|
}
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(state));
|
|
}
|
|
#ifdef USE_DOMOTICZ
|
|
if (!(DomoticzSendKey(key, device, state, strlen(mqtt_data)))) {
|
|
#endif
|
|
MqttPublish(stopic, ((key) ? Settings.flag.mqtt_switch_retain
|
|
: Settings.flag.mqtt_button_retain) &&
|
|
(state != POWER_HOLD || !Settings.flag3.no_hold_retain));
|
|
#ifdef USE_DOMOTICZ
|
|
}
|
|
#endif
|
|
result = !Settings.flag3.button_switch_force_local;
|
|
} else {
|
|
Response_P(PSTR("{\"%s%d\":{\"State\":%d}}"), (key) ? "Switch" : "Button", device, state);
|
|
result = XdrvRulesProcess();
|
|
}
|
|
int32_t payload_save = XdrvMailbox.payload;
|
|
XdrvMailbox.payload = key << 16 | state << 8 | device;
|
|
XdrvCall(FUNC_ANY_KEY);
|
|
XdrvMailbox.payload = payload_save;
|
|
return result;
|
|
}
|
|
|
|
void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
|
|
{
|
|
# 483 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) {
|
|
blink_mask &= 1;
|
|
Settings.flag.interlock = 0;
|
|
Settings.pulse_timer[1] = 0;
|
|
Settings.pulse_timer[2] = 0;
|
|
Settings.pulse_timer[3] = 0;
|
|
}
|
|
#endif
|
|
|
|
bool publish_power = true;
|
|
if ((state >= POWER_OFF_NO_STATE) && (state <= POWER_TOGGLE_NO_STATE)) {
|
|
state &= 3;
|
|
publish_power = false;
|
|
}
|
|
|
|
if ((device < 1) || (device > devices_present)) {
|
|
device = 1;
|
|
}
|
|
active_device = device;
|
|
|
|
if (device <= MAX_PULSETIMERS) {
|
|
SetPulseTimer(device -1, 0);
|
|
}
|
|
power_t mask = 1 << (device -1);
|
|
if (state <= POWER_TOGGLE) {
|
|
if ((blink_mask & mask)) {
|
|
blink_mask &= (POWER_MASK ^ mask);
|
|
MqttPublishPowerBlinkState(device);
|
|
}
|
|
|
|
if (Settings.flag.interlock &&
|
|
!interlock_mutex &&
|
|
((POWER_ON == state) || ((POWER_TOGGLE == state) && !(power & mask)))
|
|
) {
|
|
interlock_mutex = true;
|
|
for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) {
|
|
if (Settings.interlock[i] & mask) {
|
|
for (uint32_t j = 0; j < devices_present; j++) {
|
|
power_t imask = 1 << j;
|
|
if ((Settings.interlock[i] & imask) && (power & imask) && (mask != imask)) {
|
|
ExecuteCommandPower(j +1, POWER_OFF, SRC_IGNORE);
|
|
delay(50);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
interlock_mutex = false;
|
|
}
|
|
|
|
switch (state) {
|
|
case POWER_OFF: {
|
|
power &= (POWER_MASK ^ mask);
|
|
break; }
|
|
case POWER_ON:
|
|
power |= mask;
|
|
break;
|
|
case POWER_TOGGLE:
|
|
power ^= mask;
|
|
}
|
|
SetDevicePower(power, source);
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzUpdatePowerState(device);
|
|
#endif
|
|
#ifdef USE_KNX
|
|
KnxUpdatePowerState(device, power);
|
|
#endif
|
|
if (publish_power && Settings.flag3.hass_tele_on_power) {
|
|
MqttPublishTeleState();
|
|
}
|
|
if (device <= MAX_PULSETIMERS) {
|
|
SetPulseTimer(device -1, (((POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? ~power : power) & mask) ? Settings.pulse_timer[device -1] : 0);
|
|
}
|
|
}
|
|
else if (POWER_BLINK == state) {
|
|
if (!(blink_mask & mask)) {
|
|
blink_powersave = (blink_powersave & (POWER_MASK ^ mask)) | (power & mask);
|
|
blink_power = (power >> (device -1))&1;
|
|
}
|
|
blink_timer = millis() + 100;
|
|
blink_counter = ((!Settings.blinkcount) ? 64000 : (Settings.blinkcount *2)) +1;
|
|
blink_mask |= mask;
|
|
MqttPublishPowerBlinkState(device);
|
|
return;
|
|
}
|
|
else if (POWER_BLINK_STOP == state) {
|
|
bool flag = (blink_mask & mask);
|
|
blink_mask &= (POWER_MASK ^ mask);
|
|
MqttPublishPowerBlinkState(device);
|
|
if (flag) {
|
|
ExecuteCommandPower(device, (blink_powersave >> (device -1))&1, SRC_IGNORE);
|
|
}
|
|
return;
|
|
}
|
|
if (publish_power) {
|
|
MqttPublishPowerState(device);
|
|
}
|
|
}
|
|
|
|
void StopAllPowerBlink(void)
|
|
{
|
|
power_t mask;
|
|
|
|
for (uint32_t i = 1; i <= devices_present; i++) {
|
|
mask = 1 << (i -1);
|
|
if (blink_mask & mask) {
|
|
blink_mask &= (POWER_MASK ^ mask);
|
|
MqttPublishPowerBlinkState(i);
|
|
ExecuteCommandPower(i, (blink_powersave >> (i -1))&1, SRC_IGNORE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MqttShowPWMState(void)
|
|
{
|
|
ResponseAppend_P(PSTR("\"" D_CMND_PWM "\":{"));
|
|
bool first = true;
|
|
for (uint32_t i = 0; i < MAX_PWMS; i++) {
|
|
if (pin[GPIO_PWM1 + i] < 99) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_PWM "%d\":%d"), first ? "" : ",", i+1, Settings.pwm_value[i]);
|
|
first = false;
|
|
}
|
|
}
|
|
ResponseJsonEnd();
|
|
}
|
|
|
|
void MqttShowState(void)
|
|
{
|
|
char stemp1[TOPSZ];
|
|
|
|
ResponseAppendTime();
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime());
|
|
|
|
#ifdef USE_ADC_VCC
|
|
dtostrfd((double)ESP.getVcc()/1000, 3, stemp1);
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_VCC "\":%s"), stemp1);
|
|
#endif
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_HEAPSIZE "\":%d,\"SleepMode\":\"%s\",\"Sleep\":%u,\"LoadAvg\":%u,\"MqttCount\":%u"),
|
|
ESP.getFreeHeap()/1024, GetTextIndexed(stemp1, sizeof(stemp1), Settings.flag3.sleep_normal, kSleepMode),
|
|
sleep, loop_load_avg, MqttConnectCount());
|
|
|
|
for (uint32_t i = 1; i <= devices_present; i++) {
|
|
#ifdef USE_LIGHT
|
|
if ((LightDevice()) && (i >= LightDevice())) {
|
|
if (i == LightDevice()) { LightState(1); }
|
|
} else {
|
|
#endif
|
|
ResponseAppend_P(PSTR(",\"%s\":\"%s\""), GetPowerDevice(stemp1, i, sizeof(stemp1), Settings.flag.device_index_enable),
|
|
GetStateText(bitRead(power, i-1)));
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_FANSPEED "\":%d"), GetFanspeed());
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef USE_LIGHT
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (pwm_present) {
|
|
ResponseAppend_P(PSTR(","));
|
|
MqttShowPWMState();
|
|
}
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_WIFI "\":{\"" D_JSON_AP "\":%d,\"" D_JSON_SSID "\":\"%s\",\"" D_JSON_BSSID "\":\"%s\",\"" D_JSON_CHANNEL "\":%d,\"" D_JSON_RSSI "\":%d,\"" D_JSON_SIGNAL "\":%d,\"" D_JSON_LINK_COUNT "\":%d,\"" D_JSON_DOWNTIME "\":\"%s\"}}"),
|
|
Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WiFi.BSSIDstr().c_str(), WiFi.channel(),
|
|
WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI(), WifiLinkCount(), WifiDowntime().c_str());
|
|
}
|
|
|
|
void MqttPublishTeleState(void)
|
|
{
|
|
mqtt_data[0] = '\0';
|
|
MqttShowState();
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
|
|
#if defined(USE_RULES) || defined(USE_SCRIPT)
|
|
RulesTeleperiod();
|
|
#endif
|
|
}
|
|
|
|
bool MqttShowSensor(void)
|
|
{
|
|
ResponseAppendTime();
|
|
|
|
int json_data_start = strlen(mqtt_data);
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
#ifdef USE_TM1638
|
|
if ((pin[GPIO_SWT1 +i] < 99) || ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99))) {
|
|
#else
|
|
if (pin[GPIO_SWT1 +i] < 99) {
|
|
#endif
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_SWITCH "%d\":\"%s\""), i +1, GetStateText(SwitchState(i)));
|
|
}
|
|
}
|
|
XsnsCall(FUNC_JSON_APPEND);
|
|
XdrvCall(FUNC_JSON_APPEND);
|
|
|
|
bool json_data_available = (strlen(mqtt_data) - json_data_start);
|
|
if (strstr_P(mqtt_data, PSTR(D_JSON_PRESSURE)) != nullptr) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_PRESSURE_UNIT "\":\"%s\""), PressureUnit().c_str());
|
|
}
|
|
if (strstr_P(mqtt_data, PSTR(D_JSON_TEMPERATURE)) != nullptr) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE_UNIT "\":\"%c\""), TempUnit());
|
|
}
|
|
ResponseJsonEnd();
|
|
|
|
if (json_data_available) { XdrvCall(FUNC_SHOW_SENSOR); }
|
|
return json_data_available;
|
|
}
|
|
|
|
void MqttPublishSensor(void)
|
|
{
|
|
mqtt_data[0] = '\0';
|
|
if (MqttShowSensor()) {
|
|
MqttPublishTeleSensor();
|
|
}
|
|
}
|
|
# 710 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
void PerformEverySecond(void)
|
|
{
|
|
uptime++;
|
|
|
|
if (POWER_CYCLE_TIME == uptime) {
|
|
UpdateQuickPowerCycle(false);
|
|
}
|
|
|
|
if (BOOT_LOOP_TIME == uptime) {
|
|
RtcRebootReset();
|
|
|
|
#ifdef USE_DEEPSLEEP
|
|
if (!(DeepSleepEnabled() && !Settings.flag3.bootcount_update)) {
|
|
#endif
|
|
Settings.bootcount++;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_BOOT_COUNT " %d"), Settings.bootcount);
|
|
#ifdef USE_DEEPSLEEP
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (mqtt_cmnd_blocked_reset) {
|
|
mqtt_cmnd_blocked_reset--;
|
|
if (!mqtt_cmnd_blocked_reset) {
|
|
mqtt_cmnd_blocked = 0;
|
|
}
|
|
}
|
|
|
|
if (seriallog_timer) {
|
|
seriallog_timer--;
|
|
if (!seriallog_timer) {
|
|
if (seriallog_level) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SERIAL_LOGGING_DISABLED));
|
|
}
|
|
seriallog_level = 0;
|
|
}
|
|
}
|
|
|
|
if (syslog_timer) {
|
|
syslog_timer--;
|
|
if (!syslog_timer) {
|
|
syslog_level = Settings.syslog_level;
|
|
if (Settings.syslog_level) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_LOGGING_REENABLED));
|
|
}
|
|
}
|
|
}
|
|
|
|
ResetGlobalValues();
|
|
|
|
if (Settings.tele_period) {
|
|
if (tele_period >= 9999) {
|
|
if (!global_state.wifi_down) {
|
|
tele_period = 0;
|
|
}
|
|
} else {
|
|
tele_period++;
|
|
if (tele_period >= Settings.tele_period) {
|
|
tele_period = 0;
|
|
|
|
MqttPublishTeleState();
|
|
|
|
mqtt_data[0] = '\0';
|
|
if (MqttShowSensor()) {
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
|
#if defined(USE_RULES) || defined(USE_SCRIPT)
|
|
RulesTeleperiod();
|
|
#endif
|
|
}
|
|
|
|
XdrvCall(FUNC_AFTER_TELEPERIOD);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Every100mSeconds(void)
|
|
{
|
|
|
|
power_t power_now;
|
|
|
|
if (prepped_loglevel) {
|
|
AddLog(prepped_loglevel);
|
|
prepped_loglevel = 0;
|
|
}
|
|
|
|
if (latching_relay_pulse) {
|
|
latching_relay_pulse--;
|
|
if (!latching_relay_pulse) SetLatchingRelay(0, 0);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_PULSETIMERS; i++) {
|
|
if (pulse_timer[i] != 0L) {
|
|
if (TimeReached(pulse_timer[i])) {
|
|
pulse_timer[i] = 0L;
|
|
ExecuteCommandPower(i +1, (POWER_ALL_OFF_PULSETIME_ON == Settings.poweronstate) ? POWER_ON : POWER_OFF, SRC_PULSETIMER);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (blink_mask) {
|
|
if (TimeReached(blink_timer)) {
|
|
SetNextTimeInterval(blink_timer, 100 * Settings.blinktime);
|
|
blink_counter--;
|
|
if (!blink_counter) {
|
|
StopAllPowerBlink();
|
|
} else {
|
|
blink_power ^= 1;
|
|
power_now = (power & (POWER_MASK ^ blink_mask)) | ((blink_power) ? blink_mask : 0);
|
|
SetDevicePower(power_now, SRC_IGNORE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Every250mSeconds(void)
|
|
{
|
|
|
|
|
|
uint32_t blinkinterval = 1;
|
|
|
|
state_250mS++;
|
|
state_250mS &= 0x3;
|
|
|
|
if (!Settings.flag.global_state) {
|
|
if (global_state.data) {
|
|
if (global_state.mqtt_down) { blinkinterval = 7; }
|
|
if (global_state.wifi_down) { blinkinterval = 3; }
|
|
blinks = 201;
|
|
}
|
|
}
|
|
if (blinks || restart_flag || ota_state_flag) {
|
|
if (restart_flag || ota_state_flag) {
|
|
blinkstate = true;
|
|
} else {
|
|
blinkspeed--;
|
|
if (!blinkspeed) {
|
|
blinkspeed = blinkinterval;
|
|
blinkstate ^= 1;
|
|
}
|
|
}
|
|
if ((!(Settings.ledstate &0x08)) && ((Settings.ledstate &0x06) || (blinks > 200) || (blinkstate))) {
|
|
SetLedLink(blinkstate);
|
|
}
|
|
if (!blinkstate) {
|
|
blinks--;
|
|
if (200 == blinks) blinks = 0;
|
|
}
|
|
}
|
|
if (Settings.ledstate &1 && (pin[GPIO_LEDLNK] < 99 || !(blinks || restart_flag || ota_state_flag)) ) {
|
|
bool tstate = power & Settings.ledmask;
|
|
if ((SONOFF_TOUCH == my_module_type) || (SONOFF_T11 == my_module_type) || (SONOFF_T12 == my_module_type) || (SONOFF_T13 == my_module_type)) {
|
|
tstate = (!power) ? 1 : 0;
|
|
}
|
|
SetLedPower(tstate);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (state_250mS) {
|
|
case 0:
|
|
if (ota_state_flag && BACKLOG_EMPTY) {
|
|
ota_state_flag--;
|
|
if (2 == ota_state_flag) {
|
|
RtcSettings.ota_loader = 0;
|
|
ota_retry_counter = OTA_ATTEMPTS;
|
|
ESPhttpUpdate.rebootOnUpdate(false);
|
|
SettingsSave(1);
|
|
}
|
|
if (ota_state_flag <= 0) {
|
|
#ifdef USE_WEBSERVER
|
|
if (Settings.webserver) StopWebserver();
|
|
#endif
|
|
#ifdef USE_ARILUX_RF
|
|
AriluxRfDisable();
|
|
#endif
|
|
ota_state_flag = 92;
|
|
ota_result = 0;
|
|
ota_retry_counter--;
|
|
if (ota_retry_counter) {
|
|
strlcpy(mqtt_data, GetOtaUrl(log_data, sizeof(log_data)), sizeof(mqtt_data));
|
|
#ifndef FIRMWARE_MINIMAL
|
|
if (RtcSettings.ota_loader) {
|
|
# 915 "C:/shared/sonoff/Git/Tasmota/tasmota/support_tasmota.ino"
|
|
char *bch = strrchr(mqtt_data, '/');
|
|
if (bch == nullptr) { bch = mqtt_data; }
|
|
|
|
char *ech = strrchr(bch, '.');
|
|
if ((ech != nullptr) && (0 == strncasecmp_P(ech, PSTR(".GZ"), 3))) {
|
|
char *fch = ech;
|
|
*fch = '\0';
|
|
ech = strrchr(bch, '.');
|
|
*fch = '.';
|
|
}
|
|
if (ech == nullptr) { ech = mqtt_data + strlen(mqtt_data); }
|
|
char ota_url_type[strlen(ech) +1];
|
|
strncpy(ota_url_type, ech, sizeof(ota_url_type));
|
|
|
|
char *pch = strrchr(bch, '-');
|
|
if (pch == nullptr) { pch = ech; }
|
|
*pch = '\0';
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s-" D_JSON_MINIMAL "%s"), mqtt_data, ota_url_type);
|
|
}
|
|
#endif
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "%s"), mqtt_data);
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(mqtt_data));
|
|
#else
|
|
|
|
WiFiClient OTAclient;
|
|
ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(OTAclient, mqtt_data));
|
|
#endif
|
|
if (!ota_result) {
|
|
#ifndef FIRMWARE_MINIMAL
|
|
int ota_error = ESPhttpUpdate.getLastError();
|
|
DEBUG_CORE_LOG(PSTR("OTA: Error %d"), ota_error);
|
|
if ((HTTP_UE_TOO_LESS_SPACE == ota_error) || (HTTP_UE_BIN_FOR_WRONG_FLASH == ota_error)) {
|
|
RtcSettings.ota_loader = 1;
|
|
}
|
|
#endif
|
|
ota_state_flag = 2;
|
|
}
|
|
}
|
|
}
|
|
if (90 == ota_state_flag) {
|
|
ota_state_flag = 0;
|
|
Response_P(PSTR("{\"" D_CMND_UPGRADE "\":\""));
|
|
if (ota_result) {
|
|
|
|
if (!VersionCompatible()) {
|
|
ResponseAppend_P(PSTR(D_JSON_FAILED " " D_UPLOAD_ERR_14));
|
|
} else {
|
|
ResponseAppend_P(PSTR(D_JSON_SUCCESSFUL ". " D_JSON_RESTARTING));
|
|
restart_flag = 2;
|
|
}
|
|
} else {
|
|
ResponseAppend_P(PSTR(D_JSON_FAILED " %s"), ESPhttpUpdate.getLastErrorString().c_str());
|
|
}
|
|
ResponseAppend_P(PSTR("\"}"));
|
|
|
|
MqttPublishPrefixTopic_P(STAT, PSTR(D_CMND_UPGRADE));
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
if (MidnightNow()) {
|
|
XsnsCall(FUNC_SAVE_AT_MIDNIGHT);
|
|
}
|
|
if (save_data_counter && BACKLOG_EMPTY) {
|
|
save_data_counter--;
|
|
if (save_data_counter <= 0) {
|
|
if (Settings.flag.save_state) {
|
|
power_t mask = POWER_MASK;
|
|
for (uint32_t i = 0; i < MAX_PULSETIMERS; i++) {
|
|
if ((Settings.pulse_timer[i] > 0) && (Settings.pulse_timer[i] < 30)) {
|
|
mask &= ~(1 << i);
|
|
}
|
|
}
|
|
if (!((Settings.power &mask) == (power &mask))) {
|
|
Settings.power = power;
|
|
}
|
|
} else {
|
|
Settings.power = 0;
|
|
}
|
|
SettingsSave(0);
|
|
save_data_counter = Settings.save_data;
|
|
}
|
|
}
|
|
if (restart_flag && BACKLOG_EMPTY) {
|
|
if ((214 == restart_flag) || (215 == restart_flag) || (216 == restart_flag)) {
|
|
|
|
char storage_ssid1[strlen(SettingsText(SET_STASSID1)) +1];
|
|
strncpy(storage_ssid1, SettingsText(SET_STASSID1), sizeof(storage_ssid1));
|
|
char storage_ssid2[strlen(SettingsText(SET_STASSID2)) +1];
|
|
strncpy(storage_ssid2, SettingsText(SET_STASSID2), sizeof(storage_ssid2));
|
|
char storage_pass1[strlen(SettingsText(SET_STAPWD1)) +1];
|
|
strncpy(storage_pass1, SettingsText(SET_STAPWD1), sizeof(storage_pass1));
|
|
char storage_pass2[strlen(SettingsText(SET_STAPWD2)) +1];
|
|
strncpy(storage_pass2, SettingsText(SET_STAPWD2), sizeof(storage_pass2));
|
|
|
|
char storage_mqtthost[strlen(SettingsText(SET_MQTT_HOST)) +1];
|
|
strncpy(storage_mqtthost, SettingsText(SET_MQTT_HOST), sizeof(storage_mqtthost));
|
|
char storage_mqttuser[strlen(SettingsText(SET_MQTT_USER)) +1];
|
|
strncpy(storage_mqttuser, SettingsText(SET_MQTT_USER), sizeof(storage_mqttuser));
|
|
char storage_mqttpwd[strlen(SettingsText(SET_MQTT_PWD)) +1];
|
|
strncpy(storage_mqttpwd, SettingsText(SET_MQTT_PWD), sizeof(storage_mqttpwd));
|
|
char storage_mqtttopic[strlen(SettingsText(SET_MQTT_TOPIC)) +1];
|
|
strncpy(storage_mqtttopic, SettingsText(SET_MQTT_TOPIC), sizeof(storage_mqtttopic));
|
|
uint16_t mqtt_port = Settings.mqtt_port;
|
|
|
|
|
|
|
|
|
|
if ((215 == restart_flag) || (216 == restart_flag)) {
|
|
SettingsErase(0);
|
|
}
|
|
SettingsDefault();
|
|
|
|
SettingsUpdateText(SET_STASSID1, storage_ssid1);
|
|
SettingsUpdateText(SET_STASSID2, storage_ssid2);
|
|
SettingsUpdateText(SET_STAPWD1, storage_pass1);
|
|
SettingsUpdateText(SET_STAPWD2, storage_pass2);
|
|
if (216 == restart_flag) {
|
|
|
|
SettingsUpdateText(SET_MQTT_HOST, storage_mqtthost);
|
|
SettingsUpdateText(SET_MQTT_USER, storage_mqttuser);
|
|
SettingsUpdateText(SET_MQTT_PWD, storage_mqttpwd);
|
|
SettingsUpdateText(SET_MQTT_TOPIC, storage_mqtttopic);
|
|
Settings.mqtt_port = mqtt_port;
|
|
}
|
|
restart_flag = 2;
|
|
}
|
|
else if (213 == restart_flag) {
|
|
SettingsSdkErase();
|
|
restart_flag = 2;
|
|
}
|
|
else if (212 == restart_flag) {
|
|
SettingsErase(0);
|
|
restart_flag = 211;
|
|
}
|
|
if (211 == restart_flag) {
|
|
SettingsDefault();
|
|
restart_flag = 2;
|
|
}
|
|
if (2 == restart_flag) {
|
|
SettingsSaveAll();
|
|
}
|
|
restart_flag--;
|
|
if (restart_flag <= 0) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING));
|
|
EspRestart();
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
WifiCheck(wifi_state_flag);
|
|
wifi_state_flag = WIFI_RESTART;
|
|
break;
|
|
case 3:
|
|
if (!global_state.wifi_down) { MqttCheck(); }
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ARDUINO_OTA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool arduino_ota_triggered = false;
|
|
uint16_t arduino_ota_progress_dot_count = 0;
|
|
|
|
void ArduinoOTAInit(void)
|
|
{
|
|
ArduinoOTA.setPort(8266);
|
|
ArduinoOTA.setHostname(my_hostname);
|
|
if (strlen(SettingsText(SET_WEBPWD))) {
|
|
ArduinoOTA.setPassword(SettingsText(SET_WEBPWD));
|
|
}
|
|
|
|
ArduinoOTA.onStart([]()
|
|
{
|
|
SettingsSave(1);
|
|
#ifdef USE_WEBSERVER
|
|
if (Settings.webserver) { StopWebserver(); }
|
|
#endif
|
|
#ifdef USE_ARILUX_RF
|
|
AriluxRfDisable();
|
|
#endif
|
|
if (Settings.flag.mqtt_enabled) {
|
|
MqttDisconnect();
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_UPLOAD_STARTED));
|
|
arduino_ota_triggered = true;
|
|
arduino_ota_progress_dot_count = 0;
|
|
delay(100);
|
|
});
|
|
|
|
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
|
|
{
|
|
if ((LOG_LEVEL_DEBUG <= seriallog_level)) {
|
|
arduino_ota_progress_dot_count++;
|
|
Serial.printf(".");
|
|
if (!(arduino_ota_progress_dot_count % 80)) { Serial.println(); }
|
|
}
|
|
});
|
|
|
|
ArduinoOTA.onError([](ota_error_t error)
|
|
{
|
|
|
|
|
|
|
|
|
|
char error_str[100];
|
|
|
|
if ((LOG_LEVEL_DEBUG <= seriallog_level) && arduino_ota_progress_dot_count) { Serial.println(); }
|
|
switch (error) {
|
|
case OTA_BEGIN_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_2), sizeof(error_str)); break;
|
|
case OTA_RECEIVE_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_5), sizeof(error_str)); break;
|
|
case OTA_END_ERROR: strncpy_P(error_str, PSTR(D_UPLOAD_ERR_7), sizeof(error_str)); break;
|
|
default:
|
|
snprintf_P(error_str, sizeof(error_str), PSTR(D_UPLOAD_ERROR_CODE " %d"), error);
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA %s. " D_RESTARTING), error_str);
|
|
EspRestart();
|
|
});
|
|
|
|
ArduinoOTA.onEnd([]()
|
|
{
|
|
if ((LOG_LEVEL_DEBUG <= seriallog_level)) { Serial.println(); }
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_SUCCESSFUL ". " D_RESTARTING));
|
|
EspRestart();
|
|
});
|
|
|
|
ArduinoOTA.begin();
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD "Arduino OTA " D_ENABLED " " D_PORT " 8266"));
|
|
}
|
|
|
|
void ArduinoOtaLoop(void)
|
|
{
|
|
MDNS.update();
|
|
ArduinoOTA.handle();
|
|
|
|
while (arduino_ota_triggered) { ArduinoOTA.handle(); }
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void SerialInput(void)
|
|
{
|
|
while (Serial.available()) {
|
|
|
|
delay(0);
|
|
serial_in_byte = Serial.read();
|
|
|
|
|
|
|
|
|
|
if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) {
|
|
serial_in_byte = ButtonSerial(serial_in_byte);
|
|
}
|
|
|
|
|
|
|
|
if (XdrvCall(FUNC_SERIAL)) {
|
|
serial_in_byte_counter = 0;
|
|
Serial.flush();
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
if (serial_in_byte > 127 && !Settings.flag.mqtt_serial_raw) {
|
|
serial_in_byte_counter = 0;
|
|
Serial.flush();
|
|
return;
|
|
}
|
|
if (!Settings.flag.mqtt_serial) {
|
|
if (isprint(serial_in_byte)) {
|
|
if (serial_in_byte_counter < INPUT_BUFFER_SIZE -1) {
|
|
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
|
|
} else {
|
|
serial_in_byte_counter = 0;
|
|
}
|
|
}
|
|
} else {
|
|
if (serial_in_byte || Settings.flag.mqtt_serial_raw) {
|
|
if ((serial_in_byte_counter < INPUT_BUFFER_SIZE -1) &&
|
|
((isprint(serial_in_byte) && (128 == Settings.serial_delimiter)) ||
|
|
((serial_in_byte != Settings.serial_delimiter) && (128 != Settings.serial_delimiter)) ||
|
|
Settings.flag.mqtt_serial_raw)) {
|
|
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
|
|
serial_polling_window = millis();
|
|
} else {
|
|
serial_polling_window = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_SONOFF_SC
|
|
|
|
|
|
|
|
if (SONOFF_SC == my_module_type) {
|
|
if (serial_in_byte == '\x1B') {
|
|
serial_in_buffer[serial_in_byte_counter] = 0;
|
|
SonoffScSerialInput(serial_in_buffer);
|
|
serial_in_byte_counter = 0;
|
|
Serial.flush();
|
|
return;
|
|
}
|
|
} else
|
|
#endif
|
|
|
|
|
|
if (!Settings.flag.mqtt_serial && (serial_in_byte == '\n')) {
|
|
serial_in_buffer[serial_in_byte_counter] = 0;
|
|
seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? (uint8_t)LOG_LEVEL_INFO : Settings.seriallog_level;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), serial_in_buffer);
|
|
ExecuteCommand(serial_in_buffer, SRC_SERIAL);
|
|
serial_in_byte_counter = 0;
|
|
serial_polling_window = 0;
|
|
Serial.flush();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (Settings.flag.mqtt_serial && serial_in_byte_counter && (millis() > (serial_polling_window + SERIAL_POLLING))) {
|
|
serial_in_buffer[serial_in_byte_counter] = 0;
|
|
char hex_char[(serial_in_byte_counter * 2) + 2];
|
|
bool assume_json = (!Settings.flag.mqtt_serial_raw && (serial_in_buffer[0] == '{'));
|
|
Response_P(PSTR("{\"" D_JSON_SERIALRECEIVED "\":%s%s%s}"),
|
|
(assume_json) ? "" : """",
|
|
(Settings.flag.mqtt_serial_raw) ? ToHex_P((unsigned char*)serial_in_buffer, serial_in_byte_counter, hex_char, sizeof(hex_char)) : serial_in_buffer,
|
|
(assume_json) ? "" : """");
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SERIALRECEIVED));
|
|
XdrvRulesProcess();
|
|
serial_in_byte_counter = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void GpioInit(void)
|
|
{
|
|
uint32_t mpin;
|
|
|
|
if (!ValidModule(Settings.module)) {
|
|
uint32_t module = MODULE;
|
|
if (!ValidModule(MODULE)) { module = SONOFF_BASIC; }
|
|
Settings.module = module;
|
|
Settings.last_module = module;
|
|
}
|
|
SetModuleType();
|
|
|
|
if (Settings.module != Settings.last_module) {
|
|
Settings.baudrate = APP_BAUDRATE / 300;
|
|
Settings.serial_config = TS_SERIAL_8N1;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) {
|
|
if ((Settings.user_template.gp.io[i] >= GPIO_SENSOR_END) && (Settings.user_template.gp.io[i] < GPIO_USER)) {
|
|
Settings.user_template.gp.io[i] = GPIO_USER;
|
|
}
|
|
}
|
|
|
|
myio def_gp;
|
|
ModuleGpios(&def_gp);
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
if ((Settings.my_gp.io[i] >= GPIO_SENSOR_END) && (Settings.my_gp.io[i] < GPIO_USER)) {
|
|
Settings.my_gp.io[i] = GPIO_NONE;
|
|
}
|
|
else if (Settings.my_gp.io[i] > GPIO_NONE) {
|
|
my_module.io[i] = Settings.my_gp.io[i];
|
|
}
|
|
if ((def_gp.io[i] > GPIO_NONE) && (def_gp.io[i] < GPIO_USER)) {
|
|
my_module.io[i] = def_gp.io[i];
|
|
}
|
|
}
|
|
if ((Settings.my_adc0 >= ADC0_END) && (Settings.my_adc0 < ADC0_USER)) {
|
|
Settings.my_adc0 = ADC0_NONE;
|
|
}
|
|
else if (Settings.my_adc0 > ADC0_NONE) {
|
|
my_adc0 = Settings.my_adc0;
|
|
}
|
|
my_module_flag = ModuleFlag();
|
|
uint32_t template_adc0 = my_module_flag.data &15;
|
|
if ((template_adc0 > ADC0_NONE) && (template_adc0 < ADC0_USER)) {
|
|
my_adc0 = template_adc0;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < GPIO_MAX; i++) {
|
|
pin[i] = 99;
|
|
}
|
|
for (uint32_t i = 0; i < sizeof(my_module.io); i++) {
|
|
mpin = ValidPin(i, my_module.io[i]);
|
|
|
|
DEBUG_CORE_LOG(PSTR("INI: gpio pin %d, mpin %d"), i, mpin);
|
|
|
|
if (mpin) {
|
|
XdrvMailbox.index = mpin;
|
|
XdrvMailbox.payload = i;
|
|
|
|
if ((mpin >= GPIO_SWT1_NP) && (mpin < (GPIO_SWT1_NP + MAX_SWITCHES))) {
|
|
SwitchPullupFlag(mpin - GPIO_SWT1_NP);
|
|
mpin -= (GPIO_SWT1_NP - GPIO_SWT1);
|
|
}
|
|
else if ((mpin >= GPIO_KEY1_NP) && (mpin < (GPIO_KEY1_NP + MAX_KEYS))) {
|
|
ButtonPullupFlag(mpin - GPIO_KEY1_NP);
|
|
mpin -= (GPIO_KEY1_NP - GPIO_KEY1);
|
|
}
|
|
else if ((mpin >= GPIO_KEY1_INV) && (mpin < (GPIO_KEY1_INV + MAX_KEYS))) {
|
|
ButtonInvertFlag(mpin - GPIO_KEY1_INV);
|
|
mpin -= (GPIO_KEY1_INV - GPIO_KEY1);
|
|
}
|
|
else if ((mpin >= GPIO_KEY1_INV_NP) && (mpin < (GPIO_KEY1_INV_NP + MAX_KEYS))) {
|
|
ButtonPullupFlag(mpin - GPIO_KEY1_INV_NP);
|
|
ButtonInvertFlag(mpin - GPIO_KEY1_INV_NP);
|
|
mpin -= (GPIO_KEY1_INV_NP - GPIO_KEY1);
|
|
}
|
|
else if ((mpin >= GPIO_REL1_INV) && (mpin < (GPIO_REL1_INV + MAX_RELAYS))) {
|
|
bitSet(rel_inverted, mpin - GPIO_REL1_INV);
|
|
mpin -= (GPIO_REL1_INV - GPIO_REL1);
|
|
}
|
|
else if ((mpin >= GPIO_LED1_INV) && (mpin < (GPIO_LED1_INV + MAX_LEDS))) {
|
|
bitSet(led_inverted, mpin - GPIO_LED1_INV);
|
|
mpin -= (GPIO_LED1_INV - GPIO_LED1);
|
|
}
|
|
else if (mpin == GPIO_LEDLNK_INV) {
|
|
ledlnk_inverted = 1;
|
|
mpin -= (GPIO_LEDLNK_INV - GPIO_LEDLNK);
|
|
}
|
|
else if ((mpin >= GPIO_PWM1_INV) && (mpin < (GPIO_PWM1_INV + MAX_PWMS))) {
|
|
bitSet(pwm_inverted, mpin - GPIO_PWM1_INV);
|
|
mpin -= (GPIO_PWM1_INV - GPIO_PWM1);
|
|
}
|
|
else if (XdrvCall(FUNC_PIN_STATE)) {
|
|
mpin = XdrvMailbox.index;
|
|
}
|
|
else if (XsnsCall(FUNC_PIN_STATE)) {
|
|
mpin = XdrvMailbox.index;
|
|
};
|
|
}
|
|
if (mpin) pin[mpin] = i;
|
|
}
|
|
|
|
if ((2 == pin[GPIO_TXD]) || (H801 == my_module_type)) { Serial.set_tx(2); }
|
|
|
|
analogWriteRange(Settings.pwm_range);
|
|
analogWriteFreq(Settings.pwm_frequency);
|
|
|
|
#ifdef USE_SPI
|
|
spi_flg = ((((pin[GPIO_SPI_CS] < 99) && (pin[GPIO_SPI_CS] > 14)) || (pin[GPIO_SPI_CS] < 12)) || (((pin[GPIO_SPI_DC] < 99) && (pin[GPIO_SPI_DC] > 14)) || (pin[GPIO_SPI_DC] < 12)));
|
|
if (spi_flg) {
|
|
for (uint32_t i = 0; i < GPIO_MAX; i++) {
|
|
if ((pin[i] >= 12) && (pin[i] <=14)) pin[i] = 99;
|
|
}
|
|
my_module.io[12] = GPIO_SPI_MISO;
|
|
pin[GPIO_SPI_MISO] = 12;
|
|
my_module.io[13] = GPIO_SPI_MOSI;
|
|
pin[GPIO_SPI_MOSI] = 13;
|
|
my_module.io[14] = GPIO_SPI_CLK;
|
|
pin[GPIO_SPI_CLK] = 14;
|
|
}
|
|
soft_spi_flg = ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && ((pin[GPIO_SSPI_MOSI] < 99) || (pin[GPIO_SSPI_MOSI] < 99)));
|
|
#endif
|
|
|
|
#ifdef USE_I2C
|
|
i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99));
|
|
if (i2c_flg) {
|
|
Wire.begin(pin[GPIO_I2C_SDA], pin[GPIO_I2C_SCL]);
|
|
}
|
|
#endif
|
|
|
|
devices_present = 0;
|
|
light_type = LT_BASIC;
|
|
if (XdrvCall(FUNC_MODULE_INIT)) {
|
|
|
|
}
|
|
else if (YTF_IR_BRIDGE == my_module_type) {
|
|
ClaimSerial();
|
|
|
|
}
|
|
else if (SONOFF_DUAL == my_module_type) {
|
|
devices_present = 2;
|
|
SetSerial(19200, TS_SERIAL_8N1);
|
|
}
|
|
else if (CH4 == my_module_type) {
|
|
devices_present = 4;
|
|
SetSerial(19200, TS_SERIAL_8N1);
|
|
}
|
|
#ifdef USE_SONOFF_SC
|
|
else if (SONOFF_SC == my_module_type) {
|
|
SetSerial(19200, TS_SERIAL_8N1);
|
|
}
|
|
#endif
|
|
|
|
for (uint32_t i = 0; i < MAX_PWMS; i++) {
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
|
|
if (light_type) {
|
|
|
|
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range : 0);
|
|
} else {
|
|
pwm_present = true;
|
|
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_RELAYS; i++) {
|
|
if (pin[GPIO_REL1 +i] < 99) {
|
|
pinMode(pin[GPIO_REL1 +i], OUTPUT);
|
|
devices_present++;
|
|
if (EXS_RELAY == my_module_type) {
|
|
digitalWrite(pin[GPIO_REL1 +i], bitRead(rel_inverted, i) ? 1 : 0);
|
|
if (i &1) { devices_present--; }
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_LEDS; i++) {
|
|
if (pin[GPIO_LED1 +i] < 99) {
|
|
#ifdef USE_ARILUX_RF
|
|
if ((3 == i) && (leds_present < 2) && (99 == pin[GPIO_ARIRFSEL])) {
|
|
pin[GPIO_ARIRFSEL] = pin[GPIO_LED4];
|
|
pin[GPIO_LED4] = 99;
|
|
} else {
|
|
#endif
|
|
pinMode(pin[GPIO_LED1 +i], OUTPUT);
|
|
leds_present++;
|
|
digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i));
|
|
#ifdef USE_ARILUX_RF
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
if (pin[GPIO_LEDLNK] < 99) {
|
|
pinMode(pin[GPIO_LEDLNK], OUTPUT);
|
|
digitalWrite(pin[GPIO_LEDLNK], ledlnk_inverted);
|
|
}
|
|
|
|
ButtonInit();
|
|
SwitchInit();
|
|
#ifdef ROTARY_V1
|
|
RotaryInit();
|
|
#endif
|
|
|
|
SetLedPower(Settings.ledstate &8);
|
|
SetLedLink(Settings.ledstate &8);
|
|
|
|
XdrvCall(FUNC_PRE_INIT);
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_udp.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/support_udp.ino"
|
|
#ifdef USE_EMULATION
|
|
|
|
#define UDP_BUFFER_SIZE 200
|
|
#define UDP_MSEARCH_SEND_DELAY 1500
|
|
|
|
#include <Ticker.h>
|
|
Ticker TickerMSearch;
|
|
|
|
IPAddress udp_remote_ip;
|
|
uint16_t udp_remote_port;
|
|
|
|
bool udp_connected = false;
|
|
bool udp_response_mutex = false;
|
|
|
|
|
|
|
|
|
|
|
|
const char URN_BELKIN_DEVICE[] PROGMEM = "urn:belkin:device:**";
|
|
const char URN_BELKIN_DEVICE_CAP[] PROGMEM = "urn:Belkin:device:**";
|
|
const char UPNP_ROOTDEVICE[] PROGMEM = "upnp:rootdevice";
|
|
const char SSDPSEARCH_ALL[] PROGMEM = "ssdpsearch:all";
|
|
const char SSDP_ALL[] PROGMEM = "ssdp:all";
|
|
|
|
|
|
|
|
|
|
|
|
bool UdpDisconnect(void)
|
|
{
|
|
if (udp_connected) {
|
|
PortUdp.flush();
|
|
WiFiUDP::stopAll();
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED));
|
|
udp_connected = false;
|
|
}
|
|
return udp_connected;
|
|
}
|
|
|
|
bool UdpConnect(void)
|
|
{
|
|
if (!udp_connected) {
|
|
|
|
if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED));
|
|
udp_response_mutex = false;
|
|
udp_connected = true;
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED));
|
|
udp_connected = false;
|
|
}
|
|
}
|
|
return udp_connected;
|
|
}
|
|
|
|
void PollUdp(void)
|
|
{
|
|
if (udp_connected) {
|
|
while (PortUdp.parsePacket()) {
|
|
char packet_buffer[UDP_BUFFER_SIZE];
|
|
|
|
int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1);
|
|
packet_buffer[len] = 0;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len);
|
|
|
|
|
|
|
|
#ifdef USE_SCRIPT_HUE
|
|
if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
|
#else
|
|
if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
|
|
#endif
|
|
udp_response_mutex = true;
|
|
|
|
udp_remote_ip = PortUdp.remoteIP();
|
|
udp_remote_port = PortUdp.remotePort();
|
|
|
|
|
|
|
|
|
|
uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100);
|
|
|
|
LowerCase(packet_buffer, packet_buffer);
|
|
RemoveSpace(packet_buffer);
|
|
|
|
#ifdef USE_EMULATION_WEMO
|
|
if (EMUL_WEMO == Settings.flag2.emulation) {
|
|
if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) {
|
|
TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1);
|
|
return;
|
|
}
|
|
else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
|
|
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
|
|
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
|
|
TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_EMULATION_HUE
|
|
if (EMUL_HUE == Settings.flag2.emulation) {
|
|
if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) ||
|
|
(strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
|
|
(strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
|
|
(strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
|
|
TickerMSearch.attach_ms(response_delay, HueRespondToMSearch);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
udp_response_mutex = false;
|
|
}
|
|
|
|
}
|
|
optimistic_yield(100);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino"
|
|
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino"
|
|
#ifndef WIFI_RSSI_THRESHOLD
|
|
|
|
#define WIFI_RSSI_THRESHOLD 5
|
|
#endif
|
|
#ifndef WIFI_RESCAN_MINUTES
|
|
|
|
#define WIFI_RESCAN_MINUTES 5
|
|
#endif
|
|
|
|
const uint8_t WIFI_CONFIG_SEC = 180;
|
|
|
|
const uint8_t WIFI_CHECK_SEC = 5;
|
|
const uint8_t WIFI_RETRY_OFFSET_SEC = 20;
|
|
|
|
#include <ESP8266WiFi.h>
|
|
#if LWIP_IPV6
|
|
#include <AddrList.h>
|
|
#endif
|
|
|
|
struct WIFI {
|
|
uint32_t last_event = 0;
|
|
uint32_t downtime = 0;
|
|
uint16_t link_count = 0;
|
|
uint8_t counter;
|
|
uint8_t retry_init;
|
|
uint8_t retry;
|
|
uint8_t status;
|
|
uint8_t config_type = 0;
|
|
uint8_t config_counter = 0;
|
|
uint8_t mdns_begun = 0;
|
|
uint8_t scan_state;
|
|
uint8_t bssid[6] = {0};
|
|
uint8_t bssid_last[6] = {0};
|
|
int8_t best_network_db;
|
|
} Wifi;
|
|
|
|
int WifiGetRssiAsQuality(int rssi)
|
|
{
|
|
int quality = 0;
|
|
|
|
if (rssi <= -100) {
|
|
quality = 0;
|
|
} else if (rssi >= -50) {
|
|
quality = 100;
|
|
} else {
|
|
quality = 2 * (rssi + 100);
|
|
}
|
|
return quality;
|
|
}
|
|
|
|
bool WifiConfigCounter(void)
|
|
{
|
|
if (Wifi.config_counter) {
|
|
Wifi.config_counter = WIFI_CONFIG_SEC;
|
|
}
|
|
return (Wifi.config_counter);
|
|
}
|
|
|
|
void WifiConfig(uint8_t type)
|
|
{
|
|
if (!Wifi.config_type) {
|
|
if ((WIFI_RETRY == type) || (WIFI_WAIT == type)) { return; }
|
|
#ifdef USE_EMULATION
|
|
UdpDisconnect();
|
|
#endif
|
|
WiFi.disconnect();
|
|
Wifi.config_type = type;
|
|
|
|
#ifndef USE_WEBSERVER
|
|
if (WIFI_MANAGER == Wifi.config_type) {
|
|
Wifi.config_type = WIFI_SERIAL;
|
|
}
|
|
#endif
|
|
|
|
Wifi.config_counter = WIFI_CONFIG_SEC;
|
|
Wifi.counter = Wifi.config_counter +5;
|
|
blinks = 1999;
|
|
if (WIFI_RESTART == Wifi.config_type) {
|
|
restart_flag = 2;
|
|
}
|
|
else if (WIFI_SERIAL == Wifi.config_type) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_6_SERIAL " " D_ACTIVE_FOR_3_MINUTES));
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
else if (WIFI_MANAGER == Wifi.config_type || WIFI_MANAGER_RESET_ONLY == Wifi.config_type) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_WCFG_2_WIFIMANAGER " " D_ACTIVE_FOR_3_MINUTES));
|
|
WifiManagerBegin(WIFI_MANAGER_RESET_ONLY == Wifi.config_type);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void WifiSetMode(WiFiMode_t wifi_mode)
|
|
{
|
|
if (WiFi.getMode() == wifi_mode) { return; }
|
|
|
|
if (wifi_mode != WIFI_OFF) {
|
|
|
|
WiFi.forceSleepWake();
|
|
delay(100);
|
|
}
|
|
|
|
uint32_t retry = 2;
|
|
while (!WiFi.mode(wifi_mode) && retry--) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR("Retry set Mode..."));
|
|
delay(100);
|
|
}
|
|
|
|
if (wifi_mode == WIFI_OFF) {
|
|
delay(1000);
|
|
WiFi.forceSleepBegin();
|
|
delay(1);
|
|
} else {
|
|
delay(30);
|
|
}
|
|
}
|
|
|
|
void WiFiSetSleepMode(void)
|
|
{
|
|
# 156 "C:/shared/sonoff/Git/Tasmota/tasmota/support_wifi.ino"
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
#else
|
|
if (sleep && Settings.flag3.sleep_normal) {
|
|
WiFi.setSleepMode(WIFI_LIGHT_SLEEP);
|
|
} else {
|
|
WiFi.setSleepMode(WIFI_MODEM_SLEEP);
|
|
}
|
|
#endif
|
|
WifiSetOutputPower();
|
|
}
|
|
|
|
void WifiBegin(uint8_t flag, uint8_t channel)
|
|
{
|
|
const char kWifiPhyMode[] = " BGN";
|
|
|
|
#ifdef USE_EMULATION
|
|
UdpDisconnect();
|
|
#endif
|
|
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_PATCH_ISSUE_2186));
|
|
|
|
WifiSetMode(WIFI_OFF);
|
|
#endif
|
|
|
|
WiFi.persistent(false);
|
|
WiFi.disconnect(true);
|
|
delay(200);
|
|
|
|
WifiSetMode(WIFI_STA);
|
|
WiFiSetSleepMode();
|
|
|
|
|
|
if (!WiFi.getAutoConnect()) { WiFi.setAutoConnect(true); }
|
|
|
|
|
|
|
|
|
|
|
|
switch (flag) {
|
|
case 0:
|
|
case 1:
|
|
Settings.sta_active = flag;
|
|
break;
|
|
case 2:
|
|
Settings.sta_active ^= 1;
|
|
}
|
|
if (!strlen(SettingsText(SET_STASSID1 + Settings.sta_active))) {
|
|
Settings.sta_active ^= 1;
|
|
}
|
|
if (Settings.ip_address[0]) {
|
|
WiFi.config(Settings.ip_address[0], Settings.ip_address[1], Settings.ip_address[2], Settings.ip_address[3]);
|
|
}
|
|
WiFi.hostname(my_hostname);
|
|
|
|
char stemp[40] = { 0 };
|
|
if (channel) {
|
|
WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active), channel, Wifi.bssid);
|
|
|
|
char hex_char[18];
|
|
snprintf_P(stemp, sizeof(stemp), PSTR(" Channel %d BSSId %s"), channel, ToHex_P((unsigned char*)Wifi.bssid, 6, hex_char, sizeof(hex_char), ':'));
|
|
} else {
|
|
WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active));
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECTING_TO_AP "%d %s%s " D_IN_MODE " 11%c " D_AS " %s..."),
|
|
Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), stemp, kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname);
|
|
|
|
#if LWIP_IPV6
|
|
for (bool configured = false; !configured;) {
|
|
uint16_t cfgcnt = 0;
|
|
for (auto addr : addrList) {
|
|
if ((configured = !addr.isLocal() && addr.isV6()) || cfgcnt==30) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI "Got IPv6 global address %s"), addr.toString().c_str());
|
|
break;
|
|
}
|
|
delay(500);
|
|
cfgcnt++;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void WifiBeginAfterScan(void)
|
|
{
|
|
|
|
if (0 == Wifi.scan_state) { return; }
|
|
|
|
if (1 == Wifi.scan_state) {
|
|
memset((void*) &Wifi.bssid, 0, sizeof(Wifi.bssid));
|
|
Wifi.best_network_db = -127;
|
|
Wifi.scan_state = 3;
|
|
}
|
|
|
|
if (2 == Wifi.scan_state) {
|
|
uint8_t* bssid = WiFi.BSSID();
|
|
memcpy((void*) &Wifi.bssid, (void*) bssid, sizeof(Wifi.bssid));
|
|
Wifi.best_network_db = WiFi.RSSI();
|
|
if (Wifi.best_network_db < -WIFI_RSSI_THRESHOLD) {
|
|
Wifi.best_network_db += WIFI_RSSI_THRESHOLD;
|
|
}
|
|
Wifi.scan_state = 3;
|
|
}
|
|
|
|
if (3 == Wifi.scan_state) {
|
|
if (WiFi.scanComplete() != WIFI_SCAN_RUNNING) {
|
|
WiFi.scanNetworks(true);
|
|
Wifi.scan_state++;
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR("Network (re)scan started..."));
|
|
return;
|
|
}
|
|
}
|
|
int8_t wifi_scan_result = WiFi.scanComplete();
|
|
|
|
if (4 == Wifi.scan_state) {
|
|
if (wifi_scan_result != WIFI_SCAN_RUNNING) {
|
|
Wifi.scan_state++;
|
|
}
|
|
}
|
|
|
|
if (5 == Wifi.scan_state) {
|
|
uint32_t number_known = 0;
|
|
int32_t channel_max = 0;
|
|
int8_t ap_max = 3;
|
|
uint8_t bssid_max[6];
|
|
memcpy((void*) &bssid_max, (void*) &Wifi.bssid, sizeof(bssid_max));
|
|
|
|
int32_t channel = 0;
|
|
int8_t ap = 3;
|
|
uint8_t last_bssid[6];
|
|
memcpy((void*) &last_bssid, (void*) &Wifi.bssid, sizeof(last_bssid));
|
|
|
|
if (wifi_scan_result > 0) {
|
|
|
|
for (uint32_t i = 0; i < wifi_scan_result; ++i) {
|
|
|
|
String ssid_scan;
|
|
int32_t rssi_scan;
|
|
uint8_t sec_scan;
|
|
uint8_t* bssid_scan;
|
|
int32_t chan_scan;
|
|
bool hidden_scan;
|
|
|
|
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, bssid_scan, chan_scan, hidden_scan);
|
|
|
|
bool known = false;
|
|
uint32_t j;
|
|
for (j = 0; j < MAX_SSIDS; j++) {
|
|
if (ssid_scan == SettingsText(SET_STASSID1 + j)) {
|
|
known = true;
|
|
number_known++;
|
|
if (rssi_scan > Wifi.best_network_db) {
|
|
if (sec_scan == ENC_TYPE_NONE || SettingsText(SET_STAPWD1 + j)) {
|
|
|
|
memcpy((void*) &bssid_max, (void*) bssid_scan, sizeof(bssid_max));
|
|
channel_max = chan_scan;
|
|
ap_max = j;
|
|
|
|
for (uint32_t i = 0; i < sizeof(Wifi.bssid_last); i++) {
|
|
if (bssid_scan[i] != Wifi.bssid_last[i]) {
|
|
Wifi.best_network_db = (int8_t)rssi_scan;
|
|
channel = chan_scan;
|
|
ap = j;
|
|
memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid));
|
|
|
|
memcpy((void*) &Wifi.bssid_last, (void*) bssid_scan, sizeof(Wifi.bssid_last));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
char hex_char[18];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "Network %d, AP%c, SSId %s, Channel %d, BSSId %s, RSSI %d, Encryption %d"),
|
|
i,
|
|
(known) ? (j) ? '2' : '1' : '-',
|
|
ssid_scan.c_str(),
|
|
chan_scan,
|
|
ToHex_P((unsigned char*)bssid_scan, 6, hex_char, sizeof(hex_char), ':'),
|
|
rssi_scan,
|
|
(sec_scan == ENC_TYPE_NONE) ? 0 : 1);
|
|
delay(0);
|
|
}
|
|
WiFi.scanDelete();
|
|
delay(0);
|
|
}
|
|
|
|
|
|
if (number_known == 1) {
|
|
|
|
memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last));
|
|
memcpy((void*) &Wifi.bssid, (void*) bssid_max, sizeof(Wifi.bssid));
|
|
channel = channel_max;
|
|
ap = ap_max;
|
|
}
|
|
|
|
Wifi.scan_state = 0;
|
|
|
|
for (uint32_t i = 0; i < sizeof(Wifi.bssid); i++) {
|
|
if (last_bssid[i] != Wifi.bssid[i]) {
|
|
WifiBegin(ap, channel);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16_t WifiLinkCount(void)
|
|
{
|
|
return Wifi.link_count;
|
|
}
|
|
|
|
String WifiDowntime(void)
|
|
{
|
|
return GetDuration(Wifi.downtime);
|
|
}
|
|
|
|
void WifiSetState(uint8_t state)
|
|
{
|
|
if (state == global_state.wifi_down) {
|
|
if (state) {
|
|
rules_flag.wifi_connected = 1;
|
|
Wifi.link_count++;
|
|
Wifi.downtime += UpTime() - Wifi.last_event;
|
|
} else {
|
|
rules_flag.wifi_disconnected = 1;
|
|
Wifi.last_event = UpTime();
|
|
}
|
|
}
|
|
global_state.wifi_down = state ^1;
|
|
}
|
|
|
|
#if LWIP_IPV6
|
|
bool WifiCheckIPv6(void)
|
|
{
|
|
bool ipv6_global=false;
|
|
|
|
for (auto a : addrList) {
|
|
if(!a.isLocal() && a.isV6()) ipv6_global=true;
|
|
}
|
|
return ipv6_global;
|
|
}
|
|
|
|
String WifiGetIPv6(void)
|
|
{
|
|
for (auto a : addrList) {
|
|
if(!a.isLocal() && a.isV6()) return a.toString();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool WifiCheckIPAddrStatus(void)
|
|
{
|
|
bool ip_global=false;
|
|
|
|
for (auto a : addrList) {
|
|
if(!a.isLocal()) ip_global=true;
|
|
}
|
|
return ip_global;
|
|
}
|
|
#endif
|
|
|
|
void WifiCheckIp(void)
|
|
{
|
|
#if LWIP_IPV6
|
|
if(WifiCheckIPAddrStatus()) {
|
|
Wifi.status = WL_CONNECTED;
|
|
#else
|
|
if ((WL_CONNECTED == WiFi.status()) && (static_cast<uint32_t>(WiFi.localIP()) != 0)) {
|
|
#endif
|
|
|
|
memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last));
|
|
WifiSetState(1);
|
|
Wifi.counter = WIFI_CHECK_SEC;
|
|
Wifi.retry = Wifi.retry_init;
|
|
if (Wifi.status != WL_CONNECTED) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECTED));
|
|
}
|
|
Wifi.status = WL_CONNECTED;
|
|
#ifdef USE_DISCOVERY
|
|
#ifdef WEBSERVER_ADVERTISE
|
|
if (2 == Wifi.mdns_begun) {
|
|
MDNS.update();
|
|
AddLog_P(LOG_LEVEL_DEBUG_MORE, D_LOG_MDNS, "MDNS.update");
|
|
}
|
|
#endif
|
|
#endif
|
|
} else {
|
|
WifiSetState(0);
|
|
uint8_t wifi_config_tool = Settings.sta_config;
|
|
Wifi.status = WiFi.status();
|
|
switch (Wifi.status) {
|
|
case WL_CONNECTED:
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_NO_IP_ADDRESS));
|
|
|
|
|
|
wifi_station_dhcpc_start();
|
|
break;
|
|
case WL_NO_SSID_AVAIL:
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_NOT_REACHED));
|
|
|
|
if (WIFI_WAIT == Settings.sta_config) {
|
|
Wifi.retry = Wifi.retry_init;
|
|
} else {
|
|
if (Wifi.retry > (Wifi.retry_init / 2)) {
|
|
Wifi.retry = Wifi.retry_init / 2;
|
|
}
|
|
else if (Wifi.retry) {
|
|
Wifi.retry = 0;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case WL_CONNECT_FAILED:
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_WRONG_PASSWORD));
|
|
|
|
if (Wifi.retry > (Wifi.retry_init / 2)) {
|
|
Wifi.retry = Wifi.retry_init / 2;
|
|
}
|
|
else if (Wifi.retry) {
|
|
Wifi.retry = 0;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
|
|
if (!Wifi.retry || ((Wifi.retry_init / 2) == Wifi.retry)) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_TIMEOUT));
|
|
} else {
|
|
if (!strlen(SettingsText(SET_STASSID1)) && !strlen(SettingsText(SET_STASSID2))) {
|
|
wifi_config_tool = WIFI_MANAGER;
|
|
Wifi.retry = 0;
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, PSTR(D_ATTEMPTING_CONNECTION));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Wifi.retry) {
|
|
if (Settings.flag3.use_wifi_scan) {
|
|
|
|
if ((Wifi.retry_init == Wifi.retry) || ((Wifi.retry_init / 2) == Wifi.retry)){
|
|
Wifi.scan_state = 1;
|
|
}
|
|
} else {
|
|
if (Wifi.retry_init == Wifi.retry) {
|
|
WifiBegin(3, 0);
|
|
}
|
|
if ((Settings.sta_config != WIFI_WAIT) && ((Wifi.retry_init / 2) == Wifi.retry)) {
|
|
WifiBegin(2, 0);
|
|
}
|
|
}
|
|
Wifi.counter = 1;
|
|
Wifi.retry--;
|
|
} else {
|
|
WifiConfig(wifi_config_tool);
|
|
Wifi.counter = 1;
|
|
Wifi.retry = Wifi.retry_init;
|
|
}
|
|
}
|
|
}
|
|
|
|
void WifiCheck(uint8_t param)
|
|
{
|
|
Wifi.counter--;
|
|
switch (param) {
|
|
case WIFI_SERIAL:
|
|
case WIFI_MANAGER:
|
|
WifiConfig(param);
|
|
break;
|
|
default:
|
|
if (Wifi.config_counter) {
|
|
Wifi.config_counter--;
|
|
Wifi.counter = Wifi.config_counter +5;
|
|
if (Wifi.config_counter) {
|
|
if (!Wifi.config_counter) {
|
|
if (strlen(WiFi.SSID().c_str())) {
|
|
SettingsUpdateText(SET_STASSID1, WiFi.SSID().c_str());
|
|
}
|
|
if (strlen(WiFi.psk().c_str())) {
|
|
SettingsUpdateText(SET_STAPWD1, WiFi.psk().c_str());
|
|
}
|
|
Settings.sta_active = 0;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_WCFG_2_WIFIMANAGER D_CMND_SSID "1 %s"), SettingsText(SET_STASSID1));
|
|
}
|
|
}
|
|
if (!Wifi.config_counter) {
|
|
|
|
restart_flag = 2;
|
|
}
|
|
} else {
|
|
if (Wifi.scan_state) { WifiBeginAfterScan(); }
|
|
|
|
if (Wifi.counter <= 0) {
|
|
AddLog_P(LOG_LEVEL_DEBUG_MORE, S_LOG_WIFI, PSTR(D_CHECKING_CONNECTION));
|
|
Wifi.counter = WIFI_CHECK_SEC;
|
|
WifiCheckIp();
|
|
}
|
|
#if LWIP_IPV6
|
|
if (WifiCheckIPAddrStatus()) {
|
|
#else
|
|
if ((WL_CONNECTED == WiFi.status()) && (static_cast<uint32_t>(WiFi.localIP()) != 0) && !Wifi.config_type) {
|
|
#endif
|
|
WifiSetState(1);
|
|
|
|
if (Settings.flag3.use_wifi_rescan) {
|
|
if (!(uptime % (60 * WIFI_RESCAN_MINUTES))) {
|
|
Wifi.scan_state = 2;
|
|
}
|
|
}
|
|
|
|
#ifdef FIRMWARE_MINIMAL
|
|
if (1 == RtcSettings.ota_loader) {
|
|
RtcSettings.ota_loader = 0;
|
|
ota_state_flag = 3;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_DISCOVERY
|
|
if (Settings.flag3.mdns_enabled) {
|
|
if (!Wifi.mdns_begun) {
|
|
|
|
|
|
|
|
|
|
|
|
Wifi.mdns_begun = (uint8_t)MDNS.begin(my_hostname);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS "%s"), (Wifi.mdns_begun) ? D_INITIALIZED : D_FAILED);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_WEBSERVER
|
|
if (Settings.webserver) {
|
|
StartWebserver(Settings.webserver, WiFi.localIP());
|
|
#ifdef USE_DISCOVERY
|
|
#ifdef WEBSERVER_ADVERTISE
|
|
if (1 == Wifi.mdns_begun) {
|
|
Wifi.mdns_begun = 2;
|
|
MDNS.addService("http", "tcp", WEB_PORT);
|
|
}
|
|
#endif
|
|
#endif
|
|
} else {
|
|
StopWebserver();
|
|
}
|
|
#ifdef USE_EMULATION
|
|
if (Settings.flag2.emulation) { UdpConnect(); }
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_KNX
|
|
if (!knx_started && Settings.flag.knx_enabled) {
|
|
KNXStart();
|
|
knx_started = true;
|
|
}
|
|
#endif
|
|
|
|
} else {
|
|
WifiSetState(0);
|
|
#ifdef USE_EMULATION
|
|
UdpDisconnect();
|
|
#endif
|
|
Wifi.mdns_begun = 0;
|
|
#ifdef USE_KNX
|
|
knx_started = false;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int WifiState(void)
|
|
{
|
|
int state = -1;
|
|
|
|
if (!global_state.wifi_down) { state = WIFI_RESTART; }
|
|
if (Wifi.config_type) { state = Wifi.config_type; }
|
|
return state;
|
|
}
|
|
|
|
String WifiGetOutputPower(void)
|
|
{
|
|
char stemp1[TOPSZ];
|
|
dtostrfd((float)(Settings.wifi_output_power) / 10, 1, stemp1);
|
|
return String(stemp1);
|
|
}
|
|
void WifiSetOutputPower(void)
|
|
{
|
|
WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10);
|
|
}
|
|
|
|
void WifiConnect(void)
|
|
{
|
|
WifiSetState(0);
|
|
WifiSetOutputPower();
|
|
WiFi.persistent(false);
|
|
Wifi.status = 0;
|
|
|
|
|
|
Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + (ESP.getChipId() & 0xF);
|
|
Wifi.retry = Wifi.retry_init;
|
|
Wifi.counter = 1;
|
|
}
|
|
|
|
|
|
|
|
void WifiDisconnect(void)
|
|
{
|
|
|
|
WiFi.persistent(true);
|
|
ETS_UART_INTR_DISABLE();
|
|
wifi_station_disconnect();
|
|
ETS_UART_INTR_ENABLE();
|
|
WiFi.persistent(false);
|
|
}
|
|
|
|
void WifiShutdown(void)
|
|
{
|
|
delay(100);
|
|
if (Settings.flag.mqtt_enabled) {
|
|
MqttDisconnect();
|
|
}
|
|
WifiDisconnect();
|
|
}
|
|
|
|
void EspRestart(void)
|
|
{
|
|
WifiShutdown();
|
|
CrashDumpClear();
|
|
|
|
ESP.reset();
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino"
|
|
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino"
|
|
#ifdef USE_MQTT_TLS_CA_CERT
|
|
|
|
#ifndef USE_MQTT_AWS_IOT
|
|
# 38 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino"
|
|
static const unsigned char PROGMEM TA0_DN[] = {
|
|
0x30, 0x4A, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
|
0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A,
|
|
0x13, 0x0D, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72,
|
|
0x79, 0x70, 0x74, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03,
|
|
0x13, 0x1A, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72,
|
|
0x79, 0x70, 0x74, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74,
|
|
0x79, 0x20, 0x58, 0x33
|
|
};
|
|
|
|
static const unsigned char PROGMEM TA0_RSA_N[] = {
|
|
0x9C, 0xD3, 0x0C, 0xF0, 0x5A, 0xE5, 0x2E, 0x47, 0xB7, 0x72, 0x5D, 0x37,
|
|
0x83, 0xB3, 0x68, 0x63, 0x30, 0xEA, 0xD7, 0x35, 0x26, 0x19, 0x25, 0xE1,
|
|
0xBD, 0xBE, 0x35, 0xF1, 0x70, 0x92, 0x2F, 0xB7, 0xB8, 0x4B, 0x41, 0x05,
|
|
0xAB, 0xA9, 0x9E, 0x35, 0x08, 0x58, 0xEC, 0xB1, 0x2A, 0xC4, 0x68, 0x87,
|
|
0x0B, 0xA3, 0xE3, 0x75, 0xE4, 0xE6, 0xF3, 0xA7, 0x62, 0x71, 0xBA, 0x79,
|
|
0x81, 0x60, 0x1F, 0xD7, 0x91, 0x9A, 0x9F, 0xF3, 0xD0, 0x78, 0x67, 0x71,
|
|
0xC8, 0x69, 0x0E, 0x95, 0x91, 0xCF, 0xFE, 0xE6, 0x99, 0xE9, 0x60, 0x3C,
|
|
0x48, 0xCC, 0x7E, 0xCA, 0x4D, 0x77, 0x12, 0x24, 0x9D, 0x47, 0x1B, 0x5A,
|
|
0xEB, 0xB9, 0xEC, 0x1E, 0x37, 0x00, 0x1C, 0x9C, 0xAC, 0x7B, 0xA7, 0x05,
|
|
0xEA, 0xCE, 0x4A, 0xEB, 0xBD, 0x41, 0xE5, 0x36, 0x98, 0xB9, 0xCB, 0xFD,
|
|
0x6D, 0x3C, 0x96, 0x68, 0xDF, 0x23, 0x2A, 0x42, 0x90, 0x0C, 0x86, 0x74,
|
|
0x67, 0xC8, 0x7F, 0xA5, 0x9A, 0xB8, 0x52, 0x61, 0x14, 0x13, 0x3F, 0x65,
|
|
0xE9, 0x82, 0x87, 0xCB, 0xDB, 0xFA, 0x0E, 0x56, 0xF6, 0x86, 0x89, 0xF3,
|
|
0x85, 0x3F, 0x97, 0x86, 0xAF, 0xB0, 0xDC, 0x1A, 0xEF, 0x6B, 0x0D, 0x95,
|
|
0x16, 0x7D, 0xC4, 0x2B, 0xA0, 0x65, 0xB2, 0x99, 0x04, 0x36, 0x75, 0x80,
|
|
0x6B, 0xAC, 0x4A, 0xF3, 0x1B, 0x90, 0x49, 0x78, 0x2F, 0xA2, 0x96, 0x4F,
|
|
0x2A, 0x20, 0x25, 0x29, 0x04, 0xC6, 0x74, 0xC0, 0xD0, 0x31, 0xCD, 0x8F,
|
|
0x31, 0x38, 0x95, 0x16, 0xBA, 0xA8, 0x33, 0xB8, 0x43, 0xF1, 0xB1, 0x1F,
|
|
0xC3, 0x30, 0x7F, 0xA2, 0x79, 0x31, 0x13, 0x3D, 0x2D, 0x36, 0xF8, 0xE3,
|
|
0xFC, 0xF2, 0x33, 0x6A, 0xB9, 0x39, 0x31, 0xC5, 0xAF, 0xC4, 0x8D, 0x0D,
|
|
0x1D, 0x64, 0x16, 0x33, 0xAA, 0xFA, 0x84, 0x29, 0xB6, 0xD4, 0x0B, 0xC0,
|
|
0xD8, 0x7D, 0xC3, 0x93
|
|
};
|
|
|
|
static const unsigned char TA0_RSA_E[] = {
|
|
0x01, 0x00, 0x01
|
|
};
|
|
|
|
static const br_x509_trust_anchor PROGMEM LetsEncryptX3CrossSigned_TA = {
|
|
{ (unsigned char *)TA0_DN, sizeof TA0_DN },
|
|
BR_X509_TA_CA,
|
|
{
|
|
BR_KEYTYPE_RSA,
|
|
{ .rsa = {
|
|
(unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N,
|
|
(unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E,
|
|
} }
|
|
}
|
|
};
|
|
|
|
#define TAs_NUM 1
|
|
|
|
#endif
|
|
|
|
#ifdef USE_MQTT_AWS_IOT
|
|
# 106 "C:/shared/sonoff/Git/Tasmota/tasmota/tasmota_ca.ino"
|
|
const unsigned char PROGMEM TA0_DN[] = {
|
|
0x30, 0x39, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
|
|
0x02, 0x55, 0x53, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x0A,
|
|
0x13, 0x06, 0x41, 0x6D, 0x61, 0x7A, 0x6F, 0x6E, 0x31, 0x19, 0x30, 0x17,
|
|
0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, 0x41, 0x6D, 0x61, 0x7A, 0x6F,
|
|
0x6E, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31
|
|
};
|
|
|
|
const unsigned char PROGMEM TA0_RSA_N[] = {
|
|
0xB2, 0x78, 0x80, 0x71, 0xCA, 0x78, 0xD5, 0xE3, 0x71, 0xAF, 0x47, 0x80,
|
|
0x50, 0x74, 0x7D, 0x6E, 0xD8, 0xD7, 0x88, 0x76, 0xF4, 0x99, 0x68, 0xF7,
|
|
0x58, 0x21, 0x60, 0xF9, 0x74, 0x84, 0x01, 0x2F, 0xAC, 0x02, 0x2D, 0x86,
|
|
0xD3, 0xA0, 0x43, 0x7A, 0x4E, 0xB2, 0xA4, 0xD0, 0x36, 0xBA, 0x01, 0xBE,
|
|
0x8D, 0xDB, 0x48, 0xC8, 0x07, 0x17, 0x36, 0x4C, 0xF4, 0xEE, 0x88, 0x23,
|
|
0xC7, 0x3E, 0xEB, 0x37, 0xF5, 0xB5, 0x19, 0xF8, 0x49, 0x68, 0xB0, 0xDE,
|
|
0xD7, 0xB9, 0x76, 0x38, 0x1D, 0x61, 0x9E, 0xA4, 0xFE, 0x82, 0x36, 0xA5,
|
|
0xE5, 0x4A, 0x56, 0xE4, 0x45, 0xE1, 0xF9, 0xFD, 0xB4, 0x16, 0xFA, 0x74,
|
|
0xDA, 0x9C, 0x9B, 0x35, 0x39, 0x2F, 0xFA, 0xB0, 0x20, 0x50, 0x06, 0x6C,
|
|
0x7A, 0xD0, 0x80, 0xB2, 0xA6, 0xF9, 0xAF, 0xEC, 0x47, 0x19, 0x8F, 0x50,
|
|
0x38, 0x07, 0xDC, 0xA2, 0x87, 0x39, 0x58, 0xF8, 0xBA, 0xD5, 0xA9, 0xF9,
|
|
0x48, 0x67, 0x30, 0x96, 0xEE, 0x94, 0x78, 0x5E, 0x6F, 0x89, 0xA3, 0x51,
|
|
0xC0, 0x30, 0x86, 0x66, 0xA1, 0x45, 0x66, 0xBA, 0x54, 0xEB, 0xA3, 0xC3,
|
|
0x91, 0xF9, 0x48, 0xDC, 0xFF, 0xD1, 0xE8, 0x30, 0x2D, 0x7D, 0x2D, 0x74,
|
|
0x70, 0x35, 0xD7, 0x88, 0x24, 0xF7, 0x9E, 0xC4, 0x59, 0x6E, 0xBB, 0x73,
|
|
0x87, 0x17, 0xF2, 0x32, 0x46, 0x28, 0xB8, 0x43, 0xFA, 0xB7, 0x1D, 0xAA,
|
|
0xCA, 0xB4, 0xF2, 0x9F, 0x24, 0x0E, 0x2D, 0x4B, 0xF7, 0x71, 0x5C, 0x5E,
|
|
0x69, 0xFF, 0xEA, 0x95, 0x02, 0xCB, 0x38, 0x8A, 0xAE, 0x50, 0x38, 0x6F,
|
|
0xDB, 0xFB, 0x2D, 0x62, 0x1B, 0xC5, 0xC7, 0x1E, 0x54, 0xE1, 0x77, 0xE0,
|
|
0x67, 0xC8, 0x0F, 0x9C, 0x87, 0x23, 0xD6, 0x3F, 0x40, 0x20, 0x7F, 0x20,
|
|
0x80, 0xC4, 0x80, 0x4C, 0x3E, 0x3B, 0x24, 0x26, 0x8E, 0x04, 0xAE, 0x6C,
|
|
0x9A, 0xC8, 0xAA, 0x0D
|
|
};
|
|
|
|
static const unsigned char PROGMEM TA0_RSA_E[] = {
|
|
0x01, 0x00, 0x01
|
|
};
|
|
|
|
const br_x509_trust_anchor PROGMEM AmazonRootCA1_TA = {
|
|
{ (unsigned char *)TA0_DN, sizeof TA0_DN },
|
|
BR_X509_TA_CA,
|
|
{
|
|
BR_KEYTYPE_RSA,
|
|
{ .rsa = {
|
|
(unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N,
|
|
(unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E,
|
|
} }
|
|
}
|
|
};
|
|
|
|
#define TAs_NUM 1
|
|
|
|
#endif
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_01_webserver.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_01_webserver.ino"
|
|
#ifdef USE_WEBSERVER
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_01 1
|
|
|
|
#ifndef WIFI_SOFT_AP_CHANNEL
|
|
#define WIFI_SOFT_AP_CHANNEL 1
|
|
#endif
|
|
|
|
const uint16_t CHUNKED_BUFFER_SIZE = 400;
|
|
|
|
const uint16_t HTTP_REFRESH_TIME = 2345;
|
|
#define HTTP_RESTART_RECONNECT_TIME 9000
|
|
#define HTTP_OTA_RESTART_RECONNECT_TIME 20000
|
|
|
|
#include <ESP8266WebServer.h>
|
|
#include <DNSServer.h>
|
|
|
|
#ifdef USE_RF_FLASH
|
|
uint8_t *efm8bb1_update = nullptr;
|
|
#endif
|
|
|
|
enum UploadTypes { UPL_TASMOTA, UPL_SETTINGS, UPL_EFM8BB1, UPL_TASMOTASLAVE };
|
|
|
|
static const char * HEADER_KEYS[] = { "User-Agent", };
|
|
|
|
const char HTTP_HEADER[] PROGMEM =
|
|
"<!DOCTYPE html><html lang=\"" D_HTML_LANGUAGE "\" class=\"\">"
|
|
"<head>"
|
|
"<meta charset='utf-8'>"
|
|
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,user-scalable=no\"/>"
|
|
"<title>%s - %s</title>"
|
|
|
|
"<script>"
|
|
"var x=null,lt,to,tp,pc='';"
|
|
|
|
#ifdef USE_JAVASCRIPT_ES6
|
|
|
|
"eb=s=>document.getElementById(s);"
|
|
"qs=s=>document.querySelector(s);"
|
|
"sp=i=>eb(i).type=(eb(i).type==='text'?'password':'text');"
|
|
"wl=f=>window.addEventListener('load',f);"
|
|
;
|
|
#else
|
|
"function eb(s){"
|
|
"return document.getElementById(s);"
|
|
"}"
|
|
"function qs(s){"
|
|
"return document.querySelector(s);"
|
|
"}"
|
|
"function sp(i){"
|
|
"eb(i).type=(eb(i).type==='text'?'password':'text');"
|
|
"}"
|
|
"function wl(f){"
|
|
"window.addEventListener('load',f);"
|
|
"}";
|
|
#endif
|
|
|
|
const char HTTP_SCRIPT_COUNTER[] PROGMEM =
|
|
"var cn=180;"
|
|
"function u(){"
|
|
"if(cn>=0){"
|
|
"eb('t').innerHTML='" D_RESTART_IN " '+cn+' " D_SECONDS "';"
|
|
"cn--;"
|
|
"setTimeout(u,1000);"
|
|
"}"
|
|
"}"
|
|
"wl(u);";
|
|
|
|
const char HTTP_SCRIPT_ROOT[] PROGMEM =
|
|
#ifdef USE_SCRIPT_WEB_DISPLAY
|
|
"var rfsh=1;"
|
|
"function la(p){"
|
|
"var a='';"
|
|
"if(la.arguments.length==1){"
|
|
"a=p;"
|
|
"clearTimeout(lt);"
|
|
"}"
|
|
"if(x!=null){x.abort();}"
|
|
"x=new XMLHttpRequest();"
|
|
"x.onreadystatechange=function(){"
|
|
"if(x.readyState==4&&x.status==200){"
|
|
"var s=x.responseText.replace(/{t}/g,\"<table style='width:100%%'>\").replace(/{s}/g,\"<tr><th>\").replace(/{m}/g,\"</th><td>\").replace(/{e}/g,\"</td></tr>\").replace(/{c}/g,\"%%'><div style='text-align:center;font-weight:\");"
|
|
"eb('l1').innerHTML=s;"
|
|
"}"
|
|
"};"
|
|
"if (rfsh) {"
|
|
"x.open('GET','.?m=1'+a,true);"
|
|
"x.send();"
|
|
"lt=setTimeout(la,%d);"
|
|
"}"
|
|
"}"
|
|
"function seva(par,ivar){"
|
|
"la('&sv='+ivar+'_'+par);"
|
|
"}"
|
|
"function siva(par,ivar){"
|
|
"rfsh=1;"
|
|
"la('&sv='+ivar+'_'+par);"
|
|
"rfsh=0;"
|
|
"}"
|
|
"function pr(f){"
|
|
"if (f) {"
|
|
"lt=setTimeout(la,%d);"
|
|
"rfsh=1;"
|
|
"} else {"
|
|
"clearTimeout(lt);"
|
|
"rfsh=0;"
|
|
"}"
|
|
"}";
|
|
#else
|
|
"function la(p){"
|
|
"var a='';"
|
|
"if(la.arguments.length==1){"
|
|
"a=p;"
|
|
"clearTimeout(lt);"
|
|
"}"
|
|
"if(x!=null){x.abort();}"
|
|
"x=new XMLHttpRequest();"
|
|
"x.onreadystatechange=function(){"
|
|
"if(x.readyState==4&&x.status==200){"
|
|
"var s=x.responseText.replace(/{t}/g,\"<table style='width:100%%'>\").replace(/{s}/g,\"<tr><th>\").replace(/{m}/g,\"</th><td>\").replace(/{e}/g,\"</td></tr>\").replace(/{c}/g,\"%%'><div style='text-align:center;font-weight:\");"
|
|
"eb('l1').innerHTML=s;"
|
|
"}"
|
|
"};"
|
|
"x.open('GET','.?m=1'+a,true);"
|
|
"x.send();"
|
|
"lt=setTimeout(la,%d);"
|
|
"}";
|
|
#endif
|
|
|
|
const char HTTP_SCRIPT_ROOT_PART2[] PROGMEM =
|
|
"function lc(v,i,p){"
|
|
"if(eb('s')){"
|
|
"if(v=='h'||v=='d'){"
|
|
"var sl=eb('sl4').value;"
|
|
"eb('s').style.background='linear-gradient(to right,rgb('+sl+'%%,'+sl+'%%,'+sl+'%%),hsl('+eb('sl2').value+',100%%,50%%))';"
|
|
"}"
|
|
"}"
|
|
"la('&'+v+i+'='+p);"
|
|
"}"
|
|
"wl(la);";
|
|
|
|
const char HTTP_SCRIPT_WIFI[] PROGMEM =
|
|
"function c(l){"
|
|
"eb('s1').value=l.innerText||l.textContent;"
|
|
"eb('p1').focus();"
|
|
"}";
|
|
|
|
const char HTTP_SCRIPT_RELOAD[] PROGMEM =
|
|
"setTimeout(function(){location.href='.';}," STR(HTTP_RESTART_RECONNECT_TIME) ");";
|
|
|
|
|
|
const char HTTP_SCRIPT_RELOAD_OTA[] PROGMEM =
|
|
"setTimeout(function(){location.href='.';}," STR(HTTP_OTA_RESTART_RECONNECT_TIME) ");";
|
|
|
|
const char HTTP_SCRIPT_CONSOL[] PROGMEM =
|
|
"var sn=0,id=0;"
|
|
"function l(p){"
|
|
"var c,o='',t;"
|
|
"clearTimeout(lt);"
|
|
"t=eb('t1');"
|
|
"if(p==1){"
|
|
"c=eb('c1');"
|
|
"o='&c1='+encodeURIComponent(c.value);"
|
|
"c.value='';"
|
|
"t.scrollTop=99999;"
|
|
"sn=t.scrollTop;"
|
|
"}"
|
|
"if(t.scrollTop>=sn){"
|
|
"if(x!=null){x.abort();}"
|
|
"x=new XMLHttpRequest();"
|
|
"x.onreadystatechange=function(){"
|
|
"if(x.readyState==4&&x.status==200){"
|
|
"var z,d;"
|
|
"d=x.responseText.split(/}1/);"
|
|
"id=d.shift();"
|
|
"if(d.shift()==0){t.value='';}"
|
|
"z=d.shift();"
|
|
"if(z.length>0){t.value+=z;}"
|
|
"t.scrollTop=99999;"
|
|
"sn=t.scrollTop;"
|
|
"}"
|
|
"};"
|
|
"x.open('GET','cs?c2='+id+o,true);"
|
|
"x.send();"
|
|
"}"
|
|
"lt=setTimeout(l,%d);"
|
|
"return false;"
|
|
"}"
|
|
"wl(l);";
|
|
|
|
const char HTTP_MODULE_TEMPLATE_REPLACE[] PROGMEM =
|
|
"}2%d'>%s (%d}3";
|
|
|
|
const char HTTP_SCRIPT_MODULE_TEMPLATE[] PROGMEM =
|
|
"var os;"
|
|
"function sk(s,g){"
|
|
"var o=os.replace(/}2/g,\"<option value='\").replace(/}3/g,\")</option>\");"
|
|
"eb('g'+g).innerHTML=o;"
|
|
"eb('g'+g).value=s;"
|
|
"}"
|
|
"function ld(u,f){"
|
|
"var x=new XMLHttpRequest();"
|
|
"x.onreadystatechange=function(){"
|
|
"if(this.readyState==4&&this.status==200){"
|
|
"f(this);"
|
|
"}"
|
|
"};"
|
|
"x.open('GET',u,true);"
|
|
"x.send();"
|
|
"}";
|
|
|
|
const char HTTP_SCRIPT_TEMPLATE[] PROGMEM =
|
|
"var c;"
|
|
"function x1(b){"
|
|
"var i,j,g,k,o;"
|
|
"o=b.responseText.split(/}1/);"
|
|
"k=o.shift();"
|
|
"if(eb('s1').value==''){"
|
|
"eb('s1').value=k;"
|
|
"}"
|
|
"os=o.shift();"
|
|
"as=o.shift();"
|
|
"g=o.shift().split(',');"
|
|
"j=0;"
|
|
"for(i=0;i<13;i++){"
|
|
"if(6==i){j=9;}"
|
|
"if(8==i){j=12;}"
|
|
"sk(g[i],j);"
|
|
"j++;"
|
|
"}"
|
|
"g=o.shift();"
|
|
"os=as;"
|
|
"sk(g&15,17);"
|
|
"g>>=4;"
|
|
"for(i=0;i<" STR(GPIO_FLAG_USED) ";i++){"
|
|
"p=(g>>i)&1;"
|
|
"eb('c'+i).checked=p;"
|
|
"}"
|
|
"if(" STR(USER_MODULE) "==c){"
|
|
"g=o.shift();"
|
|
"eb('g99').value=g;"
|
|
"}"
|
|
"}"
|
|
"function st(t){"
|
|
"c=t;"
|
|
"var a='tp?t='+t;"
|
|
"ld(a,x1);"
|
|
"}"
|
|
|
|
"function x2(a){"
|
|
"os=a.responseText;"
|
|
"sk(17,99);"
|
|
"st(" STR(USER_MODULE) ");"
|
|
"}"
|
|
|
|
#ifdef USE_JAVASCRIPT_ES6
|
|
"sl=()=>ld('tp?m=1',x2);"
|
|
#else
|
|
"function sl(){"
|
|
"ld('tp?m=1',x2);"
|
|
"}"
|
|
#endif
|
|
|
|
"wl(sl);";
|
|
|
|
const char HTTP_SCRIPT_MODULE1[] PROGMEM =
|
|
"function x1(a){"
|
|
"os=a.responseText;"
|
|
"sk(%d,99);"
|
|
"}"
|
|
"function x2(b){"
|
|
"os=b.responseText;";
|
|
const char HTTP_SCRIPT_MODULE2[] PROGMEM =
|
|
"}"
|
|
"function x3(a){"
|
|
"os=a.responseText;"
|
|
"sk(%d,17);"
|
|
"}"
|
|
"function sl(){"
|
|
"ld('md?m=1',x1);"
|
|
"ld('md?g=1',x2);"
|
|
"if(eb('g17')){"
|
|
"ld('md?a=1',x3);"
|
|
"}"
|
|
"}"
|
|
"wl(sl);";
|
|
|
|
const char HTTP_SCRIPT_INFO_BEGIN[] PROGMEM =
|
|
"function i(){"
|
|
"var s,o=\"";
|
|
const char HTTP_SCRIPT_INFO_END[] PROGMEM =
|
|
"\";"
|
|
"s=o.replace(/}1/g,\"</td></tr><tr><th>\").replace(/}2/g,\"</th><td>\");"
|
|
"eb('i').innerHTML=s;"
|
|
"}"
|
|
"wl(i);";
|
|
|
|
const char HTTP_HEAD_LAST_SCRIPT[] PROGMEM =
|
|
"function jd(){"
|
|
"var t=0,i=document.querySelectorAll('input,button,textarea,select');"
|
|
"while(i.length>=t){"
|
|
"if(i[t]){"
|
|
"i[t]['name']=(i[t].hasAttribute('id')&&(!i[t].hasAttribute('name')))?i[t]['id']:i[t]['name'];"
|
|
"}"
|
|
"t++;"
|
|
"}"
|
|
"}"
|
|
"wl(jd);"
|
|
"</script>";
|
|
|
|
const char HTTP_HEAD_STYLE1[] PROGMEM =
|
|
"<style>"
|
|
"div,fieldset,input,select{padding:5px;font-size:1em;}"
|
|
"fieldset{background:#%06x;}"
|
|
"p{margin:0.5em 0;}"
|
|
"input{width:100%%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;background:#%06x;color:#%06x;}"
|
|
"input[type=checkbox],input[type=radio]{width:1em;margin-right:6px;vertical-align:-1px;}"
|
|
"input[type=range]{width:99%%;}"
|
|
"select{width:100%%;background:#%06x;color:#%06x;}"
|
|
"textarea{resize:vertical;width:98%%;height:318px;padding:5px;overflow:auto;background:#%06x;color:#%06x;}"
|
|
"body{text-align:center;font-family:verdana,sans-serif;background:#%06x;}"
|
|
"td{padding:0px;}";
|
|
const char HTTP_HEAD_STYLE2[] PROGMEM =
|
|
"button{border:0;border-radius:0.3rem;background:#%06x;color:#%06x;line-height:2.4rem;font-size:1.2rem;width:100%%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}"
|
|
"button:hover{background:#%06x;}"
|
|
".bred{background:#%06x;}"
|
|
".bred:hover{background:#%06x;}"
|
|
".bgrn{background:#%06x;}"
|
|
".bgrn:hover{background:#%06x;}"
|
|
"a{color:#%06x;text-decoration:none;}"
|
|
".p{float:left;text-align:left;}"
|
|
".q{float:right;text-align:right;}"
|
|
".r{border-radius:0.3em;padding:2px;margin:6px 2px;}";
|
|
const char HTTP_HEAD_STYLE3[] PROGMEM =
|
|
"</style>"
|
|
|
|
"</head>"
|
|
"<body>"
|
|
"<div style='text-align:left;display:inline-block;color:#%06x;min-width:340px;'>"
|
|
#ifdef FIRMWARE_MINIMAL
|
|
"<div style='text-align:center;color:#%06x;'><h3>" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "</h3></div>"
|
|
#endif
|
|
"<div style='text-align:center;color:#%06x;'><noscript>" D_NOSCRIPT "<br></noscript>"
|
|
#ifdef LANGUAGE_MODULE_NAME
|
|
"<h3>" D_MODULE " %s</h3>"
|
|
#else
|
|
"<h3>%s " D_MODULE "</h3>"
|
|
#endif
|
|
"<h2>%s</h2>";
|
|
|
|
const char HTTP_MSG_SLIDER_GRADIENT[] PROGMEM =
|
|
"<div id='%s' class='r' style='background-image:linear-gradient(to right,%s,%s);'>"
|
|
"<input id='sl%d' type='range' min='%d' max='%d' value='%d' onchange='lc(\"%c\",%d,value)'>"
|
|
"</div>";
|
|
const char HTTP_MSG_SLIDER_SHUTTER[] PROGMEM =
|
|
"<div><span class='p'>" D_CLOSE "</span><span class='q'>" D_OPEN "</span></div>"
|
|
"<div><input type='range' min='0' max='100' value='%d' onchange='lc(\"u\",%d,value)'></div>";
|
|
|
|
const char HTTP_MSG_RSTRT[] PROGMEM =
|
|
"<br><div style='text-align:center;'>" D_DEVICE_WILL_RESTART "</div><br>";
|
|
|
|
const char HTTP_FORM_LOGIN[] PROGMEM =
|
|
"<fieldset>"
|
|
"<form method='post' action='/'>"
|
|
"<p><b>" D_USER "</b><br><input name='USER1' placeholder='" D_USER "'></p>"
|
|
"<p><b>" D_PASSWORD "</b><br><input name='PASS1' type='password' placeholder='" D_PASSWORD "'></p>"
|
|
"<br>"
|
|
"<button>" D_OK "</button>"
|
|
"</form></fieldset>";
|
|
|
|
const char HTTP_FORM_TEMPLATE[] PROGMEM =
|
|
"<fieldset><legend><b> " D_TEMPLATE_PARAMETERS " </b></legend>"
|
|
"<form method='get' action='tp'>";
|
|
const char HTTP_FORM_TEMPLATE_FLAG[] PROGMEM =
|
|
"<p></p>"
|
|
"<fieldset><legend><b> " D_TEMPLATE_FLAGS " </b></legend><p>"
|
|
|
|
"</p></fieldset>";
|
|
|
|
const char HTTP_FORM_MODULE[] PROGMEM =
|
|
"<fieldset><legend><b> " D_MODULE_PARAMETERS " </b></legend>"
|
|
"<form method='get' action='md'>"
|
|
"<p></p><b>" D_MODULE_TYPE "</b> (%s)<br><select id='g99'></select><br>"
|
|
"<br><table>";
|
|
|
|
const char HTTP_FORM_WIFI[] PROGMEM =
|
|
"<fieldset><legend><b> " D_WIFI_PARAMETERS " </b></legend>"
|
|
"<form method='get' action='wi'>"
|
|
"<p><b>" D_AP1_SSID "</b> (" STA_SSID1 ")<br><input id='s1' placeholder='" STA_SSID1 "' value='%s'></p>"
|
|
"<p><b>" D_AP1_PASSWORD "</b><input type='checkbox' onclick='sp(\"p1\")'><br><input id='p1' type='password' placeholder='" D_AP1_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
|
|
"<p><b>" D_AP2_SSID "</b> (" STA_SSID2 ")<br><input id='s2' placeholder='" STA_SSID2 "' value='%s'></p>"
|
|
"<p><b>" D_AP2_PASSWORD "</b><input type='checkbox' onclick='sp(\"p2\")'><br><input id='p2' type='password' placeholder='" D_AP2_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
|
|
"<p><b>" D_HOSTNAME "</b> (%s)<br><input id='h' placeholder='%s' value='%s'></p>"
|
|
"<p><b>" D_CORS_DOMAIN "</b><input id='c' placeholder='" CORS_DOMAIN "' value='%s'></p>";
|
|
|
|
const char HTTP_FORM_LOG1[] PROGMEM =
|
|
"<fieldset><legend><b> " D_LOGGING_PARAMETERS " </b>"
|
|
"</legend><form method='get' action='lg'>";
|
|
const char HTTP_FORM_LOG2[] PROGMEM =
|
|
"<p><b>" D_SYSLOG_HOST "</b> (" SYS_LOG_HOST ")<br><input id='lh' placeholder='" SYS_LOG_HOST "' value='%s'></p>"
|
|
"<p><b>" D_SYSLOG_PORT "</b> (" STR(SYS_LOG_PORT) ")<br><input id='lp' placeholder='" STR(SYS_LOG_PORT) "' value='%d'></p>"
|
|
"<p><b>" D_TELEMETRY_PERIOD "</b> (" STR(TELE_PERIOD) ")<br><input id='lt' placeholder='" STR(TELE_PERIOD) "' value='%d'></p>";
|
|
|
|
const char HTTP_FORM_OTHER[] PROGMEM =
|
|
"<fieldset><legend><b> " D_OTHER_PARAMETERS " </b></legend>"
|
|
"<form method='get' action='co'>"
|
|
"<p></p>"
|
|
"<fieldset><legend><b> " D_TEMPLATE " </b></legend>"
|
|
"<p><input id='t1' placeholder='" D_TEMPLATE "' value='%s'></p>"
|
|
"<p><input id='t2' type='checkbox'%s><b>" D_ACTIVATE "</b></p>"
|
|
"</fieldset>"
|
|
"<br>"
|
|
"<b>" D_WEB_ADMIN_PASSWORD "</b><input type='checkbox' onclick='sp(\"wp\")'><br><input id='wp' type='password' placeholder='" D_WEB_ADMIN_PASSWORD "' value='" D_ASTERISK_PWD "'><br>"
|
|
"<br>"
|
|
"<input id='b1' type='checkbox'%s><b>" D_MQTT_ENABLE "</b><br>"
|
|
"<br>";
|
|
|
|
const char HTTP_FORM_END[] PROGMEM =
|
|
"<br>"
|
|
"<button name='save' type='submit' class='button bgrn'>" D_SAVE "</button>"
|
|
"</form></fieldset>";
|
|
|
|
const char HTTP_FORM_RST[] PROGMEM =
|
|
"<div id='f1' style='display:block;'>"
|
|
"<fieldset><legend><b> " D_RESTORE_CONFIGURATION " </b></legend>";
|
|
const char HTTP_FORM_UPG[] PROGMEM =
|
|
"<div id='f1' style='display:block;'>"
|
|
"<fieldset><legend><b> " D_UPGRADE_BY_WEBSERVER " </b></legend>"
|
|
"<form method='get' action='u1'>"
|
|
"<br><b>" D_OTA_URL "</b><br><input id='o' placeholder='OTA_URL' value='%s'><br>"
|
|
"<br><button type='submit'>" D_START_UPGRADE "</button></form>"
|
|
"</fieldset><br><br>"
|
|
"<fieldset><legend><b> " D_UPGRADE_BY_FILE_UPLOAD " </b></legend>";
|
|
const char HTTP_FORM_RST_UPG[] PROGMEM =
|
|
"<form method='post' action='u2' enctype='multipart/form-data'>"
|
|
"<br><input type='file' name='u2'><br>"
|
|
"<br><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>" D_START " %s</button></form>"
|
|
"</fieldset>"
|
|
"</div>"
|
|
"<div id='f2' style='display:none;text-align:center;'><b>" D_UPLOAD_STARTED " ...</b></div>";
|
|
|
|
const char HTTP_FORM_CMND[] PROGMEM =
|
|
"<br><textarea readonly id='t1' cols='340' wrap='off'></textarea><br><br>"
|
|
"<form method='get' onsubmit='return l(1);'>"
|
|
"<input id='c1' placeholder='" D_ENTER_COMMAND "' autofocus><br>"
|
|
|
|
"</form>";
|
|
|
|
const char HTTP_TABLE100[] PROGMEM =
|
|
"<table style='width:100%%'>";
|
|
|
|
const char HTTP_COUNTER[] PROGMEM =
|
|
"<br><div id='t' style='text-align:center;'></div>";
|
|
|
|
const char HTTP_END[] PROGMEM =
|
|
"<div style='text-align:right;font-size:11px;'><hr/><a href='https://bit.ly/tasmota' target='_blank' style='color:#aaa;'>Tasmota %s " D_BY " Theo Arends</a></div>"
|
|
"</div>"
|
|
"</body>"
|
|
"</html>";
|
|
|
|
const char HTTP_DEVICE_CONTROL[] PROGMEM = "<td style='width:%d%%'><button onclick='la(\"&o=%d\");'>%s%s</button></td>";
|
|
const char HTTP_DEVICE_STATE[] PROGMEM = "<td style='width:%d{c}%s;font-size:%dpx'>%s</div></td>";
|
|
|
|
enum ButtonTitle {
|
|
BUTTON_RESTART, BUTTON_RESET_CONFIGURATION,
|
|
BUTTON_MAIN, BUTTON_CONFIGURATION, BUTTON_INFORMATION, BUTTON_FIRMWARE_UPGRADE, BUTTON_CONSOLE,
|
|
BUTTON_MODULE, BUTTON_WIFI, BUTTON_LOGGING, BUTTON_OTHER, BUTTON_TEMPLATE, BUTTON_BACKUP, BUTTON_RESTORE };
|
|
const char kButtonTitle[] PROGMEM =
|
|
D_RESTART "|" D_RESET_CONFIGURATION "|"
|
|
D_MAIN_MENU "|" D_CONFIGURATION "|" D_INFORMATION "|" D_FIRMWARE_UPGRADE "|" D_CONSOLE "|"
|
|
D_CONFIGURE_MODULE "|" D_CONFIGURE_WIFI"|" D_CONFIGURE_LOGGING "|" D_CONFIGURE_OTHER "|" D_CONFIGURE_TEMPLATE "|" D_BACKUP_CONFIGURATION "|" D_RESTORE_CONFIGURATION;
|
|
const char kButtonAction[] PROGMEM =
|
|
".|rt|"
|
|
".|cn|in|up|cs|"
|
|
"md|wi|lg|co|tp|dl|rs";
|
|
const char kButtonConfirm[] PROGMEM = D_CONFIRM_RESTART "|" D_CONFIRM_RESET_CONFIGURATION;
|
|
|
|
enum CTypes { CT_HTML, CT_PLAIN, CT_XML, CT_JSON, CT_STREAM };
|
|
const char kContentTypes[] PROGMEM = "text/html|text/plain|text/xml|application/json|application/octet-stream";
|
|
|
|
const char kLoggingOptions[] PROGMEM = D_SERIAL_LOG_LEVEL "|" D_WEB_LOG_LEVEL "|" D_MQTT_LOG_LEVEL "|" D_SYS_LOG_LEVEL;
|
|
const char kLoggingLevels[] PROGMEM = D_NONE "|" D_ERROR "|" D_INFO "|" D_DEBUG "|" D_MORE_DEBUG;
|
|
|
|
const char kEmulationOptions[] PROGMEM = D_NONE "|" D_BELKIN_WEMO "|" D_HUE_BRIDGE;
|
|
|
|
const char kUploadErrors[] PROGMEM =
|
|
D_UPLOAD_ERR_1 "|" D_UPLOAD_ERR_2 "|" D_UPLOAD_ERR_3 "|" D_UPLOAD_ERR_4 "|" D_UPLOAD_ERR_5 "|" D_UPLOAD_ERR_6 "|" D_UPLOAD_ERR_7 "|" D_UPLOAD_ERR_8 "|" D_UPLOAD_ERR_9
|
|
#ifdef USE_RF_FLASH
|
|
"|" D_UPLOAD_ERR_10 "|" D_UPLOAD_ERR_11 "|" D_UPLOAD_ERR_12 "|" D_UPLOAD_ERR_13
|
|
#endif
|
|
"|" D_UPLOAD_ERR_14
|
|
;
|
|
|
|
const uint16_t DNS_PORT = 53;
|
|
enum HttpOptions {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER, HTTP_MANAGER_RESET_ONLY};
|
|
|
|
DNSServer *DnsServer;
|
|
ESP8266WebServer *WebServer;
|
|
|
|
struct WEB {
|
|
String chunk_buffer = "";
|
|
bool reset_web_log_flag = false;
|
|
uint8_t state = HTTP_OFF;
|
|
uint8_t upload_error = 0;
|
|
uint8_t upload_file_type;
|
|
uint8_t upload_progress_dot_count;
|
|
uint8_t config_block_count = 0;
|
|
uint8_t config_xor_on = 0;
|
|
uint8_t config_xor_on_set = CONFIG_FILE_XOR;
|
|
} Web;
|
|
|
|
|
|
static void WebGetArg(const char* arg, char* out, size_t max)
|
|
{
|
|
String s = WebServer->arg(arg);
|
|
strlcpy(out, s.c_str(), max);
|
|
|
|
}
|
|
|
|
static bool WifiIsInManagerMode(){
|
|
return (HTTP_MANAGER == Web.state || HTTP_MANAGER_RESET_ONLY == Web.state);
|
|
}
|
|
|
|
void ShowWebSource(uint32_t source)
|
|
{
|
|
if ((source > 0) && (source < SRC_MAX)) {
|
|
char stemp1[20];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SRC: %s from %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource), WebServer->client().remoteIP().toString().c_str());
|
|
}
|
|
}
|
|
|
|
void ExecuteWebCommand(char* svalue, uint32_t source)
|
|
{
|
|
ShowWebSource(source);
|
|
last_source = source;
|
|
ExecuteCommand(svalue, SRC_IGNORE);
|
|
}
|
|
|
|
void StartWebserver(int type, IPAddress ipweb)
|
|
{
|
|
if (!Settings.web_refresh) { Settings.web_refresh = HTTP_REFRESH_TIME; }
|
|
if (!Web.state) {
|
|
if (!WebServer) {
|
|
WebServer = new ESP8266WebServer((HTTP_MANAGER == type || HTTP_MANAGER_RESET_ONLY == type) ? 80 : WEB_PORT);
|
|
WebServer->on("/", HandleRoot);
|
|
WebServer->onNotFound(HandleNotFound);
|
|
WebServer->on("/up", HandleUpgradeFirmware);
|
|
WebServer->on("/u1", HandleUpgradeFirmwareStart);
|
|
WebServer->on("/u2", HTTP_POST, HandleUploadDone, HandleUploadLoop);
|
|
WebServer->on("/u2", HTTP_OPTIONS, HandlePreflightRequest);
|
|
WebServer->on("/cs", HTTP_GET, HandleConsole);
|
|
WebServer->on("/cs", HTTP_OPTIONS, HandlePreflightRequest);
|
|
WebServer->on("/cm", HandleHttpCommand);
|
|
#ifndef FIRMWARE_MINIMAL
|
|
WebServer->on("/cn", HandleConfiguration);
|
|
WebServer->on("/md", HandleModuleConfiguration);
|
|
WebServer->on("/wi", HandleWifiConfiguration);
|
|
WebServer->on("/lg", HandleLoggingConfiguration);
|
|
WebServer->on("/tp", HandleTemplateConfiguration);
|
|
WebServer->on("/co", HandleOtherConfiguration);
|
|
WebServer->on("/dl", HandleBackupConfiguration);
|
|
WebServer->on("/rs", HandleRestoreConfiguration);
|
|
WebServer->on("/rt", HandleResetConfiguration);
|
|
WebServer->on("/in", HandleInformation);
|
|
XdrvCall(FUNC_WEB_ADD_HANDLER);
|
|
XsnsCall(FUNC_WEB_ADD_HANDLER);
|
|
#endif
|
|
}
|
|
Web.reset_web_log_flag = false;
|
|
|
|
|
|
|
|
WebServer->collectHeaders(HEADER_KEYS, sizeof(HEADER_KEYS)/sizeof(char*));
|
|
|
|
WebServer->begin();
|
|
}
|
|
if (Web.state != type) {
|
|
#if LWIP_IPV6
|
|
String ipv6_addr = WifiGetIPv6();
|
|
if(ipv6_addr!="") AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s and IPv6 global address %s "), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str(),ipv6_addr.c_str());
|
|
else AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str());
|
|
#else
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str());
|
|
#endif
|
|
rules_flag.http_init = 1;
|
|
}
|
|
if (type) { Web.state = type; }
|
|
}
|
|
|
|
void StopWebserver(void)
|
|
{
|
|
if (Web.state) {
|
|
WebServer->close();
|
|
Web.state = HTTP_OFF;
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_STOPPED));
|
|
}
|
|
}
|
|
|
|
void WifiManagerBegin(bool reset_only)
|
|
{
|
|
|
|
if (!global_state.wifi_down) {
|
|
|
|
WifiSetMode(WIFI_AP_STA);
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT_AND_STATION));
|
|
} else {
|
|
|
|
WifiSetMode(WIFI_AP);
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_WIFIMANAGER_SET_ACCESSPOINT));
|
|
}
|
|
|
|
StopWebserver();
|
|
|
|
DnsServer = new DNSServer();
|
|
|
|
int channel = WIFI_SOFT_AP_CHANNEL;
|
|
if ((channel < 1) || (channel > 13)) { channel = 1; }
|
|
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
|
|
WiFi.softAP(my_hostname, WIFI_AP_PASSPHRASE, channel, 0);
|
|
#else
|
|
|
|
WiFi.softAP(my_hostname, WIFI_AP_PASSPHRASE, channel, 0, 1);
|
|
#endif
|
|
|
|
delay(500);
|
|
|
|
DnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
|
DnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
|
|
|
|
StartWebserver((reset_only ? HTTP_MANAGER_RESET_ONLY : HTTP_MANAGER), WiFi.softAPIP());
|
|
}
|
|
|
|
void PollDnsWebserver(void)
|
|
{
|
|
if (DnsServer) { DnsServer->processNextRequest(); }
|
|
if (WebServer) { WebServer->handleClient(); }
|
|
}
|
|
|
|
|
|
|
|
bool WebAuthenticate(void)
|
|
{
|
|
if (strlen(SettingsText(SET_WEBPWD)) && (HTTP_MANAGER_RESET_ONLY != Web.state)) {
|
|
return WebServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD));
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool HttpCheckPriviledgedAccess(bool autorequestauth = true)
|
|
{
|
|
if (HTTP_USER == Web.state) {
|
|
HandleRoot();
|
|
return false;
|
|
}
|
|
if (autorequestauth && !WebAuthenticate()) {
|
|
WebServer->requestAuthentication();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void HttpHeaderCors(void)
|
|
{
|
|
if (strlen(SettingsText(SET_CORS))) {
|
|
WebServer->sendHeader(F("Access-Control-Allow-Origin"), SettingsText(SET_CORS));
|
|
}
|
|
}
|
|
|
|
void WSHeaderSend(void)
|
|
{
|
|
WebServer->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate"));
|
|
WebServer->sendHeader(F("Pragma"), F("no-cache"));
|
|
WebServer->sendHeader(F("Expires"), F("-1"));
|
|
HttpHeaderCors();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void WSSend(int code, int ctype, const String& content)
|
|
{
|
|
char ct[25];
|
|
WebServer->send(code, GetTextIndexed(ct, sizeof(ct), ctype, kContentTypes), content);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void WSContentBegin(int code, int ctype)
|
|
{
|
|
WebServer->client().flush();
|
|
WSHeaderSend();
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
WebServer->sendHeader(F("Accept-Ranges"),F("none"));
|
|
WebServer->sendHeader(F("Transfer-Encoding"),F("chunked"));
|
|
#endif
|
|
WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
|
WSSend(code, ctype, "");
|
|
Web.chunk_buffer = "";
|
|
}
|
|
|
|
void _WSContentSend(const String& content)
|
|
{
|
|
size_t len = content.length();
|
|
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
const char * footer = "\r\n";
|
|
char chunk_size[11];
|
|
sprintf(chunk_size, "%x\r\n", len);
|
|
WebServer->sendContent(String() + chunk_size + content + footer);
|
|
#else
|
|
WebServer->sendContent(content);
|
|
#endif
|
|
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("WSContentSend"));
|
|
#endif
|
|
DEBUG_CORE_LOG(PSTR("WEB: Chunk size %d"), len);
|
|
}
|
|
|
|
void WSContentFlush(void)
|
|
{
|
|
if (Web.chunk_buffer.length() > 0) {
|
|
_WSContentSend(Web.chunk_buffer);
|
|
Web.chunk_buffer = "";
|
|
}
|
|
}
|
|
|
|
void _WSContentSendBuffer(void)
|
|
{
|
|
int len = strlen(mqtt_data);
|
|
|
|
if (0 == len) {
|
|
return;
|
|
}
|
|
else if (len == sizeof(mqtt_data)) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: Content too large"));
|
|
}
|
|
else if (len < CHUNKED_BUFFER_SIZE) {
|
|
Web.chunk_buffer += mqtt_data;
|
|
len = Web.chunk_buffer.length();
|
|
}
|
|
|
|
if (len >= CHUNKED_BUFFER_SIZE) {
|
|
WSContentFlush();
|
|
}
|
|
if (strlen(mqtt_data) >= CHUNKED_BUFFER_SIZE) {
|
|
_WSContentSend(mqtt_data);
|
|
}
|
|
}
|
|
|
|
void WSContentSend_P(const char* formatP, ...)
|
|
{
|
|
|
|
va_list arg;
|
|
va_start(arg, formatP);
|
|
int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg);
|
|
va_end(arg);
|
|
|
|
#ifdef DEBUG_TASMOTA_CORE
|
|
if (len > (sizeof(mqtt_data) -1)) {
|
|
mqtt_data[33] = '\0';
|
|
DEBUG_CORE_LOG(PSTR("ERROR: WSContentSend_P size %d > mqtt_data size %d. Start of data [%s...]"), len, sizeof(mqtt_data), mqtt_data);
|
|
}
|
|
#endif
|
|
|
|
_WSContentSendBuffer();
|
|
}
|
|
|
|
void WSContentSend_PD(const char* formatP, ...)
|
|
{
|
|
|
|
va_list arg;
|
|
va_start(arg, formatP);
|
|
int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg);
|
|
va_end(arg);
|
|
|
|
#ifdef DEBUG_TASMOTA_CORE
|
|
if (len > (sizeof(mqtt_data) -1)) {
|
|
mqtt_data[33] = '\0';
|
|
DEBUG_CORE_LOG(PSTR("ERROR: WSContentSend_PD size %d > mqtt_data size %d. Start of data [%s...]"), len, sizeof(mqtt_data), mqtt_data);
|
|
}
|
|
#endif
|
|
|
|
if (D_DECIMAL_SEPARATOR[0] != '.') {
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
if ('.' == mqtt_data[i]) {
|
|
mqtt_data[i] = D_DECIMAL_SEPARATOR[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
_WSContentSendBuffer();
|
|
}
|
|
|
|
void WSContentStart_P(const char* title, bool auth)
|
|
{
|
|
if (auth && strlen(SettingsText(SET_WEBPWD)) && !WebServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD))) {
|
|
return WebServer->requestAuthentication();
|
|
}
|
|
|
|
WSContentBegin(200, CT_HTML);
|
|
|
|
if (title != nullptr) {
|
|
char ctitle[strlen_P(title) +1];
|
|
strcpy_P(ctitle, title);
|
|
WSContentSend_P(HTTP_HEADER, SettingsText(SET_FRIENDLYNAME1), ctitle);
|
|
}
|
|
}
|
|
|
|
void WSContentStart_P(const char* title)
|
|
{
|
|
WSContentStart_P(title, true);
|
|
}
|
|
|
|
void WSContentSendStyle_P(const char* formatP, ...)
|
|
{
|
|
if (WifiIsInManagerMode()) {
|
|
if (WifiConfigCounter()) {
|
|
WSContentSend_P(HTTP_SCRIPT_COUNTER);
|
|
}
|
|
}
|
|
WSContentSend_P(HTTP_HEAD_LAST_SCRIPT);
|
|
|
|
WSContentSend_P(HTTP_HEAD_STYLE1, WebColor(COL_FORM), WebColor(COL_INPUT), WebColor(COL_INPUT_TEXT), WebColor(COL_INPUT),
|
|
WebColor(COL_INPUT_TEXT), WebColor(COL_CONSOLE), WebColor(COL_CONSOLE_TEXT), WebColor(COL_BACKGROUND));
|
|
WSContentSend_P(HTTP_HEAD_STYLE2, WebColor(COL_BUTTON), WebColor(COL_BUTTON_TEXT), WebColor(COL_BUTTON_HOVER),
|
|
WebColor(COL_BUTTON_RESET), WebColor(COL_BUTTON_RESET_HOVER), WebColor(COL_BUTTON_SAVE), WebColor(COL_BUTTON_SAVE_HOVER),
|
|
WebColor(COL_BUTTON));
|
|
if (formatP != nullptr) {
|
|
|
|
va_list arg;
|
|
va_start(arg, formatP);
|
|
int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg);
|
|
va_end(arg);
|
|
|
|
#ifdef DEBUG_TASMOTA_CORE
|
|
if (len > (sizeof(mqtt_data) -1)) {
|
|
mqtt_data[33] = '\0';
|
|
DEBUG_CORE_LOG(PSTR("ERROR: WSContentSendStyle_P size %d > mqtt_data size %d. Start of data [%s...]"), len, sizeof(mqtt_data), mqtt_data);
|
|
}
|
|
#endif
|
|
|
|
_WSContentSendBuffer();
|
|
}
|
|
WSContentSend_P(HTTP_HEAD_STYLE3, WebColor(COL_TEXT),
|
|
#ifdef FIRMWARE_MINIMAL
|
|
WebColor(COL_TEXT_WARNING),
|
|
#endif
|
|
WebColor(COL_TITLE),
|
|
ModuleName().c_str(), SettingsText(SET_FRIENDLYNAME1));
|
|
if (Settings.flag3.gui_hostname_ip) {
|
|
bool lip = (static_cast<uint32_t>(WiFi.localIP()) != 0);
|
|
bool sip = (static_cast<uint32_t>(WiFi.softAPIP()) != 0);
|
|
WSContentSend_P(PSTR("<h4>%s%s (%s%s%s)</h4>"),
|
|
my_hostname,
|
|
(Wifi.mdns_begun) ? ".local" : "",
|
|
(lip) ? WiFi.localIP().toString().c_str() : "",
|
|
(lip && sip) ? ", " : "",
|
|
(sip) ? WiFi.softAPIP().toString().c_str() : "");
|
|
}
|
|
WSContentSend_P(PSTR("</div>"));
|
|
}
|
|
|
|
void WSContentSendStyle(void)
|
|
{
|
|
WSContentSendStyle_P(nullptr);
|
|
}
|
|
|
|
void WSContentButton(uint32_t title_index)
|
|
{
|
|
char action[4];
|
|
char title[100];
|
|
|
|
if (title_index <= BUTTON_RESET_CONFIGURATION) {
|
|
char confirm[100];
|
|
WSContentSend_P(PSTR("<p><form action='%s' method='get' onsubmit='return confirm(\"%s\");'><button name='%s' class='button bred'>%s</button></form></p>"),
|
|
GetTextIndexed(action, sizeof(action), title_index, kButtonAction),
|
|
GetTextIndexed(confirm, sizeof(confirm), title_index, kButtonConfirm),
|
|
(!title_index) ? "rst" : "non",
|
|
GetTextIndexed(title, sizeof(title), title_index, kButtonTitle));
|
|
} else {
|
|
WSContentSend_P(PSTR("<p><form action='%s' method='get'><button>%s</button></form></p>"),
|
|
GetTextIndexed(action, sizeof(action), title_index, kButtonAction),
|
|
GetTextIndexed(title, sizeof(title), title_index, kButtonTitle));
|
|
}
|
|
}
|
|
|
|
void WSContentSpaceButton(uint32_t title_index)
|
|
{
|
|
WSContentSend_P(PSTR("<div></div>"));
|
|
WSContentButton(title_index);
|
|
}
|
|
|
|
void WSContentEnd(void)
|
|
{
|
|
WSContentFlush();
|
|
_WSContentSend("");
|
|
WebServer->client().stop();
|
|
}
|
|
|
|
void WSContentStop(void)
|
|
{
|
|
if (WifiIsInManagerMode()) {
|
|
if (WifiConfigCounter()) {
|
|
WSContentSend_P(HTTP_COUNTER);
|
|
}
|
|
}
|
|
WSContentSend_P(HTTP_END, my_version);
|
|
WSContentEnd();
|
|
}
|
|
|
|
|
|
|
|
void WebRestart(uint32_t type)
|
|
{
|
|
|
|
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTART);
|
|
|
|
bool reset_only = (HTTP_MANAGER_RESET_ONLY == Web.state);
|
|
|
|
WSContentStart_P((type) ? S_SAVE_CONFIGURATION : S_RESTART, !reset_only);
|
|
WSContentSend_P(HTTP_SCRIPT_RELOAD);
|
|
WSContentSendStyle();
|
|
if (type) {
|
|
WSContentSend_P(PSTR("<div style='text-align:center;'><b>" D_CONFIGURATION_SAVED "</b><br>"));
|
|
if (2 == type) {
|
|
WSContentSend_P(PSTR("<br>" D_TRYING_TO_CONNECT "<br>"));
|
|
}
|
|
WSContentSend_P(PSTR("</div>"));
|
|
}
|
|
WSContentSend_P(HTTP_MSG_RSTRT);
|
|
if (HTTP_MANAGER == Web.state || reset_only) {
|
|
Web.state = HTTP_ADMIN;
|
|
} else {
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
}
|
|
WSContentStop();
|
|
|
|
ShowWebSource(SRC_WEBGUI);
|
|
restart_flag = 2;
|
|
}
|
|
|
|
|
|
|
|
void HandleWifiLogin(void)
|
|
{
|
|
WSContentStart_P(S_CONFIGURE_WIFI, false);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_LOGIN);
|
|
|
|
if (HTTP_MANAGER_RESET_ONLY == Web.state) {
|
|
WSContentSpaceButton(BUTTON_RESTART);
|
|
#ifndef FIRMWARE_MINIMAL
|
|
WSContentSpaceButton(BUTTON_RESET_CONFIGURATION);
|
|
#endif
|
|
}
|
|
|
|
WSContentStop();
|
|
}
|
|
|
|
void WebSliderColdWarm(void)
|
|
{
|
|
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT,
|
|
"a",
|
|
"#fff", "#ff0",
|
|
1,
|
|
153, 500,
|
|
LightGetColorTemp(),
|
|
't', 0);
|
|
}
|
|
|
|
void HandleRoot(void)
|
|
{
|
|
if (CaptivePortal()) { return; }
|
|
|
|
if (WebServer->hasArg("rst")) {
|
|
WebRestart(0);
|
|
return;
|
|
}
|
|
|
|
if (WifiIsInManagerMode()) {
|
|
#ifndef FIRMWARE_MINIMAL
|
|
if (strlen(SettingsText(SET_WEBPWD)) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1")) && HTTP_MANAGER_RESET_ONLY != Web.state) {
|
|
HandleWifiLogin();
|
|
} else {
|
|
if (!strlen(SettingsText(SET_WEBPWD)) || (((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == SettingsText(SET_WEBPWD) )) || HTTP_MANAGER_RESET_ONLY == Web.state)) {
|
|
HandleWifiConfiguration();
|
|
} else {
|
|
|
|
HandleWifiLogin();
|
|
}
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (HandleRootStatusRefresh()) {
|
|
return;
|
|
}
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU);
|
|
|
|
char stemp[33];
|
|
|
|
WSContentStart_P(S_MAIN_MENU);
|
|
#ifdef USE_SCRIPT_WEB_DISPLAY
|
|
WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh, Settings.web_refresh);
|
|
#else
|
|
WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh);
|
|
#endif
|
|
WSContentSend_P(HTTP_SCRIPT_ROOT_PART2);
|
|
|
|
WSContentSendStyle();
|
|
|
|
WSContentSend_P(PSTR("<div id='l1' name='l1'></div>"));
|
|
if (devices_present) {
|
|
#ifdef USE_LIGHT
|
|
if (light_type) {
|
|
uint8_t light_subtype = light_type &7;
|
|
if (!Settings.flag3.pwm_multi_channels) {
|
|
bool split_white = ((LST_RGBW <= light_subtype) && (devices_present > 1));
|
|
|
|
if ((LST_COLDWARM == light_subtype) || ((LST_RGBCW == light_subtype) && !split_white)) {
|
|
WebSliderColdWarm();
|
|
}
|
|
|
|
if (light_subtype > 2) {
|
|
uint16_t hue;
|
|
uint8_t sat;
|
|
LightGetHSB(&hue, &sat, nullptr);
|
|
|
|
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT,
|
|
"b",
|
|
"#800", "#f00 5%,#ff0 20%,#0f0 35%,#0ff 50%,#00f 65%,#f0f 80%,#f00 95%,#800",
|
|
2,
|
|
1, 359,
|
|
hue,
|
|
'h', 0);
|
|
|
|
uint8_t dcolor = changeUIntScale(Settings.light_dimmer, 0, 100, 0, 255);
|
|
char scolor[8];
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("#%02X%02X%02X"), dcolor, dcolor, dcolor);
|
|
uint8_t red, green, blue;
|
|
LightHsToRgb(hue, 255, &red, &green, &blue);
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("#%02X%02X%02X"), red, green, blue);
|
|
|
|
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT,
|
|
"s",
|
|
scolor, stemp,
|
|
3,
|
|
0, 100,
|
|
changeUIntScale(sat, 0, 255, 0, 100),
|
|
'n', 0);
|
|
}
|
|
|
|
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT,
|
|
"c",
|
|
"#000", "#fff",
|
|
4,
|
|
Settings.flag3.slider_dimmer_stay_on, 100,
|
|
Settings.light_dimmer,
|
|
'd', 0);
|
|
|
|
if (split_white) {
|
|
if (LST_RGBCW == light_subtype) {
|
|
WebSliderColdWarm();
|
|
}
|
|
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT,
|
|
"f",
|
|
"#000", "#fff",
|
|
5,
|
|
Settings.flag3.slider_dimmer_stay_on, 100,
|
|
LightGetDimmer(2),
|
|
'w', 0);
|
|
}
|
|
} else {
|
|
uint32_t pwm_channels = light_subtype > LST_MAX ? LST_MAX : light_subtype;
|
|
stemp[0] = 'e'; stemp[1] = '0'; stemp[2] = '\0';
|
|
for (uint32_t i = 0; i < pwm_channels; i++) {
|
|
stemp[1]++;
|
|
|
|
WSContentSend_P(HTTP_MSG_SLIDER_GRADIENT,
|
|
stemp,
|
|
"#000", "#fff",
|
|
i+1,
|
|
1, 100,
|
|
changeUIntScale(Settings.light_color[i], 0, 255, 0, 100),
|
|
'e', i+1);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_SHUTTER
|
|
if (Settings.flag3.shutter_mode) {
|
|
for (uint32_t i = 0; i < shutters_present; i++) {
|
|
WSContentSend_P(HTTP_MSG_SLIDER_SHUTTER, Settings.shutter_position[i], i+1);
|
|
}
|
|
}
|
|
#endif
|
|
WSContentSend_P(HTTP_TABLE100);
|
|
WSContentSend_P(PSTR("<tr>"));
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) {
|
|
WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1,
|
|
(strlen(SettingsText(SET_BUTTON1))) ? SettingsText(SET_BUTTON1) : D_BUTTON_TOGGLE,
|
|
"");
|
|
for (uint32_t i = 0; i < MaxFanspeed(); i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i);
|
|
WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2,
|
|
(strlen(SettingsText(SET_BUTTON2 + i))) ? SettingsText(SET_BUTTON2 + i) : stemp,
|
|
"");
|
|
}
|
|
} else {
|
|
#endif
|
|
for (uint32_t idx = 1; idx <= devices_present; idx++) {
|
|
bool set_button = ((idx <= MAX_BUTTON_TEXT) && strlen(SettingsText(SET_BUTTON1 + idx -1)));
|
|
#ifdef USE_SHUTTER
|
|
int32_t ShutterWebButton;
|
|
if (ShutterWebButton = IsShutterWebButton(idx)) {
|
|
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx,
|
|
(set_button) ? SettingsText(SET_BUTTON1 + idx -1) : ((Settings.shutter_options[abs(ShutterWebButton)-1] & 2) ? "-" : ((ShutterWebButton>0) ? "▲" : "▼")),
|
|
"");
|
|
continue;
|
|
}
|
|
#endif
|
|
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx);
|
|
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx,
|
|
(set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (devices_present < 5) ? D_BUTTON_TOGGLE : "",
|
|
(set_button) ? "" : (devices_present > 1) ? stemp : "");
|
|
}
|
|
#ifdef USE_SONOFF_IFAN
|
|
}
|
|
#endif
|
|
WSContentSend_P(PSTR("</tr></table>"));
|
|
}
|
|
#ifdef USE_SONOFF_RF
|
|
if (SONOFF_BRIDGE == my_module_type) {
|
|
WSContentSend_P(HTTP_TABLE100);
|
|
WSContentSend_P(PSTR("<tr>"));
|
|
uint32_t idx = 0;
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
if (idx > 0) { WSContentSend_P(PSTR("</tr><tr>")); }
|
|
for (uint32_t j = 0; j < 4; j++) {
|
|
idx++;
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), idx);
|
|
WSContentSend_P(PSTR("<td style='width:25%%'><button onclick='la(\"&k=%d\");'>%s</button></td>"), idx,
|
|
(strlen(SettingsText(SET_BUTTON1 + idx -1))) ? SettingsText(SET_BUTTON1 + idx -1) : stemp);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("</tr></table>"));
|
|
}
|
|
#endif
|
|
|
|
#ifndef FIRMWARE_MINIMAL
|
|
XdrvCall(FUNC_WEB_ADD_MAIN_BUTTON);
|
|
XsnsCall(FUNC_WEB_ADD_MAIN_BUTTON);
|
|
#endif
|
|
|
|
if (HTTP_ADMIN == Web.state) {
|
|
#ifdef FIRMWARE_MINIMAL
|
|
WSContentSpaceButton(BUTTON_FIRMWARE_UPGRADE);
|
|
#else
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentButton(BUTTON_INFORMATION);
|
|
WSContentButton(BUTTON_FIRMWARE_UPGRADE);
|
|
#endif
|
|
WSContentButton(BUTTON_CONSOLE);
|
|
WSContentButton(BUTTON_RESTART);
|
|
}
|
|
WSContentStop();
|
|
}
|
|
|
|
bool HandleRootStatusRefresh(void)
|
|
{
|
|
if (!WebAuthenticate()) {
|
|
WebServer->requestAuthentication();
|
|
return true;
|
|
}
|
|
|
|
if (!WebServer->hasArg("m")) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef USE_SCRIPT_WEB_DISPLAY
|
|
Script_Check_HTML_Setvars();
|
|
#endif
|
|
|
|
char tmp[8];
|
|
char svalue[32];
|
|
char webindex[5];
|
|
|
|
WebGetArg("o", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
ShowWebSource(SRC_WEBGUI);
|
|
uint32_t device = atoi(tmp);
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) {
|
|
if (device < 2) {
|
|
ExecuteCommandPower(1, POWER_TOGGLE, SRC_IGNORE);
|
|
} else {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), device -2);
|
|
ExecuteCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
} else {
|
|
#endif
|
|
#ifdef USE_SHUTTER
|
|
int32_t ShutterWebButton;
|
|
if (ShutterWebButton = IsShutterWebButton(device)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("ShutterPosition%d %s"), abs(ShutterWebButton), (ShutterWebButton>0) ? PSTR(D_CMND_SHUTTER_TOGGLEUP) : PSTR(D_CMND_SHUTTER_TOGGLEDOWN));
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
} else {
|
|
#endif
|
|
ExecuteCommandPower(device, POWER_TOGGLE, SRC_IGNORE);
|
|
#ifdef USE_SHUTTER
|
|
}
|
|
#endif
|
|
#ifdef USE_SONOFF_IFAN
|
|
}
|
|
#endif
|
|
}
|
|
WebGetArg("d0", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_DIMMER " %s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
WebGetArg("w0", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_WHITE " %s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
uint32_t pwm_channels = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7);
|
|
for (uint32_t j = 1; j <= pwm_channels; j++) {
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("e%d"), j);
|
|
WebGetArg(webindex, tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_CHANNEL "%d %s"), j, tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
}
|
|
WebGetArg("t0", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
WebGetArg("h0", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_HSBCOLOR "1 %s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
WebGetArg("n0", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_HSBCOLOR "2 %s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
#ifdef USE_SHUTTER
|
|
for (uint32_t j = 1; j <= shutters_present; j++) {
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("u%d"), j);
|
|
WebGetArg(webindex, tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("ShutterPosition%d %s"), j, tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_SONOFF_RF
|
|
WebGetArg("k", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
#endif
|
|
WSContentBegin(200, CT_HTML);
|
|
WSContentSend_P(PSTR("{t}"));
|
|
XsnsCall(FUNC_WEB_SENSOR);
|
|
#ifdef USE_SCRIPT_WEB_DISPLAY
|
|
XdrvCall(FUNC_WEB_SENSOR);
|
|
#endif
|
|
|
|
WSContentSend_P(PSTR("</table>"));
|
|
|
|
if (devices_present) {
|
|
WSContentSend_P(PSTR("{t}<tr>"));
|
|
uint32_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32;
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) {
|
|
WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(power, 0)) ? "bold" : "normal", 54, GetStateText(bitRead(power, 0)));
|
|
uint32_t fanspeed = GetFanspeed();
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed);
|
|
WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? "bold" : "normal", 54, (fanspeed) ? svalue : GetStateText(0));
|
|
} else {
|
|
#endif
|
|
for (uint32_t idx = 1; idx <= devices_present; idx++) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1));
|
|
WSContentSend_P(HTTP_DEVICE_STATE, 100 / devices_present, (bitRead(power, idx -1)) ? "bold" : "normal", fsize, (devices_present < 5) ? GetStateText(bitRead(power, idx -1)) : svalue);
|
|
}
|
|
#ifdef USE_SONOFF_IFAN
|
|
}
|
|
#endif
|
|
WSContentSend_P(PSTR("</tr></table>"));
|
|
}
|
|
WSContentEnd();
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef USE_SHUTTER
|
|
int32_t IsShutterWebButton(uint32_t idx) {
|
|
|
|
int32_t ShutterWebButton = 0;
|
|
if (Settings.flag3.shutter_mode) {
|
|
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
|
|
if (Settings.shutter_startrelay[i] && ((Settings.shutter_startrelay[i] == idx) || (Settings.shutter_startrelay[i] == (idx-1)))) {
|
|
ShutterWebButton = (Settings.shutter_startrelay[i] == idx) ? (i+1): (-1-i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ShutterWebButton;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef FIRMWARE_MINIMAL
|
|
|
|
void HandleConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURATION);
|
|
|
|
WSContentStart_P(S_CONFIGURATION);
|
|
WSContentSendStyle();
|
|
|
|
WSContentButton(BUTTON_MODULE);
|
|
WSContentButton(BUTTON_WIFI);
|
|
|
|
XdrvCall(FUNC_WEB_ADD_BUTTON);
|
|
XsnsCall(FUNC_WEB_ADD_BUTTON);
|
|
|
|
WSContentButton(BUTTON_LOGGING);
|
|
WSContentButton(BUTTON_OTHER);
|
|
WSContentButton(BUTTON_TEMPLATE);
|
|
|
|
WSContentSpaceButton(BUTTON_RESET_CONFIGURATION);
|
|
WSContentButton(BUTTON_BACKUP);
|
|
WSContentButton(BUTTON_RESTORE);
|
|
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
}
|
|
|
|
|
|
|
|
void HandleTemplateConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
TemplateSaveSettings();
|
|
WebRestart(1);
|
|
return;
|
|
}
|
|
|
|
char stemp[30];
|
|
|
|
if (WebServer->hasArg("m")) {
|
|
WSContentBegin(200, CT_PLAIN);
|
|
for (uint32_t i = 0; i < sizeof(kModuleNiceList); i++) {
|
|
uint32_t midx = pgm_read_byte(kModuleNiceList + i);
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, AnyModuleName(midx).c_str(), midx +1);
|
|
}
|
|
WSContentEnd();
|
|
return;
|
|
}
|
|
|
|
WebGetArg("t", stemp, sizeof(stemp));
|
|
if (strlen(stemp)) {
|
|
uint32_t module = atoi(stemp);
|
|
uint32_t module_save = Settings.module;
|
|
Settings.module = module;
|
|
myio cmodule;
|
|
ModuleGpios(&cmodule);
|
|
gpio_flag flag = ModuleFlag();
|
|
Settings.module = module_save;
|
|
|
|
WSContentBegin(200, CT_PLAIN);
|
|
WSContentSend_P(PSTR("%s}1"), AnyModuleName(module).c_str());
|
|
for (uint32_t i = 0; i < sizeof(kGpioNiceList); i++) {
|
|
if (1 == i) {
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, 255, D_SENSOR_USER, 255);
|
|
}
|
|
uint32_t midx = pgm_read_byte(kGpioNiceList + i);
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx);
|
|
}
|
|
WSContentSend_P(PSTR("}1"));
|
|
|
|
for (uint32_t i = 0; i < ADC0_END; i++) {
|
|
if (1 == i) {
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, ADC0_USER, D_SENSOR_USER, ADC0_USER);
|
|
}
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, i, GetTextIndexed(stemp, sizeof(stemp), i, kAdc0Names), i);
|
|
}
|
|
WSContentSend_P(PSTR("}1"));
|
|
|
|
for (uint32_t i = 0; i < sizeof(cmodule); i++) {
|
|
if ((i < 6) || ((i > 8) && (i != 11))) {
|
|
WSContentSend_P(PSTR("%s%d"), (i>0)?",":"", cmodule.io[i]);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("}1%d}1%d"), flag, Settings.user_template_base);
|
|
WSContentEnd();
|
|
return;
|
|
}
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_TEMPLATE);
|
|
|
|
WSContentStart_P(S_CONFIGURE_TEMPLATE);
|
|
WSContentSend_P(HTTP_SCRIPT_MODULE_TEMPLATE);
|
|
WSContentSend_P(HTTP_SCRIPT_TEMPLATE);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_TEMPLATE);
|
|
WSContentSend_P(HTTP_TABLE100);
|
|
WSContentSend_P(PSTR("<tr><td><b>" D_TEMPLATE_NAME "</b></td><td style='width:200px'><input id='s1' placeholder='" D_TEMPLATE_NAME "'></td></tr>"
|
|
"<tr><td><b>" D_BASE_TYPE "</b></td><td><select id='g99' onchange='st(this.value)'></select></td></tr>"
|
|
"</table>"
|
|
"<hr/>"));
|
|
WSContentSend_P(HTTP_TABLE100);
|
|
for (uint32_t i = 0; i < 17; i++) {
|
|
if ((i < 6) || ((i > 8) && (i != 11))) {
|
|
WSContentSend_P(PSTR("<tr><td><b><font color='#%06x'>" D_GPIO "%d</font></b></td><td%s><select id='g%d'></select></td></tr>"),
|
|
((9==i)||(10==i)) ? WebColor(COL_TEXT_WARNING) : WebColor(COL_TEXT), i, (0==i) ? " style='width:200px'" : "", i);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("<tr><td><b><font color='#%06x'>" D_ADC "0</font></b></td><td><select id='g17'></select></td></tr>"), WebColor(COL_TEXT));
|
|
WSContentSend_P(PSTR("</table>"));
|
|
gpio_flag flag = ModuleFlag();
|
|
if (flag.data > ADC0_USER) {
|
|
WSContentSend_P(HTTP_FORM_TEMPLATE_FLAG);
|
|
}
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void TemplateSaveSettings(void)
|
|
{
|
|
char tmp[sizeof(Settings.user_template.name)];
|
|
char webindex[5];
|
|
char svalue[128];
|
|
|
|
WebGetArg("s1", tmp, sizeof(tmp));
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " {\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), tmp);
|
|
|
|
uint32_t j = 0;
|
|
for (uint32_t i = 0; i < sizeof(Settings.user_template.gp); i++) {
|
|
if (6 == i) { j = 9; }
|
|
if (8 == i) { j = 12; }
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("g%d"), j);
|
|
WebGetArg(webindex, tmp, sizeof(tmp));
|
|
uint8_t gpio = atoi(tmp);
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s%s%d"), svalue, (i>0)?",":"", gpio);
|
|
j++;
|
|
}
|
|
|
|
WebGetArg("g17", tmp, sizeof(tmp));
|
|
uint32_t flag = atoi(tmp);
|
|
for (uint32_t i = 0; i < GPIO_FLAG_USED; i++) {
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("c%d"), i);
|
|
uint32_t state = WebServer->hasArg(webindex) << i +4;
|
|
flag += state;
|
|
}
|
|
WebGetArg("g99", tmp, sizeof(tmp));
|
|
uint32_t base = atoi(tmp) +1;
|
|
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), svalue, flag, base);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
|
|
|
|
|
|
void HandleModuleConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
ModuleSaveSettings();
|
|
WebRestart(1);
|
|
return;
|
|
}
|
|
|
|
char stemp[30];
|
|
uint32_t midx;
|
|
myio cmodule;
|
|
ModuleGpios(&cmodule);
|
|
|
|
if (WebServer->hasArg("m")) {
|
|
WSContentBegin(200, CT_PLAIN);
|
|
uint32_t vidx = 0;
|
|
for (uint32_t i = 0; i <= sizeof(kModuleNiceList); i++) {
|
|
if (0 == i) {
|
|
midx = USER_MODULE;
|
|
vidx = 0;
|
|
} else {
|
|
midx = pgm_read_byte(kModuleNiceList + i -1);
|
|
vidx = midx +1;
|
|
}
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, AnyModuleName(midx).c_str(), vidx);
|
|
}
|
|
WSContentEnd();
|
|
return;
|
|
}
|
|
|
|
if (WebServer->hasArg("g")) {
|
|
WSContentBegin(200, CT_PLAIN);
|
|
for (uint32_t j = 0; j < sizeof(kGpioNiceList); j++) {
|
|
midx = pgm_read_byte(kGpioNiceList + j);
|
|
if (!GetUsedInModule(midx, cmodule.io)) {
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, midx, GetTextIndexed(stemp, sizeof(stemp), midx, kSensorNames), midx);
|
|
}
|
|
}
|
|
WSContentEnd();
|
|
return;
|
|
}
|
|
|
|
#ifndef USE_ADC_VCC
|
|
if (WebServer->hasArg("a")) {
|
|
WSContentBegin(200, CT_PLAIN);
|
|
for (uint32_t j = 0; j < ADC0_END; j++) {
|
|
WSContentSend_P(HTTP_MODULE_TEMPLATE_REPLACE, j, GetTextIndexed(stemp, sizeof(stemp), j, kAdc0Names), j);
|
|
}
|
|
WSContentEnd();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MODULE);
|
|
|
|
WSContentStart_P(S_CONFIGURE_MODULE);
|
|
WSContentSend_P(HTTP_SCRIPT_MODULE_TEMPLATE);
|
|
WSContentSend_P(HTTP_SCRIPT_MODULE1, Settings.module);
|
|
for (uint32_t i = 0; i < sizeof(cmodule); i++) {
|
|
if (ValidGPIO(i, cmodule.io[i])) {
|
|
WSContentSend_P(PSTR("sk(%d,%d);"), my_module.io[i], i);
|
|
}
|
|
}
|
|
WSContentSend_P(HTTP_SCRIPT_MODULE2, Settings.my_adc0);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_MODULE, AnyModuleName(MODULE).c_str());
|
|
for (uint32_t i = 0; i < sizeof(cmodule); i++) {
|
|
if (ValidGPIO(i, cmodule.io[i])) {
|
|
snprintf_P(stemp, 3, PINS_WEMOS +i*2);
|
|
char sesp8285[40];
|
|
snprintf_P(sesp8285, sizeof(sesp8285), PSTR("<font color='#%06x'>ESP8285</font>"), WebColor(COL_TEXT_WARNING));
|
|
WSContentSend_P(PSTR("<tr><td style='width:190px'>%s <b>" D_GPIO "%d</b> %s</td><td style='width:176px'><select id='g%d'></select></td></tr>"),
|
|
(WEMOS==my_module_type)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :((9==i)||(10==i))? sesp8285 :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i);
|
|
}
|
|
}
|
|
#ifndef USE_ADC_VCC
|
|
if (ValidAdc()) {
|
|
WSContentSend_P(PSTR("<tr><td>%s <b>" D_ADC "0</b></td><td style='width:176px'><select id='g17'></select></td></tr>"), (WEMOS==my_module_type)?"A0":"");
|
|
}
|
|
#endif
|
|
WSContentSend_P(PSTR("</table>"));
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void ModuleSaveSettings(void)
|
|
{
|
|
char tmp[8];
|
|
char webindex[5];
|
|
|
|
WebGetArg("g99", tmp, sizeof(tmp));
|
|
uint32_t new_module = (!strlen(tmp)) ? MODULE : atoi(tmp);
|
|
Settings.last_module = Settings.module;
|
|
Settings.module = new_module;
|
|
SetModuleType();
|
|
myio cmodule;
|
|
ModuleGpios(&cmodule);
|
|
String gpios = "";
|
|
for (uint32_t i = 0; i < sizeof(cmodule); i++) {
|
|
if (Settings.last_module != new_module) {
|
|
Settings.my_gp.io[i] = GPIO_NONE;
|
|
} else {
|
|
if (ValidGPIO(i, cmodule.io[i])) {
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("g%d"), i);
|
|
WebGetArg(webindex, tmp, sizeof(tmp));
|
|
Settings.my_gp.io[i] = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
gpios += F(", " D_GPIO ); gpios += String(i); gpios += F(" "); gpios += String(Settings.my_gp.io[i]);
|
|
}
|
|
}
|
|
}
|
|
#ifndef USE_ADC_VCC
|
|
WebGetArg("g17", tmp, sizeof(tmp));
|
|
Settings.my_adc0 = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
gpios += F(", " D_ADC "0 "); gpios += String(Settings.my_adc0);
|
|
#endif
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MODULE "%s " D_CMND_MODULE "%s"), ModuleName().c_str(), gpios.c_str());
|
|
}
|
|
|
|
|
|
|
|
const char kUnescapeCode[] = "&><\"\'";
|
|
const char kEscapeCode[] PROGMEM = "&|>|<|"|'";
|
|
|
|
String HtmlEscape(const String unescaped) {
|
|
char escaped[10];
|
|
size_t ulen = unescaped.length();
|
|
String result = "";
|
|
for (size_t i = 0; i < ulen; i++) {
|
|
char c = unescaped[i];
|
|
char *p = strchr(kUnescapeCode, c);
|
|
if (p != nullptr) {
|
|
result += GetTextIndexed(escaped, sizeof(escaped), p - kUnescapeCode, kEscapeCode);
|
|
} else {
|
|
result += c;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
const char kEncryptionType[] PROGMEM = "|||" D_WPA_PSK "||" D_WPA2_PSK "|" D_WEP "||" D_NONE "|" D_AUTO;
|
|
|
|
void HandleWifiConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess(!WifiIsInManagerMode())) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_WIFI);
|
|
|
|
if (WebServer->hasArg("save") && HTTP_MANAGER_RESET_ONLY != Web.state) {
|
|
WifiSaveSettings();
|
|
WebRestart(2);
|
|
return;
|
|
}
|
|
|
|
WSContentStart_P(S_CONFIGURE_WIFI, !WifiIsInManagerMode());
|
|
WSContentSend_P(HTTP_SCRIPT_WIFI);
|
|
WSContentSendStyle();
|
|
|
|
if (HTTP_MANAGER_RESET_ONLY != Web.state) {
|
|
if (WebServer->hasArg("scan")) {
|
|
#ifdef USE_EMULATION
|
|
UdpDisconnect();
|
|
#endif
|
|
int n = WiFi.scanNetworks();
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SCAN_DONE));
|
|
|
|
if (0 == n) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, S_NO_NETWORKS_FOUND);
|
|
WSContentSend_P(S_NO_NETWORKS_FOUND);
|
|
WSContentSend_P(PSTR(". " D_REFRESH_TO_SCAN_AGAIN "."));
|
|
} else {
|
|
|
|
int indices[n];
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
indices[i] = i;
|
|
}
|
|
|
|
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
for (uint32_t j = i + 1; j < n; j++) {
|
|
if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
|
|
std::swap(indices[i], indices[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
String cssid;
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
if (-1 == indices[i]) { continue; }
|
|
cssid = WiFi.SSID(indices[i]);
|
|
uint32_t cschn = WiFi.channel(indices[i]);
|
|
for (uint32_t j = i + 1; j < n; j++) {
|
|
if ((cssid == WiFi.SSID(indices[j])) && (cschn == WiFi.channel(indices[j]))) {
|
|
DEBUG_CORE_LOG(PSTR(D_LOG_WIFI D_DUPLICATE_ACCESSPOINT " %s"), WiFi.SSID(indices[j]).c_str());
|
|
indices[j] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
if (-1 == indices[i]) { continue; }
|
|
DEBUG_CORE_LOG(PSTR(D_LOG_WIFI D_SSID " %s, " D_BSSID " %s, " D_CHANNEL " %d, " D_RSSI " %d"),
|
|
WiFi.SSID(indices[i]).c_str(), WiFi.BSSIDstr(indices[i]).c_str(), WiFi.channel(indices[i]), WiFi.RSSI(indices[i]));
|
|
int quality = WifiGetRssiAsQuality(WiFi.RSSI(indices[i]));
|
|
int auth = WiFi.encryptionType(indices[i]);
|
|
char encryption[20];
|
|
WSContentSend_P(PSTR("<div><a href='#p' onclick='c(this)'>%s</a> (%d) <span class='q'>%s %d%% (%d dBm)</span></div>"),
|
|
HtmlEscape(WiFi.SSID(indices[i])).c_str(),
|
|
WiFi.channel(indices[i]),
|
|
GetTextIndexed(encryption, sizeof(encryption), auth +1, kEncryptionType),
|
|
quality, WiFi.RSSI(indices[i])
|
|
);
|
|
delay(0);
|
|
|
|
}
|
|
WSContentSend_P(PSTR("<br>"));
|
|
}
|
|
} else {
|
|
WSContentSend_P(PSTR("<div><a href='/wi?scan='>" D_SCAN_FOR_WIFI_NETWORKS "</a></div><br>"));
|
|
}
|
|
|
|
|
|
WSContentSend_P(HTTP_FORM_WIFI, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), WIFI_HOSTNAME, WIFI_HOSTNAME, SettingsText(SET_HOSTNAME), SettingsText(SET_CORS));
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
}
|
|
|
|
if (WifiIsInManagerMode()) {
|
|
#ifndef FIRMWARE_MINIMAL
|
|
WSContentSpaceButton(BUTTON_RESTORE);
|
|
WSContentButton(BUTTON_RESET_CONFIGURATION);
|
|
#endif
|
|
WSContentSpaceButton(BUTTON_RESTART);
|
|
} else {
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
}
|
|
WSContentStop();
|
|
}
|
|
|
|
void WifiSaveSettings(void)
|
|
{
|
|
char tmp[TOPSZ];
|
|
|
|
WebGetArg("h", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp);
|
|
if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) {
|
|
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
|
|
}
|
|
WebGetArg("c", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_CORS, (!strlen(tmp)) ? CORS_DOMAIN : tmp);
|
|
WebGetArg("s1", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_STASSID1, (!strlen(tmp)) ? STA_SSID1 : tmp);
|
|
WebGetArg("s2", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_STASSID2, (!strlen(tmp)) ? STA_SSID2 : tmp);
|
|
WebGetArg("p1", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_STAPWD1, (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? SettingsText(SET_STAPWD1) : tmp);
|
|
WebGetArg("p2", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_STAPWD2, (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? SettingsText(SET_STAPWD2) : tmp);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_SSID "2 %s, " D_CMND_CORS " %s"),
|
|
SettingsText(SET_HOSTNAME), SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), SettingsText(SET_CORS));
|
|
}
|
|
|
|
|
|
|
|
void HandleLoggingConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_LOGGING);
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
LoggingSaveSettings();
|
|
HandleConfiguration();
|
|
return;
|
|
}
|
|
|
|
WSContentStart_P(S_CONFIGURE_LOGGING);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_LOG1);
|
|
char stemp1[45];
|
|
char stemp2[32];
|
|
uint8_t dlevel[4] = { LOG_LEVEL_INFO, LOG_LEVEL_INFO, LOG_LEVEL_NONE, LOG_LEVEL_NONE };
|
|
for (uint32_t idx = 0; idx < 4; idx++) {
|
|
if ((2==idx) && !Settings.flag.mqtt_enabled) { continue; }
|
|
uint32_t llevel = (0==idx)?Settings.seriallog_level:(1==idx)?Settings.weblog_level:(2==idx)?Settings.mqttlog_level:Settings.syslog_level;
|
|
WSContentSend_P(PSTR("<p><b>%s</b> (%s)<br><select id='l%d'>"),
|
|
GetTextIndexed(stemp1, sizeof(stemp1), idx, kLoggingOptions),
|
|
GetTextIndexed(stemp2, sizeof(stemp2), dlevel[idx], kLoggingLevels),
|
|
idx);
|
|
for (uint32_t i = LOG_LEVEL_NONE; i <= LOG_LEVEL_DEBUG_MORE; i++) {
|
|
WSContentSend_P(PSTR("<option%s value='%d'>%d %s</option>"),
|
|
(i == llevel) ? " selected" : "", i, i,
|
|
GetTextIndexed(stemp1, sizeof(stemp1), i, kLoggingLevels));
|
|
}
|
|
WSContentSend_P(PSTR("</select></p>"));
|
|
}
|
|
WSContentSend_P(HTTP_FORM_LOG2, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, Settings.tele_period);
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void LoggingSaveSettings(void)
|
|
{
|
|
char tmp[TOPSZ];
|
|
|
|
WebGetArg("l0", tmp, sizeof(tmp));
|
|
SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp));
|
|
WebGetArg("l1", tmp, sizeof(tmp));
|
|
Settings.weblog_level = (!strlen(tmp)) ? WEB_LOG_LEVEL : atoi(tmp);
|
|
WebGetArg("l2", tmp, sizeof(tmp));
|
|
Settings.mqttlog_level = (!strlen(tmp)) ? MQTT_LOG_LEVEL : atoi(tmp);
|
|
WebGetArg("l3", tmp, sizeof(tmp));
|
|
SetSyslog((!strlen(tmp)) ? SYS_LOG_LEVEL : atoi(tmp));
|
|
WebGetArg("lh", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_SYSLOG_HOST, (!strlen(tmp)) ? SYS_LOG_HOST : tmp);
|
|
WebGetArg("lp", tmp, sizeof(tmp));
|
|
Settings.syslog_port = (!strlen(tmp)) ? SYS_LOG_PORT : atoi(tmp);
|
|
WebGetArg("lt", tmp, sizeof(tmp));
|
|
Settings.tele_period = (!strlen(tmp)) ? TELE_PERIOD : atoi(tmp);
|
|
if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) {
|
|
Settings.tele_period = 10;
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_LOG D_CMND_SERIALLOG " %d, " D_CMND_WEBLOG " %d, " D_CMND_MQTTLOG " %d, " D_CMND_SYSLOG " %d, " D_CMND_LOGHOST " %s, " D_CMND_LOGPORT " %d, " D_CMND_TELEPERIOD " %d"),
|
|
Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, Settings.tele_period);
|
|
}
|
|
|
|
|
|
|
|
void HandleOtherConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_OTHER);
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
OtherSaveSettings();
|
|
WebRestart(1);
|
|
return;
|
|
}
|
|
|
|
WSContentStart_P(S_CONFIGURE_OTHER);
|
|
WSContentSendStyle();
|
|
|
|
TemplateJson();
|
|
char stemp[strlen(mqtt_data) +1];
|
|
strlcpy(stemp, mqtt_data, sizeof(stemp));
|
|
WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", (Settings.flag.mqtt_enabled) ? " checked" : "");
|
|
|
|
uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present;
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) { maxfn = 1; }
|
|
#endif
|
|
for (uint32_t i = 0; i < maxfn; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i +1);
|
|
WSContentSend_P(PSTR("<b>" D_FRIENDLY_NAME " %d</b> (" FRIENDLY_NAME "%s)<br><input id='a%d' placeholder='" FRIENDLY_NAME "%s' value='%s'><p></p>"),
|
|
i +1,
|
|
(i) ? stemp : "",
|
|
i,
|
|
(i) ? stemp : "",
|
|
SettingsText(SET_FRIENDLYNAME1 + i));
|
|
}
|
|
|
|
#ifdef USE_EMULATION
|
|
WSContentSend_P(PSTR("<p></p><fieldset><legend><b> " D_EMULATION " </b></legend><p>"));
|
|
for (uint32_t i = 0; i < EMUL_MAX; i++) {
|
|
#ifndef USE_EMULATION_WEMO
|
|
if (i == EMUL_WEMO) { i++; }
|
|
#endif
|
|
#ifndef USE_EMULATION_HUE
|
|
if (i == EMUL_HUE) { i++; }
|
|
#endif
|
|
if (i < EMUL_MAX) {
|
|
WSContentSend_P(PSTR("<input id='r%d' name='b2' type='radio' value='%d'%s><b>%s</b> %s<br>"),
|
|
i, i,
|
|
(i == Settings.flag2.emulation) ? " checked" : "",
|
|
GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions),
|
|
(i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("</p></fieldset>"));
|
|
#endif
|
|
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void OtherSaveSettings(void)
|
|
{
|
|
char tmp[TOPSZ];
|
|
char webindex[5];
|
|
char friendlyname[TOPSZ];
|
|
char message[LOGSZ];
|
|
|
|
WebGetArg("wp", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp);
|
|
Settings.flag.mqtt_enabled = WebServer->hasArg("b1");
|
|
#ifdef USE_EMULATION
|
|
WebGetArg("b2", tmp, sizeof(tmp));
|
|
Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
#endif
|
|
|
|
snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation);
|
|
for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) {
|
|
snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i);
|
|
WebGetArg(webindex, tmp, sizeof(tmp));
|
|
snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1);
|
|
SettingsUpdateText(SET_FRIENDLYNAME1 +i, (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp);
|
|
snprintf_P(message, sizeof(message), PSTR("%s%s %s"), message, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i));
|
|
}
|
|
AddLog_P(LOG_LEVEL_INFO, message);
|
|
|
|
WebGetArg("t1", tmp, sizeof(tmp));
|
|
if (strlen(tmp)) {
|
|
char svalue[128];
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_TEMPLATE " %s"), tmp);
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
|
|
if (WebServer->hasArg("t2")) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_MODULE " 0"));
|
|
ExecuteWebCommand(svalue, SRC_WEBGUI);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void HandleBackupConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_BACKUP_CONFIGURATION));
|
|
|
|
if (!SettingsBufferAlloc()) { return; }
|
|
|
|
WiFiClient myClient = WebServer->client();
|
|
WebServer->setContentLength(sizeof(Settings));
|
|
|
|
char attachment[TOPSZ];
|
|
|
|
|
|
|
|
|
|
char hostname[sizeof(my_hostname)];
|
|
snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(hostname, my_hostname), my_version);
|
|
|
|
WebServer->sendHeader(F("Content-Disposition"), attachment);
|
|
|
|
WSSend(200, CT_STREAM, "");
|
|
|
|
uint32_t cfg_crc32 = Settings.cfg_crc32;
|
|
Settings.cfg_crc32 = GetSettingsCrc32();
|
|
|
|
memcpy(settings_buffer, &Settings, sizeof(Settings));
|
|
if (Web.config_xor_on_set) {
|
|
for (uint32_t i = 2; i < sizeof(Settings); i++) {
|
|
settings_buffer[i] ^= (Web.config_xor_on_set +i);
|
|
}
|
|
}
|
|
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
size_t written = myClient.write((const char*)settings_buffer, sizeof(Settings));
|
|
if (written < sizeof(Settings)) {
|
|
myClient.write((const char*)settings_buffer +written, sizeof(Settings) -written);
|
|
}
|
|
#else
|
|
myClient.write((const char*)settings_buffer, sizeof(Settings));
|
|
#endif
|
|
|
|
SettingsBufferFree();
|
|
|
|
Settings.cfg_crc32 = cfg_crc32;
|
|
}
|
|
|
|
|
|
|
|
void HandleResetConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess(!WifiIsInManagerMode())) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESET_CONFIGURATION);
|
|
|
|
WSContentStart_P(S_RESET_CONFIGURATION, !WifiIsInManagerMode());
|
|
WSContentSendStyle();
|
|
WSContentSend_P(PSTR("<div style='text-align:center;'>" D_CONFIGURATION_RESET "</div>"));
|
|
WSContentSend_P(HTTP_MSG_RSTRT);
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
|
|
char command[CMDSZ];
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_RESET " 1"));
|
|
ExecuteWebCommand(command, SRC_WEBGUI);
|
|
}
|
|
|
|
void HandleRestoreConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_RESTORE_CONFIGURATION);
|
|
|
|
WSContentStart_P(S_RESTORE_CONFIGURATION);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_RST);
|
|
WSContentSend_P(HTTP_FORM_RST_UPG, D_RESTORE);
|
|
if (WifiIsInManagerMode()) {
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
} else {
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
}
|
|
WSContentStop();
|
|
|
|
Web.upload_error = 0;
|
|
Web.upload_file_type = UPL_SETTINGS;
|
|
}
|
|
|
|
|
|
|
|
void HandleInformation(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_INFORMATION);
|
|
|
|
char stopic[TOPSZ];
|
|
|
|
int freeMem = ESP.getFreeHeap();
|
|
|
|
WSContentStart_P(S_INFORMATION);
|
|
|
|
|
|
|
|
WSContentSend_P(HTTP_SCRIPT_INFO_BEGIN);
|
|
WSContentSend_P(PSTR("<table style='width:100%%'><tr><th>"));
|
|
WSContentSend_P(PSTR(D_PROGRAM_VERSION "}2%s%s"), my_version, my_image);
|
|
WSContentSend_P(PSTR("}1" D_BUILD_DATE_AND_TIME "}2%s"), GetBuildDateAndTime().c_str());
|
|
WSContentSend_P(PSTR("}1" D_CORE_AND_SDK_VERSION "}2" ARDUINO_ESP8266_RELEASE "/%s"), ESP.getSdkVersion());
|
|
WSContentSend_P(PSTR("}1" D_UPTIME "}2%s"), GetUptime().c_str());
|
|
WSContentSend_P(PSTR("}1" D_FLASH_WRITE_COUNT "}2%d at 0x%X"), Settings.save_flag, GetSettingsAddress());
|
|
WSContentSend_P(PSTR("}1" D_BOOT_COUNT "}2%d"), Settings.bootcount);
|
|
WSContentSend_P(PSTR("}1" D_RESTART_REASON "}2%s"), GetResetReason().c_str());
|
|
uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present;
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) { maxfn = 1; }
|
|
#endif
|
|
for (uint32_t i = 0; i < maxfn; i++) {
|
|
WSContentSend_P(PSTR("}1" D_FRIENDLY_NAME " %d}2%s"), i +1, SettingsText(SET_FRIENDLYNAME1 +i));
|
|
}
|
|
WSContentSend_P(PSTR("}1}2 "));
|
|
WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%, %d dBm)"), Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI());
|
|
WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "");
|
|
#if LWIP_IPV6
|
|
String ipv6_addr = WifiGetIPv6();
|
|
if(ipv6_addr != ""){
|
|
WSContentSend_P(PSTR("}1 IPv6 Address }2%s"), ipv6_addr.c_str());
|
|
}
|
|
#endif
|
|
if (static_cast<uint32_t>(WiFi.localIP()) != 0) {
|
|
WSContentSend_P(PSTR("}1" D_IP_ADDRESS "}2%s"), WiFi.localIP().toString().c_str());
|
|
WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), IPAddress(Settings.ip_address[1]).toString().c_str());
|
|
WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%s"), IPAddress(Settings.ip_address[2]).toString().c_str());
|
|
WSContentSend_P(PSTR("}1" D_DNS_SERVER "}2%s"), IPAddress(Settings.ip_address[3]).toString().c_str());
|
|
WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), WiFi.macAddress().c_str());
|
|
}
|
|
if (static_cast<uint32_t>(WiFi.softAPIP()) != 0) {
|
|
WSContentSend_P(PSTR("}1" D_IP_ADDRESS "}2%s"), WiFi.softAPIP().toString().c_str());
|
|
WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), WiFi.softAPIP().toString().c_str());
|
|
WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), WiFi.softAPmacAddress().c_str());
|
|
}
|
|
WSContentSend_P(PSTR("}1}2 "));
|
|
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("</td></tr></table>"));
|
|
|
|
WSContentSend_P(HTTP_SCRIPT_INFO_END);
|
|
WSContentSendStyle();
|
|
|
|
WSContentSend_P(PSTR("<style>td{padding:0px 5px;}</style>"
|
|
"<div id='i' name='i'></div>"));
|
|
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void HandleUpgradeFirmware(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE);
|
|
|
|
WSContentStart_P(S_FIRMWARE_UPGRADE);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_UPG, SettingsText(SET_OTAURL));
|
|
WSContentSend_P(HTTP_FORM_RST_UPG, D_UPGRADE);
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
|
|
Web.upload_error = 0;
|
|
Web.upload_file_type = UPL_TASMOTA;
|
|
}
|
|
|
|
void HandleUpgradeFirmwareStart(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
char command[TOPSZ + 10];
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
|
|
WifiConfigCounter();
|
|
|
|
char otaurl[TOPSZ];
|
|
WebGetArg("o", otaurl, sizeof(otaurl));
|
|
if (strlen(otaurl)) {
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl);
|
|
ExecuteWebCommand(command, SRC_WEBGUI);
|
|
}
|
|
|
|
WSContentStart_P(S_INFORMATION);
|
|
WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(PSTR("<div style='text-align:center;'><b>" D_UPGRADE_STARTED " ...</b></div>"));
|
|
WSContentSend_P(HTTP_MSG_RSTRT);
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_UPGRADE " 1"));
|
|
ExecuteWebCommand(command, SRC_WEBGUI);
|
|
}
|
|
|
|
void HandleUploadDone(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPLOAD_DONE));
|
|
|
|
char error[100];
|
|
|
|
WifiConfigCounter();
|
|
restart_flag = 0;
|
|
MqttRetryCounter(0);
|
|
|
|
WSContentStart_P(S_INFORMATION);
|
|
if (!Web.upload_error) {
|
|
WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA);
|
|
}
|
|
WSContentSendStyle();
|
|
WSContentSend_P(PSTR("<div style='text-align:center;'><b>" D_UPLOAD " <font color='#"));
|
|
if (Web.upload_error) {
|
|
WSContentSend_P(PSTR("%06x'>" D_FAILED "</font></b><br><br>"), WebColor(COL_TEXT_WARNING));
|
|
#ifdef USE_RF_FLASH
|
|
if (Web.upload_error < 15) {
|
|
#else
|
|
if ((Web.upload_error < 10) || (14 == Web.upload_error)) {
|
|
if (14 == Web.upload_error) { Web.upload_error = 10; }
|
|
#endif
|
|
GetTextIndexed(error, sizeof(error), Web.upload_error -1, kUploadErrors);
|
|
} else {
|
|
snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), Web.upload_error);
|
|
}
|
|
WSContentSend_P(error);
|
|
DEBUG_CORE_LOG(PSTR("UPL: %s"), error);
|
|
stop_flash_rotate = Settings.flag.stop_flash_rotate;
|
|
} else {
|
|
WSContentSend_P(PSTR("%06x'>" D_SUCCESSFUL "</font></b><br>"), WebColor(COL_TEXT_SUCCESS));
|
|
WSContentSend_P(HTTP_MSG_RSTRT);
|
|
ShowWebSource(SRC_WEBGUI);
|
|
#ifdef USE_TASMOTA_SLAVE
|
|
if (TasmotaSlave_GetFlagFlashing()) {
|
|
restart_flag = 0;
|
|
} else {
|
|
restart_flag = 2;
|
|
}
|
|
#else
|
|
restart_flag = 2;
|
|
#endif
|
|
}
|
|
SettingsBufferFree();
|
|
WSContentSend_P(PSTR("</div><br>"));
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
#ifdef USE_TASMOTA_SLAVE
|
|
if (TasmotaSlave_GetFlagFlashing()) {
|
|
TasmotaSlave_Flash();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void HandleUploadLoop(void)
|
|
{
|
|
|
|
bool _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level);
|
|
|
|
if (HTTP_USER == Web.state) { return; }
|
|
if (Web.upload_error) {
|
|
if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); }
|
|
return;
|
|
}
|
|
|
|
HTTPUpload& upload = WebServer->upload();
|
|
|
|
if (UPLOAD_FILE_START == upload.status) {
|
|
restart_flag = 60;
|
|
if (0 == upload.filename.c_str()[0]) {
|
|
Web.upload_error = 1;
|
|
return;
|
|
}
|
|
SettingsSave(1);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_FILE " %s ..."), upload.filename.c_str());
|
|
if (UPL_SETTINGS == Web.upload_file_type) {
|
|
if (!SettingsBufferAlloc()) {
|
|
Web.upload_error = 2;
|
|
return;
|
|
}
|
|
} else {
|
|
MqttRetryCounter(60);
|
|
#ifdef USE_EMULATION
|
|
UdpDisconnect();
|
|
#endif
|
|
#ifdef USE_ARILUX_RF
|
|
AriluxRfDisable();
|
|
#endif
|
|
if (Settings.flag.mqtt_enabled) {
|
|
MqttDisconnect();
|
|
}
|
|
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
|
if (!Update.begin(maxSketchSpace)) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Web.upload_error = 2;
|
|
return;
|
|
}
|
|
}
|
|
Web.upload_progress_dot_count = 0;
|
|
} else if (!Web.upload_error && (UPLOAD_FILE_WRITE == upload.status)) {
|
|
if (0 == upload.totalSize) {
|
|
if (UPL_SETTINGS == Web.upload_file_type) {
|
|
Web.config_block_count = 0;
|
|
}
|
|
else {
|
|
#ifdef USE_RF_FLASH
|
|
if ((SONOFF_BRIDGE == my_module_type) && (upload.buf[0] == ':')) {
|
|
Update.end();
|
|
Web.upload_file_type = UPL_EFM8BB1;
|
|
|
|
Web.upload_error = SnfBrUpdateInit();
|
|
if (Web.upload_error != 0) { return; }
|
|
} else
|
|
#endif
|
|
#ifdef USE_TASMOTA_SLAVE
|
|
if ((WEMOS == my_module_type) && (upload.buf[0] == ':')) {
|
|
Update.end();
|
|
Web.upload_file_type = UPL_TASMOTASLAVE;
|
|
Web.upload_error = TasmotaSlave_UpdateInit();
|
|
if (Web.upload_error != 0) { return; }
|
|
} else
|
|
#endif
|
|
{
|
|
if ((upload.buf[0] != 0xE9) && (upload.buf[0] != 0x1F)) {
|
|
Web.upload_error = 3;
|
|
return;
|
|
}
|
|
if (0xE9 == upload.buf[0]) {
|
|
uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
|
|
if (bin_flash_size > ESP.getFlashChipRealSize()) {
|
|
Web.upload_error = 4;
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (UPL_SETTINGS == Web.upload_file_type) {
|
|
if (!Web.upload_error) {
|
|
if (upload.currentSize > (sizeof(Settings) - (Web.config_block_count * HTTP_UPLOAD_BUFLEN))) {
|
|
Web.upload_error = 9;
|
|
return;
|
|
}
|
|
memcpy(settings_buffer + (Web.config_block_count * HTTP_UPLOAD_BUFLEN), upload.buf, upload.currentSize);
|
|
Web.config_block_count++;
|
|
}
|
|
}
|
|
#ifdef USE_RF_FLASH
|
|
else if (UPL_EFM8BB1 == Web.upload_file_type) {
|
|
if (efm8bb1_update != nullptr) {
|
|
ssize_t result = rf_glue_remnant_with_new_data_and_write(efm8bb1_update, upload.buf, upload.currentSize);
|
|
free(efm8bb1_update);
|
|
efm8bb1_update = nullptr;
|
|
if (result != 0) {
|
|
Web.upload_error = abs(result);
|
|
return;
|
|
}
|
|
}
|
|
ssize_t result = rf_search_and_write(upload.buf, upload.currentSize);
|
|
if (result < 0) {
|
|
Web.upload_error = abs(result);
|
|
return;
|
|
} else if (result > 0) {
|
|
if ((size_t)result > upload.currentSize) {
|
|
|
|
Web.upload_error = 9;
|
|
return;
|
|
}
|
|
|
|
size_t remnant_sz = upload.currentSize - result;
|
|
efm8bb1_update = (uint8_t *) malloc(remnant_sz + 1);
|
|
if (efm8bb1_update == nullptr) {
|
|
Web.upload_error = 2;
|
|
return;
|
|
}
|
|
memcpy(efm8bb1_update, upload.buf + result, remnant_sz);
|
|
|
|
efm8bb1_update[remnant_sz] = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_TASMOTA_SLAVE
|
|
else if (UPL_TASMOTASLAVE == Web.upload_file_type) {
|
|
TasmotaSlave_WriteBuffer(upload.buf, upload.currentSize);
|
|
}
|
|
#endif
|
|
else {
|
|
if (!Web.upload_error && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) {
|
|
Web.upload_error = 5;
|
|
return;
|
|
}
|
|
if (_serialoutput) {
|
|
Serial.printf(".");
|
|
Web.upload_progress_dot_count++;
|
|
if (!(Web.upload_progress_dot_count % 80)) { Serial.println(); }
|
|
}
|
|
}
|
|
} else if(!Web.upload_error && (UPLOAD_FILE_END == upload.status)) {
|
|
if (_serialoutput && (Web.upload_progress_dot_count % 80)) {
|
|
Serial.println();
|
|
}
|
|
if (UPL_SETTINGS == Web.upload_file_type) {
|
|
if (Web.config_xor_on_set) {
|
|
for (uint32_t i = 2; i < sizeof(Settings); i++) {
|
|
settings_buffer[i] ^= (Web.config_xor_on_set +i);
|
|
}
|
|
}
|
|
bool valid_settings = false;
|
|
unsigned long buffer_version = settings_buffer[11] << 24 | settings_buffer[10] << 16 | settings_buffer[9] << 8 | settings_buffer[8];
|
|
if (buffer_version > 0x06000000) {
|
|
uint32_t buffer_size = settings_buffer[3] << 8 | settings_buffer[2];
|
|
if (buffer_version > 0x0606000A) {
|
|
uint32_t buffer_crc32 = settings_buffer[4095] << 24 | settings_buffer[4094] << 16 | settings_buffer[4093] << 8 | settings_buffer[4092];
|
|
valid_settings = (GetCfgCrc32(settings_buffer, buffer_size -4) == buffer_crc32);
|
|
} else {
|
|
uint16_t buffer_crc16 = settings_buffer[15] << 8 | settings_buffer[14];
|
|
valid_settings = (GetCfgCrc16(settings_buffer, buffer_size) == buffer_crc16);
|
|
}
|
|
} else {
|
|
valid_settings = (settings_buffer[0] == CONFIG_FILE_SIGN);
|
|
}
|
|
if (valid_settings) {
|
|
SettingsDefaultSet2();
|
|
memcpy((char*)&Settings +16, settings_buffer +16, sizeof(Settings) -16);
|
|
Settings.version = buffer_version;
|
|
SettingsBufferFree();
|
|
} else {
|
|
Web.upload_error = 8;
|
|
return;
|
|
}
|
|
}
|
|
#ifdef USE_RF_FLASH
|
|
else if (UPL_EFM8BB1 == Web.upload_file_type) {
|
|
|
|
Web.upload_file_type = UPL_TASMOTA;
|
|
}
|
|
#endif
|
|
#ifdef USE_TASMOTA_SLAVE
|
|
else if (UPL_TASMOTASLAVE == Web.upload_file_type) {
|
|
|
|
TasmotaSlave_SetFlagFlashing(true);
|
|
Web.upload_file_type = UPL_TASMOTA;
|
|
}
|
|
#endif
|
|
else {
|
|
if (!Update.end(true)) {
|
|
if (_serialoutput) { Update.printError(Serial); }
|
|
Web.upload_error = 6;
|
|
return;
|
|
}
|
|
if (!VersionCompatible()) {
|
|
Web.upload_error = 14;
|
|
return;
|
|
}
|
|
}
|
|
if (!Web.upload_error) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize);
|
|
}
|
|
} else if (UPLOAD_FILE_ABORTED == upload.status) {
|
|
restart_flag = 0;
|
|
MqttRetryCounter(0);
|
|
Web.upload_error = 7;
|
|
if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); }
|
|
}
|
|
delay(0);
|
|
}
|
|
|
|
|
|
|
|
void HandlePreflightRequest(void)
|
|
{
|
|
HttpHeaderCors();
|
|
WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST"));
|
|
WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("authorization"));
|
|
WSSend(200, CT_HTML, "");
|
|
}
|
|
|
|
|
|
|
|
void HandleHttpCommand(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess(false)) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
|
|
|
|
bool valid = true;
|
|
if (strlen(SettingsText(SET_WEBPWD))) {
|
|
char tmp1[33];
|
|
WebGetArg("user", tmp1, sizeof(tmp1));
|
|
char tmp2[strlen(SettingsText(SET_WEBPWD)) +1];
|
|
WebGetArg("password", tmp2, sizeof(tmp2));
|
|
if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, SettingsText(SET_WEBPWD)))) { valid = false; }
|
|
}
|
|
|
|
WSContentBegin(200, CT_JSON);
|
|
if (valid) {
|
|
uint32_t curridx = web_log_index;
|
|
String svalue = WebServer->arg("cmnd");
|
|
if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
|
|
ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCOMMAND);
|
|
if (web_log_index != curridx) {
|
|
uint32_t counter = curridx;
|
|
WSContentSend_P(PSTR("{"));
|
|
bool cflg = false;
|
|
do {
|
|
char* tmp;
|
|
size_t len;
|
|
GetLog(counter, &tmp, &len);
|
|
if (len) {
|
|
|
|
char* JSON = (char*)memchr(tmp, '{', len);
|
|
if (JSON) {
|
|
size_t JSONlen = len - (JSON - tmp);
|
|
if (JSONlen > sizeof(mqtt_data)) { JSONlen = sizeof(mqtt_data); }
|
|
char stemp[JSONlen];
|
|
strlcpy(stemp, JSON +1, JSONlen -2);
|
|
WSContentSend_P(PSTR("%s%s"), (cflg) ? "," : "", stemp);
|
|
cflg = true;
|
|
}
|
|
}
|
|
counter++;
|
|
counter &= 0xFF;
|
|
if (!counter) counter++;
|
|
} while (counter != web_log_index);
|
|
WSContentSend_P(PSTR("}"));
|
|
} else {
|
|
WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_ENABLE_WEBLOG_FOR_RESPONSE "\"}"));
|
|
}
|
|
} else {
|
|
WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_ENTER_COMMAND " cmnd=\"}"));
|
|
}
|
|
} else {
|
|
WSContentSend_P(PSTR("{\"" D_RSLT_WARNING "\":\"" D_NEED_USER_AND_PASSWORD "\"}"));
|
|
}
|
|
WSContentEnd();
|
|
}
|
|
|
|
|
|
|
|
void HandleConsole(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
if (WebServer->hasArg("c2")) {
|
|
HandleConsoleRefresh();
|
|
return;
|
|
}
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE);
|
|
|
|
WSContentStart_P(S_CONSOLE);
|
|
WSContentSend_P(HTTP_SCRIPT_CONSOL, Settings.web_refresh);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_CMND);
|
|
WSContentSpaceButton(BUTTON_MAIN);
|
|
WSContentStop();
|
|
}
|
|
|
|
void HandleConsoleRefresh(void)
|
|
{
|
|
bool cflg = true;
|
|
uint32_t counter = 0;
|
|
|
|
String svalue = WebServer->arg("c1");
|
|
if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), svalue.c_str());
|
|
ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCONSOLE);
|
|
}
|
|
|
|
char stmp[8];
|
|
WebGetArg("c2", stmp, sizeof(stmp));
|
|
if (strlen(stmp)) { counter = atoi(stmp); }
|
|
|
|
WSContentBegin(200, CT_PLAIN);
|
|
WSContentSend_P(PSTR("%d}1%d}1"), web_log_index, Web.reset_web_log_flag);
|
|
if (!Web.reset_web_log_flag) {
|
|
counter = 0;
|
|
Web.reset_web_log_flag = true;
|
|
}
|
|
if (counter != web_log_index) {
|
|
if (!counter) {
|
|
counter = web_log_index;
|
|
cflg = false;
|
|
}
|
|
do {
|
|
char* tmp;
|
|
size_t len;
|
|
GetLog(counter, &tmp, &len);
|
|
if (len) {
|
|
if (len > sizeof(mqtt_data) -2) { len = sizeof(mqtt_data); }
|
|
char stemp[len +1];
|
|
strlcpy(stemp, tmp, len);
|
|
WSContentSend_P(PSTR("%s%s"), (cflg) ? "\n" : "", stemp);
|
|
cflg = true;
|
|
}
|
|
counter++;
|
|
counter &= 0xFF;
|
|
if (!counter) { counter++; }
|
|
} while (counter != web_log_index);
|
|
}
|
|
WSContentSend_P(PSTR("}1"));
|
|
WSContentEnd();
|
|
}
|
|
|
|
|
|
|
|
void HandleNotFound(void)
|
|
{
|
|
|
|
|
|
if (CaptivePortal()) { return; }
|
|
|
|
#ifdef USE_EMULATION
|
|
#ifdef USE_EMULATION_HUE
|
|
String path = WebServer->uri();
|
|
if ((EMUL_HUE == Settings.flag2.emulation) && (path.startsWith("/api"))) {
|
|
HandleHueApi(&path);
|
|
} else
|
|
#endif
|
|
#endif
|
|
{
|
|
WSContentBegin(404, CT_PLAIN);
|
|
WSContentSend_P(PSTR(D_FILE_NOT_FOUND "\n\nURI: %s\nMethod: %s\nArguments: %d\n"), WebServer->uri().c_str(), (WebServer->method() == HTTP_GET) ? "GET" : "POST", WebServer->args());
|
|
for (uint32_t i = 0; i < WebServer->args(); i++) {
|
|
WSContentSend_P(PSTR(" %s: %s\n"), WebServer->argName(i).c_str(), WebServer->arg(i).c_str());
|
|
}
|
|
WSContentEnd();
|
|
}
|
|
}
|
|
|
|
|
|
bool CaptivePortal(void)
|
|
{
|
|
|
|
if ((WifiIsInManagerMode()) && !ValidIpAddress(WebServer->hostHeader().c_str())) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED));
|
|
|
|
WebServer->sendHeader(F("Location"), String("http://") + WebServer->client().localIP().toString(), true);
|
|
WSSend(302, CT_PLAIN, "");
|
|
WebServer->client().stop();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
String UrlEncode(const String& text)
|
|
{
|
|
const char hex[] = "0123456789ABCDEF";
|
|
|
|
String encoded = "";
|
|
int len = text.length();
|
|
int i = 0;
|
|
while (i < len) {
|
|
char decodedChar = text.charAt(i++);
|
|
# 2672 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_01_webserver.ino"
|
|
if ((' ' == decodedChar) || ('+' == decodedChar)) {
|
|
encoded += '%';
|
|
encoded += hex[decodedChar >> 4];
|
|
encoded += hex[decodedChar & 0xF];
|
|
} else {
|
|
encoded += decodedChar;
|
|
}
|
|
|
|
}
|
|
return encoded;
|
|
}
|
|
|
|
int WebSend(char *buffer)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
char *host;
|
|
char *user;
|
|
char *password;
|
|
char *command;
|
|
int status = 1;
|
|
|
|
|
|
host = strtok_r(buffer, "]", &command);
|
|
if (host && command) {
|
|
RemoveSpace(host);
|
|
host++;
|
|
host = strtok_r(host, ",", &user);
|
|
String url = F("http://");
|
|
url += host;
|
|
|
|
command = Trim(command);
|
|
if (command[0] != '/') {
|
|
url += F("/cm?");
|
|
if (user) {
|
|
user = strtok_r(user, ":", &password);
|
|
if (user && password) {
|
|
char userpass[200];
|
|
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password);
|
|
url += userpass;
|
|
}
|
|
}
|
|
url += F("cmnd=");
|
|
}
|
|
url += command;
|
|
|
|
DEBUG_CORE_LOG(PSTR("WEB: Uri |%s|"), url.c_str());
|
|
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
HTTPClient http;
|
|
if (http.begin(UrlEncode(url))) {
|
|
#else
|
|
WiFiClient http_client;
|
|
HTTPClient http;
|
|
if (http.begin(http_client, UrlEncode(url))) {
|
|
#endif
|
|
int http_code = http.GET();
|
|
if (http_code > 0) {
|
|
if (http_code == HTTP_CODE_OK || http_code == HTTP_CODE_MOVED_PERMANENTLY) {
|
|
#ifdef USE_WEBSEND_RESPONSE
|
|
|
|
const char* read = http.getString().c_str();
|
|
uint32_t j = 0;
|
|
char text = '.';
|
|
while (text != '\0') {
|
|
text = *read++;
|
|
if (text > 31) {
|
|
mqtt_data[j++] = text;
|
|
if (j == sizeof(mqtt_data) -2) { break; }
|
|
}
|
|
}
|
|
mqtt_data[j] = '\0';
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WEBSEND));
|
|
#ifdef USE_SCRIPT
|
|
extern uint8_t tasm_cmd_activ;
|
|
|
|
tasm_cmd_activ=0;
|
|
XdrvRulesProcess();
|
|
#endif
|
|
#endif
|
|
}
|
|
status = 0;
|
|
} else {
|
|
status = 2;
|
|
}
|
|
http.end();
|
|
} else {
|
|
status = 3;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool JsonWebColor(const char* dataBuf)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
char dataBufLc[strlen(dataBuf) +1];
|
|
LowerCase(dataBufLc, dataBuf);
|
|
RemoveSpace(dataBufLc);
|
|
if (strlen(dataBufLc) < 9) { return false; }
|
|
|
|
StaticJsonBuffer<450> jb;
|
|
JsonObject& obj = jb.parseObject(dataBufLc);
|
|
if (!obj.success()) { return false; }
|
|
|
|
char parm_lc[10];
|
|
if (obj[LowerCase(parm_lc, D_CMND_WEBCOLOR)].success()) {
|
|
for (uint32_t i = 0; i < COL_LAST; i++) {
|
|
const char* color = obj[parm_lc][i];
|
|
if (color != nullptr) {
|
|
WebHexCode(i, color);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const char kWebSendStatus[] PROGMEM = D_JSON_DONE "|" D_JSON_WRONG_PARAMETERS "|" D_JSON_CONNECT_FAILED "|" D_JSON_HOST_NOT_FOUND "|" D_JSON_MEMORY_ERROR;
|
|
|
|
const char kWebCommands[] PROGMEM = "|"
|
|
#ifdef USE_EMULATION
|
|
D_CMND_EMULATION "|"
|
|
#endif
|
|
#ifdef USE_SENDMAIL
|
|
D_CMND_SENDMAIL "|"
|
|
#endif
|
|
D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|"
|
|
D_CMND_WEBSENSOR "|" D_CMND_WEBBUTTON "|" D_CMND_CORS;
|
|
|
|
void (* const WebCommand[])(void) PROGMEM = {
|
|
#ifdef USE_EMULATION
|
|
&CmndEmulation,
|
|
#endif
|
|
#ifdef USE_SENDMAIL
|
|
&CmndSendmail,
|
|
#endif
|
|
&CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor,
|
|
&CmndWebSensor, &CmndWebButton, &CmndCors };
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_EMULATION
|
|
void CmndEmulation(void)
|
|
{
|
|
#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE)
|
|
if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
|
|
#else
|
|
#ifndef USE_EMULATION_WEMO
|
|
if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_HUE == XdrvMailbox.payload)) {
|
|
#endif
|
|
#ifndef USE_EMULATION_HUE
|
|
if ((EMUL_NONE == XdrvMailbox.payload) || (EMUL_WEMO == XdrvMailbox.payload)) {
|
|
#endif
|
|
#endif
|
|
Settings.flag2.emulation = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.emulation);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SENDMAIL
|
|
void CmndSendmail(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint8_t result = SendMail(XdrvMailbox.data);
|
|
char stemp1[20];
|
|
ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
void CmndWebServer(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
Settings.webserver = XdrvMailbox.payload;
|
|
}
|
|
if (Settings.webserver) {
|
|
Response_P(PSTR("{\"" D_CMND_WEBSERVER "\":\"" D_JSON_ACTIVE_FOR " %s " D_JSON_ON_DEVICE " %s " D_JSON_WITH_IP_ADDRESS " %s\"}"),
|
|
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
|
|
} else {
|
|
ResponseCmndStateText(0);
|
|
}
|
|
}
|
|
|
|
void CmndWebPassword(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_WEBPWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
|
|
ResponseCmndChar(SettingsText(SET_WEBPWD));
|
|
} else {
|
|
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
|
|
}
|
|
}
|
|
|
|
void CmndWeblog(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
|
|
Settings.weblog_level = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.weblog_level);
|
|
}
|
|
|
|
void CmndWebRefresh(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload <= 10000)) {
|
|
Settings.web_refresh = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.web_refresh);
|
|
}
|
|
|
|
void CmndWebSend(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint32_t result = WebSend(XdrvMailbox.data);
|
|
char stemp1[20];
|
|
ResponseCmndChar(GetTextIndexed(stemp1, sizeof(stemp1), result, kWebSendStatus));
|
|
}
|
|
}
|
|
|
|
void CmndWebColor(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (strstr(XdrvMailbox.data, "{") == nullptr) {
|
|
if ((XdrvMailbox.data_len > 3) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= COL_LAST)) {
|
|
WebHexCode(XdrvMailbox.index -1, XdrvMailbox.data);
|
|
}
|
|
else if (0 == XdrvMailbox.payload) {
|
|
SettingsDefaultWebColor();
|
|
}
|
|
}
|
|
else {
|
|
JsonWebColor(XdrvMailbox.data);
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_WEBCOLOR "\":["));
|
|
for (uint32_t i = 0; i < COL_LAST; i++) {
|
|
if (i) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(PSTR("\"#%06x\""), WebColor(i));
|
|
}
|
|
ResponseAppend_P(PSTR("]}"));
|
|
}
|
|
|
|
void CmndWebSensor(void)
|
|
{
|
|
if (XdrvMailbox.index < MAX_XSNS_DRIVERS) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
bitWrite(Settings.sensors[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1);
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_WEBSENSOR "\":"));
|
|
XsnsSensorState();
|
|
ResponseJsonEnd();
|
|
}
|
|
|
|
void CmndWebButton(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_BUTTON_TEXT)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_BUTTON1, MAX_BUTTON_TEXT);
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_BUTTON1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
|
|
}
|
|
ResponseCmndIdxChar(SettingsText(SET_BUTTON1 + XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndCors(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_CORS, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_CORS));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv01(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
PollDnsWebserver();
|
|
#ifdef USE_EMULATION
|
|
if (Settings.flag2.emulation) { PollUdp(); }
|
|
#endif
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kWebCommands, WebCommand);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_02_mqtt.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_02_mqtt.ino"
|
|
#define XDRV_02 2
|
|
|
|
|
|
|
|
#ifdef USE_MQTT_TLS
|
|
#include "WiFiClientSecureLightBearSSL.h"
|
|
BearSSL::WiFiClientSecure_light *tlsClient;
|
|
#else
|
|
WiFiClient EspClient;
|
|
#endif
|
|
|
|
const char kMqttCommands[] PROGMEM = "|"
|
|
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
|
|
D_CMND_MQTTFINGERPRINT "|"
|
|
#endif
|
|
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|"
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
D_CMND_TLSKEY "|"
|
|
#endif
|
|
D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTCLIENT "|"
|
|
D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_PUBLISH "|" D_CMND_MQTTLOG "|"
|
|
D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ;
|
|
|
|
void (* const MqttCommand[])(void) PROGMEM = {
|
|
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
|
|
&CmndMqttFingerprint,
|
|
#endif
|
|
&CmndMqttUser, &CmndMqttPassword,
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
&CmndTlsKey,
|
|
#endif
|
|
&CmndMqttHost, &CmndMqttPort, &CmndMqttRetry, &CmndStateText, &CmndMqttClient,
|
|
&CmndFullTopic, &CmndPrefix, &CmndGroupTopic, &CmndTopic, &CmndPublish, &CmndMqttlog,
|
|
&CmndButtonTopic, &CmndSwitchTopic, &CmndButtonRetain, &CmndSwitchRetain, &CmndPowerRetain, &CmndSensorRetain };
|
|
|
|
struct MQTT {
|
|
uint16_t connect_count = 0;
|
|
uint16_t retry_counter = 1;
|
|
uint8_t initial_connection_state = 2;
|
|
bool connected = false;
|
|
bool allowed = false;
|
|
} Mqtt;
|
|
|
|
#ifdef USE_MQTT_TLS
|
|
|
|
#ifdef USE_MQTT_AWS_IOT
|
|
#include <base64.hpp>
|
|
|
|
const br_ec_private_key *AWS_IoT_Private_Key = nullptr;
|
|
const br_x509_certificate *AWS_IoT_Client_Certificate = nullptr;
|
|
|
|
class tls_entry_t {
|
|
public:
|
|
uint32_t name;
|
|
uint16_t start;
|
|
uint16_t len;
|
|
};
|
|
|
|
const static uint32_t TLS_NAME_SKEY = 0x2079656B;
|
|
const static uint32_t TLS_NAME_CRT = 0x20747263;
|
|
|
|
class tls_dir_t {
|
|
public:
|
|
tls_entry_t entry[4];
|
|
};
|
|
|
|
tls_dir_t tls_dir;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value) {
|
|
for (uint32_t i = 0; i<20; i++) {
|
|
if (finger[i] != value) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
void MakeValidMqtt(uint32_t option, char* str)
|
|
{
|
|
|
|
|
|
uint32_t i = 0;
|
|
while (str[i] > 0) {
|
|
|
|
if ((str[i] == '+') || (str[i] == '#') || (str[i] == ' ')) {
|
|
if (option) {
|
|
uint32_t j = i;
|
|
while (str[j] > 0) {
|
|
str[j] = str[j +1];
|
|
j++;
|
|
}
|
|
i--;
|
|
} else {
|
|
str[i] = '_';
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_DISCOVERY
|
|
#ifdef MQTT_HOST_DISCOVERY
|
|
void MqttDiscoverServer(void)
|
|
{
|
|
if (!Wifi.mdns_begun) { return; }
|
|
|
|
int n = MDNS.queryService("mqtt", "tcp");
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_QUERY_DONE " %d"), n);
|
|
|
|
if (n > 0) {
|
|
uint32_t i = 0;
|
|
#ifdef MDNS_HOSTNAME
|
|
for (i = n; i > 0; i--) {
|
|
if (!strcmp(MDNS.hostname(i).c_str(), MDNS_HOSTNAME)) {
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
SettingsUpdateText(SET_MQTT_HOST, MDNS.IP(i).toString().c_str());
|
|
Settings.mqtt_port = MDNS.port(i);
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d"), MDNS.hostname(i).c_str(), SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
# 163 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_02_mqtt.ino"
|
|
#include <PubSubClient.h>
|
|
|
|
|
|
#if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MIN_MESSZ
|
|
#error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 1200"
|
|
#endif
|
|
|
|
#ifdef USE_MQTT_TLS
|
|
PubSubClient MqttClient;
|
|
#else
|
|
PubSubClient MqttClient(EspClient);
|
|
#endif
|
|
|
|
void MqttInit(void)
|
|
{
|
|
#ifdef USE_MQTT_TLS
|
|
tlsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
|
|
|
|
#ifdef USE_MQTT_AWS_IOT
|
|
loadTlsDir();
|
|
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
|
|
AWS_IoT_Private_Key,
|
|
0xFFFF , 0);
|
|
#endif
|
|
|
|
#ifdef USE_MQTT_TLS_CA_CERT
|
|
#ifdef USE_MQTT_AWS_IOT
|
|
tlsClient->setTrustAnchor(&AmazonRootCA1_TA);
|
|
#else
|
|
tlsClient->setTrustAnchor(&LetsEncryptX3CrossSigned_TA);
|
|
#endif
|
|
#endif
|
|
|
|
MqttClient.setClient(*tlsClient);
|
|
#endif
|
|
}
|
|
|
|
bool MqttIsConnected(void)
|
|
{
|
|
return MqttClient.connected();
|
|
}
|
|
|
|
void MqttDisconnect(void)
|
|
{
|
|
MqttClient.disconnect();
|
|
}
|
|
|
|
void MqttSubscribeLib(const char *topic)
|
|
{
|
|
MqttClient.subscribe(topic);
|
|
MqttClient.loop();
|
|
}
|
|
|
|
void MqttUnsubscribeLib(const char *topic)
|
|
{
|
|
MqttClient.unsubscribe(topic);
|
|
MqttClient.loop();
|
|
}
|
|
|
|
bool MqttPublishLib(const char* topic, bool retained)
|
|
{
|
|
|
|
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
|
|
char *str = strstr(topic, SettingsText(SET_MQTTPREFIX1));
|
|
if (str == topic) {
|
|
mqtt_cmnd_blocked_reset = 4;
|
|
mqtt_cmnd_blocked++;
|
|
}
|
|
}
|
|
|
|
bool result = MqttClient.publish(topic, mqtt_data, retained);
|
|
yield();
|
|
return result;
|
|
}
|
|
|
|
void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len)
|
|
{
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("MqttDataHandler"));
|
|
#endif
|
|
|
|
|
|
if (data_len >= MQTT_MAX_PACKET_SIZE) { return; }
|
|
|
|
|
|
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
|
|
char *str = strstr(mqtt_topic, SettingsText(SET_MQTTPREFIX1));
|
|
if ((str == mqtt_topic) && mqtt_cmnd_blocked) {
|
|
mqtt_cmnd_blocked--;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
char topic[TOPSZ];
|
|
strlcpy(topic, mqtt_topic, sizeof(topic));
|
|
mqtt_data[data_len] = 0;
|
|
char data[data_len +1];
|
|
memcpy(data, mqtt_data, sizeof(data));
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_MQTT D_RECEIVED_TOPIC " \"%s\", " D_DATA_SIZE " %d, " D_DATA " \"%s\""), topic, data_len, data);
|
|
|
|
|
|
|
|
XdrvMailbox.index = strlen(topic);
|
|
XdrvMailbox.data_len = data_len;
|
|
XdrvMailbox.topic = topic;
|
|
XdrvMailbox.data = (char*)data;
|
|
if (XdrvCall(FUNC_MQTT_DATA)) { return; }
|
|
|
|
ShowSource(SRC_MQTT);
|
|
|
|
CommandHandler(topic, data, data_len);
|
|
}
|
|
|
|
|
|
|
|
void MqttRetryCounter(uint8_t value)
|
|
{
|
|
Mqtt.retry_counter = value;
|
|
}
|
|
|
|
void MqttSubscribe(const char *topic)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT D_SUBSCRIBE_TO " %s"), topic);
|
|
MqttSubscribeLib(topic);
|
|
}
|
|
|
|
void MqttUnsubscribe(const char *topic)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT D_UNSUBSCRIBE_FROM " %s"), topic);
|
|
MqttUnsubscribeLib(topic);
|
|
}
|
|
|
|
void MqttPublishLogging(const char *mxtime)
|
|
{
|
|
char saved_mqtt_data[strlen(mqtt_data) +1];
|
|
memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data));
|
|
|
|
|
|
Response_P(PSTR("%s%s"), mxtime, log_data);
|
|
char stopic[TOPSZ];
|
|
GetTopic_P(stopic, STAT, mqtt_topic, PSTR("LOGGING"));
|
|
MqttPublishLib(stopic, false);
|
|
|
|
memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data));
|
|
}
|
|
|
|
void MqttPublish(const char* topic, bool retained)
|
|
{
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("MqttPublish"));
|
|
#endif
|
|
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) || defined(MQTT_NO_RETAIN)
|
|
|
|
|
|
|
|
retained = false;
|
|
#endif
|
|
|
|
char sretained[CMDSZ];
|
|
sretained[0] = '\0';
|
|
char slog_type[20];
|
|
snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_RESULT));
|
|
|
|
if (Settings.flag.mqtt_enabled) {
|
|
if (MqttPublishLib(topic, retained)) {
|
|
snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT));
|
|
if (retained) {
|
|
snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")"));
|
|
}
|
|
}
|
|
}
|
|
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%s = %s"), slog_type, (Settings.flag.mqtt_enabled) ? topic : strrchr(topic,'/')+1, mqtt_data);
|
|
if (strlen(log_data) >= (sizeof(log_data) - strlen(sretained) -1)) {
|
|
log_data[sizeof(log_data) - strlen(sretained) -5] = '\0';
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s ..."), log_data);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%s"), log_data, sretained);
|
|
AddLog(LOG_LEVEL_INFO);
|
|
|
|
if (Settings.ledstate &0x04) {
|
|
blinks++;
|
|
}
|
|
}
|
|
|
|
void MqttPublish(const char* topic)
|
|
{
|
|
MqttPublish(topic, false);
|
|
}
|
|
|
|
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retained)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char romram[64];
|
|
char stopic[TOPSZ];
|
|
|
|
snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings.flag.mqtt_response) ? S_RSLT_RESULT : subtopic);
|
|
for (uint32_t i = 0; i < strlen(romram); i++) {
|
|
romram[i] = toupper(romram[i]);
|
|
}
|
|
prefix &= 3;
|
|
GetTopic_P(stopic, prefix, mqtt_topic, romram);
|
|
MqttPublish(stopic, retained);
|
|
|
|
#ifdef USE_MQTT_AWS_IOT
|
|
if ((prefix > 0) && (Settings.flag4.awsiot_shadow)) {
|
|
|
|
char *topic = SettingsText(SET_MQTT_TOPIC);
|
|
char topic2[strlen(topic)+1];
|
|
strcpy(topic2, topic);
|
|
|
|
char *s = topic2;
|
|
while (*s) {
|
|
if ('/' == *s) {
|
|
*s = '_';
|
|
}
|
|
s++;
|
|
}
|
|
|
|
snprintf_P(romram, sizeof(romram), PSTR("$aws/things/%s/shadow/update"), topic2);
|
|
|
|
|
|
char *mqtt_save = (char*) malloc(strlen(mqtt_data)+1);
|
|
if (!mqtt_save) { return; }
|
|
strcpy(mqtt_save, mqtt_data);
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"state\":{\"reported\":%s}}"), mqtt_save);
|
|
free(mqtt_save);
|
|
|
|
bool result = MqttClient.publish(romram, mqtt_data, false);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
|
|
yield();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic)
|
|
{
|
|
MqttPublishPrefixTopic_P(prefix, subtopic, false);
|
|
}
|
|
|
|
void MqttPublishTeleSensor(void)
|
|
{
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
|
XdrvRulesProcess();
|
|
}
|
|
|
|
void MqttPublishPowerState(uint32_t device)
|
|
{
|
|
char stopic[TOPSZ];
|
|
char scommand[33];
|
|
|
|
if ((device < 1) || (device > devices_present)) { device = 1; }
|
|
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan() && (device > 1)) {
|
|
if (GetFanspeed() < MaxFanspeed()) {
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzUpdateFanState();
|
|
#endif
|
|
snprintf_P(scommand, sizeof(scommand), PSTR(D_CMND_FANSPEED));
|
|
GetTopic_P(stopic, STAT, mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT);
|
|
Response_P(S_JSON_COMMAND_NVALUE, scommand, GetFanspeed());
|
|
MqttPublish(stopic);
|
|
}
|
|
} else {
|
|
#endif
|
|
GetPowerDevice(scommand, device, sizeof(scommand), Settings.flag.device_index_enable);
|
|
GetTopic_P(stopic, STAT, mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT);
|
|
Response_P(S_JSON_COMMAND_SVALUE, scommand, GetStateText(bitRead(power, device -1)));
|
|
MqttPublish(stopic);
|
|
|
|
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
|
|
Response_P(GetStateText(bitRead(power, device -1)));
|
|
MqttPublish(stopic, Settings.flag.mqtt_power_retain);
|
|
#ifdef USE_SONOFF_IFAN
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MqttPublishAllPowerState(void)
|
|
{
|
|
for (uint32_t i = 1; i <= devices_present; i++) {
|
|
MqttPublishPowerState(i);
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan()) { break; }
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void MqttPublishPowerBlinkState(uint32_t device)
|
|
{
|
|
char scommand[33];
|
|
|
|
if ((device < 1) || (device > devices_present)) {
|
|
device = 1;
|
|
}
|
|
Response_P(PSTR("{\"%s\":\"" D_JSON_BLINK " %s\"}"),
|
|
GetPowerDevice(scommand, device, sizeof(scommand), Settings.flag.device_index_enable), GetStateText(bitRead(blink_mask, device -1)));
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, S_RSLT_POWER);
|
|
}
|
|
|
|
|
|
|
|
uint16_t MqttConnectCount(void)
|
|
{
|
|
return Mqtt.connect_count;
|
|
}
|
|
|
|
void MqttDisconnected(int state)
|
|
{
|
|
Mqtt.connected = false;
|
|
Mqtt.retry_counter = Settings.mqtt_retry;
|
|
|
|
MqttClient.disconnect();
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), SettingsText(SET_MQTT_HOST), Settings.mqtt_port, state, Mqtt.retry_counter);
|
|
rules_flag.mqtt_disconnected = 1;
|
|
}
|
|
|
|
void MqttConnected(void)
|
|
{
|
|
char stopic[TOPSZ];
|
|
|
|
if (Mqtt.allowed) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_CONNECTED));
|
|
Mqtt.connected = true;
|
|
Mqtt.retry_counter = 0;
|
|
Mqtt.connect_count++;
|
|
|
|
GetTopic_P(stopic, TELE, mqtt_topic, S_LWT);
|
|
Response_P(PSTR(D_ONLINE));
|
|
MqttPublish(stopic, true);
|
|
|
|
|
|
mqtt_data[0] = '\0';
|
|
MqttPublishPrefixTopic_P(CMND, S_RSLT_POWER);
|
|
|
|
GetTopic_P(stopic, CMND, mqtt_topic, PSTR("#"));
|
|
MqttSubscribe(stopic);
|
|
if (strstr_P(SettingsText(SET_MQTT_FULLTOPIC), MQTT_TOKEN_TOPIC) != nullptr) {
|
|
GetGroupTopic_P(stopic, PSTR("#"));
|
|
MqttSubscribe(stopic);
|
|
GetFallbackTopic_P(stopic, PSTR("#"));
|
|
MqttSubscribe(stopic);
|
|
}
|
|
|
|
XdrvCall(FUNC_MQTT_SUBSCRIBE);
|
|
}
|
|
|
|
if (Mqtt.initial_connection_state) {
|
|
char stopic2[TOPSZ];
|
|
Response_P(PSTR("{\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}"),
|
|
ModuleName().c_str(), my_version, my_image, GetFallbackTopic_P(stopic, ""), GetGroupTopic_P(stopic2, ""));
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
|
|
#ifdef USE_WEBSERVER
|
|
if (Settings.webserver) {
|
|
#if LWIP_IPV6
|
|
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}"),
|
|
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str(),WifiGetIPv6().c_str());
|
|
#else
|
|
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"),
|
|
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
|
|
#endif
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
|
|
}
|
|
#endif
|
|
Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":"));
|
|
if (CrashFlag()) {
|
|
CrashDump();
|
|
} else {
|
|
ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
|
|
}
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
|
|
MqttPublishAllPowerState();
|
|
if (Settings.tele_period) {
|
|
tele_period = Settings.tele_period -5;
|
|
}
|
|
rules_flag.system_boot = 1;
|
|
XdrvCall(FUNC_MQTT_INIT);
|
|
}
|
|
Mqtt.initial_connection_state = 0;
|
|
|
|
global_state.mqtt_down = 0;
|
|
if (Settings.flag.mqtt_enabled) {
|
|
rules_flag.mqtt_connected = 1;
|
|
}
|
|
}
|
|
|
|
void MqttReconnect(void)
|
|
{
|
|
char stopic[TOPSZ];
|
|
|
|
Mqtt.allowed = Settings.flag.mqtt_enabled;
|
|
if (Mqtt.allowed) {
|
|
#ifdef USE_DISCOVERY
|
|
#ifdef MQTT_HOST_DISCOVERY
|
|
MqttDiscoverServer();
|
|
#endif
|
|
#endif
|
|
if (!strlen(SettingsText(SET_MQTT_HOST)) || !Settings.mqtt_port) {
|
|
Mqtt.allowed = false;
|
|
}
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
|
|
if (!AWS_IoT_Private_Key || !AWS_IoT_Client_Certificate) {
|
|
Mqtt.allowed = false;
|
|
}
|
|
#endif
|
|
}
|
|
if (!Mqtt.allowed) {
|
|
MqttConnected();
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_EMULATION
|
|
UdpDisconnect();
|
|
#endif
|
|
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_ATTEMPTING_CONNECTION));
|
|
|
|
Mqtt.connected = false;
|
|
Mqtt.retry_counter = Settings.mqtt_retry;
|
|
global_state.mqtt_down = 1;
|
|
|
|
char *mqtt_user = nullptr;
|
|
char *mqtt_pwd = nullptr;
|
|
if (strlen(SettingsText(SET_MQTT_USER))) {
|
|
mqtt_user = SettingsText(SET_MQTT_USER);
|
|
}
|
|
if (strlen(SettingsText(SET_MQTT_PWD))) {
|
|
mqtt_pwd = SettingsText(SET_MQTT_PWD);
|
|
}
|
|
|
|
GetTopic_P(stopic, TELE, mqtt_topic, S_LWT);
|
|
Response_P(S_OFFLINE);
|
|
|
|
if (MqttClient.connected()) { MqttClient.disconnect(); }
|
|
#ifdef USE_MQTT_TLS
|
|
tlsClient->stop();
|
|
#else
|
|
EspClient = WiFiClient();
|
|
MqttClient.setClient(EspClient);
|
|
#endif
|
|
|
|
if (2 == Mqtt.initial_connection_state) {
|
|
Mqtt.initial_connection_state = 1;
|
|
}
|
|
|
|
MqttClient.setCallback(MqttDataHandler);
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
|
|
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
|
|
AWS_IoT_Private_Key,
|
|
0xFFFF , 0);
|
|
#endif
|
|
MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
|
|
|
|
uint32_t mqtt_connect_time = millis();
|
|
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
|
|
bool allow_all_fingerprints = false;
|
|
bool learn_fingerprint1 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0x00);
|
|
bool learn_fingerprint2 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0x00);
|
|
allow_all_fingerprints |= is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0xff);
|
|
allow_all_fingerprints |= is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0xff);
|
|
allow_all_fingerprints |= learn_fingerprint1;
|
|
allow_all_fingerprints |= learn_fingerprint2;
|
|
tlsClient->setPubKeyFingerprint(Settings.mqtt_fingerprint[0], Settings.mqtt_fingerprint[1], allow_all_fingerprints);
|
|
#endif
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), SettingsText(SET_MQTT_HOST));
|
|
if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) {
|
|
#else
|
|
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data, MQTT_CLEAN_SESSION)) {
|
|
#endif
|
|
#ifdef USE_MQTT_TLS
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, max ThunkStack used %d"),
|
|
millis() - mqtt_connect_time, tlsClient->getMaxThunkStackUse());
|
|
if (!tlsClient->getMFLNStatus()) {
|
|
AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("MFLN not supported by TLS server"));
|
|
}
|
|
#ifndef USE_MQTT_TLS_CA_CERT
|
|
|
|
char buf_fingerprint[64];
|
|
ToHex_P((unsigned char *)tlsClient->getRecvPubKeyFingerprint(), 20, buf_fingerprint, sizeof(buf_fingerprint), ' ');
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Server fingerprint: %s"), buf_fingerprint);
|
|
|
|
if (learn_fingerprint1 || learn_fingerprint2) {
|
|
|
|
bool fingerprint_matched = false;
|
|
const uint8_t *recv_fingerprint = tlsClient->getRecvPubKeyFingerprint();
|
|
if (0 == memcmp(recv_fingerprint, Settings.mqtt_fingerprint[0], 20)) {
|
|
fingerprint_matched = true;
|
|
}
|
|
if (0 == memcmp(recv_fingerprint, Settings.mqtt_fingerprint[1], 20)) {
|
|
fingerprint_matched = true;
|
|
}
|
|
if (!fingerprint_matched) {
|
|
|
|
if (learn_fingerprint1) {
|
|
memcpy(Settings.mqtt_fingerprint[0], recv_fingerprint, 20);
|
|
}
|
|
if (learn_fingerprint2) {
|
|
memcpy(Settings.mqtt_fingerprint[1], recv_fingerprint, 20);
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Fingerprint learned: %s"), buf_fingerprint);
|
|
|
|
SettingsSaveAll();
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
MqttConnected();
|
|
} else {
|
|
#ifdef USE_MQTT_TLS
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connection error: %d"), tlsClient->getLastError());
|
|
#endif
|
|
MqttDisconnected(MqttClient.state());
|
|
}
|
|
}
|
|
|
|
void MqttCheck(void)
|
|
{
|
|
if (Settings.flag.mqtt_enabled) {
|
|
if (!MqttIsConnected()) {
|
|
global_state.mqtt_down = 1;
|
|
if (!Mqtt.retry_counter) {
|
|
MqttReconnect();
|
|
} else {
|
|
Mqtt.retry_counter--;
|
|
}
|
|
} else {
|
|
global_state.mqtt_down = 0;
|
|
}
|
|
} else {
|
|
global_state.mqtt_down = 0;
|
|
if (Mqtt.initial_connection_state) {
|
|
MqttReconnect();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
|
|
void CmndMqttFingerprint(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
|
|
char fingerprint[60];
|
|
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(fingerprint))) {
|
|
strlcpy(fingerprint, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? MQTT_FINGERPRINT1 : MQTT_FINGERPRINT2 : XdrvMailbox.data, sizeof(fingerprint));
|
|
char *p = fingerprint;
|
|
for (uint32_t i = 0; i < 20; i++) {
|
|
Settings.mqtt_fingerprint[XdrvMailbox.index -1][i] = strtol(p, &p, 16);
|
|
}
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndIdxChar(ToHex_P((unsigned char *)Settings.mqtt_fingerprint[XdrvMailbox.index -1], 20, fingerprint, sizeof(fingerprint), ' '));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CmndMqttUser(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_MQTT_USER, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data);
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_USER));
|
|
}
|
|
|
|
void CmndMqttPassword(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_MQTT_PWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data);
|
|
ResponseCmndChar(SettingsText(SET_MQTT_PWD));
|
|
restart_flag = 2;
|
|
} else {
|
|
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
|
|
}
|
|
}
|
|
|
|
void CmndMqttlog(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= LOG_LEVEL_NONE) && (XdrvMailbox.payload <= LOG_LEVEL_DEBUG_MORE)) {
|
|
Settings.mqttlog_level = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.mqttlog_level);
|
|
}
|
|
|
|
void CmndMqttHost(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
SettingsUpdateText(SET_MQTT_HOST, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_HOST));
|
|
}
|
|
|
|
void CmndMqttPort(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) {
|
|
Settings.mqtt_port = (1 == XdrvMailbox.payload) ? MQTT_PORT : XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndNumber(Settings.mqtt_port);
|
|
}
|
|
|
|
void CmndMqttRetry(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= MQTT_RETRY_SECS) && (XdrvMailbox.payload < 32001)) {
|
|
Settings.mqtt_retry = XdrvMailbox.payload;
|
|
Mqtt.retry_counter = Settings.mqtt_retry;
|
|
}
|
|
ResponseCmndNumber(Settings.mqtt_retry);
|
|
}
|
|
|
|
void CmndStateText(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_STATE_TEXT)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_STATE_TXT1, MAX_STATE_TEXT);
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) {
|
|
if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_';
|
|
}
|
|
SettingsUpdateText(SET_STATE_TXT1 + XdrvMailbox.index -1, XdrvMailbox.data);
|
|
}
|
|
ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndMqttClient(void)
|
|
{
|
|
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
|
|
SettingsUpdateText(SET_MQTT_CLIENT, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data);
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_CLIENT));
|
|
}
|
|
|
|
void CmndFullTopic(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
MakeValidMqtt(1, XdrvMailbox.data);
|
|
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
|
char stemp1[TOPSZ];
|
|
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_FULLTOPIC : XdrvMailbox.data, sizeof(stemp1));
|
|
if (strcmp(stemp1, SettingsText(SET_MQTT_FULLTOPIC))) {
|
|
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : "");
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true);
|
|
SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp1);
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_FULLTOPIC));
|
|
}
|
|
|
|
void CmndPrefix(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_MQTT_PREFIXES)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_MQTTPREFIX1, MAX_MQTT_PREFIXES);
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
MakeValidMqtt(0, XdrvMailbox.data);
|
|
SettingsUpdateText(SET_MQTTPREFIX1 + XdrvMailbox.index -1,
|
|
(SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index) ? SUB_PREFIX : (2==XdrvMailbox.index) ? PUB_PREFIX : PUB_PREFIX2 : XdrvMailbox.data);
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndIdxChar(SettingsText(SET_MQTTPREFIX1 + XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndPublish(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char *payload_part;
|
|
char *mqtt_part = strtok_r(XdrvMailbox.data, " ", &payload_part);
|
|
if (mqtt_part) {
|
|
char stemp1[TOPSZ];
|
|
strlcpy(stemp1, mqtt_part, sizeof(stemp1));
|
|
if ((payload_part != nullptr) && strlen(payload_part)) {
|
|
strlcpy(mqtt_data, payload_part, sizeof(mqtt_data));
|
|
} else {
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
MqttPublish(stemp1, (XdrvMailbox.index == 2));
|
|
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndGroupTopic(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
MakeValidMqtt(0, XdrvMailbox.data);
|
|
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
|
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
|
|
}
|
|
|
|
void CmndTopic(void)
|
|
{
|
|
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
|
|
MakeValidMqtt(0, XdrvMailbox.data);
|
|
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
|
char stemp1[TOPSZ];
|
|
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_TOPIC : XdrvMailbox.data, sizeof(stemp1));
|
|
if (strcmp(stemp1, SettingsText(SET_MQTT_TOPIC))) {
|
|
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : "");
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true);
|
|
SettingsUpdateText(SET_MQTT_TOPIC, stemp1);
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_TOPIC));
|
|
}
|
|
|
|
void CmndButtonTopic(void)
|
|
{
|
|
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
|
|
MakeValidMqtt(0, XdrvMailbox.data);
|
|
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
|
switch (Shortcut()) {
|
|
case SC_CLEAR: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, ""); break;
|
|
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, mqtt_topic); break;
|
|
case SC_USER: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); break;
|
|
default: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, XdrvMailbox.data);
|
|
}
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_BUTTON_TOPIC));
|
|
}
|
|
|
|
void CmndSwitchTopic(void)
|
|
{
|
|
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
|
|
MakeValidMqtt(0, XdrvMailbox.data);
|
|
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
|
|
switch (Shortcut()) {
|
|
case SC_CLEAR: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, ""); break;
|
|
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, mqtt_topic); break;
|
|
case SC_USER: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC); break;
|
|
default: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, XdrvMailbox.data);
|
|
}
|
|
}
|
|
ResponseCmndChar(SettingsText(SET_MQTT_SWITCH_TOPIC));
|
|
}
|
|
|
|
void CmndButtonRetain(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
if (!XdrvMailbox.payload) {
|
|
for (uint32_t i = 1; i <= MAX_KEYS; i++) {
|
|
SendKey(KEY_BUTTON, i, CLEAR_RETAIN);
|
|
}
|
|
}
|
|
Settings.flag.mqtt_button_retain = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndStateText(Settings.flag.mqtt_button_retain);
|
|
}
|
|
|
|
void CmndSwitchRetain(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
if (!XdrvMailbox.payload) {
|
|
for (uint32_t i = 1; i <= MAX_SWITCHES; i++) {
|
|
SendKey(KEY_SWITCH, i, CLEAR_RETAIN);
|
|
}
|
|
}
|
|
Settings.flag.mqtt_switch_retain = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndStateText(Settings.flag.mqtt_switch_retain);
|
|
}
|
|
|
|
void CmndPowerRetain(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
if (!XdrvMailbox.payload) {
|
|
char stemp1[TOPSZ];
|
|
char scommand[CMDSZ];
|
|
for (uint32_t i = 1; i <= devices_present; i++) {
|
|
GetTopic_P(stemp1, STAT, mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand), Settings.flag.device_index_enable));
|
|
mqtt_data[0] = '\0';
|
|
MqttPublish(stemp1, Settings.flag.mqtt_power_retain);
|
|
}
|
|
}
|
|
Settings.flag.mqtt_power_retain = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndStateText(Settings.flag.mqtt_power_retain);
|
|
}
|
|
|
|
void CmndSensorRetain(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
if (!XdrvMailbox.payload) {
|
|
mqtt_data[0] = '\0';
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain);
|
|
}
|
|
Settings.flag.mqtt_sensor_retain = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndStateText(Settings.flag.mqtt_sensor_retain);
|
|
}
|
|
|
|
|
|
|
|
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
|
|
|
|
|
|
const static uint16_t tls_spi_start_sector = 0xFF;
|
|
const static uint8_t* tls_spi_start = (uint8_t*) 0x402FF000;
|
|
const static size_t tls_spi_len = 0x1000;
|
|
const static size_t tls_block_offset = 0x0400;
|
|
const static size_t tls_block_len = 0x0400;
|
|
const static size_t tls_obj_store_offset = tls_block_offset + sizeof(tls_dir_t);
|
|
|
|
|
|
inline void TlsEraseBuffer(uint8_t *buffer) {
|
|
memset(buffer + tls_block_offset, 0xFF, tls_block_len);
|
|
}
|
|
|
|
|
|
|
|
static br_ec_private_key EC = {
|
|
23,
|
|
nullptr, 0
|
|
};
|
|
|
|
static br_x509_certificate CHAIN[] = {
|
|
{ nullptr, 0 }
|
|
};
|
|
|
|
|
|
|
|
void loadTlsDir(void) {
|
|
memcpy_P(&tls_dir, tls_spi_start + tls_block_offset, sizeof(tls_dir));
|
|
|
|
|
|
if ((TLS_NAME_SKEY == tls_dir.entry[0].name) && (tls_dir.entry[0].len > 0)) {
|
|
EC.x = (unsigned char *)(tls_spi_start + tls_obj_store_offset + tls_dir.entry[0].start);
|
|
EC.xlen = tls_dir.entry[0].len;
|
|
AWS_IoT_Private_Key = &EC;
|
|
} else {
|
|
AWS_IoT_Private_Key = nullptr;
|
|
}
|
|
if ((TLS_NAME_CRT == tls_dir.entry[1].name) && (tls_dir.entry[1].len > 0)) {
|
|
CHAIN[0].data = (unsigned char *) (tls_spi_start + tls_obj_store_offset + tls_dir.entry[1].start);
|
|
CHAIN[0].data_len = tls_dir.entry[1].len;
|
|
AWS_IoT_Client_Certificate = CHAIN;
|
|
} else {
|
|
AWS_IoT_Client_Certificate = nullptr;
|
|
}
|
|
|
|
}
|
|
|
|
const char ALLOCATE_ERROR[] PROGMEM = "TLSKey " D_JSON_ERROR ": cannot allocate buffer.";
|
|
|
|
void CmndTlsKey(void) {
|
|
#ifdef DEBUG_DUMP_TLS
|
|
if (0 == XdrvMailbox.index){
|
|
CmndTlsDump();
|
|
}
|
|
#endif
|
|
if ((XdrvMailbox.index >= 1) && (XdrvMailbox.index <= 2)) {
|
|
tls_dir_t *tls_dir_write;
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
|
|
uint8_t *spi_buffer = (uint8_t*) malloc(tls_spi_len);
|
|
if (!spi_buffer) {
|
|
AddLog_P(LOG_LEVEL_ERROR, ALLOCATE_ERROR);
|
|
return;
|
|
}
|
|
memcpy_P(spi_buffer, tls_spi_start, tls_spi_len);
|
|
|
|
|
|
RemoveAllSpaces(XdrvMailbox.data);
|
|
|
|
|
|
uint32_t bin_len = decode_base64_length((unsigned char*)XdrvMailbox.data);
|
|
uint8_t *bin_buf = nullptr;
|
|
if (bin_len > 0) {
|
|
bin_buf = (uint8_t*) malloc(bin_len + 4);
|
|
if (!bin_buf) {
|
|
AddLog_P(LOG_LEVEL_ERROR, ALLOCATE_ERROR);
|
|
free(spi_buffer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if (bin_len > 0) {
|
|
decode_base64((unsigned char*)XdrvMailbox.data, bin_buf);
|
|
}
|
|
|
|
|
|
tls_dir_write = (tls_dir_t*) (spi_buffer + tls_block_offset);
|
|
|
|
if (1 == XdrvMailbox.index) {
|
|
|
|
|
|
TlsEraseBuffer(spi_buffer);
|
|
if (bin_len > 0) {
|
|
if (bin_len != 32) {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TLSKey: Certificate must be 32 bytes: %d."), bin_len);
|
|
free(spi_buffer);
|
|
free(bin_buf);
|
|
return;
|
|
}
|
|
tls_entry_t *entry = &tls_dir_write->entry[0];
|
|
entry->name = TLS_NAME_SKEY;
|
|
entry->start = 0;
|
|
entry->len = bin_len;
|
|
memcpy(spi_buffer + tls_obj_store_offset + entry->start, bin_buf, entry->len);
|
|
} else {
|
|
|
|
}
|
|
} else if (2 == XdrvMailbox.index) {
|
|
|
|
if (TLS_NAME_SKEY != tls_dir.entry[0].name) {
|
|
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("TLSKey: cannot store Cert if no Key previously stored."));
|
|
free(spi_buffer);
|
|
free(bin_buf);
|
|
return;
|
|
}
|
|
if (bin_len <= 256) {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TLSKey: Certificate length too short: %d."), bin_len);
|
|
free(spi_buffer);
|
|
free(bin_buf);
|
|
return;
|
|
}
|
|
tls_entry_t *entry = &tls_dir_write->entry[1];
|
|
entry->name = TLS_NAME_CRT;
|
|
entry->start = (tls_dir_write->entry[0].start + tls_dir_write->entry[0].len + 3) & ~0x03;
|
|
entry->len = bin_len;
|
|
memcpy(spi_buffer + tls_obj_store_offset + entry->start, bin_buf, entry->len);
|
|
}
|
|
|
|
if (ESP.flashEraseSector(tls_spi_start_sector)) {
|
|
ESP.flashWrite(tls_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
|
|
}
|
|
free(spi_buffer);
|
|
free(bin_buf);
|
|
}
|
|
|
|
loadTlsDir();
|
|
Response_P(PSTR("{\"%s1\":%d,\"%s2\":%d}"),
|
|
XdrvMailbox.command, AWS_IoT_Private_Key ? tls_dir.entry[0].len : -1,
|
|
XdrvMailbox.command, AWS_IoT_Client_Certificate ? tls_dir.entry[1].len : -1);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_DUMP_TLS
|
|
|
|
uint32_t bswap32(uint32_t x) {
|
|
return ((x << 24) & 0xff000000 ) |
|
|
((x << 8) & 0x00ff0000 ) |
|
|
((x >> 8) & 0x0000ff00 ) |
|
|
((x >> 24) & 0x000000ff );
|
|
}
|
|
void CmndTlsDump(void) {
|
|
uint32_t start = (uint32_t)tls_spi_start + tls_block_offset;
|
|
uint32_t end = start + tls_block_len -1;
|
|
for (uint32_t pos = start; pos < end; pos += 0x10) {
|
|
uint32_t* values = (uint32_t*)(pos);
|
|
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
Serial.printf("%08x: %08x %08x %08x %08x\n", pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3]));
|
|
#else
|
|
Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3]));
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
#define WEB_HANDLE_MQTT "mq"
|
|
|
|
const char S_CONFIGURE_MQTT[] PROGMEM = D_CONFIGURE_MQTT;
|
|
|
|
const char HTTP_BTN_MENU_MQTT[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_MQTT "' method='get'><button>" D_CONFIGURE_MQTT "</button></form></p>";
|
|
|
|
const char HTTP_FORM_MQTT1[] PROGMEM =
|
|
"<fieldset><legend><b> " D_MQTT_PARAMETERS " </b></legend>"
|
|
"<form method='get' action='" WEB_HANDLE_MQTT "'>"
|
|
"<p><b>" D_HOST "</b> (" MQTT_HOST ")<br><input id='mh' placeholder='" MQTT_HOST" ' value='%s'></p>"
|
|
"<p><b>" D_PORT "</b> (" STR(MQTT_PORT) ")<br><input id='ml' placeholder='" STR(MQTT_PORT) "' value='%d'></p>"
|
|
"<p><b>" D_CLIENT "</b> (%s)<br><input id='mc' placeholder='%s' value='%s'></p>";
|
|
const char HTTP_FORM_MQTT2[] PROGMEM =
|
|
"<p><b>" D_USER "</b> (" MQTT_USER ")<br><input id='mu' placeholder='" MQTT_USER "' value='%s'></p>"
|
|
"<p><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'><br><input id='mp' type='password' placeholder='" D_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
|
|
"<p><b>" D_TOPIC "</b> = %%topic%% (%s)<br><input id='mt' placeholder='%s' value='%s'></p>"
|
|
"<p><b>" D_FULL_TOPIC "</b> (%s)<br><input id='mf' placeholder='%s' value='%s'></p>";
|
|
|
|
void HandleMqttConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MQTT);
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
MqttSaveSettings();
|
|
WebRestart(1);
|
|
return;
|
|
}
|
|
|
|
char str[TOPSZ];
|
|
|
|
WSContentStart_P(S_CONFIGURE_MQTT);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_MQTT1,
|
|
SettingsText(SET_MQTT_HOST),
|
|
Settings.mqtt_port,
|
|
Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, SettingsText(SET_MQTT_CLIENT));
|
|
WSContentSend_P(HTTP_FORM_MQTT2,
|
|
(!strlen(SettingsText(SET_MQTT_USER))) ? "0" : SettingsText(SET_MQTT_USER),
|
|
Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, SettingsText(SET_MQTT_TOPIC),
|
|
MQTT_FULLTOPIC, MQTT_FULLTOPIC, SettingsText(SET_MQTT_FULLTOPIC));
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void MqttSaveSettings(void)
|
|
{
|
|
char tmp[TOPSZ];
|
|
char stemp[TOPSZ];
|
|
char stemp2[TOPSZ];
|
|
|
|
WebGetArg("mt", tmp, sizeof(tmp));
|
|
strlcpy(stemp, (!strlen(tmp)) ? MQTT_TOPIC : tmp, sizeof(stemp));
|
|
MakeValidMqtt(0, stemp);
|
|
WebGetArg("mf", tmp, sizeof(tmp));
|
|
strlcpy(stemp2, (!strlen(tmp)) ? MQTT_FULLTOPIC : tmp, sizeof(stemp2));
|
|
MakeValidMqtt(1, stemp2);
|
|
if ((strcmp(stemp, SettingsText(SET_MQTT_TOPIC))) || (strcmp(stemp2, SettingsText(SET_MQTT_FULLTOPIC)))) {
|
|
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : "");
|
|
MqttPublishPrefixTopic_P(TELE, S_LWT, true);
|
|
}
|
|
SettingsUpdateText(SET_MQTT_TOPIC, stemp);
|
|
SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp2);
|
|
WebGetArg("mh", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_MQTT_HOST, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp);
|
|
WebGetArg("ml", tmp, sizeof(tmp));
|
|
Settings.mqtt_port = (!strlen(tmp)) ? MQTT_PORT : atoi(tmp);
|
|
WebGetArg("mc", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_MQTT_CLIENT, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp);
|
|
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
|
|
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
|
|
#else
|
|
WebGetArg("mu", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_MQTT_USER, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp);
|
|
WebGetArg("mp", tmp, sizeof(tmp));
|
|
SettingsUpdateText(SET_MQTT_PWD, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? SettingsText(SET_MQTT_PWD) : tmp);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_MQTTUSER " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
|
|
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_USER), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv02(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Settings.flag.mqtt_enabled) {
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
MqttInit();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
MqttClient.loop();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_MQTT);
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/" WEB_HANDLE_MQTT, HandleMqttConfiguration);
|
|
break;
|
|
#endif
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kMqttCommands, MqttCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_03_energy.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_03_energy.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
|
|
|
|
|
|
|
|
#define XDRV_03 3
|
|
#define XSNS_03 3
|
|
|
|
|
|
|
|
|
|
#define ENERGY_NONE 0
|
|
#define ENERGY_WATCHDOG 4
|
|
|
|
#include <Ticker.h>
|
|
|
|
#define D_CMND_POWERCAL "PowerCal"
|
|
#define D_CMND_VOLTAGECAL "VoltageCal"
|
|
#define D_CMND_CURRENTCAL "CurrentCal"
|
|
#define D_CMND_TARIFF "Tariff"
|
|
#define D_CMND_MODULEADDRESS "ModuleAddress"
|
|
|
|
enum EnergyCommands {
|
|
CMND_POWERCAL, CMND_VOLTAGECAL, CMND_CURRENTCAL,
|
|
CMND_POWERSET, CMND_VOLTAGESET, CMND_CURRENTSET, CMND_FREQUENCYSET, CMND_MODULEADDRESS };
|
|
|
|
const char kEnergyCommands[] PROGMEM = "|"
|
|
D_CMND_POWERCAL "|" D_CMND_VOLTAGECAL "|" D_CMND_CURRENTCAL "|"
|
|
D_CMND_POWERSET "|" D_CMND_VOLTAGESET "|" D_CMND_CURRENTSET "|" D_CMND_FREQUENCYSET "|" D_CMND_MODULEADDRESS "|"
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
D_CMND_POWERDELTA "|" D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|"
|
|
#ifdef USE_ENERGY_POWER_LIMIT
|
|
D_CMND_MAXENERGY "|" D_CMND_MAXENERGYSTART "|"
|
|
D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|"
|
|
D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW "|"
|
|
#endif
|
|
#endif
|
|
D_CMND_ENERGYRESET "|" D_CMND_TARIFF ;
|
|
|
|
void (* const EnergyCommand[])(void) PROGMEM = {
|
|
&CmndPowerCal, &CmndVoltageCal, &CmndCurrentCal,
|
|
&CmndPowerSet, &CmndVoltageSet, &CmndCurrentSet, &CmndFrequencySet, &CmndModuleAddress,
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
&CmndPowerDelta, &CmndPowerLow, &CmndPowerHigh, &CmndVoltageLow, &CmndVoltageHigh, &CmndCurrentLow, &CmndCurrentHigh,
|
|
#ifdef USE_ENERGY_POWER_LIMIT
|
|
&CmndMaxEnergy, &CmndMaxEnergyStart,
|
|
&CmndMaxPower, &CmndMaxPowerHold, &CmndMaxPowerWindow,
|
|
&CmndSafePower, &CmndSafePowerHold, &CmndSafePowerWindow,
|
|
#endif
|
|
#endif
|
|
&CmndEnergyReset, &CmndTariff };
|
|
|
|
const char kEnergyPhases[] PROGMEM = "|%s / %s|%s / %s / %s||[%s,%s]|[%s,%s,%s]";
|
|
|
|
struct ENERGY {
|
|
float voltage[3] = { 0, 0, 0 };
|
|
float current[3] = { 0, 0, 0 };
|
|
float active_power[3] = { 0, 0, 0 };
|
|
float apparent_power[3] = { NAN, NAN, NAN };
|
|
float reactive_power[3] = { NAN, NAN, NAN };
|
|
float power_factor[3] = { NAN, NAN, NAN };
|
|
float frequency[3] = { NAN, NAN, NAN };
|
|
|
|
float start_energy = 0;
|
|
float daily = 0;
|
|
float total = 0;
|
|
float export_active = NAN;
|
|
|
|
unsigned long kWhtoday_delta = 0;
|
|
unsigned long kWhtoday_offset = 0;
|
|
unsigned long kWhtoday;
|
|
unsigned long period = 0;
|
|
|
|
uint8_t fifth_second = 0;
|
|
uint8_t command_code = 0;
|
|
uint8_t data_valid[3] = { 0, 0, 0 };
|
|
|
|
uint8_t phase_count = 1;
|
|
bool voltage_common = false;
|
|
|
|
bool voltage_available = true;
|
|
bool current_available = true;
|
|
|
|
bool type_dc = false;
|
|
bool power_on = true;
|
|
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
uint16_t power_history[3] = { 0 };
|
|
uint8_t power_steady_counter = 8;
|
|
bool power_delta = false;
|
|
bool min_power_flag = false;
|
|
bool max_power_flag = false;
|
|
bool min_voltage_flag = false;
|
|
bool max_voltage_flag = false;
|
|
bool min_current_flag = false;
|
|
bool max_current_flag = false;
|
|
|
|
#ifdef USE_ENERGY_POWER_LIMIT
|
|
uint16_t mplh_counter = 0;
|
|
uint16_t mplw_counter = 0;
|
|
uint8_t mplr_counter = 0;
|
|
uint8_t max_energy_state = 0;
|
|
#endif
|
|
#endif
|
|
} Energy;
|
|
|
|
Ticker ticker_energy;
|
|
|
|
|
|
|
|
bool EnergyTariff1Active()
|
|
{
|
|
uint8_t dst = 0;
|
|
if (IsDst() && (Settings.tariff[0][1] != Settings.tariff[1][1])) {
|
|
dst = 1;
|
|
}
|
|
if (Settings.tariff[0][dst] != Settings.tariff[1][dst]) {
|
|
if (Settings.flag3.energy_weekend && ((RtcTime.day_of_week == 1) ||
|
|
(RtcTime.day_of_week == 7))) {
|
|
return true;
|
|
}
|
|
uint32_t minutes = MinutesPastMidnight();
|
|
if (Settings.tariff[0][dst] > Settings.tariff[1][dst]) {
|
|
|
|
return ((minutes >= Settings.tariff[0][dst]) || (minutes < Settings.tariff[1][dst]));
|
|
} else {
|
|
|
|
return ((minutes >= Settings.tariff[0][dst]) && (minutes < Settings.tariff[1][dst]));
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void EnergyUpdateToday(void)
|
|
{
|
|
if (Energy.kWhtoday_delta > 1000) {
|
|
unsigned long delta = Energy.kWhtoday_delta / 1000;
|
|
Energy.kWhtoday_delta -= (delta * 1000);
|
|
Energy.kWhtoday += delta;
|
|
}
|
|
|
|
RtcSettings.energy_kWhtoday = Energy.kWhtoday_offset + Energy.kWhtoday;
|
|
Energy.daily = (float)(RtcSettings.energy_kWhtoday) / 100000;
|
|
Energy.total = (float)(RtcSettings.energy_kWhtotal + RtcSettings.energy_kWhtoday) / 100000;
|
|
|
|
if (RtcTime.valid){
|
|
|
|
uint32_t energy_diff = (uint32_t)(Energy.total * 100000) - RtcSettings.energy_usage.last_usage_kWhtotal;
|
|
RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 100000);
|
|
|
|
uint32_t return_diff = 0;
|
|
if (!isnan(Energy.export_active)) {
|
|
return_diff = (uint32_t)(Energy.export_active * 100000) - RtcSettings.energy_usage.last_return_kWhtotal;
|
|
RtcSettings.energy_usage.last_return_kWhtotal = (uint32_t)(Energy.export_active * 100000);
|
|
}
|
|
|
|
if (EnergyTariff1Active()) {
|
|
RtcSettings.energy_usage.usage1_kWhtotal += energy_diff;
|
|
RtcSettings.energy_usage.return1_kWhtotal += return_diff;
|
|
} else {
|
|
RtcSettings.energy_usage.usage2_kWhtotal += energy_diff;
|
|
RtcSettings.energy_usage.return2_kWhtotal += return_diff;
|
|
}
|
|
}
|
|
}
|
|
|
|
void EnergyUpdateTotal(float value, bool kwh)
|
|
{
|
|
|
|
|
|
|
|
|
|
uint32_t multiplier = (kwh) ? 100000 : 100;
|
|
|
|
if (0 == Energy.start_energy || (value < Energy.start_energy)) {
|
|
Energy.start_energy = value;
|
|
}
|
|
else if (value != Energy.start_energy) {
|
|
Energy.kWhtoday = (unsigned long)((value - Energy.start_energy) * multiplier);
|
|
}
|
|
|
|
if ((Energy.total < (value - 0.01)) &&
|
|
Settings.flag3.hardware_energy_total) {
|
|
RtcSettings.energy_kWhtotal = (unsigned long)((value * multiplier) - Energy.kWhtoday_offset - Energy.kWhtoday);
|
|
Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
|
|
Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000;
|
|
Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight();
|
|
|
|
}
|
|
EnergyUpdateToday();
|
|
}
|
|
|
|
|
|
|
|
void Energy200ms(void)
|
|
{
|
|
Energy.power_on = (power != 0) | Settings.flag.no_power_on_check;
|
|
|
|
Energy.fifth_second++;
|
|
if (5 == Energy.fifth_second) {
|
|
Energy.fifth_second = 0;
|
|
|
|
XnrgCall(FUNC_ENERGY_EVERY_SECOND);
|
|
|
|
if (RtcTime.valid) {
|
|
if (LocalTime() == Midnight()) {
|
|
Settings.energy_kWhyesterday = RtcSettings.energy_kWhtoday;
|
|
|
|
RtcSettings.energy_kWhtotal += RtcSettings.energy_kWhtoday;
|
|
Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
|
|
Energy.kWhtoday = 0;
|
|
Energy.kWhtoday_offset = 0;
|
|
RtcSettings.energy_kWhtoday = 0;
|
|
Energy.start_energy = 0;
|
|
|
|
Energy.kWhtoday_delta = 0;
|
|
Energy.period = Energy.kWhtoday;
|
|
EnergyUpdateToday();
|
|
#if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT)
|
|
Energy.max_energy_state = 3;
|
|
#endif
|
|
}
|
|
#if defined(USE_ENERGY_MARGIN_DETECTION) && defined(USE_ENERGY_POWER_LIMIT)
|
|
if ((RtcTime.hour == Settings.energy_max_energy_start) && (3 == Energy.max_energy_state )) {
|
|
Energy.max_energy_state = 0;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
XnrgCall(FUNC_EVERY_200_MSECOND);
|
|
}
|
|
|
|
void EnergySaveState(void)
|
|
{
|
|
Settings.energy_kWhdoy = (RtcTime.valid) ? RtcTime.day_of_year : 0;
|
|
|
|
Settings.energy_kWhtoday = RtcSettings.energy_kWhtoday;
|
|
Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
|
|
|
|
Settings.energy_usage = RtcSettings.energy_usage;
|
|
}
|
|
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
bool EnergyMargin(bool type, uint16_t margin, uint16_t value, bool &flag, bool &save_flag)
|
|
{
|
|
bool change;
|
|
|
|
if (!margin) return false;
|
|
change = save_flag;
|
|
if (type) {
|
|
flag = (value > margin);
|
|
} else {
|
|
flag = (value < margin);
|
|
}
|
|
save_flag = flag;
|
|
return (change != save_flag);
|
|
}
|
|
|
|
void EnergyMarginCheck(void)
|
|
{
|
|
if (Energy.power_steady_counter) {
|
|
Energy.power_steady_counter--;
|
|
return;
|
|
}
|
|
|
|
uint16_t energy_power_u = (uint16_t)(Energy.active_power[0]);
|
|
|
|
if (Settings.energy_power_delta) {
|
|
uint16_t delta = abs(Energy.power_history[0] - energy_power_u);
|
|
if (delta > 0) {
|
|
if (Settings.energy_power_delta < 101) {
|
|
uint16_t min_power = (Energy.power_history[0] > energy_power_u) ? energy_power_u : Energy.power_history[0];
|
|
if (0 == min_power) { min_power++; }
|
|
if (((delta * 100) / min_power) > Settings.energy_power_delta) {
|
|
Energy.power_delta = true;
|
|
}
|
|
} else {
|
|
if (delta > (Settings.energy_power_delta -100)) {
|
|
Energy.power_delta = true;
|
|
}
|
|
}
|
|
if (Energy.power_delta) {
|
|
Energy.power_history[1] = Energy.active_power[0];
|
|
Energy.power_history[2] = Energy.active_power[0];
|
|
}
|
|
}
|
|
}
|
|
Energy.power_history[0] = Energy.power_history[1];
|
|
Energy.power_history[1] = Energy.power_history[2];
|
|
Energy.power_history[2] = energy_power_u;
|
|
|
|
if (Energy.power_on && (Settings.energy_min_power || Settings.energy_max_power || Settings.energy_min_voltage || Settings.energy_max_voltage || Settings.energy_min_current || Settings.energy_max_current)) {
|
|
uint16_t energy_voltage_u = (uint16_t)(Energy.voltage[0]);
|
|
uint16_t energy_current_u = (uint16_t)(Energy.current[0] * 1000);
|
|
|
|
DEBUG_DRIVER_LOG(PSTR("NRG: W %d, U %d, I %d"), energy_power_u, energy_voltage_u, energy_current_u);
|
|
|
|
Response_P(PSTR("{"));
|
|
bool flag;
|
|
bool jsonflg = false;
|
|
if (EnergyMargin(false, Settings.energy_min_power, energy_power_u, flag, Energy.min_power_flag)) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_POWERLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag));
|
|
jsonflg = true;
|
|
}
|
|
if (EnergyMargin(true, Settings.energy_max_power, energy_power_u, flag, Energy.max_power_flag)) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_POWERHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag));
|
|
jsonflg = true;
|
|
}
|
|
if (EnergyMargin(false, Settings.energy_min_voltage, energy_voltage_u, flag, Energy.min_voltage_flag)) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGELOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag));
|
|
jsonflg = true;
|
|
}
|
|
if (EnergyMargin(true, Settings.energy_max_voltage, energy_voltage_u, flag, Energy.max_voltage_flag)) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_VOLTAGEHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag));
|
|
jsonflg = true;
|
|
}
|
|
if (EnergyMargin(false, Settings.energy_min_current, energy_current_u, flag, Energy.min_current_flag)) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTLOW "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag));
|
|
jsonflg = true;
|
|
}
|
|
if (EnergyMargin(true, Settings.energy_max_current, energy_current_u, flag, Energy.max_current_flag)) {
|
|
ResponseAppend_P(PSTR("%s\"" D_CMND_CURRENTHIGH "\":\"%s\""), (jsonflg)?",":"", GetStateText(flag));
|
|
jsonflg = true;
|
|
}
|
|
if (jsonflg) {
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_MARGINS), MQTT_TELE_RETAIN);
|
|
EnergyMqttShow();
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ENERGY_POWER_LIMIT
|
|
|
|
if (Settings.energy_max_power_limit) {
|
|
if (Energy.active_power[0] > Settings.energy_max_power_limit) {
|
|
if (!Energy.mplh_counter) {
|
|
Energy.mplh_counter = Settings.energy_max_power_limit_hold;
|
|
} else {
|
|
Energy.mplh_counter--;
|
|
if (!Energy.mplh_counter) {
|
|
ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHED "\":%d}"), energy_power_u);
|
|
MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
|
|
EnergyMqttShow();
|
|
SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER);
|
|
if (!Energy.mplr_counter) {
|
|
Energy.mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1;
|
|
}
|
|
Energy.mplw_counter = Settings.energy_max_power_limit_window;
|
|
}
|
|
}
|
|
}
|
|
else if (power && (energy_power_u <= Settings.energy_max_power_limit)) {
|
|
Energy.mplh_counter = 0;
|
|
Energy.mplr_counter = 0;
|
|
Energy.mplw_counter = 0;
|
|
}
|
|
if (!power) {
|
|
if (Energy.mplw_counter) {
|
|
Energy.mplw_counter--;
|
|
} else {
|
|
if (Energy.mplr_counter) {
|
|
Energy.mplr_counter--;
|
|
if (Energy.mplr_counter) {
|
|
ResponseTime_P(PSTR(",\"" D_JSON_POWERMONITOR "\":\"%s\"}"), GetStateText(1));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_POWERMONITOR));
|
|
RestorePower(true, SRC_MAXPOWER);
|
|
} else {
|
|
ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0));
|
|
MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
|
|
EnergyMqttShow();
|
|
SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (Settings.energy_max_energy) {
|
|
uint16_t energy_daily_u = (uint16_t)(Energy.daily * 1000);
|
|
if (!Energy.max_energy_state && (RtcTime.hour == Settings.energy_max_energy_start)) {
|
|
Energy.max_energy_state = 1;
|
|
ResponseTime_P(PSTR(",\"" D_JSON_ENERGYMONITOR "\":\"%s\"}"), GetStateText(1));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_JSON_ENERGYMONITOR));
|
|
RestorePower(true, SRC_MAXENERGY);
|
|
}
|
|
else if ((1 == Energy.max_energy_state ) && (energy_daily_u >= Settings.energy_max_energy)) {
|
|
Energy.max_energy_state = 2;
|
|
char stemp[FLOATSZ];
|
|
dtostrfd(Energy.daily, 3, stemp);
|
|
ResponseTime_P(PSTR(",\"" D_JSON_MAXENERGYREACHED "\":%s}"), stemp);
|
|
MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
|
|
EnergyMqttShow();
|
|
SetAllPower(POWER_ALL_OFF, SRC_MAXENERGY);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (Energy.power_delta) { EnergyMqttShow(); }
|
|
}
|
|
|
|
void EnergyMqttShow(void)
|
|
{
|
|
|
|
int tele_period_save = tele_period;
|
|
tele_period = 2;
|
|
mqtt_data[0] = '\0';
|
|
ResponseAppendTime();
|
|
EnergyShow(true);
|
|
tele_period = tele_period_save;
|
|
ResponseJsonEnd();
|
|
MqttPublishTeleSensor();
|
|
Energy.power_delta = false;
|
|
}
|
|
#endif
|
|
|
|
void EnergyEverySecond(void)
|
|
{
|
|
|
|
if (global_update) {
|
|
if (power && (global_temperature != 9999) && (global_temperature > Settings.param[P_OVER_TEMP])) {
|
|
SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP);
|
|
}
|
|
}
|
|
|
|
|
|
uint32_t data_valid = Energy.phase_count;
|
|
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
|
if (Energy.data_valid[i] <= ENERGY_WATCHDOG) {
|
|
Energy.data_valid[i]++;
|
|
if (Energy.data_valid[i] > ENERGY_WATCHDOG) {
|
|
|
|
Energy.voltage[i] = 0;
|
|
Energy.current[i] = 0;
|
|
Energy.active_power[i] = 0;
|
|
if (!isnan(Energy.apparent_power[i])) { Energy.apparent_power[i] = 0; }
|
|
if (!isnan(Energy.reactive_power[i])) { Energy.reactive_power[i] = 0; }
|
|
if (!isnan(Energy.frequency[i])) { Energy.frequency[i] = 0; }
|
|
if (!isnan(Energy.power_factor[i])) { Energy.power_factor[i] = 0; }
|
|
|
|
data_valid--;
|
|
}
|
|
}
|
|
}
|
|
if (!data_valid) {
|
|
if (!isnan(Energy.export_active)) { Energy.export_active = 0; }
|
|
Energy.start_energy = 0;
|
|
|
|
XnrgCall(FUNC_ENERGY_RESET);
|
|
}
|
|
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
EnergyMarginCheck();
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EnergyCommandCalResponse(uint32_t nvalue)
|
|
{
|
|
snprintf_P(XdrvMailbox.command, CMDSZ, PSTR("%sCal"), XdrvMailbox.command);
|
|
ResponseCmndNumber(nvalue);
|
|
}
|
|
|
|
void CmndEnergyReset(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
|
|
char *p;
|
|
unsigned long lnum = strtoul(XdrvMailbox.data, &p, 10);
|
|
if (p != XdrvMailbox.data) {
|
|
switch (XdrvMailbox.index) {
|
|
case 1:
|
|
|
|
Energy.kWhtoday_offset = lnum *100;
|
|
Energy.kWhtoday = 0;
|
|
Energy.kWhtoday_delta = 0;
|
|
Energy.start_energy = 0;
|
|
Energy.period = Energy.kWhtoday_offset;
|
|
Settings.energy_kWhtoday = Energy.kWhtoday_offset;
|
|
RtcSettings.energy_kWhtoday = Energy.kWhtoday_offset;
|
|
Energy.daily = (float)Energy.kWhtoday_offset / 100000;
|
|
if (!RtcSettings.energy_kWhtotal && !Energy.kWhtoday_offset) {
|
|
Settings.energy_kWhtotal_time = LocalTime();
|
|
}
|
|
break;
|
|
case 2:
|
|
|
|
Settings.energy_kWhyesterday = lnum *100;
|
|
break;
|
|
case 3:
|
|
|
|
RtcSettings.energy_kWhtotal = lnum *100;
|
|
Settings.energy_kWhtotal = RtcSettings.energy_kWhtotal;
|
|
|
|
Settings.energy_kWhtotal_time = (!Energy.kWhtoday_offset) ? LocalTime() : Midnight();
|
|
RtcSettings.energy_usage.last_usage_kWhtotal = (uint32_t)(Energy.total * 1000);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ((XdrvMailbox.index > 3) && (XdrvMailbox.index <= 5)) {
|
|
uint32_t values[2] = { 0 };
|
|
uint32_t position = ParseParameters(2, values);
|
|
values[0] *= 100;
|
|
values[1] *= 100;
|
|
|
|
switch (XdrvMailbox.index)
|
|
{
|
|
case 4:
|
|
|
|
if (position > 0) {
|
|
RtcSettings.energy_usage.usage1_kWhtotal = values[0];
|
|
}
|
|
if (position > 1) {
|
|
RtcSettings.energy_usage.usage2_kWhtotal = values[1];
|
|
}
|
|
Settings.energy_usage.usage1_kWhtotal = RtcSettings.energy_usage.usage1_kWhtotal;
|
|
Settings.energy_usage.usage2_kWhtotal = RtcSettings.energy_usage.usage2_kWhtotal;
|
|
break;
|
|
case 5:
|
|
|
|
if (position > 0) {
|
|
RtcSettings.energy_usage.return1_kWhtotal = values[0];
|
|
}
|
|
if (position > 1) {
|
|
RtcSettings.energy_usage.return2_kWhtotal = values[1];
|
|
}
|
|
Settings.energy_usage.return1_kWhtotal = RtcSettings.energy_usage.return1_kWhtotal;
|
|
Settings.energy_usage.return2_kWhtotal = RtcSettings.energy_usage.return2_kWhtotal;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Energy.total = (float)(RtcSettings.energy_kWhtotal + Energy.kWhtoday_offset + Energy.kWhtoday) / 100000;
|
|
|
|
char energy_total_chr[FLOATSZ];
|
|
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr);
|
|
char energy_daily_chr[FLOATSZ];
|
|
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
|
|
char energy_yesterday_chr[FLOATSZ];
|
|
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
|
|
|
|
char energy_usage1_chr[FLOATSZ];
|
|
dtostrfd((float)Settings.energy_usage.usage1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_usage1_chr);
|
|
char energy_usage2_chr[FLOATSZ];
|
|
dtostrfd((float)Settings.energy_usage.usage2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_usage2_chr);
|
|
char energy_return1_chr[FLOATSZ];
|
|
dtostrfd((float)Settings.energy_usage.return1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_return1_chr);
|
|
char energy_return2_chr[FLOATSZ];
|
|
dtostrfd((float)Settings.energy_usage.return2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_return2_chr);
|
|
|
|
Response_P(PSTR("{\"%s\":{\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s,\"" D_JSON_USAGE "\":[%s,%s],\"" D_JSON_EXPORT "\":[%s,%s]}}"),
|
|
XdrvMailbox.command, energy_total_chr, energy_yesterday_chr, energy_daily_chr, energy_usage1_chr, energy_usage2_chr, energy_return1_chr, energy_return2_chr);
|
|
}
|
|
|
|
void CmndTariff(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
|
|
uint32_t tariff = XdrvMailbox.index -1;
|
|
uint32_t time_type = 0;
|
|
char *p;
|
|
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
|
|
while ((str != nullptr) && (time_type < 2)) {
|
|
char *q;
|
|
uint32_t value = strtol(str, &q, 10);
|
|
Settings.tariff[tariff][time_type] = value;
|
|
if (value < 24) {
|
|
Settings.tariff[tariff][time_type] *= 60;
|
|
char *minute = strtok_r(nullptr, ":", &q);
|
|
if (minute) {
|
|
value = strtol(minute, nullptr, 10);
|
|
if (value > 59) {
|
|
value = 59;
|
|
}
|
|
Settings.tariff[tariff][time_type] += value;
|
|
}
|
|
}
|
|
if (Settings.tariff[tariff][time_type] > 1439) {
|
|
Settings.tariff[tariff][time_type] = 1439;
|
|
}
|
|
str = strtok_r(nullptr, ", ", &p);
|
|
time_type++;
|
|
}
|
|
}
|
|
else if (XdrvMailbox.index == 9) {
|
|
Settings.flag3.energy_weekend = XdrvMailbox.payload & 1;
|
|
}
|
|
Response_P(PSTR("{\"%s\":{\"Off-Peak\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Standard\":{\"STD\":\"%s\",\"DST\":\"%s\"},\"Weekend\":\"%s\"}}"),
|
|
XdrvMailbox.command,
|
|
GetMinuteTime(Settings.tariff[0][0]).c_str(),GetMinuteTime(Settings.tariff[0][1]).c_str(),
|
|
GetMinuteTime(Settings.tariff[1][0]).c_str(),GetMinuteTime(Settings.tariff[1][1]).c_str(),
|
|
GetStateText(Settings.flag3.energy_weekend));
|
|
}
|
|
|
|
void CmndPowerCal(void)
|
|
{
|
|
Energy.command_code = CMND_POWERCAL;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) {
|
|
Settings.energy_power_calibration = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_power_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndVoltageCal(void)
|
|
{
|
|
Energy.command_code = CMND_VOLTAGECAL;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) {
|
|
Settings.energy_voltage_calibration = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_voltage_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndCurrentCal(void)
|
|
{
|
|
Energy.command_code = CMND_CURRENTCAL;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) {
|
|
Settings.energy_current_calibration = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_current_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndPowerSet(void)
|
|
{
|
|
Energy.command_code = CMND_POWERSET;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
EnergyCommandCalResponse(Settings.energy_power_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndVoltageSet(void)
|
|
{
|
|
Energy.command_code = CMND_VOLTAGESET;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
EnergyCommandCalResponse(Settings.energy_voltage_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndCurrentSet(void)
|
|
{
|
|
Energy.command_code = CMND_CURRENTSET;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
EnergyCommandCalResponse(Settings.energy_current_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndFrequencySet(void)
|
|
{
|
|
Energy.command_code = CMND_FREQUENCYSET;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
EnergyCommandCalResponse(Settings.energy_frequency_calibration);
|
|
}
|
|
}
|
|
|
|
void CmndModuleAddress(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4) && (1 == Energy.phase_count)) {
|
|
Energy.command_code = CMND_MODULEADDRESS;
|
|
if (XnrgCall(FUNC_COMMAND)) {
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
void CmndPowerDelta(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) {
|
|
Settings.energy_power_delta = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_power_delta);
|
|
}
|
|
|
|
void CmndPowerLow(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_min_power = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_min_power);
|
|
}
|
|
|
|
void CmndPowerHigh(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_power = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power);
|
|
}
|
|
|
|
void CmndVoltageLow(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
|
|
Settings.energy_min_voltage = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_min_voltage);
|
|
}
|
|
|
|
void CmndVoltageHigh(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
|
|
Settings.energy_max_voltage = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_voltage);
|
|
}
|
|
|
|
void CmndCurrentLow(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
|
|
Settings.energy_min_current = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_min_current);
|
|
}
|
|
|
|
void CmndCurrentHigh(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
|
|
Settings.energy_max_current = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_current);
|
|
}
|
|
|
|
#ifdef USE_ENERGY_POWER_LIMIT
|
|
void CmndMaxPower(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_power_limit = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power_limit);
|
|
}
|
|
|
|
void CmndMaxPowerHold(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power_limit_hold);
|
|
}
|
|
|
|
void CmndMaxPowerWindow(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power_limit_window);
|
|
}
|
|
|
|
void CmndSafePower(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_power_safe_limit = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power_safe_limit);
|
|
}
|
|
|
|
void CmndSafePowerHold(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_power_safe_limit_hold = (1 == XdrvMailbox.payload) ? SAFE_POWER_HOLD : XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power_safe_limit_hold);
|
|
}
|
|
|
|
void CmndSafePowerWindow(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 1440)) {
|
|
Settings.energy_max_power_safe_limit_window = (1 == XdrvMailbox.payload) ? SAFE_POWER_WINDOW : XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_power_safe_limit_window);
|
|
}
|
|
|
|
void CmndMaxEnergy(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.energy_max_energy = XdrvMailbox.payload;
|
|
Energy.max_energy_state = 3;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_energy);
|
|
}
|
|
|
|
void CmndMaxEnergyStart(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) {
|
|
Settings.energy_max_energy_start = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.energy_max_energy_start);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
void EnergySnsInit(void)
|
|
{
|
|
XnrgCall(FUNC_INIT);
|
|
|
|
if (energy_flg) {
|
|
if (RtcSettingsValid()) {
|
|
Energy.kWhtoday_offset = RtcSettings.energy_kWhtoday;
|
|
}
|
|
else if (RtcTime.day_of_year == Settings.energy_kWhdoy) {
|
|
Energy.kWhtoday_offset = Settings.energy_kWhtoday;
|
|
}
|
|
else {
|
|
Energy.kWhtoday_offset = 0;
|
|
}
|
|
Energy.kWhtoday = 0;
|
|
Energy.kWhtoday_delta = 0;
|
|
Energy.period = Energy.kWhtoday_offset;
|
|
EnergyUpdateToday();
|
|
ticker_energy.attach_ms(200, Energy200ms);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_ENERGY_SNS1[] PROGMEM =
|
|
"{s}" D_POWERUSAGE_APPARENT "{m}%s " D_UNIT_VA "{e}"
|
|
"{s}" D_POWERUSAGE_REACTIVE "{m}%s " D_UNIT_VAR "{e}"
|
|
"{s}" D_POWER_FACTOR "{m}%s{e}";
|
|
|
|
const char HTTP_ENERGY_SNS2[] PROGMEM =
|
|
"{s}" D_ENERGY_TODAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
|
|
"{s}" D_ENERGY_YESTERDAY "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
|
|
"{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
|
|
|
|
const char HTTP_ENERGY_SNS3[] PROGMEM =
|
|
"{s}" D_EXPORT_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
|
|
#endif
|
|
|
|
char* EnergyFormatIndex(char* result, char* input, bool json, uint32_t index, bool single = false)
|
|
{
|
|
char layout[16];
|
|
GetTextIndexed(layout, sizeof(layout), (index -1) + (3 * json), kEnergyPhases);
|
|
switch (index) {
|
|
case 2:
|
|
snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ);
|
|
break;
|
|
case 3:
|
|
snprintf_P(result, FLOATSZ *3, layout, input, input + FLOATSZ, input + FLOATSZ + FLOATSZ);
|
|
break;
|
|
default:
|
|
snprintf_P(result, FLOATSZ *3, input);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
char* EnergyFormat(char* result, char* input, bool json, bool single = false)
|
|
{
|
|
uint8_t index = (single) ? 1 : Energy.phase_count;
|
|
return EnergyFormatIndex(result, input, json, index, single);
|
|
}
|
|
|
|
void EnergyShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
|
if (Energy.voltage_common) {
|
|
Energy.voltage[i] = Energy.voltage[0];
|
|
}
|
|
}
|
|
|
|
float power_factor_knx = Energy.power_factor[0];
|
|
|
|
char apparent_power_chr[Energy.phase_count][FLOATSZ];
|
|
char reactive_power_chr[Energy.phase_count][FLOATSZ];
|
|
char power_factor_chr[Energy.phase_count][FLOATSZ];
|
|
char frequency_chr[Energy.phase_count][FLOATSZ];
|
|
if (!Energy.type_dc) {
|
|
if (Energy.current_available && Energy.voltage_available) {
|
|
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
|
float apparent_power = Energy.apparent_power[i];
|
|
if (isnan(apparent_power)) {
|
|
apparent_power = Energy.voltage[i] * Energy.current[i];
|
|
}
|
|
if (apparent_power < Energy.active_power[i]) {
|
|
Energy.active_power[i] = apparent_power;
|
|
}
|
|
|
|
float power_factor = Energy.power_factor[i];
|
|
if (isnan(power_factor)) {
|
|
power_factor = (Energy.active_power[i] && apparent_power) ? Energy.active_power[i] / apparent_power : 0;
|
|
if (power_factor > 1) {
|
|
power_factor = 1;
|
|
}
|
|
}
|
|
if (0 == i) { power_factor_knx = power_factor; }
|
|
|
|
float reactive_power = Energy.reactive_power[i];
|
|
if (isnan(reactive_power)) {
|
|
reactive_power = 0;
|
|
uint32_t difference = ((uint32_t)(apparent_power * 100) - (uint32_t)(Energy.active_power[i] * 100)) / 10;
|
|
if ((Energy.current[i] > 0.005) && ((difference > 15) || (difference > (uint32_t)(apparent_power * 100 / 1000)))) {
|
|
|
|
|
|
reactive_power = (float)(RoundSqrtInt((uint32_t)(apparent_power * apparent_power * 100) - (uint32_t)(Energy.active_power[i] * Energy.active_power[i] * 100))) / 10;
|
|
}
|
|
}
|
|
|
|
dtostrfd(apparent_power, Settings.flag2.wattage_resolution, apparent_power_chr[i]);
|
|
dtostrfd(reactive_power, Settings.flag2.wattage_resolution, reactive_power_chr[i]);
|
|
dtostrfd(power_factor, 2, power_factor_chr[i]);
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
|
float frequency = Energy.frequency[i];
|
|
if (isnan(Energy.frequency[i])) {
|
|
frequency = 0;
|
|
}
|
|
dtostrfd(frequency, Settings.flag2.frequency_resolution, frequency_chr[i]);
|
|
}
|
|
}
|
|
|
|
char voltage_chr[Energy.phase_count][FLOATSZ];
|
|
char current_chr[Energy.phase_count][FLOATSZ];
|
|
char active_power_chr[Energy.phase_count][FLOATSZ];
|
|
for (uint32_t i = 0; i < Energy.phase_count; i++) {
|
|
dtostrfd(Energy.voltage[i], Settings.flag2.voltage_resolution, voltage_chr[i]);
|
|
dtostrfd(Energy.current[i], Settings.flag2.current_resolution, current_chr[i]);
|
|
dtostrfd(Energy.active_power[i], Settings.flag2.wattage_resolution, active_power_chr[i]);
|
|
}
|
|
|
|
char energy_daily_chr[FLOATSZ];
|
|
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, energy_daily_chr);
|
|
char energy_yesterday_chr[FLOATSZ];
|
|
dtostrfd((float)Settings.energy_kWhyesterday / 100000, Settings.flag2.energy_resolution, energy_yesterday_chr);
|
|
|
|
char energy_total_chr[3][FLOATSZ];
|
|
dtostrfd(Energy.total, Settings.flag2.energy_resolution, energy_total_chr[0]);
|
|
char export_active_chr[3][FLOATSZ];
|
|
dtostrfd(Energy.export_active, Settings.flag2.energy_resolution, export_active_chr[0]);
|
|
uint8_t energy_total_fields = 1;
|
|
|
|
if (Settings.tariff[0][0] != Settings.tariff[1][0]) {
|
|
dtostrfd((float)RtcSettings.energy_usage.usage1_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_total_chr[1]);
|
|
dtostrfd((float)RtcSettings.energy_usage.usage2_kWhtotal / 100000, Settings.flag2.energy_resolution, energy_total_chr[2]);
|
|
dtostrfd((float)RtcSettings.energy_usage.return1_kWhtotal / 100000, Settings.flag2.energy_resolution, export_active_chr[1]);
|
|
dtostrfd((float)RtcSettings.energy_usage.return2_kWhtotal / 100000, Settings.flag2.energy_resolution, export_active_chr[2]);
|
|
energy_total_fields = 3;
|
|
}
|
|
|
|
char value_chr[FLOATSZ *3];
|
|
char value2_chr[FLOATSZ *3];
|
|
char value3_chr[FLOATSZ *3];
|
|
|
|
if (json) {
|
|
bool show_energy_period = (0 == tele_period);
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_RSLT_ENERGY "\":{\"" D_JSON_TOTAL_START_TIME "\":\"%s\",\"" D_JSON_TOTAL "\":%s,\"" D_JSON_YESTERDAY "\":%s,\"" D_JSON_TODAY "\":%s"),
|
|
GetDateAndTime(DT_ENERGY).c_str(),
|
|
EnergyFormatIndex(value_chr, energy_total_chr[0], json, energy_total_fields),
|
|
energy_yesterday_chr,
|
|
energy_daily_chr);
|
|
|
|
if (!isnan(Energy.export_active)) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_EXPORT_ACTIVE "\":%s"),
|
|
EnergyFormatIndex(value_chr, export_active_chr[0], json, energy_total_fields));
|
|
}
|
|
|
|
if (show_energy_period) {
|
|
float energy = 0;
|
|
if (Energy.period) {
|
|
energy = (float)(RtcSettings.energy_kWhtoday - Energy.period) / 100;
|
|
}
|
|
Energy.period = RtcSettings.energy_kWhtoday;
|
|
char energy_period_chr[FLOATSZ];
|
|
dtostrfd(energy, Settings.flag2.wattage_resolution, energy_period_chr);
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_PERIOD "\":%s"), energy_period_chr);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_POWERUSAGE "\":%s"),
|
|
EnergyFormat(value_chr, active_power_chr[0], json));
|
|
if (!Energy.type_dc) {
|
|
if (Energy.current_available && Energy.voltage_available) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_APPARENT_POWERUSAGE "\":%s,\"" D_JSON_REACTIVE_POWERUSAGE "\":%s,\"" D_JSON_POWERFACTOR "\":%s"),
|
|
EnergyFormat(value_chr, apparent_power_chr[0], json),
|
|
EnergyFormat(value2_chr, reactive_power_chr[0], json),
|
|
EnergyFormat(value3_chr, power_factor_chr[0], json));
|
|
}
|
|
if (!isnan(Energy.frequency[0])) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_FREQUENCY "\":%s"),
|
|
EnergyFormat(value_chr, frequency_chr[0], json, Energy.voltage_common));
|
|
}
|
|
}
|
|
if (Energy.voltage_available) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_VOLTAGE "\":%s"),
|
|
EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common));
|
|
}
|
|
if (Energy.current_available) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_CURRENT "\":%s"),
|
|
EnergyFormat(value_chr, current_chr[0], json));
|
|
}
|
|
XnrgCall(FUNC_JSON_APPEND);
|
|
ResponseJsonEnd();
|
|
|
|
#ifdef USE_DOMOTICZ
|
|
if (show_energy_period) {
|
|
dtostrfd(Energy.total * 1000, 1, energy_total_chr[0]);
|
|
DomoticzSensorPowerEnergy((int)Energy.active_power[0], energy_total_chr[0]);
|
|
|
|
dtostrfd((float)RtcSettings.energy_usage.usage1_kWhtotal / 100, 1, energy_total_chr[1]);
|
|
dtostrfd((float)RtcSettings.energy_usage.usage2_kWhtotal / 100, 1, energy_total_chr[2]);
|
|
dtostrfd((float)RtcSettings.energy_usage.return1_kWhtotal / 100, 1, export_active_chr[1]);
|
|
dtostrfd((float)RtcSettings.energy_usage.return2_kWhtotal / 100, 1, export_active_chr[2]);
|
|
DomoticzSensorP1SmartMeter(energy_total_chr[1], energy_total_chr[2], export_active_chr[1], export_active_chr[2], (int)Energy.active_power[0]);
|
|
|
|
if (Energy.voltage_available) {
|
|
DomoticzSensor(DZ_VOLTAGE, voltage_chr[0]);
|
|
}
|
|
if (Energy.current_available) {
|
|
DomoticzSensor(DZ_CURRENT, current_chr[0]);
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (show_energy_period) {
|
|
if (Energy.voltage_available) {
|
|
KnxSensor(KNX_ENERGY_VOLTAGE, Energy.voltage[0]);
|
|
}
|
|
if (Energy.current_available) {
|
|
KnxSensor(KNX_ENERGY_CURRENT, Energy.current[0]);
|
|
}
|
|
KnxSensor(KNX_ENERGY_POWER, Energy.active_power[0]);
|
|
if (!Energy.type_dc) {
|
|
KnxSensor(KNX_ENERGY_POWERFACTOR, power_factor_knx);
|
|
}
|
|
KnxSensor(KNX_ENERGY_DAILY, Energy.daily);
|
|
KnxSensor(KNX_ENERGY_TOTAL, Energy.total);
|
|
KnxSensor(KNX_ENERGY_START, Energy.start_energy);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
if (Energy.voltage_available) {
|
|
WSContentSend_PD(HTTP_SNS_VOLTAGE, EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common));
|
|
}
|
|
if (Energy.current_available) {
|
|
WSContentSend_PD(HTTP_SNS_CURRENT, EnergyFormat(value_chr, current_chr[0], json));
|
|
}
|
|
WSContentSend_PD(HTTP_SNS_POWER, EnergyFormat(value_chr, active_power_chr[0], json));
|
|
if (!Energy.type_dc) {
|
|
if (Energy.current_available && Energy.voltage_available) {
|
|
WSContentSend_PD(HTTP_ENERGY_SNS1, EnergyFormat(value_chr, apparent_power_chr[0], json),
|
|
EnergyFormat(value2_chr, reactive_power_chr[0], json),
|
|
EnergyFormat(value3_chr, power_factor_chr[0], json));
|
|
}
|
|
if (!isnan(Energy.frequency[0])) {
|
|
WSContentSend_PD(PSTR("{s}" D_FREQUENCY "{m}%s " D_UNIT_HERTZ "{e}"),
|
|
EnergyFormat(value_chr, frequency_chr[0], json, Energy.voltage_common));
|
|
}
|
|
}
|
|
WSContentSend_PD(HTTP_ENERGY_SNS2, energy_daily_chr, energy_yesterday_chr, energy_total_chr[0]);
|
|
if (!isnan(Energy.export_active)) {
|
|
WSContentSend_PD(HTTP_ENERGY_SNS3, export_active_chr[0]);
|
|
}
|
|
|
|
XnrgCall(FUNC_WEB_SENSOR);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv03(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_PRE_INIT == function) {
|
|
energy_flg = ENERGY_NONE;
|
|
XnrgCall(FUNC_PRE_INIT);
|
|
}
|
|
else if (energy_flg) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
XnrgCall(FUNC_LOOP);
|
|
break;
|
|
case FUNC_EVERY_250_MSECOND:
|
|
XnrgCall(FUNC_EVERY_250_MSECOND);
|
|
break;
|
|
case FUNC_SERIAL:
|
|
result = XnrgCall(FUNC_SERIAL);
|
|
break;
|
|
#ifdef USE_ENERGY_MARGIN_DETECTION
|
|
case FUNC_SET_POWER:
|
|
Energy.power_steady_counter = 2;
|
|
break;
|
|
#endif
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kEnergyCommands, EnergyCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Xsns03(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (energy_flg) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
EnergyEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
EnergyShow(true);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
EnergyShow(false);
|
|
break;
|
|
#endif
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
EnergySaveState();
|
|
break;
|
|
case FUNC_INIT:
|
|
EnergySnsInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
#ifdef USE_LIGHT
|
|
# 124 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
#define XDRV_04 4
|
|
|
|
|
|
enum LightSchemes { LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX };
|
|
|
|
const uint8_t LIGHT_COLOR_SIZE = 25;
|
|
|
|
const char kLightCommands[] PROGMEM = "|"
|
|
D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|"
|
|
D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|"
|
|
D_CMND_WHITE "|" D_CMND_CHANNEL "|" D_CMND_HSBCOLOR "|UNDOCA" ;
|
|
|
|
void (* const LightCommand[])(void) PROGMEM = {
|
|
&CmndColor, &CmndColorTemperature, &CmndDimmer, &CmndDimmerRange, &CmndLedTable, &CmndFade,
|
|
&CmndRgbwwTable, &CmndScheme, &CmndSpeed, &CmndWakeup, &CmndWakeupDuration,
|
|
&CmndWhite, &CmndChannel, &CmndHsbColor, &CmndUndocA };
|
|
|
|
|
|
enum LightColorModes {
|
|
LCM_RGB = 1, LCM_CT = 2, LCM_BOTH = 3 };
|
|
|
|
struct LRgbColor {
|
|
uint8_t R, G, B;
|
|
};
|
|
const uint8_t MAX_FIXED_COLOR = 12;
|
|
const LRgbColor kFixedColor[MAX_FIXED_COLOR] PROGMEM =
|
|
{ 255,0,0, 0,255,0, 0,0,255, 228,32,0, 0,228,32, 0,32,228, 188,64,0, 0,160,96, 160,32,240, 255,255,0, 255,0,170, 255,255,255 };
|
|
|
|
struct LWColor {
|
|
uint8_t W;
|
|
};
|
|
const uint8_t MAX_FIXED_WHITE = 4;
|
|
const LWColor kFixedWhite[MAX_FIXED_WHITE] PROGMEM = { 0, 255, 128, 32 };
|
|
|
|
struct LCwColor {
|
|
uint8_t C, W;
|
|
};
|
|
const uint8_t MAX_FIXED_COLD_WARM = 4;
|
|
const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 };
|
|
|
|
|
|
const uint16_t CT_MIN = 153;
|
|
const uint16_t CT_MAX = 500;
|
|
|
|
const uint16_t CT_MIN_ALEXA = 200;
|
|
const uint16_t CT_MAX_ALEXA = 380;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct gamma_table_t {
|
|
uint16_t to_src;
|
|
uint16_t to_gamma;
|
|
} gamma_table_t;
|
|
|
|
const gamma_table_t gamma_table[] = {
|
|
{ 1, 1 },
|
|
{ 4, 1 },
|
|
{ 209, 13 },
|
|
{ 312, 41 },
|
|
{ 457, 106 },
|
|
{ 626, 261 },
|
|
{ 762, 450 },
|
|
{ 895, 703 },
|
|
{ 1023, 1023 },
|
|
{ 0xFFFF, 0xFFFF }
|
|
};
|
|
|
|
|
|
const gamma_table_t gamma_table_fast[] = {
|
|
{ 384, 192 },
|
|
{ 768, 576 },
|
|
{ 1023, 1023 },
|
|
{ 0xFFFF, 0xFFFF }
|
|
};
|
|
# 248 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
struct LIGHT {
|
|
uint32_t strip_timer_counter = 0;
|
|
power_t power = 0;
|
|
|
|
uint16_t wakeup_counter = 0;
|
|
|
|
uint8_t entry_color[LST_MAX];
|
|
uint8_t current_color[LST_MAX];
|
|
uint8_t new_color[LST_MAX];
|
|
uint8_t last_color[LST_MAX];
|
|
uint8_t color_remap[LST_MAX];
|
|
|
|
uint8_t wheel = 0;
|
|
uint8_t random = 0;
|
|
uint8_t subtype = 0;
|
|
uint8_t device = 0;
|
|
uint8_t old_power = 1;
|
|
uint8_t wakeup_active = 0;
|
|
uint8_t wakeup_dimmer = 0;
|
|
uint8_t fixed_color_index = 1;
|
|
uint8_t pwm_offset = 0;
|
|
uint8_t max_scheme = LS_MAX -1;
|
|
|
|
bool update = true;
|
|
bool pwm_multi_channels = false;
|
|
|
|
bool fade_initialized = false;
|
|
bool fade_running = false;
|
|
uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0};
|
|
uint16_t fade_cur_10[LST_MAX];
|
|
uint16_t fade_end_10[LST_MAX];
|
|
uint16_t fade_duration = 0;
|
|
uint32_t fade_start = 0;
|
|
} Light;
|
|
|
|
power_t LightPower(void)
|
|
{
|
|
return Light.power;
|
|
}
|
|
|
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
power_t LightPowerIRAM(void) ICACHE_RAM_ATTR;
|
|
#endif
|
|
|
|
power_t LightPowerIRAM(void)
|
|
{
|
|
return Light.power;
|
|
}
|
|
|
|
uint8_t LightDevice(void)
|
|
{
|
|
return Light.device;
|
|
}
|
|
|
|
static uint32_t min3(uint32_t a, uint32_t b, uint32_t c) {
|
|
return (a < b && a < c) ? a : (b < c) ? b : c;
|
|
}
|
|
# 344 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
class LightStateClass {
|
|
private:
|
|
uint16_t _hue = 0;
|
|
uint8_t _sat = 255;
|
|
uint8_t _briRGB = 255;
|
|
|
|
uint8_t _r = 255;
|
|
uint8_t _g = 255;
|
|
uint8_t _b = 255;
|
|
|
|
uint8_t _subtype = 0;
|
|
uint16_t _ct = CT_MIN;
|
|
uint8_t _wc = 255;
|
|
uint8_t _ww = 0;
|
|
uint8_t _briCT = 255;
|
|
|
|
uint8_t _color_mode = LCM_RGB;
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t _ct_min_range = CT_MIN;
|
|
uint16_t _ct_max_range = CT_MAX;
|
|
|
|
public:
|
|
LightStateClass() {
|
|
|
|
}
|
|
|
|
void setSubType(uint8_t sub_type) {
|
|
_subtype = sub_type;
|
|
}
|
|
# 386 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
uint8_t setColorMode(uint8_t cm) {
|
|
uint8_t prev_cm = _color_mode;
|
|
if (cm < LCM_RGB) { cm = LCM_RGB; }
|
|
if (cm > LCM_BOTH) { cm = LCM_BOTH; }
|
|
uint8_t maxbri = (_briRGB >= _briCT) ? _briRGB : _briCT;
|
|
|
|
switch (_subtype) {
|
|
case LST_COLDWARM:
|
|
_color_mode = LCM_CT;
|
|
break;
|
|
|
|
case LST_NONE:
|
|
case LST_SINGLE:
|
|
case LST_RGB:
|
|
default:
|
|
_color_mode = LCM_RGB;
|
|
break;
|
|
|
|
case LST_RGBW:
|
|
case LST_RGBCW:
|
|
_color_mode = cm;
|
|
break;
|
|
}
|
|
if (LCM_RGB == _color_mode) {
|
|
_briCT = 0;
|
|
if (0 == _briRGB) { _briRGB = maxbri; }
|
|
}
|
|
if (LCM_CT == _color_mode) {
|
|
_briRGB = 0;
|
|
if (0 == _briCT) { _briCT = maxbri; }
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setColorMode prev_cm (%d) req_cm (%d) new_cm (%d)", prev_cm, cm, _color_mode);
|
|
#endif
|
|
return prev_cm;
|
|
}
|
|
|
|
inline uint8_t getColorMode() {
|
|
return _color_mode;
|
|
}
|
|
|
|
void addRGBMode() {
|
|
setColorMode(_color_mode | LCM_RGB);
|
|
}
|
|
void addCTMode() {
|
|
setColorMode(_color_mode | LCM_CT);
|
|
}
|
|
|
|
|
|
void getRGB(uint8_t *r, uint8_t *g, uint8_t *b) {
|
|
if (r) { *r = _r; }
|
|
if (g) { *g = _g; }
|
|
if (b) { *b = _b; }
|
|
}
|
|
|
|
|
|
|
|
void getCW(uint8_t *rc, uint8_t *rw) {
|
|
if (rc) { *rc = _wc; }
|
|
if (rw) { *rw = _ww; }
|
|
}
|
|
|
|
|
|
void getActualRGBCW(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *c, uint8_t *w) {
|
|
bool rgb_channels_on = _color_mode & LCM_RGB;
|
|
bool ct_channels_on = _color_mode & LCM_CT;
|
|
|
|
if (r) { *r = rgb_channels_on ? changeUIntScale(_r, 0, 255, 0, _briRGB) : 0; }
|
|
if (g) { *g = rgb_channels_on ? changeUIntScale(_g, 0, 255, 0, _briRGB) : 0; }
|
|
if (b) { *b = rgb_channels_on ? changeUIntScale(_b, 0, 255, 0, _briRGB) : 0; }
|
|
|
|
if (c) { *c = ct_channels_on ? changeUIntScale(_wc, 0, 255, 0, _briCT) : 0; }
|
|
if (w) { *w = ct_channels_on ? changeUIntScale(_ww, 0, 255, 0, _briCT) : 0; }
|
|
}
|
|
|
|
uint8_t getChannels(uint8_t *channels) {
|
|
getActualRGBCW(&channels[0], &channels[1], &channels[2], &channels[3], &channels[4]);
|
|
}
|
|
|
|
void getChannelsRaw(uint8_t *channels) {
|
|
channels[0] = _r;
|
|
channels[1] = _g;
|
|
channels[2] = _b;
|
|
channels[3] = _wc;
|
|
channels[4] = _ww;
|
|
}
|
|
|
|
void getHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
|
|
if (hue) { *hue = _hue; }
|
|
if (sat) { *sat = _sat; }
|
|
if (bri) { *bri = _briRGB; }
|
|
}
|
|
|
|
|
|
uint8_t getBri(void) {
|
|
|
|
return (_briRGB >= _briCT) ? _briRGB : _briCT;
|
|
}
|
|
|
|
|
|
inline uint8_t getBriCT() {
|
|
return _briCT;
|
|
}
|
|
|
|
static inline uint8_t DimmerToBri(uint8_t dimmer) {
|
|
return changeUIntScale(dimmer, 0, 100, 0, 255);
|
|
}
|
|
static uint8_t BriToDimmer(uint8_t bri) {
|
|
uint8_t dimmer = changeUIntScale(bri, 0, 255, 0, 100);
|
|
|
|
if ((dimmer == 0) && (bri > 0)) { dimmer = 1; }
|
|
return dimmer;
|
|
}
|
|
|
|
uint8_t getDimmer(uint32_t mode = 0) {
|
|
uint8_t bri;
|
|
switch (mode) {
|
|
case 1:
|
|
bri = getBriRGB();
|
|
break;
|
|
case 2:
|
|
bri = getBriCT();
|
|
break;
|
|
default:
|
|
bri = getBri();
|
|
break;
|
|
}
|
|
return BriToDimmer(bri);
|
|
}
|
|
|
|
inline uint16_t getCT() const {
|
|
return _ct;
|
|
}
|
|
|
|
|
|
uint16_t getCT10bits() const {
|
|
return changeUIntScale(_ct, _ct_min_range, _ct_max_range, 0, 1023);
|
|
}
|
|
|
|
inline void setCTRange(uint16_t ct_min_range, uint16_t ct_max_range) {
|
|
_ct_min_range = ct_min_range;
|
|
_ct_max_range = ct_max_range;
|
|
}
|
|
|
|
inline void getCTRange(uint16_t *ct_min_range, uint16_t *ct_max_range) const {
|
|
if (ct_min_range) { *ct_min_range = _ct_min_range; }
|
|
if (ct_max_range) { *ct_max_range = _ct_max_range; }
|
|
}
|
|
|
|
|
|
void getXY(float *x, float *y) {
|
|
RgbToXy(_r, _g, _b, x, y);
|
|
}
|
|
|
|
|
|
|
|
void setBri(uint8_t bri) {
|
|
setBriRGB(_color_mode & LCM_RGB ? bri : 0);
|
|
setBriCT(_color_mode & LCM_CT ? bri : 0);
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setBri RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
|
#endif
|
|
}
|
|
|
|
|
|
uint8_t setBriRGB(uint8_t bri_rgb) {
|
|
uint8_t prev_bri = _briRGB;
|
|
_briRGB = bri_rgb;
|
|
if (bri_rgb > 0) { addRGBMode(); }
|
|
return prev_bri;
|
|
}
|
|
|
|
|
|
uint8_t setBriCT(uint8_t bri_ct) {
|
|
uint8_t prev_bri = _briCT;
|
|
_briCT = bri_ct;
|
|
if (bri_ct > 0) { addCTMode(); }
|
|
return prev_bri;
|
|
}
|
|
|
|
inline uint8_t getBriRGB() {
|
|
return _briRGB;
|
|
}
|
|
|
|
void setDimmer(uint8_t dimmer) {
|
|
setBri(DimmerToBri(dimmer));
|
|
}
|
|
|
|
void setCT(uint16_t ct) {
|
|
if (0 == ct) {
|
|
|
|
setColorMode(LCM_RGB);
|
|
} else {
|
|
ct = (ct < CT_MIN ? CT_MIN : (ct > CT_MAX ? CT_MAX : ct));
|
|
_ww = changeUIntScale(ct, _ct_min_range, _ct_max_range, 0, 255);
|
|
_wc = 255 - _ww;
|
|
_ct = ct;
|
|
addCTMode();
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setCT RGB raw (%d %d %d) HS (%d %d) briRGB (%d) briCT (%d) CT (%d)", _r, _g, _b, _hue, _sat, _briRGB, _briCT, _ct);
|
|
#endif
|
|
}
|
|
# 604 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
void setCW(uint8_t c, uint8_t w, bool free_range = false) {
|
|
uint16_t max = (w > c) ? w : c;
|
|
uint16_t sum = c + w;
|
|
|
|
if (0 == max) {
|
|
_briCT = 0;
|
|
setColorMode(LCM_RGB);
|
|
} else {
|
|
if (!free_range) {
|
|
|
|
_ww = changeUIntScale(w, 0, sum, 0, 255);
|
|
_wc = 255 - _ww;
|
|
} else {
|
|
_ww = changeUIntScale(w, 0, max, 0, 255);
|
|
_wc = changeUIntScale(c, 0, max, 0, 255);
|
|
}
|
|
_ct = changeUIntScale(w, 0, sum, _ct_min_range, _ct_max_range);
|
|
addCTMode();
|
|
if (_color_mode & LCM_CT) { _briCT = free_range ? max : (sum > 255 ? 255 : sum); }
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setCW CW (%d %d) CT (%d) briCT (%d)", c, w, _ct, _briCT);
|
|
#endif
|
|
}
|
|
|
|
|
|
uint8_t setRGB(uint8_t r, uint8_t g, uint8_t b, bool keep_bri = false) {
|
|
uint16_t hue;
|
|
uint8_t sat;
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB input (%d %d %d)", r, g, b);
|
|
#endif
|
|
|
|
uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b;
|
|
|
|
if (0 == max) {
|
|
r = g = b = 255;
|
|
setColorMode(LCM_CT);
|
|
} else {
|
|
if (255 > max) {
|
|
|
|
r = changeUIntScale(r, 0, max, 0, 255);
|
|
g = changeUIntScale(g, 0, max, 0, 255);
|
|
b = changeUIntScale(b, 0, max, 0, 255);
|
|
}
|
|
addRGBMode();
|
|
}
|
|
if (!keep_bri) {
|
|
_briRGB = (_color_mode & LCM_RGB) ? max : 0;
|
|
}
|
|
|
|
RgbToHsb(r, g, b, &hue, &sat, nullptr);
|
|
_r = r;
|
|
_g = g;
|
|
_b = b;
|
|
_hue = hue;
|
|
_sat = sat;
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setRGB RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
|
#endif
|
|
return max;
|
|
}
|
|
|
|
void setHS(uint16_t hue, uint8_t sat) {
|
|
uint8_t r, g, b;
|
|
HsToRgb(hue, sat, &r, &g, &b);
|
|
_r = r;
|
|
_g = g;
|
|
_b = b;
|
|
_hue = hue;
|
|
_sat = sat;
|
|
addRGBMode();
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS HS (%d %d) rgb (%d %d %d)", hue, sat, r, g, b);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setHS RGB raw (%d %d %d) HS (%d %d) bri (%d)", _r, _g, _b, _hue, _sat, _briRGB);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
void setChannelsRaw(uint8_t *channels) {
|
|
_r = channels[0];
|
|
_g = channels[1];
|
|
_b = channels[2];
|
|
_wc = channels[3];
|
|
_ww = channels[4];
|
|
}
|
|
|
|
|
|
|
|
|
|
void setChannels(uint8_t *channels) {
|
|
setRGB(channels[0], channels[1], channels[2]);
|
|
setCW(channels[3], channels[4], true);
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels (%d %d %d %d %d)",
|
|
channels[0], channels[1], channels[2], channels[3], channels[4]);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightStateClass::setChannels CT (%d) briRGB (%d) briCT (%d)", _ct, _briRGB, _briCT);
|
|
#endif
|
|
}
|
|
|
|
|
|
static void RgbToHsb(uint8_t r, uint8_t g, uint8_t b, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri);
|
|
static void HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b);
|
|
static void RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y);
|
|
static void XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb);
|
|
|
|
};
|
|
# 720 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_04_light.ino"
|
|
void LightStateClass::RgbToHsb(uint8_t ir, uint8_t ig, uint8_t ib, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri) {
|
|
uint32_t r = ir;
|
|
uint32_t g = ig;
|
|
uint32_t b = ib;
|
|
uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b;
|
|
uint32_t min = (r < g && r < b) ? r : (g < b) ? g : b;
|
|
uint32_t d = max - min;
|
|
|
|
uint16_t hue = 0;
|
|
uint8_t sat = 0;
|
|
uint8_t bri = max;
|
|
|
|
if (d != 0) {
|
|
sat = changeUIntScale(d, 0, max, 0, 255);
|
|
if (r == max) {
|
|
hue = (g > b) ? changeUIntScale(g-b,0,d,0,60) : 360 - changeUIntScale(b-g,0,d,0,60);
|
|
} else if (g == max) {
|
|
hue = (b > r) ? 120 + changeUIntScale(b-r,0,d,0,60) : 120 - changeUIntScale(r-b,0,d,0,60);
|
|
} else {
|
|
hue = (r > g) ? 240 + changeUIntScale(r-g,0,d,0,60) : 240 - changeUIntScale(g-r,0,d,0,60);
|
|
}
|
|
hue = hue % 360;
|
|
}
|
|
|
|
if (r_hue) *r_hue = hue;
|
|
if (r_sat) *r_sat = sat;
|
|
if (r_bri) *r_bri = bri;
|
|
|
|
}
|
|
|
|
void LightStateClass::HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) {
|
|
uint32_t r = 255;
|
|
uint32_t g = 255;
|
|
uint32_t b = 255;
|
|
|
|
hue = hue % 360;
|
|
|
|
if (sat > 0) {
|
|
uint32_t i = hue / 60;
|
|
uint32_t f = hue % 60;
|
|
uint32_t q = 255 - changeUIntScale(f, 0, 60, 0, sat);
|
|
uint32_t p = 255 - sat;
|
|
uint32_t t = 255 - changeUIntScale(60 - f, 0, 60, 0, sat);
|
|
|
|
switch (i) {
|
|
case 0:
|
|
|
|
g = t;
|
|
b = p;
|
|
break;
|
|
case 1:
|
|
r = q;
|
|
|
|
b = p;
|
|
break;
|
|
case 2:
|
|
r = p;
|
|
|
|
b = t;
|
|
break;
|
|
case 3:
|
|
r = p;
|
|
g = q;
|
|
|
|
break;
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
|
|
break;
|
|
default:
|
|
|
|
g = p;
|
|
b = q;
|
|
break;
|
|
}
|
|
}
|
|
if (r_r) *r_r = r;
|
|
if (r_g) *r_g = g;
|
|
if (r_b) *r_b = b;
|
|
}
|
|
|
|
#define POW FastPrecisePowf
|
|
|
|
void LightStateClass::RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) {
|
|
float x = 0.31271f;
|
|
float y = 0.32902f;
|
|
|
|
if (i_r + i_b + i_g > 0) {
|
|
float r = (float)i_r / 255.0f;
|
|
float g = (float)i_g / 255.0f;
|
|
float b = (float)i_b / 255.0f;
|
|
|
|
|
|
r = (r > 0.04045f) ? POW((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f);
|
|
g = (g > 0.04045f) ? POW((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f);
|
|
b = (b > 0.04045f) ? POW((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f);
|
|
|
|
|
|
|
|
float X = r * 0.649926f + g * 0.103455f + b * 0.197109f;
|
|
float Y = r * 0.234327f + g * 0.743075f + b * 0.022598f;
|
|
float Z = r * 0.000000f + g * 0.053077f + b * 1.035763f;
|
|
|
|
x = X / (X + Y + Z);
|
|
y = Y / (X + Y + Z);
|
|
|
|
}
|
|
if (r_x) *r_x = x;
|
|
if (r_y) *r_y = y;
|
|
}
|
|
|
|
void LightStateClass::XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb)
|
|
{
|
|
x = (x > 0.99f ? 0.99f : (x < 0.01f ? 0.01f : x));
|
|
y = (y > 0.99f ? 0.99f : (y < 0.01f ? 0.01f : y));
|
|
float z = 1.0f - x - y;
|
|
|
|
float X = x / y;
|
|
float Z = z / y;
|
|
|
|
|
|
|
|
float r = X * 3.2406f - 1.5372f - Z * 0.4986f;
|
|
float g = -X * 0.9689f + 1.8758f + Z * 0.0415f;
|
|
float b = X * 0.0557f - 0.2040f + Z * 1.0570f;
|
|
float max = (r > g && r > b) ? r : (g > b) ? g : b;
|
|
r = r / max;
|
|
g = g / max;
|
|
b = b / max;
|
|
r = (r <= 0.0031308f) ? 12.92f * r : 1.055f * POW(r, (1.0f / 2.4f)) - 0.055f;
|
|
g = (g <= 0.0031308f) ? 12.92f * g : 1.055f * POW(g, (1.0f / 2.4f)) - 0.055f;
|
|
b = (b <= 0.0031308f) ? 12.92f * b : 1.055f * POW(b, (1.0f / 2.4f)) - 0.055f;
|
|
|
|
|
|
|
|
|
|
|
|
int32_t ir = r * 255.0f + 0.5f;
|
|
int32_t ig = g * 255.0f + 0.5f;
|
|
int32_t ib = b * 255.0f + 0.5f;
|
|
if (rr) { *rr = (ir > 255 ? 255: (ir < 0 ? 0 : ir)); }
|
|
if (rg) { *rg = (ig > 255 ? 255: (ig < 0 ? 0 : ig)); }
|
|
if (rb) { *rb = (ib > 255 ? 255: (ib < 0 ? 0 : ib)); }
|
|
}
|
|
|
|
class LightControllerClass {
|
|
private:
|
|
LightStateClass *_state;
|
|
|
|
|
|
bool _ct_rgb_linked = true;
|
|
bool _pwm_multi_channels = false;
|
|
|
|
public:
|
|
LightControllerClass(LightStateClass& state) {
|
|
_state = &state;
|
|
}
|
|
|
|
void setSubType(uint8_t sub_type) {
|
|
_state->setSubType(sub_type);
|
|
}
|
|
|
|
inline bool setCTRGBLinked(bool ct_rgb_linked) {
|
|
bool prev = _ct_rgb_linked;
|
|
if (_pwm_multi_channels) {
|
|
_ct_rgb_linked = false;
|
|
} else {
|
|
_ct_rgb_linked = ct_rgb_linked;
|
|
}
|
|
return prev;
|
|
}
|
|
|
|
void setAlexaCTRange(bool alexa_ct_range) {
|
|
|
|
if (alexa_ct_range) {
|
|
_state->setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA);
|
|
} else {
|
|
_state->setCTRange(CT_MIN, CT_MAX);
|
|
}
|
|
}
|
|
|
|
inline bool isCTRGBLinked() {
|
|
return _ct_rgb_linked;
|
|
}
|
|
|
|
inline bool setPWMMultiChannel(bool pwm_multi_channels) {
|
|
bool prev = _pwm_multi_channels;
|
|
_pwm_multi_channels = pwm_multi_channels;
|
|
if (pwm_multi_channels) setCTRGBLinked(false);
|
|
return prev;
|
|
}
|
|
|
|
inline bool isPWMMultiChannel(void) {
|
|
return _pwm_multi_channels;
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
|
void debugLogs() {
|
|
uint8_t r,g,b,c,w;
|
|
_state->getActualRGBCW(&r,&g,&b,&c,&w);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::debugLogs rgb (%d %d %d) cw (%d %d)",
|
|
r, g, b, c, w);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::debugLogs lightCurrent (%d %d %d %d %d)",
|
|
Light.current_color[0], Light.current_color[1], Light.current_color[2],
|
|
Light.current_color[3], Light.current_color[4]);
|
|
}
|
|
#endif
|
|
|
|
void loadSettings() {
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::loadSettings Settings.light_color (%d %d %d %d %d - %d)",
|
|
Settings.light_color[0], Settings.light_color[1], Settings.light_color[2],
|
|
Settings.light_color[3], Settings.light_color[4], Settings.light_dimmer);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::loadSettings light_type/sub (%d %d)",
|
|
light_type, Light.subtype);
|
|
#endif
|
|
if (_pwm_multi_channels) {
|
|
_state->setChannelsRaw(Settings.light_color);
|
|
} else {
|
|
|
|
_state->setCW(Settings.light_color[3], Settings.light_color[4], true);
|
|
_state->setRGB(Settings.light_color[0], Settings.light_color[1], Settings.light_color[2]);
|
|
|
|
|
|
|
|
uint8_t bri = _state->DimmerToBri(Settings.light_dimmer);
|
|
if (Settings.light_color[0] + Settings.light_color[1] + Settings.light_color[2] > 0) {
|
|
|
|
|
|
if ( (DEFAULT_LIGHT_COMPONENT == Settings.light_color[0]) &&
|
|
(DEFAULT_LIGHT_COMPONENT == Settings.light_color[1]) &&
|
|
(DEFAULT_LIGHT_COMPONENT == Settings.light_color[2]) &&
|
|
(DEFAULT_LIGHT_COMPONENT == Settings.light_color[3]) &&
|
|
(DEFAULT_LIGHT_COMPONENT == Settings.light_color[4]) &&
|
|
(DEFAULT_LIGHT_DIMMER == Settings.light_dimmer) ) {
|
|
_state->setColorMode(LCM_RGB);
|
|
}
|
|
_state->setBriRGB(bri);
|
|
} else {
|
|
_state->setBriCT(bri);
|
|
}
|
|
}
|
|
}
|
|
|
|
void changeCTB(uint16_t new_ct, uint8_t briCT) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBW > Light.subtype)) {
|
|
return;
|
|
}
|
|
_state->setCT(new_ct);
|
|
_state->setBriCT(briCT);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); }
|
|
saveSettings();
|
|
calcLevels();
|
|
|
|
}
|
|
|
|
void changeDimmer(uint8_t dimmer, uint32_t mode = 0) {
|
|
uint8_t bri = changeUIntScale(dimmer, 0, 100, 0, 255);
|
|
switch (mode) {
|
|
case 1:
|
|
changeBriRGB(bri);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); }
|
|
break;
|
|
case 2:
|
|
changeBriCT(bri);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); }
|
|
break;
|
|
default:
|
|
changeBri(bri);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void changeBri(uint8_t bri) {
|
|
_state->setBri(bri);
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
void changeBriRGB(uint8_t bri) {
|
|
_state->setBriRGB(bri);
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
void changeBriCT(uint8_t bri) {
|
|
_state->setBriCT(bri);
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
void changeRGB(uint8_t r, uint8_t g, uint8_t b, bool keep_bri = false) {
|
|
_state->setRGB(r, g, b, keep_bri);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); }
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
|
|
|
|
void calcLevels(uint8_t *current_color = nullptr) {
|
|
uint8_t r,g,b,c,w,briRGB,briCT;
|
|
if (current_color == nullptr) { current_color = Light.current_color; }
|
|
|
|
if (_pwm_multi_channels) {
|
|
_state->getChannelsRaw(current_color);
|
|
return;
|
|
}
|
|
|
|
_state->getActualRGBCW(&r,&g,&b,&c,&w);
|
|
briRGB = _state->getBriRGB();
|
|
briCT = _state->getBriCT();
|
|
|
|
current_color[0] = current_color[1] = current_color[2] = 0;
|
|
current_color[3] = current_color[4] = 0;
|
|
switch (Light.subtype) {
|
|
case LST_NONE:
|
|
current_color[0] = 255;
|
|
break;
|
|
case LST_SINGLE:
|
|
current_color[0] = briRGB;
|
|
break;
|
|
case LST_COLDWARM:
|
|
current_color[0] = c;
|
|
current_color[1] = w;
|
|
break;
|
|
case LST_RGBW:
|
|
case LST_RGBCW:
|
|
if (LST_RGBCW == Light.subtype) {
|
|
current_color[3] = c;
|
|
current_color[4] = w;
|
|
} else {
|
|
current_color[3] = briCT;
|
|
}
|
|
|
|
case LST_RGB:
|
|
current_color[0] = r;
|
|
current_color[1] = g;
|
|
current_color[2] = b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void changeHSB(uint16_t hue, uint8_t sat, uint8_t briRGB) {
|
|
_state->setHS(hue, sat);
|
|
_state->setBriRGB(briRGB);
|
|
if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); }
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
|
|
|
|
void saveSettings() {
|
|
if (Light.pwm_multi_channels) {
|
|
|
|
_state->getChannelsRaw(Settings.light_color);
|
|
Settings.light_dimmer = 100;
|
|
} else {
|
|
uint8_t cm = _state->getColorMode();
|
|
|
|
memset(&Settings.light_color[0], 0, sizeof(Settings.light_color));
|
|
if (LCM_RGB & cm) {
|
|
_state->getRGB(&Settings.light_color[0], &Settings.light_color[1], &Settings.light_color[2]);
|
|
Settings.light_dimmer = _state->BriToDimmer(_state->getBriRGB());
|
|
|
|
if (LCM_BOTH == cm) {
|
|
|
|
_state->getActualRGBCW(nullptr, nullptr, nullptr, &Settings.light_color[3], &Settings.light_color[4]);
|
|
}
|
|
} else if (LCM_CT == cm) {
|
|
_state->getCW(&Settings.light_color[3], &Settings.light_color[4]);
|
|
Settings.light_dimmer = _state->BriToDimmer(_state->getBriCT());
|
|
}
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightControllerClass::saveSettings Settings.light_color (%d %d %d %d %d - %d)",
|
|
Settings.light_color[0], Settings.light_color[1], Settings.light_color[2],
|
|
Settings.light_color[3], Settings.light_color[4], Settings.light_dimmer);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
void changeChannels(uint8_t *channels) {
|
|
if (Light.pwm_multi_channels) {
|
|
_state->setChannelsRaw(channels);
|
|
} else if (LST_COLDWARM == Light.subtype) {
|
|
|
|
uint8_t remapped_channels[5] = {0,0,0,channels[0],channels[1]};
|
|
_state->setChannels(remapped_channels);
|
|
} else {
|
|
_state->setChannels(channels);
|
|
}
|
|
|
|
saveSettings();
|
|
calcLevels();
|
|
}
|
|
};
|
|
|
|
|
|
|
|
LightStateClass light_state = LightStateClass();
|
|
LightControllerClass light_controller = LightControllerClass(light_state);
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t change8to10(uint8_t v) {
|
|
return changeUIntScale(v, 0, 255, 0, 1023);
|
|
}
|
|
|
|
uint8_t change10to8(uint16_t v) {
|
|
return (0 == v) ? 0 : changeUIntScale(v, 4, 1023, 1, 255);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr) {
|
|
uint16_t from_src = 0;
|
|
uint16_t from_gamma = 0;
|
|
|
|
for (const gamma_table_t *gt = gt_ptr; ; gt++) {
|
|
uint16_t to_src = gt->to_src;
|
|
uint16_t to_gamma = gt->to_gamma;
|
|
if (v <= to_src) {
|
|
return changeUIntScale(v, from_src, to_src, from_gamma, to_gamma);
|
|
}
|
|
from_src = to_src;
|
|
from_gamma = to_gamma;
|
|
}
|
|
}
|
|
|
|
uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr) {
|
|
uint16_t from_src = 0;
|
|
uint16_t from_gamma = 0;
|
|
|
|
for (const gamma_table_t *gt = gt_ptr; ; gt++) {
|
|
uint16_t to_src = gt->to_src;
|
|
uint16_t to_gamma = gt->to_gamma;
|
|
if (vg <= to_gamma) {
|
|
return changeUIntScale(vg, from_gamma, to_gamma, from_src, to_src);
|
|
}
|
|
from_src = to_src;
|
|
from_gamma = to_gamma;
|
|
}
|
|
}
|
|
|
|
|
|
uint16_t ledGamma10_10(uint16_t v) {
|
|
return ledGamma_internal(v, gamma_table);
|
|
}
|
|
|
|
uint16_t ledGamma10(uint8_t v) {
|
|
return ledGamma10_10(change8to10(v));
|
|
}
|
|
|
|
|
|
uint8_t ledGamma(uint8_t v) {
|
|
return change10to8(ledGamma10(v));
|
|
}
|
|
|
|
|
|
|
|
void LightPwmOffset(uint32_t offset)
|
|
{
|
|
Light.pwm_offset = offset;
|
|
}
|
|
|
|
bool LightModuleInit(void)
|
|
{
|
|
light_type = LT_BASIC;
|
|
|
|
if (Settings.flag.pwm_control) {
|
|
for (uint32_t i = 0; i < MAX_PWMS; i++) {
|
|
if (pin[GPIO_PWM1 +i] < 99) { light_type++; }
|
|
}
|
|
}
|
|
|
|
light_flg = 0;
|
|
if (XlgtCall(FUNC_MODULE_INIT)) {
|
|
|
|
}
|
|
else if (SONOFF_BN == my_module_type) {
|
|
light_type = LT_PWM1;
|
|
}
|
|
else if (SONOFF_LED == my_module_type) {
|
|
if (!my_module.io[4]) {
|
|
pinMode(4, OUTPUT);
|
|
digitalWrite(4, LOW);
|
|
}
|
|
if (!my_module.io[5]) {
|
|
pinMode(5, OUTPUT);
|
|
digitalWrite(5, LOW);
|
|
}
|
|
if (!my_module.io[14]) {
|
|
pinMode(14, OUTPUT);
|
|
digitalWrite(14, LOW);
|
|
}
|
|
light_type = LT_PWM2;
|
|
}
|
|
|
|
if (light_type > LT_BASIC) {
|
|
devices_present++;
|
|
}
|
|
|
|
|
|
if (Settings.flag3.pwm_multi_channels) {
|
|
uint32_t pwm_channels = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7);
|
|
if (0 == pwm_channels) { pwm_channels = 1; }
|
|
devices_present += pwm_channels - 1;
|
|
} else if ((Settings.param[P_RGB_REMAP] & 128) && (LST_RGBW <= (light_type & 7))) {
|
|
|
|
devices_present++;
|
|
}
|
|
|
|
return (light_type > LT_BASIC);
|
|
}
|
|
|
|
void LightInit(void)
|
|
{
|
|
Light.device = devices_present;
|
|
Light.subtype = (light_type & 7) > LST_MAX ? LST_MAX : (light_type & 7);
|
|
Light.pwm_multi_channels = Settings.flag3.pwm_multi_channels;
|
|
|
|
if (LST_RGBW <= Light.subtype) {
|
|
|
|
|
|
bool ct_rgb_linked = !(Settings.param[P_RGB_REMAP] & 128);
|
|
light_controller.setCTRGBLinked(ct_rgb_linked);
|
|
}
|
|
|
|
if ((LST_SINGLE <= Light.subtype) && Light.pwm_multi_channels) {
|
|
|
|
light_controller.setPWMMultiChannel(true);
|
|
Light.device = devices_present - Light.subtype + 1;
|
|
} else if (!light_controller.isCTRGBLinked()) {
|
|
|
|
Light.device--;
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightInit Light.pwm_multi_channels=%d Light.subtype=%d Light.device=%d devices_present=%d",
|
|
Light.pwm_multi_channels, Light.subtype, Light.device, devices_present);
|
|
#endif
|
|
|
|
light_controller.setSubType(Light.subtype);
|
|
light_controller.loadSettings();
|
|
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
|
|
|
if (LST_SINGLE == Light.subtype) {
|
|
Settings.light_color[0] = 255;
|
|
}
|
|
if (light_type < LT_PWM6) {
|
|
for (uint32_t i = 0; i < light_type; i++) {
|
|
Settings.pwm_value[i] = 0;
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
|
|
}
|
|
}
|
|
if (pin[GPIO_ARIRFRCV] < 99) {
|
|
if (pin[GPIO_ARIRFSEL] < 99) {
|
|
pinMode(pin[GPIO_ARIRFSEL], OUTPUT);
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t max_scheme = Light.max_scheme;
|
|
if (Light.subtype < LST_RGB) {
|
|
max_scheme = LS_POWER;
|
|
}
|
|
if ((LS_WAKEUP == Settings.light_scheme) || (Settings.light_scheme > max_scheme)) {
|
|
Settings.light_scheme = LS_POWER;
|
|
}
|
|
Light.power = 0;
|
|
Light.update = true;
|
|
Light.wakeup_active = 0;
|
|
|
|
LightUpdateColorMapping();
|
|
}
|
|
|
|
void LightUpdateColorMapping(void)
|
|
{
|
|
uint8_t param = Settings.param[P_RGB_REMAP] & 127;
|
|
if (param > 119){ param = 0; }
|
|
|
|
uint8_t tmp[] = {0,1,2,3,4};
|
|
Light.color_remap[0] = tmp[param / 24];
|
|
for (uint32_t i = param / 24; i<4; ++i){
|
|
tmp[i] = tmp[i+1];
|
|
}
|
|
param = param % 24;
|
|
Light.color_remap[1] = tmp[(param / 6)];
|
|
for (uint32_t i = param / 6; i<3; ++i){
|
|
tmp[i] = tmp[i+1];
|
|
}
|
|
param = param % 6;
|
|
Light.color_remap[2] = tmp[(param / 2)];
|
|
for (uint32_t i = param / 2; i<2; ++i){
|
|
tmp[i] = tmp[i+1];
|
|
}
|
|
param = param % 2;
|
|
Light.color_remap[3] = tmp[param];
|
|
Light.color_remap[4] = tmp[1-param];
|
|
|
|
Light.update = true;
|
|
|
|
}
|
|
|
|
uint8_t LightGetDimmer(uint8_t dimmer) {
|
|
return light_state.getDimmer(dimmer);
|
|
}
|
|
|
|
void LightSetDimmer(uint8_t dimmer) {
|
|
light_controller.changeDimmer(dimmer);
|
|
}
|
|
|
|
void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
|
|
light_state.getHSB(hue, sat, bri);
|
|
}
|
|
|
|
void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) {
|
|
light_state.HsToRgb(hue, sat, r_r, r_g, r_b);
|
|
}
|
|
|
|
|
|
uint8_t LightGetBri(uint8_t device) {
|
|
uint8_t bri = 254;
|
|
if (Light.pwm_multi_channels) {
|
|
if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) {
|
|
bri = Light.current_color[device - Light.device];
|
|
}
|
|
} else if (light_controller.isCTRGBLinked()) {
|
|
if (device == Light.device) {
|
|
bri = light_state.getBri();
|
|
}
|
|
} else {
|
|
if (device == Light.device) {
|
|
bri = light_state.getBriRGB();
|
|
} else if (device == Light.device + 1) {
|
|
bri = light_state.getBriCT();
|
|
}
|
|
}
|
|
return bri;
|
|
}
|
|
|
|
|
|
void LightSetBri(uint8_t device, uint8_t bri) {
|
|
if (Light.pwm_multi_channels) {
|
|
if ((device >= Light.device) && (device < Light.device + LST_MAX) && (device <= devices_present)) {
|
|
Light.current_color[device - Light.device] = bri;
|
|
light_controller.changeChannels(Light.current_color);
|
|
}
|
|
} else if (light_controller.isCTRGBLinked()) {
|
|
if (device == Light.device) {
|
|
light_controller.changeBri(bri);
|
|
}
|
|
} else {
|
|
if (device == Light.device) {
|
|
light_controller.changeBriRGB(bri);
|
|
} else if (device == Light.device + 1) {
|
|
light_controller.changeBriCT(bri);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LightSetColorTemp(uint16_t ct)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
return;
|
|
}
|
|
light_controller.changeCTB(ct, light_state.getBriCT());
|
|
}
|
|
|
|
uint16_t LightGetColorTemp(void)
|
|
{
|
|
|
|
if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
|
|
return 0;
|
|
}
|
|
return (light_state.getColorMode() & LCM_CT) ? light_state.getCT() : 0;
|
|
}
|
|
|
|
void LightSetSignal(uint16_t lo, uint16_t hi, uint16_t value)
|
|
{
|
|
|
|
|
|
|
|
if (Settings.flag.light_signal) {
|
|
uint16_t signal = changeUIntScale(value, lo, hi, 0, 255);
|
|
|
|
light_controller.changeRGB(signal, 255 - signal, 0, true);
|
|
Settings.light_scheme = 0;
|
|
if (0 == light_state.getBri()) {
|
|
light_controller.changeBri(50);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
char* LightGetColor(char* scolor, boolean force_hex = false)
|
|
{
|
|
light_controller.calcLevels();
|
|
scolor[0] = '\0';
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
if (!force_hex && Settings.flag.decimal_text) {
|
|
snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Light.current_color[i]);
|
|
} else {
|
|
snprintf_P(scolor, LIGHT_COLOR_SIZE, PSTR("%s%02X"), scolor, Light.current_color[i]);
|
|
}
|
|
}
|
|
return scolor;
|
|
}
|
|
|
|
void LightPowerOn(void)
|
|
{
|
|
if (light_state.getBri() && !(Light.power)) {
|
|
ExecuteCommandPower(Light.device, POWER_ON, SRC_LIGHT);
|
|
}
|
|
}
|
|
|
|
void LightState(uint8_t append)
|
|
{
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
char scommand[33];
|
|
bool unlinked = !light_controller.isCTRGBLinked() && (Light.subtype >= LST_RGBW);
|
|
|
|
if (append) {
|
|
ResponseAppend_P(PSTR(","));
|
|
} else {
|
|
Response_P(PSTR("{"));
|
|
}
|
|
if (!Light.pwm_multi_channels) {
|
|
if (unlinked) {
|
|
|
|
ResponseAppend_P(PSTR("\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d"
|
|
",\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d"),
|
|
Light.device, GetStateText(Light.power & 1), Light.device, light_state.getDimmer(1),
|
|
Light.device + 1, GetStateText(Light.power & 2 ? 1 : 0), Light.device + 1, light_state.getDimmer(2));
|
|
} else {
|
|
GetPowerDevice(scommand, Light.device, sizeof(scommand), Settings.flag.device_index_enable);
|
|
ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"), scommand, GetStateText(Light.power & 1),
|
|
light_state.getDimmer());
|
|
}
|
|
|
|
|
|
if (Light.subtype > LST_SINGLE) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
|
|
if (LST_RGB <= Light.subtype) {
|
|
uint16_t hue;
|
|
uint8_t sat, bri;
|
|
light_state.getHSB(&hue, &sat, &bri);
|
|
sat = changeUIntScale(sat, 0, 255, 0, 100);
|
|
bri = changeUIntScale(bri, 0, 255, 0, 100);
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_HSBCOLOR "\":\"%d,%d,%d\""), hue,sat,bri);
|
|
}
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_WHITE "\":%d"), light_state.getDimmer(2));
|
|
}
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
|
|
}
|
|
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_CHANNEL "\":[" ));
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
uint8_t channel_raw = Light.current_color[i];
|
|
uint8_t channel = changeUIntScale(channel_raw,0,255,0,100);
|
|
|
|
if ((0 == channel) && (channel_raw > 0)) { channel = 1; }
|
|
ResponseAppend_P(PSTR("%s%d" ), (i > 0 ? "," : ""), channel);
|
|
}
|
|
ResponseAppend_P(PSTR("]"));
|
|
}
|
|
|
|
if (append) {
|
|
if (Light.subtype >= LST_RGB) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_SCHEME "\":%d"), Settings.light_scheme);
|
|
}
|
|
if (Light.max_scheme > LS_MAX) {
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_WIDTH "\":%d"), Settings.light_width);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"" D_CMND_FADE "\":\"%s\",\"" D_CMND_SPEED "\":%d,\"" D_CMND_LEDTABLE "\":\"%s\""),
|
|
GetStateText(Settings.light_fade), Settings.light_speed, GetStateText(Settings.light_correction));
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
GetPowerDevice(scommand, Light.device + i, sizeof(scommand), 1);
|
|
uint32_t light_power_masked = Light.power & (1 << i);
|
|
light_power_masked = light_power_masked ? 1 : 0;
|
|
ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_CHANNEL "%d\":%d,"), scommand, GetStateText(light_power_masked), Light.device + i,
|
|
changeUIntScale(Light.current_color[i], 0, 255, 0, 100));
|
|
}
|
|
ResponseAppend_P(PSTR("\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
|
|
}
|
|
|
|
if (!append) {
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
void LightPreparePower(power_t channels = 0xFFFFFFFF) {
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower power=%d Light.power=%d", power, Light.power);
|
|
#endif
|
|
|
|
if (Light.pwm_multi_channels) {
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
if (bitRead(channels, i)) {
|
|
|
|
if ((Light.current_color[i]) && (!bitRead(Light.power, i))) {
|
|
if (!Settings.flag.not_power_linked) {
|
|
ExecuteCommandPower(Light.device + i, POWER_ON_NO_STATE, SRC_LIGHT);
|
|
}
|
|
} else {
|
|
|
|
if ((0 == Light.current_color[i]) && bitRead(Light.power, i)) {
|
|
ExecuteCommandPower(Light.device + i, POWER_OFF_NO_STATE, SRC_LIGHT);
|
|
}
|
|
}
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzUpdatePowerState(Light.device + i);
|
|
#endif
|
|
}
|
|
}
|
|
} else {
|
|
if (light_controller.isCTRGBLinked()) {
|
|
if (light_state.getBri() && !(Light.power)) {
|
|
if (!Settings.flag.not_power_linked) {
|
|
ExecuteCommandPower(Light.device, POWER_ON_NO_STATE, SRC_LIGHT);
|
|
}
|
|
} else if (!light_state.getBri() && Light.power) {
|
|
ExecuteCommandPower(Light.device, POWER_OFF_NO_STATE, SRC_LIGHT);
|
|
}
|
|
} else {
|
|
|
|
if (channels & 1) {
|
|
if (light_state.getBriRGB() && !(Light.power & 1)) {
|
|
if (!Settings.flag.not_power_linked) {
|
|
ExecuteCommandPower(Light.device, POWER_ON_NO_STATE, SRC_LIGHT);
|
|
}
|
|
} else if (!light_state.getBriRGB() && (Light.power & 1)) {
|
|
ExecuteCommandPower(Light.device, POWER_OFF_NO_STATE, SRC_LIGHT);
|
|
}
|
|
}
|
|
|
|
if (channels & 2) {
|
|
if (light_state.getBriCT() && !(Light.power & 2)) {
|
|
if (!Settings.flag.not_power_linked) {
|
|
ExecuteCommandPower(Light.device + 1, POWER_ON_NO_STATE, SRC_LIGHT);
|
|
}
|
|
} else if (!light_state.getBriCT() && (Light.power & 2)) {
|
|
ExecuteCommandPower(Light.device + 1, POWER_OFF_NO_STATE, SRC_LIGHT);
|
|
}
|
|
}
|
|
}
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzUpdatePowerState(Light.device);
|
|
#endif
|
|
}
|
|
|
|
if (Settings.flag3.hass_tele_on_power) {
|
|
MqttPublishTeleState();
|
|
}
|
|
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG, "LightPreparePower End power=%d Light.power=%d", power, Light.power);
|
|
#endif
|
|
Light.power = power >> (Light.device - 1);
|
|
LightState(0);
|
|
}
|
|
|
|
void LightCycleColor(int8_t direction)
|
|
{
|
|
if (Light.strip_timer_counter % (Settings.light_speed * 2)) {
|
|
return;
|
|
}
|
|
|
|
if (0 == direction) {
|
|
if (Light.random == Light.wheel) {
|
|
Light.random = random(255);
|
|
|
|
uint8_t my_dir = (Light.random < Light.wheel -128) ? 1 :
|
|
(Light.random < Light.wheel ) ? 0 :
|
|
(Light.random > Light.wheel +128) ? 0 : 1;
|
|
Light.random = (Light.random & 0xFE) | my_dir;
|
|
|
|
|
|
}
|
|
|
|
direction = (Light.random &0x01) ? 1 : -1;
|
|
}
|
|
Light.wheel += direction;
|
|
uint16_t hue = changeUIntScale(Light.wheel, 0, 255, 0, 359);
|
|
|
|
|
|
|
|
uint8_t sat;
|
|
light_state.getHSB(nullptr, &sat, nullptr);
|
|
light_state.setHS(hue, sat);
|
|
light_controller.calcLevels(Light.new_color);
|
|
}
|
|
|
|
void LightSetPower(void)
|
|
{
|
|
|
|
Light.old_power = Light.power;
|
|
|
|
uint32_t mask = 1;
|
|
if (Light.pwm_multi_channels) {
|
|
mask = (1 << Light.subtype) - 1;
|
|
} else if (!light_controller.isCTRGBLinked()) {
|
|
mask = 3;
|
|
}
|
|
uint32_t shift = Light.device - 1;
|
|
|
|
|
|
|
|
|
|
|
|
Light.power = (XdrvMailbox.index & (mask << shift)) >> shift;
|
|
if (Light.wakeup_active) {
|
|
Light.wakeup_active--;
|
|
}
|
|
#ifdef DEBUG_LIGHT
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "LightSetPower XdrvMailbox.index=%d Light.old_power=%d Light.power=%d mask=%d shift=%d",
|
|
XdrvMailbox.index, Light.old_power, Light.power, mask, shift);
|
|
#endif
|
|
if (Light.power != Light.old_power) {
|
|
Light.update = true;
|
|
}
|
|
LightAnimate();
|
|
}
|
|
|
|
|
|
|
|
|
|
void LightAnimate(void)
|
|
{
|
|
uint16_t light_still_on = 0;
|
|
bool power_off = false;
|
|
|
|
|
|
light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
|
|
Light.strip_timer_counter++;
|
|
|
|
|
|
|
|
if (Light.power || Light.fade_running) {
|
|
if (Settings.sleep > PWM_MAX_SLEEP) {
|
|
sleep = PWM_MAX_SLEEP;
|
|
} else {
|
|
sleep = Settings.sleep;
|
|
}
|
|
} else {
|
|
sleep = Settings.sleep;
|
|
}
|
|
|
|
if (!Light.power) {
|
|
Light.strip_timer_counter = 0;
|
|
if (Settings.light_scheme >= LS_MAX) {
|
|
power_off = true;
|
|
}
|
|
} else {
|
|
switch (Settings.light_scheme) {
|
|
case LS_POWER:
|
|
light_controller.calcLevels(Light.new_color);
|
|
break;
|
|
case LS_WAKEUP:
|
|
if (2 == Light.wakeup_active) {
|
|
Light.wakeup_active = 1;
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
Light.new_color[i] = 0;
|
|
}
|
|
Light.wakeup_counter = 0;
|
|
Light.wakeup_dimmer = 0;
|
|
}
|
|
Light.wakeup_counter++;
|
|
if (Light.wakeup_counter > ((Settings.light_wakeup * STATES) / Settings.light_dimmer)) {
|
|
Light.wakeup_counter = 0;
|
|
Light.wakeup_dimmer++;
|
|
if (Light.wakeup_dimmer <= Settings.light_dimmer) {
|
|
light_state.setDimmer(Light.wakeup_dimmer);
|
|
light_controller.calcLevels();
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
Light.new_color[i] = Light.current_color[i];
|
|
}
|
|
} else {
|
|
|
|
|
|
|
|
|
|
Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\""));
|
|
LightState(1);
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WAKEUP));
|
|
XdrvRulesProcess();
|
|
|
|
Light.wakeup_active = 0;
|
|
Settings.light_scheme = LS_POWER;
|
|
}
|
|
}
|
|
break;
|
|
case LS_CYCLEUP:
|
|
LightCycleColor(1);
|
|
break;
|
|
case LS_CYCLEDN:
|
|
LightCycleColor(-1);
|
|
break;
|
|
case LS_RANDOM:
|
|
LightCycleColor(0);
|
|
break;
|
|
default:
|
|
XlgtCall(FUNC_SET_SCHEME);
|
|
}
|
|
}
|
|
|
|
if ((Settings.light_scheme < LS_MAX) || power_off) {
|
|
|
|
|
|
LightApplyPower(Light.new_color, Light.power);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (memcmp(Light.last_color, Light.new_color, Light.subtype)) {
|
|
Light.update = true;
|
|
}
|
|
if (Light.update) {
|
|
uint16_t cur_col_10[LST_MAX];
|
|
Light.update = false;
|
|
|
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
Light.last_color[i] = Light.new_color[i];
|
|
|
|
cur_col_10[i] = change8to10(Light.new_color[i]);
|
|
}
|
|
|
|
if (Light.pwm_multi_channels) {
|
|
calcGammaMultiChannels(cur_col_10);
|
|
} else {
|
|
calcGammaBulbs(cur_col_10);
|
|
|
|
|
|
|
|
if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col_10[3]+cur_col_10[4])) {
|
|
uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]);
|
|
for (uint32_t i=0; i<3; i++) {
|
|
|
|
uint32_t adjust10 = change8to10(Settings.rgbwwTable[i]);
|
|
cur_col_10[i] = changeUIntScale(cur_col_10[i] - min_rgb_10, 0, 1023, 0, adjust10);
|
|
}
|
|
|
|
|
|
uint32_t adjust_w_10 = changeUIntScale(Settings.rgbwwTable[3], 0, 255, 0, 1023);
|
|
uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 1023, 0, adjust_w_10);
|
|
if (LST_RGBW == Light.subtype) {
|
|
|
|
cur_col_10[3] = white_10;
|
|
} else {
|
|
|
|
uint32_t ct = light_state.getCT10bits();
|
|
cur_col_10[4] = changeUIntScale(ct, 0, 1023, 0, white_10);
|
|
cur_col_10[3] = white_10 - cur_col_10[4];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (0 != Settings.rgbwwTable[4]) {
|
|
for (uint32_t i = 0; i<Light.subtype; i++) {
|
|
uint32_t adjust = change8to10(Settings.rgbwwTable[i]);
|
|
cur_col_10[i] = changeUIntScale(cur_col_10[i], 0, 1023, 0, adjust);
|
|
}
|
|
}
|
|
|
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
|
|
cur_col_10[i] = (cur_col_10[i] > 0) ? changeUIntScale(cur_col_10[i], 1, 1023, 1, Settings.pwm_range) : 0;
|
|
}
|
|
|
|
|
|
uint16_t orig_col_10bits[LST_MAX];
|
|
memcpy(orig_col_10bits, cur_col_10, sizeof(orig_col_10bits));
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
cur_col_10[i] = orig_col_10bits[Light.color_remap[i]];
|
|
}
|
|
|
|
if (!Settings.light_fade || power_off || (!Light.fade_initialized)) {
|
|
|
|
memcpy(Light.fade_start_10, cur_col_10, sizeof(Light.fade_start_10));
|
|
|
|
LightSetOutputs(cur_col_10);
|
|
Light.fade_initialized = true;
|
|
} else {
|
|
if (Light.fade_running) {
|
|
|
|
memcpy(Light.fade_start_10, Light.fade_cur_10, sizeof(Light.fade_start_10));
|
|
}
|
|
memcpy(Light.fade_end_10, cur_col_10, sizeof(Light.fade_start_10));
|
|
Light.fade_running = true;
|
|
Light.fade_duration = 0;
|
|
Light.fade_start = 0;
|
|
|
|
}
|
|
}
|
|
if (Light.fade_running) {
|
|
if (LightApplyFade()) {
|
|
|
|
|
|
|
|
LightSetOutputs(Light.fade_cur_10);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isChannelGammaCorrected(uint32_t channel) {
|
|
if (!Settings.light_correction) { return false; }
|
|
if (channel >= Light.subtype) { return false; }
|
|
|
|
if (PHILIPS == my_module_type) {
|
|
if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return false; }
|
|
if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return false; }
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
uint16_t fadeGamma(uint32_t channel, uint16_t v) {
|
|
if (isChannelGammaCorrected(channel)) {
|
|
return ledGamma_internal(v, gamma_table_fast);
|
|
} else {
|
|
return v;
|
|
}
|
|
}
|
|
uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg) {
|
|
if (isChannelGammaCorrected(channel)) {
|
|
return ledGammaReverse_internal(vg, gamma_table_fast);
|
|
} else {
|
|
return vg;
|
|
}
|
|
}
|
|
|
|
bool LightApplyFade(void) {
|
|
static uint32_t last_millis = 0;
|
|
uint32_t now = millis();
|
|
|
|
if ((now - last_millis) <= 5) {
|
|
return false;
|
|
}
|
|
last_millis = now;
|
|
|
|
|
|
if (0 == Light.fade_duration) {
|
|
Light.fade_start = now;
|
|
|
|
uint32_t distance = 0;
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
int32_t channel_distance = fadeGammaReverse(i, Light.fade_end_10[i]) - fadeGammaReverse(i, Light.fade_start_10[i]);
|
|
if (channel_distance < 0) { channel_distance = - channel_distance; }
|
|
if (channel_distance > distance) { distance = channel_distance; }
|
|
}
|
|
if (distance > 0) {
|
|
|
|
|
|
|
|
Light.fade_duration = (distance * Settings.light_speed * 500) / 1023;
|
|
if (Settings.save_data) {
|
|
|
|
uint32_t delay_seconds = 1 + (Light.fade_duration + 999) / 1000;
|
|
|
|
if (save_data_counter < delay_seconds) {
|
|
save_data_counter = delay_seconds;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
Light.fade_running = false;
|
|
}
|
|
}
|
|
|
|
uint16_t fade_current = now - Light.fade_start;
|
|
if (fade_current <= Light.fade_duration) {
|
|
|
|
for (uint32_t i = 0; i < Light.subtype; i++) {
|
|
Light.fade_cur_10[i] = fadeGamma(i,
|
|
changeUIntScale(fadeGammaReverse(i, fade_current),
|
|
0, Light.fade_duration,
|
|
fadeGammaReverse(i, Light.fade_start_10[i]),
|
|
fadeGammaReverse(i, Light.fade_end_10[i])));
|
|
|
|
|
|
|
|
}
|
|
} else {
|
|
|
|
|
|
Light.fade_running = false;
|
|
Light.fade_start = 0;
|
|
Light.fade_duration = 0;
|
|
|
|
memcpy(Light.fade_cur_10, Light.fade_end_10, sizeof(Light.fade_end_10));
|
|
|
|
memcpy(Light.fade_start_10, Light.fade_end_10, sizeof(Light.fade_start_10));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void LightApplyPower(uint8_t new_color[LST_MAX], power_t power) {
|
|
|
|
if (Light.pwm_multi_channels) {
|
|
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
if (0 == bitRead(power,i)) {
|
|
new_color[i] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
if (!light_controller.isCTRGBLinked()) {
|
|
|
|
if (0 == (power & 1)) {
|
|
new_color[0] = new_color[1] = new_color[2] = 0;
|
|
}
|
|
if (0 == (power & 2)) {
|
|
new_color[3] = new_color[4] = 0;
|
|
}
|
|
} else if (!power) {
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
new_color[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LightSetOutputs(const uint16_t *cur_col_10) {
|
|
|
|
if (light_type < LT_PWM6) {
|
|
for (uint32_t i = 0; i < (Light.subtype - Light.pwm_offset); i++) {
|
|
if (pin[GPIO_PWM1 +i] < 99) {
|
|
|
|
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col_10[(i + Light.pwm_offset)] : cur_col_10[(i + Light.pwm_offset)]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t cur_col[LST_MAX];
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
cur_col[i] = change10to8(cur_col_10[i]);
|
|
}
|
|
|
|
|
|
uint8_t scale_col[3];
|
|
uint32_t max = (cur_col[0] > cur_col[1] && cur_col[0] > cur_col[2]) ? cur_col[0] : (cur_col[1] > cur_col[2]) ? cur_col[1] : cur_col[2];
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
scale_col[i] = (0 == max) ? 255 : (255 > max) ? changeUIntScale(cur_col[i], 0, max, 0, 255) : cur_col[i];
|
|
}
|
|
|
|
char *tmp_data = XdrvMailbox.data;
|
|
char *tmp_topic = XdrvMailbox.topic;
|
|
XdrvMailbox.data = (char*)cur_col;
|
|
XdrvMailbox.topic = (char*)scale_col;
|
|
if (XlgtCall(FUNC_SET_CHANNELS)) { }
|
|
else if (XdrvCall(FUNC_SET_CHANNELS)) { }
|
|
XdrvMailbox.data = tmp_data;
|
|
XdrvMailbox.topic = tmp_topic;
|
|
}
|
|
|
|
|
|
void calcGammaMultiChannels(uint16_t cur_col_10[5]) {
|
|
|
|
if (Settings.light_correction) {
|
|
for (uint32_t i = 0; i < LST_MAX; i++) {
|
|
cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void calcGammaBulbs(uint16_t cur_col_10[5]) {
|
|
|
|
|
|
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
|
|
uint32_t cw1 = Light.subtype - 1;
|
|
uint32_t cw0 = Light.subtype - 2;
|
|
uint16_t white_bri10 = cur_col_10[cw0] + cur_col_10[cw1];
|
|
uint16_t white_bri10_1023 = (white_bri10 > 1023) ? 1023 : white_bri10;
|
|
|
|
if (PHILIPS == my_module_type) {
|
|
|
|
cur_col_10[cw1] = light_state.getCT10bits();
|
|
|
|
if (Settings.light_correction) {
|
|
cur_col_10[cw0] = ledGamma10_10(white_bri10_1023);
|
|
} else {
|
|
cur_col_10[cw0] = white_bri10_1023;
|
|
}
|
|
} else if (Settings.light_correction) {
|
|
|
|
if (white_bri10 <= 1031) {
|
|
|
|
uint16_t white_bri_gamma10 = ledGamma10_10(white_bri10_1023);
|
|
|
|
cur_col_10[cw0] = changeUIntScale(cur_col_10[cw0], 0, white_bri10_1023, 0, white_bri_gamma10);
|
|
cur_col_10[cw1] = changeUIntScale(cur_col_10[cw1], 0, white_bri10_1023, 0, white_bri_gamma10);
|
|
} else {
|
|
cur_col_10[cw0] = ledGamma10_10(cur_col_10[cw0]);
|
|
cur_col_10[cw1] = ledGamma10_10(cur_col_10[cw1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Settings.light_correction) {
|
|
|
|
if (LST_RGB <= Light.subtype) {
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
|
|
}
|
|
}
|
|
|
|
if ((LST_SINGLE == Light.subtype) || (LST_RGBW == Light.subtype)) {
|
|
cur_col_10[Light.subtype - 1] = ledGamma10_10(cur_col_10[Light.subtype - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool LightColorEntry(char *buffer, uint32_t buffer_length)
|
|
{
|
|
char scolor[10];
|
|
char *p;
|
|
char *str;
|
|
uint32_t entry_type = 0;
|
|
uint8_t value = Light.fixed_color_index;
|
|
|
|
if (buffer[0] == '#') {
|
|
buffer++;
|
|
buffer_length--;
|
|
}
|
|
|
|
if (Light.subtype >= LST_RGB) {
|
|
char option = (1 == buffer_length) ? buffer[0] : '\0';
|
|
if (('+' == option) && (Light.fixed_color_index < MAX_FIXED_COLOR)) {
|
|
value++;
|
|
}
|
|
else if (('-' == option) && (Light.fixed_color_index > 1)) {
|
|
value--;
|
|
} else {
|
|
value = atoi(buffer);
|
|
}
|
|
}
|
|
|
|
memset(&Light.entry_color, 0x00, sizeof(Light.entry_color));
|
|
|
|
while ((buffer_length > 0) && ('=' == buffer[buffer_length - 1])) {
|
|
buffer_length--;
|
|
memcpy(&Light.entry_color, &Light.current_color, sizeof(Light.entry_color));
|
|
}
|
|
if (strstr(buffer, ",") != nullptr) {
|
|
int8_t i = 0;
|
|
for (str = strtok_r(buffer, ",", &p); str && i < 6; str = strtok_r(nullptr, ",", &p)) {
|
|
if (i < LST_MAX) {
|
|
Light.entry_color[i++] = atoi(str);
|
|
}
|
|
}
|
|
entry_type = 2;
|
|
}
|
|
else if (((2 * Light.subtype) == buffer_length) || (buffer_length > 3)) {
|
|
for (uint32_t i = 0; i < tmin((uint)(buffer_length / 2), sizeof(Light.entry_color)); i++) {
|
|
strlcpy(scolor, buffer + (i *2), 3);
|
|
Light.entry_color[i] = (uint8_t)strtol(scolor, &p, 16);
|
|
}
|
|
entry_type = 1;
|
|
}
|
|
else if ((Light.subtype >= LST_RGB) && (value > 0) && (value <= MAX_FIXED_COLOR)) {
|
|
Light.fixed_color_index = value;
|
|
memcpy_P(&Light.entry_color, &kFixedColor[value -1], 3);
|
|
entry_type = 1;
|
|
}
|
|
else if ((value > 199) && (value <= 199 + MAX_FIXED_COLD_WARM)) {
|
|
if (LST_RGBW == Light.subtype) {
|
|
memcpy_P(&Light.entry_color[3], &kFixedWhite[value -200], 1);
|
|
entry_type = 1;
|
|
}
|
|
else if (LST_COLDWARM == Light.subtype) {
|
|
memcpy_P(&Light.entry_color, &kFixedColdWarm[value -200], 2);
|
|
entry_type = 1;
|
|
}
|
|
else if (LST_RGBCW == Light.subtype) {
|
|
memcpy_P(&Light.entry_color[3], &kFixedColdWarm[value -200], 2);
|
|
entry_type = 1;
|
|
}
|
|
}
|
|
if (entry_type) {
|
|
Settings.flag.decimal_text = entry_type -1;
|
|
}
|
|
return (entry_type);
|
|
}
|
|
|
|
|
|
|
|
void CmndSupportColor(void)
|
|
{
|
|
bool valid_entry = false;
|
|
bool coldim = false;
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
valid_entry = LightColorEntry(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
if (valid_entry) {
|
|
if (XdrvMailbox.index <= 2) {
|
|
uint32_t old_bri = light_state.getBri();
|
|
|
|
light_controller.changeChannels(Light.entry_color);
|
|
if (2 == XdrvMailbox.index) {
|
|
|
|
light_controller.changeBri(old_bri);
|
|
}
|
|
|
|
Settings.light_scheme = 0;
|
|
coldim = true;
|
|
} else {
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
|
Settings.ws_color[XdrvMailbox.index -3][i] = Light.entry_color[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
if (!valid_entry && (XdrvMailbox.index <= 2)) {
|
|
ResponseCmndChar(LightGetColor(scolor));
|
|
}
|
|
if (XdrvMailbox.index >= 3) {
|
|
scolor[0] = '\0';
|
|
for (uint32_t i = 0; i < LST_RGB; i++) {
|
|
if (Settings.flag.decimal_text) {
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.ws_color[XdrvMailbox.index -3][i]);
|
|
} else {
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%02X"), scolor, Settings.ws_color[XdrvMailbox.index -3][i]);
|
|
}
|
|
}
|
|
ResponseCmndIdxChar(scolor);
|
|
}
|
|
if (coldim) {
|
|
LightPreparePower();
|
|
}
|
|
}
|
|
|
|
void CmndColor(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((Light.subtype > LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
|
|
CmndSupportColor();
|
|
}
|
|
}
|
|
|
|
void CmndWhite(void)
|
|
{
|
|
|
|
|
|
if (Light.pwm_multi_channels) { return; }
|
|
if ( ((Light.subtype >= LST_RGBW) || (LST_COLDWARM == Light.subtype)) && (XdrvMailbox.index == 1)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
light_controller.changeDimmer(XdrvMailbox.payload, 2);
|
|
LightPreparePower(2);
|
|
} else {
|
|
ResponseCmndNumber(light_state.getDimmer(2));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndChannel(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) {
|
|
uint32_t light_index = XdrvMailbox.index - Light.device;
|
|
power_t coldim = 0;
|
|
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
uint8_t channel = changeUIntScale(Light.current_color[light_index],0,255,0,100);
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (channel > 89) ? 100 : channel + 10;
|
|
} else if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (channel < 11) ? 1 : channel - 10;
|
|
}
|
|
}
|
|
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
Light.current_color[light_index] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
|
|
if (Light.pwm_multi_channels) {
|
|
coldim = 1 << light_index;
|
|
} else {
|
|
if (light_controller.isCTRGBLinked()) {
|
|
|
|
if ((light_index < 3) && (light_controller.isCTRGBLinked())) {
|
|
Light.current_color[3] = Light.current_color[4] = 0;
|
|
} else {
|
|
Light.current_color[0] = Light.current_color[1] = Light.current_color[2] = 0;
|
|
}
|
|
coldim = 1;
|
|
} else {
|
|
if (light_index < 3) { coldim = 1; }
|
|
else { coldim = 2; }
|
|
}
|
|
}
|
|
light_controller.changeChannels(Light.current_color);
|
|
}
|
|
ResponseCmndIdxNumber(changeUIntScale(Light.current_color[light_index],0,255,0,100));
|
|
if (coldim) {
|
|
LightPreparePower(coldim);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndHsbColor(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Light.subtype >= LST_RGB) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint16_t c_hue;
|
|
uint8_t c_sat;
|
|
light_state.getHSB(&c_hue, &c_sat, nullptr);
|
|
uint32_t HSB[3];
|
|
HSB[0] = c_hue;
|
|
HSB[1] = c_sat;
|
|
HSB[2] = light_state.getBriRGB();
|
|
if ((2 == XdrvMailbox.index) || (3 == XdrvMailbox.index)) {
|
|
if ((uint32_t)XdrvMailbox.payload > 100) { XdrvMailbox.payload = 100; }
|
|
HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 255);
|
|
} else {
|
|
uint32_t paramcount = ParseParameters(3, HSB);
|
|
if (HSB[0] > 360) { HSB[0] = 360; }
|
|
for (uint32_t i = 1; i < paramcount; i++) {
|
|
if (HSB[i] > 100) { HSB[i] == 100; }
|
|
HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255);
|
|
}
|
|
}
|
|
light_controller.changeHSB(HSB[0], HSB[1], HSB[2]);
|
|
LightPreparePower(1);
|
|
} else {
|
|
LightState(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndScheme(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
if (Light.subtype >= LST_RGB) {
|
|
uint32_t max_scheme = Light.max_scheme;
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if (('+' == XdrvMailbox.data[0]) && (Settings.light_scheme < max_scheme)) {
|
|
XdrvMailbox.payload = Settings.light_scheme + ((0 == Settings.light_scheme) ? 2 : 1);
|
|
}
|
|
else if (('-' == XdrvMailbox.data[0]) && (Settings.light_scheme > 0)) {
|
|
XdrvMailbox.payload = Settings.light_scheme - ((2 == Settings.light_scheme) ? 2 : 1);
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) {
|
|
uint32_t parm[2];
|
|
if (ParseParameters(2, parm) > 1) {
|
|
Light.wheel = parm[1];
|
|
}
|
|
Settings.light_scheme = XdrvMailbox.payload;
|
|
if (LS_WAKEUP == Settings.light_scheme) {
|
|
Light.wakeup_active = 3;
|
|
}
|
|
LightPowerOn();
|
|
Light.strip_timer_counter = 0;
|
|
|
|
if (Settings.flag3.hass_tele_on_power) {
|
|
MqttPublishTeleState();
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.light_scheme);
|
|
}
|
|
}
|
|
|
|
void CmndWakeup(void)
|
|
{
|
|
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
light_controller.changeDimmer(XdrvMailbox.payload);
|
|
}
|
|
Light.wakeup_active = 3;
|
|
Settings.light_scheme = LS_WAKEUP;
|
|
LightPowerOn();
|
|
ResponseCmndChar(D_JSON_STARTED);
|
|
}
|
|
|
|
void CmndColorTemperature(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if (Light.pwm_multi_channels) { return; }
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
|
|
uint32_t ct = light_state.getCT();
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (ct > (CT_MAX-34)) ? CT_MAX : ct + 34;
|
|
}
|
|
else if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (ct < (CT_MIN+34)) ? CT_MIN : ct - 34;
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload >= CT_MIN) && (XdrvMailbox.payload <= CT_MAX)) {
|
|
light_controller.changeCTB(XdrvMailbox.payload, light_state.getBriCT());
|
|
LightPreparePower(2);
|
|
} else {
|
|
ResponseCmndNumber(ct);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndDimmer(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t dimmer;
|
|
if (XdrvMailbox.index > 2) { XdrvMailbox.index = 1; }
|
|
|
|
if ((light_controller.isCTRGBLinked()) || (0 == XdrvMailbox.index)) {
|
|
dimmer = light_state.getDimmer();
|
|
} else {
|
|
dimmer = light_state.getDimmer(XdrvMailbox.index);
|
|
}
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (dimmer > 89) ? 100 : dimmer + 10;
|
|
} else if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (dimmer < 11) ? 1 : dimmer - 10;
|
|
}
|
|
}
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
if (light_controller.isCTRGBLinked()) {
|
|
|
|
light_controller.changeDimmer(XdrvMailbox.payload);
|
|
LightPreparePower();
|
|
} else {
|
|
if (0 != XdrvMailbox.index) {
|
|
light_controller.changeDimmer(XdrvMailbox.payload, XdrvMailbox.index);
|
|
LightPreparePower(1 << (XdrvMailbox.index - 1));
|
|
} else {
|
|
|
|
light_controller.changeDimmer(XdrvMailbox.payload, 1);
|
|
light_controller.changeDimmer(XdrvMailbox.payload, 2);
|
|
LightPreparePower();
|
|
}
|
|
}
|
|
Light.update = true;
|
|
} else {
|
|
ResponseCmndNumber(dimmer);
|
|
}
|
|
}
|
|
|
|
void CmndDimmerRange(void)
|
|
{
|
|
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint32_t parm[2];
|
|
parm[0] = Settings.dimmer_hw_min;
|
|
parm[1] = Settings.dimmer_hw_max;
|
|
ParseParameters(2, parm);
|
|
if (parm[0] < parm[1]) {
|
|
Settings.dimmer_hw_min = parm[0];
|
|
Settings.dimmer_hw_max = parm[1];
|
|
} else {
|
|
Settings.dimmer_hw_min = parm[1];
|
|
Settings.dimmer_hw_max = parm[0];
|
|
}
|
|
restart_flag = 2;
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_DIMMER_RANGE "\":{\"Min\":%d,\"Max\":%d}}"), Settings.dimmer_hw_min, Settings.dimmer_hw_max);
|
|
}
|
|
|
|
void CmndLedTable(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
case 1:
|
|
Settings.light_correction = XdrvMailbox.payload;
|
|
break;
|
|
case 2:
|
|
Settings.light_correction ^= 1;
|
|
break;
|
|
}
|
|
Light.update = true;
|
|
}
|
|
ResponseCmndStateText(Settings.light_correction);
|
|
}
|
|
|
|
void CmndRgbwwTable(void)
|
|
{
|
|
|
|
|
|
if ((XdrvMailbox.data_len > 0)) {
|
|
uint32_t parm[LST_RGBCW -1];
|
|
uint32_t parmcount = ParseParameters(LST_RGBCW, parm);
|
|
for (uint32_t i = 0; i < parmcount; i++) {
|
|
Settings.rgbwwTable[i] = parm[i];
|
|
}
|
|
Light.update = true;
|
|
}
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
scolor[0] = '\0';
|
|
for (uint32_t i = 0; i < LST_RGBCW; i++) {
|
|
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
|
|
}
|
|
ResponseCmndChar(scolor);
|
|
}
|
|
|
|
void CmndFade(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
case 1:
|
|
Settings.light_fade = XdrvMailbox.payload;
|
|
break;
|
|
case 2:
|
|
Settings.light_fade ^= 1;
|
|
break;
|
|
}
|
|
if (!Settings.light_fade) { Light.fade_running = false; }
|
|
ResponseCmndStateText(Settings.light_fade);
|
|
}
|
|
|
|
void CmndSpeed(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if (1 == XdrvMailbox.data_len) {
|
|
if (('+' == XdrvMailbox.data[0]) && (Settings.light_speed > 1)) {
|
|
XdrvMailbox.payload = Settings.light_speed - 1;
|
|
}
|
|
else if (('-' == XdrvMailbox.data[0]) && (Settings.light_speed < 40)) {
|
|
XdrvMailbox.payload = Settings.light_speed + 1;
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
|
|
Settings.light_speed = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.light_speed);
|
|
}
|
|
|
|
void CmndWakeupDuration(void)
|
|
{
|
|
|
|
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) {
|
|
Settings.light_wakeup = XdrvMailbox.payload;
|
|
Light.wakeup_active = 0;
|
|
}
|
|
ResponseCmndNumber(Settings.light_wakeup);
|
|
}
|
|
|
|
void CmndUndocA(void)
|
|
{
|
|
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
LightGetColor(scolor, true);
|
|
scolor[6] = '\0';
|
|
Response_P(PSTR("%s,%d,%d,%d,%d,%d"), scolor, Settings.light_fade, Settings.light_correction, Settings.light_scheme, Settings.light_speed, Settings.light_width);
|
|
MqttPublishPrefixTopic_P(STAT, XdrvMailbox.topic);
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv04(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_MODULE_INIT == function) {
|
|
return LightModuleInit();
|
|
}
|
|
else if (light_type) {
|
|
switch (function) {
|
|
case FUNC_SERIAL:
|
|
result = XlgtCall(FUNC_SERIAL);
|
|
break;
|
|
case FUNC_LOOP:
|
|
if (Light.fade_running) {
|
|
if (LightApplyFade()) {
|
|
LightSetOutputs(Light.fade_cur_10);
|
|
}
|
|
}
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
LightAnimate();
|
|
break;
|
|
case FUNC_SET_POWER:
|
|
LightSetPower();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kLightCommands, LightCommand);
|
|
if (!result) {
|
|
result = XlgtCall(FUNC_COMMAND);
|
|
}
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
LightInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote.ino"
|
|
#if defined(USE_IR_REMOTE) && !defined(USE_IR_REMOTE_FULL)
|
|
|
|
|
|
|
|
|
|
#define XDRV_05 5
|
|
|
|
#include <IRremoteESP8266.h>
|
|
|
|
enum IrErrors { IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND };
|
|
|
|
const char kIrRemoteCommands[] PROGMEM = "|" D_CMND_IRSEND ;
|
|
|
|
|
|
void (* const IrRemoteCommand[])(void) PROGMEM = {
|
|
&CmndIrSend };
|
|
|
|
|
|
static const uint8_t MAX_STANDARD_IR = NEC;
|
|
const char kIrRemoteProtocols[] PROGMEM = "UNKNOWN|RC5|RC6|NEC";
|
|
|
|
|
|
|
|
|
|
|
|
#include <IRsend.h>
|
|
|
|
IRsend *irsend = nullptr;
|
|
bool irsend_active = false;
|
|
|
|
void IrSendInit(void)
|
|
{
|
|
irsend = new IRsend(pin[GPIO_IRSEND]);
|
|
irsend->begin();
|
|
}
|
|
|
|
#ifdef USE_IR_RECEIVE
|
|
|
|
|
|
|
|
|
|
const bool IR_RCV_SAVE_BUFFER = false;
|
|
const uint32_t IR_TIME_AVOID_DUPLICATE = 500;
|
|
|
|
#include <IRrecv.h>
|
|
|
|
IRrecv *irrecv = nullptr;
|
|
|
|
unsigned long ir_lasttime = 0;
|
|
|
|
void IrReceiveUpdateThreshold(void)
|
|
{
|
|
if (irrecv != nullptr) {
|
|
if (Settings.param[P_IR_UNKNOW_THRESHOLD] < 6) { Settings.param[P_IR_UNKNOW_THRESHOLD] = 6; }
|
|
irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]);
|
|
}
|
|
}
|
|
|
|
void IrReceiveInit(void)
|
|
{
|
|
|
|
irrecv = new IRrecv(pin[GPIO_IRRECV], IR_RCV_BUFFER_SIZE, IR_RCV_TIMEOUT, IR_RCV_SAVE_BUFFER);
|
|
irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]);
|
|
irrecv->enableIRIn();
|
|
|
|
|
|
}
|
|
|
|
void IrReceiveCheck(void)
|
|
{
|
|
char sirtype[8];
|
|
int8_t iridx = 0;
|
|
|
|
decode_results results;
|
|
|
|
if (irrecv->decode(&results)) {
|
|
char hvalue[65];
|
|
|
|
iridx = results.decode_type;
|
|
if ((iridx < 0) || (iridx > MAX_STANDARD_IR)) { iridx = 0; }
|
|
|
|
if (iridx) {
|
|
if (results.bits > 64) {
|
|
|
|
uint32_t digits2 = results.bits / 8;
|
|
if (results.bits % 8) { digits2++; }
|
|
ToHex_P((unsigned char*)results.state, digits2, hvalue, sizeof(hvalue));
|
|
} else {
|
|
Uint64toHex(results.value, hvalue, results.bits);
|
|
}
|
|
} else {
|
|
Uint64toHex(results.value, hvalue, 32);
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_IRR "Echo %d, RawLen %d, Overflow %d, Bits %d, Value 0x%s, Decode %d"),
|
|
irsend_active, results.rawlen, results.overflow, results.bits, hvalue, results.decode_type);
|
|
|
|
unsigned long now = millis();
|
|
|
|
if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) {
|
|
ir_lasttime = now;
|
|
|
|
char svalue[64];
|
|
if (Settings.flag.ir_receive_decimal) {
|
|
ulltoa(results.value, svalue, 10);
|
|
} else {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("\"0x%s\""), hvalue);
|
|
}
|
|
ResponseTime_P(PSTR(",\"" D_JSON_IRRECEIVED "\":{\"" D_JSON_IR_PROTOCOL "\":\"%s\",\"" D_JSON_IR_BITS "\":%d"),
|
|
GetTextIndexed(sirtype, sizeof(sirtype), iridx, kIrRemoteProtocols), results.bits);
|
|
if (iridx) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_IR_DATA "\":%s"), svalue);
|
|
} else {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_IR_HASH "\":%s"), svalue);
|
|
}
|
|
|
|
if (Settings.flag3.receive_raw) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_IR_RAWDATA "\":["));
|
|
uint16_t i;
|
|
for (i = 1; i < results.rawlen; i++) {
|
|
if (i > 1) { ResponseAppend_P(PSTR(",")); }
|
|
uint32_t usecs;
|
|
for (usecs = results.rawbuf[i] * kRawTick; usecs > UINT16_MAX; usecs -= UINT16_MAX) {
|
|
ResponseAppend_P(PSTR("%d,0,"), UINT16_MAX);
|
|
}
|
|
ResponseAppend_P(PSTR("%d"), usecs);
|
|
if (strlen(mqtt_data) > sizeof(mqtt_data) - 40) { break; }
|
|
}
|
|
uint16_t extended_length = results.rawlen - 1;
|
|
for (uint32_t j = 0; j < results.rawlen - 1; j++) {
|
|
uint32_t usecs = results.rawbuf[j] * kRawTick;
|
|
|
|
extended_length += (usecs / (UINT16_MAX + 1)) * 2;
|
|
}
|
|
ResponseAppend_P(PSTR("],\"" D_JSON_IR_RAWDATA "Info\":[%d,%d,%d]"), extended_length, i -1, results.overflow);
|
|
}
|
|
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_IRRECEIVED));
|
|
|
|
XdrvRulesProcess();
|
|
#ifdef USE_DOMOTICZ
|
|
if (iridx) {
|
|
unsigned long value = results.value | (iridx << 28);
|
|
DomoticzSensor(DZ_COUNT, value);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
irrecv->resume();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t IrRemoteCmndIrSendJson(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
char dataBufUc[XdrvMailbox.data_len + 1];
|
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
|
RemoveSpace(dataBufUc);
|
|
if (strlen(dataBufUc) < 8) {
|
|
return IE_INVALID_JSON;
|
|
}
|
|
|
|
StaticJsonBuffer<140> jsonBuf;
|
|
JsonObject &root = jsonBuf.parseObject(dataBufUc);
|
|
if (!root.success()) {
|
|
return IE_INVALID_JSON;
|
|
}
|
|
|
|
|
|
|
|
char parm_uc[10];
|
|
const char *protocol = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_PROTOCOL))];
|
|
uint16_t bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS))];
|
|
uint64_t data = strtoull(root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA))], nullptr, 0);
|
|
uint16_t repeat = root[UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT))];
|
|
|
|
if (XdrvMailbox.index > repeat + 1) {
|
|
repeat = XdrvMailbox.index - 1;
|
|
}
|
|
if (!(protocol && bits)) {
|
|
return IE_SYNTAX_IRSEND;
|
|
}
|
|
|
|
char protocol_text[20];
|
|
int protocol_code = GetCommandCode(protocol_text, sizeof(protocol_text), protocol, kIrRemoteProtocols);
|
|
|
|
char dvalue[64];
|
|
char hvalue[20];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol_text %s, protocol %s, bits %d, data %s (0x%s), repeat %d, protocol_code %d"),
|
|
protocol_text, protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat, protocol_code);
|
|
|
|
irsend_active = true;
|
|
switch (protocol_code) {
|
|
#ifdef USE_IR_SEND_RC5
|
|
case RC5:
|
|
irsend->sendRC5(data, bits, repeat); break;
|
|
#endif
|
|
#ifdef USE_IR_SEND_RC6
|
|
case RC6:
|
|
irsend->sendRC6(data, bits, repeat); break;
|
|
#endif
|
|
#ifdef USE_IR_SEND_NEC
|
|
case NEC:
|
|
irsend->sendNEC(data, (bits > NEC_BITS) ? NEC_BITS : bits, repeat); break;
|
|
#endif
|
|
default:
|
|
irsend_active = false;
|
|
ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED);
|
|
}
|
|
|
|
return IE_NO_ERROR;
|
|
}
|
|
|
|
void CmndIrSend(void)
|
|
{
|
|
uint8_t error = IE_SYNTAX_IRSEND;
|
|
|
|
if (XdrvMailbox.data_len) {
|
|
|
|
if (strstr(XdrvMailbox.data, "{") == nullptr) {
|
|
error = IE_INVALID_JSON;
|
|
} else {
|
|
error = IrRemoteCmndIrSendJson();
|
|
}
|
|
}
|
|
IrRemoteCmndResponse(error);
|
|
}
|
|
|
|
void IrRemoteCmndResponse(uint32_t error)
|
|
{
|
|
switch (error) {
|
|
case IE_INVALID_RAWDATA:
|
|
ResponseCmndChar(D_JSON_INVALID_RAWDATA);
|
|
break;
|
|
case IE_INVALID_JSON:
|
|
ResponseCmndChar(D_JSON_INVALID_JSON);
|
|
break;
|
|
case IE_SYNTAX_IRSEND:
|
|
Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_PROTOCOL ", " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}"));
|
|
break;
|
|
default:
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv05(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if ((pin[GPIO_IRSEND] < 99) || (pin[GPIO_IRRECV] < 99)) {
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
if (pin[GPIO_IRSEND] < 99) {
|
|
IrSendInit();
|
|
}
|
|
#ifdef USE_IR_RECEIVE
|
|
if (pin[GPIO_IRRECV] < 99) {
|
|
IrReceiveInit();
|
|
}
|
|
#endif
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
#ifdef USE_IR_RECEIVE
|
|
if (pin[GPIO_IRRECV] < 99) {
|
|
IrReceiveCheck();
|
|
}
|
|
#endif
|
|
irsend_active = false;
|
|
break;
|
|
case FUNC_COMMAND:
|
|
if (pin[GPIO_IRSEND] < 99) {
|
|
result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote_full.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_05_irremote_full.ino"
|
|
#ifdef USE_IR_REMOTE_FULL
|
|
|
|
|
|
|
|
|
|
#define XDRV_05 5
|
|
|
|
#include <IRremoteESP8266.h>
|
|
#include <IRsend.h>
|
|
#include <IRrecv.h>
|
|
#include <IRutils.h>
|
|
#include <IRac.h>
|
|
|
|
enum IrErrors { IE_RESPONSE_PROVIDED, IE_NO_ERROR, IE_INVALID_RAWDATA, IE_INVALID_JSON, IE_SYNTAX_IRSEND, IE_SYNTAX_IRHVAC,
|
|
IE_UNSUPPORTED_HVAC, IE_UNSUPPORTED_PROTOCOL };
|
|
|
|
const char kIrRemoteCommands[] PROGMEM = "|"
|
|
D_CMND_IRHVAC "|" D_CMND_IRSEND ;
|
|
|
|
void (* const IrRemoteCommand[])(void) PROGMEM = {
|
|
&CmndIrHvac, &CmndIrSend };
|
|
|
|
|
|
|
|
|
|
|
|
IRsend *irsend = nullptr;
|
|
bool irsend_active = false;
|
|
|
|
void IrSendInit(void)
|
|
{
|
|
irsend = new IRsend(pin[GPIO_IRSEND]);
|
|
irsend->begin();
|
|
}
|
|
|
|
|
|
|
|
uint8_t reverseBitsInByte(uint8_t b) {
|
|
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
|
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
|
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
|
return b;
|
|
}
|
|
|
|
|
|
uint64_t reverseBitsInBytes64(uint64_t b) {
|
|
union {
|
|
uint8_t b[8];
|
|
uint64_t i;
|
|
} a;
|
|
a.i = b;
|
|
for (uint32_t i=0; i<8; i++) {
|
|
a.b[i] = reverseBitsInByte(a.b[i]);
|
|
}
|
|
return a.i;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bool IR_FULL_RCV_SAVE_BUFFER = false;
|
|
const uint32_t IR_TIME_AVOID_DUPLICATE = 500;
|
|
|
|
|
|
|
|
|
|
const uint16_t IR_FULL_BUFFER_SIZE = 1024;
|
|
|
|
|
|
|
|
const uint8_t IR__FULL_RCV_TIMEOUT = 50;
|
|
|
|
IRrecv *irrecv = nullptr;
|
|
|
|
unsigned long ir_lasttime = 0;
|
|
|
|
void IrReceiveUpdateThreshold(void)
|
|
{
|
|
if (irrecv != nullptr) {
|
|
if (Settings.param[P_IR_UNKNOW_THRESHOLD] < 6) { Settings.param[P_IR_UNKNOW_THRESHOLD] = 6; }
|
|
irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]);
|
|
}
|
|
}
|
|
|
|
void IrReceiveInit(void)
|
|
{
|
|
|
|
irrecv = new IRrecv(pin[GPIO_IRRECV], IR_FULL_BUFFER_SIZE, IR__FULL_RCV_TIMEOUT, IR_FULL_RCV_SAVE_BUFFER);
|
|
irrecv->setUnknownThreshold(Settings.param[P_IR_UNKNOW_THRESHOLD]);
|
|
irrecv->enableIRIn();
|
|
}
|
|
|
|
String sendACJsonState(const stdAc::state_t &state) {
|
|
DynamicJsonBuffer jsonBuffer;
|
|
JsonObject& json = jsonBuffer.createObject();
|
|
json[D_JSON_IRHVAC_VENDOR] = typeToString(state.protocol);
|
|
json[D_JSON_IRHVAC_MODEL] = state.model;
|
|
json[D_JSON_IRHVAC_POWER] = IRac::boolToString(state.power);
|
|
json[D_JSON_IRHVAC_MODE] = IRac::opmodeToString(state.mode);
|
|
|
|
if (state.mode == stdAc::opmode_t::kOff || !state.power) {
|
|
json[D_JSON_IRHVAC_MODE] = IRac::opmodeToString(stdAc::opmode_t::kOff);
|
|
json[D_JSON_IRHVAC_POWER] = IRac::boolToString(false);
|
|
}
|
|
json[D_JSON_IRHVAC_CELSIUS] = IRac::boolToString(state.celsius);
|
|
if (floorf(state.degrees) == state.degrees) {
|
|
json[D_JSON_IRHVAC_TEMP] = floorf(state.degrees);
|
|
} else {
|
|
json[D_JSON_IRHVAC_TEMP] = RawJson(String(state.degrees, 1));
|
|
}
|
|
json[D_JSON_IRHVAC_FANSPEED] = IRac::fanspeedToString(state.fanspeed);
|
|
json[D_JSON_IRHVAC_SWINGV] = IRac::swingvToString(state.swingv);
|
|
json[D_JSON_IRHVAC_SWINGH] = IRac::swinghToString(state.swingh);
|
|
json[D_JSON_IRHVAC_QUIET] = IRac::boolToString(state.quiet);
|
|
json[D_JSON_IRHVAC_TURBO] = IRac::boolToString(state.turbo);
|
|
json[D_JSON_IRHVAC_ECONO] = IRac::boolToString(state.econo);
|
|
json[D_JSON_IRHVAC_LIGHT] = IRac::boolToString(state.light);
|
|
json[D_JSON_IRHVAC_FILTER] = IRac::boolToString(state.filter);
|
|
json[D_JSON_IRHVAC_CLEAN] = IRac::boolToString(state.clean);
|
|
json[D_JSON_IRHVAC_BEEP] = IRac::boolToString(state.beep);
|
|
json[D_JSON_IRHVAC_SLEEP] = state.sleep;
|
|
|
|
String payload = "";
|
|
payload.reserve(200);
|
|
json.printTo(payload);
|
|
return payload;
|
|
}
|
|
|
|
String sendIRJsonState(const struct decode_results &results) {
|
|
String json("{");
|
|
json += "\"" D_JSON_IR_PROTOCOL "\":\"";
|
|
json += typeToString(results.decode_type);
|
|
json += "\",\"" D_JSON_IR_BITS "\":";
|
|
json += results.bits;
|
|
|
|
if (hasACState(results.decode_type)) {
|
|
json += ",\"" D_JSON_IR_DATA "\":\"0x";
|
|
json += resultToHexidecimal(&results);
|
|
json += "\"";
|
|
} else {
|
|
if (UNKNOWN != results.decode_type) {
|
|
json += ",\"" D_JSON_IR_DATA "\":";
|
|
} else {
|
|
json += ",\"" D_JSON_IR_HASH "\":";
|
|
}
|
|
if (Settings.flag.ir_receive_decimal) {
|
|
char svalue[32];
|
|
ulltoa(results.value, svalue, 10);
|
|
json += svalue;
|
|
} else {
|
|
char hvalue[64];
|
|
if (UNKNOWN != results.decode_type) {
|
|
Uint64toHex(results.value, hvalue, results.bits);
|
|
json += "\"0x";
|
|
json += hvalue;
|
|
json += "\",\"" D_JSON_IR_DATALSB "\":\"0x";
|
|
Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits);
|
|
json += hvalue;
|
|
json += "\"";
|
|
} else {
|
|
Uint64toHex(results.value, hvalue, 32);
|
|
json += "\"0x";
|
|
json += hvalue;
|
|
json += "\"";
|
|
}
|
|
}
|
|
}
|
|
json += ",\"" D_JSON_IR_REPEAT "\":";
|
|
json += results.repeat;
|
|
|
|
stdAc::state_t ac_result;
|
|
if (IRAcUtils::decodeToState(&results, &ac_result, nullptr)) {
|
|
|
|
json += ",\"" D_CMND_IRHVAC "\":";
|
|
json += sendACJsonState(ac_result);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
void IrReceiveCheck(void)
|
|
{
|
|
decode_results results;
|
|
|
|
if (irrecv->decode(&results)) {
|
|
uint32_t now = millis();
|
|
|
|
|
|
if (!irsend_active && (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE)) {
|
|
ir_lasttime = now;
|
|
Response_P(PSTR("{\"" D_JSON_IRRECEIVED "\":%s"), sendIRJsonState(results).c_str());
|
|
|
|
if (Settings.flag3.receive_raw) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_IR_RAWDATA "\":["));
|
|
uint16_t i;
|
|
for (i = 1; i < results.rawlen; i++) {
|
|
if (i > 1) { ResponseAppend_P(PSTR(",")); }
|
|
uint32_t usecs;
|
|
for (usecs = results.rawbuf[i] * kRawTick; usecs > UINT16_MAX; usecs -= UINT16_MAX) {
|
|
ResponseAppend_P(PSTR("%d,0,"), UINT16_MAX);
|
|
}
|
|
ResponseAppend_P(PSTR("%d"), usecs);
|
|
if (strlen(mqtt_data) > sizeof(mqtt_data) - 40) { break; }
|
|
}
|
|
uint16_t extended_length = results.rawlen - 1;
|
|
for (uint32_t j = 0; j < results.rawlen - 1; j++) {
|
|
uint32_t usecs = results.rawbuf[j] * kRawTick;
|
|
|
|
extended_length += (usecs / (UINT16_MAX + 1)) * 2;
|
|
}
|
|
ResponseAppend_P(PSTR("],\"" D_JSON_IR_RAWDATA "Info\":[%d,%d,%d]"), extended_length, i -1, results.overflow);
|
|
}
|
|
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_IRRECEIVED));
|
|
|
|
XdrvRulesProcess();
|
|
}
|
|
|
|
irrecv->resume();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String listSupportedProtocols(bool hvac) {
|
|
String l("");
|
|
bool first = true;
|
|
for (uint32_t i = UNUSED + 1; i <= kLastDecodeType; i++) {
|
|
bool found = false;
|
|
if (hvac) {
|
|
found = IRac::isProtocolSupported((decode_type_t)i);
|
|
} else {
|
|
found = (IRsend::defaultBits((decode_type_t)i) > 0) && (!IRac::isProtocolSupported((decode_type_t)i));
|
|
}
|
|
if (found) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
l += "|";
|
|
}
|
|
l += typeToString((decode_type_t)i);
|
|
}
|
|
}
|
|
return l;
|
|
}
|
|
|
|
|
|
const stdAc::fanspeed_t IrHvacFanSpeed[] PROGMEM = { stdAc::fanspeed_t::kAuto,
|
|
stdAc::fanspeed_t::kMin, stdAc::fanspeed_t::kLow,stdAc::fanspeed_t::kMedium,
|
|
stdAc::fanspeed_t::kHigh, stdAc::fanspeed_t::kMax };
|
|
|
|
uint32_t IrRemoteCmndIrHvacJson(void)
|
|
{
|
|
stdAc::state_t state, prev;
|
|
char parm_uc[12];
|
|
|
|
|
|
char dataBufUc[XdrvMailbox.data_len + 1];
|
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
|
RemoveSpace(dataBufUc);
|
|
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
|
|
|
|
DynamicJsonBuffer jsonBuf;
|
|
JsonObject &json = jsonBuf.parseObject(dataBufUc);
|
|
if (!json.success()) { return IE_INVALID_JSON; }
|
|
|
|
|
|
state.protocol = decode_type_t::UNKNOWN;
|
|
state.model = 1;
|
|
state.mode = stdAc::opmode_t::kAuto;
|
|
state.power = false;
|
|
state.celsius = true;
|
|
state.degrees = 21.0f;
|
|
state.fanspeed = stdAc::fanspeed_t::kMedium;
|
|
state.swingv = stdAc::swingv_t::kOff;
|
|
state.swingh = stdAc::swingh_t::kOff;
|
|
state.light = false;
|
|
state.beep = false;
|
|
state.econo = false;
|
|
state.filter = false;
|
|
state.turbo = false;
|
|
state.quiet = false;
|
|
state.sleep = -1;
|
|
state.clean = false;
|
|
state.clock = -1;
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
|
|
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL));
|
|
if (json.containsKey(parm_uc)) { state.protocol = strToDecodeType(json[parm_uc]); }
|
|
if (decode_type_t::UNKNOWN == state.protocol) { return IE_UNSUPPORTED_HVAC; }
|
|
if (!IRac::isProtocolSupported(state.protocol)) { return IE_UNSUPPORTED_HVAC; }
|
|
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FANSPEED));
|
|
if (json.containsKey(parm_uc)) {
|
|
uint32_t fan_speed = json[parm_uc];
|
|
if ((fan_speed >= 1) && (fan_speed <= 5)) {
|
|
state.fanspeed = (stdAc::fanspeed_t) pgm_read_byte(&IrHvacFanSpeed[fan_speed]);
|
|
} else {
|
|
state.fanspeed = IRac::strToFanspeed(json[parm_uc]);
|
|
}
|
|
}
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODEL));
|
|
if (json.containsKey(parm_uc)) { state.model = IRac::strToModel(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_MODE));
|
|
if (json.containsKey(parm_uc)) { state.mode = IRac::strToOpmode(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGV));
|
|
if (json.containsKey(parm_uc)) { state.swingv = IRac::strToSwingV(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SWINGH));
|
|
if (json.containsKey(parm_uc)) { state.swingh = IRac::strToSwingH(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TEMP));
|
|
if (json.containsKey(parm_uc)) { state.degrees = json[parm_uc]; }
|
|
|
|
|
|
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_POWER));
|
|
if (json.containsKey(parm_uc)) { state.power = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CELSIUS));
|
|
if (json.containsKey(parm_uc)) { state.celsius = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_LIGHT));
|
|
if (json.containsKey(parm_uc)) { state.light = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_BEEP));
|
|
if (json.containsKey(parm_uc)) { state.beep = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_ECONO));
|
|
if (json.containsKey(parm_uc)) { state.econo = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_FILTER));
|
|
if (json.containsKey(parm_uc)) { state.filter = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_TURBO));
|
|
if (json.containsKey(parm_uc)) { state.turbo = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_QUIET));
|
|
if (json.containsKey(parm_uc)) { state.quiet = IRac::strToBool(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_CLEAN));
|
|
if (json.containsKey(parm_uc)) { state.clean = IRac::strToBool(json[parm_uc]); }
|
|
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_SLEEP));
|
|
if (json[parm_uc]) { state.sleep = json[parm_uc]; }
|
|
|
|
|
|
IRac ac(pin[GPIO_IRSEND]);
|
|
bool success = ac.sendAc(state, &prev);
|
|
if (!success) { return IE_SYNTAX_IRHVAC; }
|
|
|
|
Response_P(PSTR("{\"" D_CMND_IRHVAC "\":%s}"), sendACJsonState(state).c_str());
|
|
return IE_RESPONSE_PROVIDED;
|
|
}
|
|
|
|
void CmndIrHvac(void)
|
|
{
|
|
uint8_t error = IE_SYNTAX_IRHVAC;
|
|
|
|
if (XdrvMailbox.data_len) {
|
|
error = IrRemoteCmndIrHvacJson();
|
|
}
|
|
if (error != IE_RESPONSE_PROVIDED) { IrRemoteCmndResponse(error); }
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t IrRemoteCmndIrSendJson(void)
|
|
{
|
|
char parm_uc[12];
|
|
|
|
|
|
|
|
char dataBufUc[XdrvMailbox.data_len + 1];
|
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
|
RemoveSpace(dataBufUc);
|
|
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
|
|
|
|
DynamicJsonBuffer jsonBuf;
|
|
JsonObject &json = jsonBuf.parseObject(dataBufUc);
|
|
if (!json.success()) { return IE_INVALID_JSON; }
|
|
|
|
|
|
|
|
decode_type_t protocol = decode_type_t::UNKNOWN;
|
|
uint16_t bits = 0;
|
|
uint64_t data;
|
|
uint8_t repeat = 0;
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_VENDOR));
|
|
if (json.containsKey(parm_uc)) { protocol = strToDecodeType(json[parm_uc]); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IRHVAC_PROTOCOL));
|
|
if (json.containsKey(parm_uc)) { protocol = strToDecodeType(json[parm_uc]); }
|
|
if (decode_type_t::UNKNOWN == protocol) { return IE_UNSUPPORTED_PROTOCOL; }
|
|
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IR_BITS));
|
|
if (json.containsKey(parm_uc)) { bits = json[parm_uc]; }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IR_REPEAT));
|
|
if (json.containsKey(parm_uc)) { repeat = json[parm_uc]; }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATALSB));
|
|
if (json.containsKey(parm_uc)) { data = reverseBitsInBytes64(strtoull(json[parm_uc], nullptr, 0)); }
|
|
UpperCase_P(parm_uc, PSTR(D_JSON_IR_DATA));
|
|
if (json.containsKey(parm_uc)) { data = strtoull(json[parm_uc], nullptr, 0); }
|
|
if (0 == bits) { return IE_SYNTAX_IRSEND; }
|
|
|
|
|
|
if (XdrvMailbox.index > repeat + 1) { repeat = XdrvMailbox.index - 1; }
|
|
|
|
char dvalue[32];
|
|
char hvalue[32];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRS: protocol %d, bits %d, data 0x%s (%s), repeat %d"),
|
|
protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat);
|
|
|
|
irsend_active = true;
|
|
bool success = irsend->send(protocol, data, bits, repeat);
|
|
|
|
if (!success) {
|
|
irsend_active = false;
|
|
ResponseCmndChar(D_JSON_PROTOCOL_NOT_SUPPORTED);
|
|
}
|
|
return IE_NO_ERROR;
|
|
}
|
|
|
|
uint32_t IrRemoteCmndIrSendRaw(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char *p;
|
|
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
|
|
if (p == nullptr) {
|
|
return IE_INVALID_RAWDATA;
|
|
}
|
|
|
|
|
|
uint16_t repeat = XdrvMailbox.index > 0 ? XdrvMailbox.index - 1 : 0;
|
|
|
|
uint16_t freq = atoi(str);
|
|
if (!freq && (*str != '0')) {
|
|
uint16_t count = 0;
|
|
char *q = p;
|
|
for (; *q; count += (*q++ == ','));
|
|
if (count < 2) {
|
|
return IE_INVALID_RAWDATA;
|
|
}
|
|
|
|
uint16_t parm[count];
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
parm[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0);
|
|
if (!parm[i]) {
|
|
if (!i) {
|
|
parm[0] = 38000;
|
|
} else {
|
|
return IE_INVALID_RAWDATA;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16_t i = 0;
|
|
if (count < 4) {
|
|
|
|
uint16_t mark = parm[1] *2;
|
|
if (3 == count) {
|
|
if (parm[2] < parm[1]) {
|
|
|
|
mark = parm[1] * parm[2];
|
|
} else {
|
|
|
|
mark = parm[2];
|
|
}
|
|
}
|
|
uint16_t raw_array[strlen(p)];
|
|
for (; *p; *p++) {
|
|
if (*p == '0') {
|
|
raw_array[i++] = parm[1];
|
|
}
|
|
else if (*p == '1') {
|
|
raw_array[i++] = mark;
|
|
}
|
|
}
|
|
irsend_active = true;
|
|
for (uint32_t r = 0; r <= repeat; r++) {
|
|
irsend->sendRaw(raw_array, i, parm[0]);
|
|
if (r < repeat) {
|
|
irsend->space(40000);
|
|
}
|
|
}
|
|
}
|
|
else if (6 == count) {
|
|
|
|
uint16_t raw_array[strlen(p)*2+3];
|
|
raw_array[i++] = parm[1];
|
|
raw_array[i++] = parm[2];
|
|
uint32_t inter_message_32 = (parm[1] + parm[2]) * 3;
|
|
uint16_t inter_message = (inter_message_32 > 65000) ? 65000 : inter_message_32;
|
|
for (; *p; *p++) {
|
|
if (*p == '0') {
|
|
raw_array[i++] = parm[3];
|
|
raw_array[i++] = parm[4];
|
|
}
|
|
else if (*p == '1') {
|
|
raw_array[i++] = parm[3];
|
|
raw_array[i++] = parm[5];
|
|
}
|
|
}
|
|
raw_array[i++] = parm[3];
|
|
irsend_active = true;
|
|
for (uint32_t r = 0; r <= repeat; r++) {
|
|
irsend->sendRaw(raw_array, i, parm[0]);
|
|
if (r < repeat) {
|
|
irsend->space(inter_message);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return IE_INVALID_RAWDATA;
|
|
}
|
|
} else {
|
|
if (!freq) { freq = 38000; }
|
|
uint16_t count = 0;
|
|
char *q = p;
|
|
for (; *q; count += (*q++ == ','));
|
|
if (0 == count) {
|
|
return IE_INVALID_RAWDATA;
|
|
}
|
|
|
|
|
|
count++;
|
|
if (count < 200) {
|
|
uint16_t raw_array[count];
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
raw_array[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0);
|
|
}
|
|
|
|
|
|
|
|
irsend_active = true;
|
|
for (uint32_t r = 0; r <= repeat; r++) {
|
|
irsend->sendRaw(raw_array, count, freq);
|
|
}
|
|
} else {
|
|
uint16_t *raw_array = reinterpret_cast<uint16_t*>(malloc(count * sizeof(uint16_t)));
|
|
if (raw_array == nullptr) {
|
|
return IE_INVALID_RAWDATA;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
raw_array[i] = strtol(strtok_r(nullptr, ", ", &p), nullptr, 0);
|
|
}
|
|
|
|
|
|
|
|
irsend_active = true;
|
|
for (uint32_t r = 0; r <= repeat; r++) {
|
|
irsend->sendRaw(raw_array, count, freq);
|
|
}
|
|
free(raw_array);
|
|
}
|
|
}
|
|
|
|
return IE_NO_ERROR;
|
|
}
|
|
|
|
void CmndIrSend(void)
|
|
{
|
|
uint8_t error = IE_SYNTAX_IRSEND;
|
|
|
|
if (XdrvMailbox.data_len) {
|
|
if (strstr(XdrvMailbox.data, "{") == nullptr) {
|
|
error = IrRemoteCmndIrSendRaw();
|
|
} else {
|
|
error = IrRemoteCmndIrSendJson();
|
|
}
|
|
}
|
|
IrRemoteCmndResponse(error);
|
|
}
|
|
|
|
void IrRemoteCmndResponse(uint32_t error)
|
|
{
|
|
switch (error) {
|
|
case IE_INVALID_RAWDATA:
|
|
ResponseCmndChar(D_JSON_INVALID_RAWDATA);
|
|
break;
|
|
case IE_INVALID_JSON:
|
|
ResponseCmndChar(D_JSON_INVALID_JSON);
|
|
break;
|
|
case IE_SYNTAX_IRSEND:
|
|
Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_NO " " D_JSON_IR_BITS " " D_JSON_OR " " D_JSON_IR_DATA "\"}"));
|
|
break;
|
|
case IE_SYNTAX_IRHVAC:
|
|
Response_P(PSTR("{\"" D_CMND_IRHVAC "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_VENDOR ", " D_JSON_IRHVAC_MODE " " D_JSON_OR " " D_JSON_IRHVAC_FANSPEED "\"}"));
|
|
break;
|
|
case IE_UNSUPPORTED_HVAC:
|
|
Response_P(PSTR("{\"" D_CMND_IRHVAC "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_VENDOR " (%s)\"}"), listSupportedProtocols(true).c_str());
|
|
break;
|
|
case IE_UNSUPPORTED_PROTOCOL:
|
|
Response_P(PSTR("{\"" D_CMND_IRSEND "\":\"" D_JSON_WRONG " " D_JSON_IRHVAC_PROTOCOL " (%s)\"}"), listSupportedProtocols(false).c_str());
|
|
break;
|
|
default:
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv05(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if ((pin[GPIO_IRSEND] < 99) || (pin[GPIO_IRRECV] < 99)) {
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
if (pin[GPIO_IRSEND] < 99) {
|
|
IrSendInit();
|
|
}
|
|
if (pin[GPIO_IRRECV] < 99) {
|
|
IrReceiveInit();
|
|
}
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (pin[GPIO_IRRECV] < 99) {
|
|
IrReceiveCheck();
|
|
}
|
|
irsend_active = false;
|
|
break;
|
|
case FUNC_COMMAND:
|
|
if (pin[GPIO_IRSEND] < 99) {
|
|
result = DecodeCommand(kIrRemoteCommands, IrRemoteCommand);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_06_snfbridge.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_06_snfbridge.ino"
|
|
#ifdef USE_SONOFF_RF
|
|
|
|
|
|
|
|
|
|
#define XDRV_06 6
|
|
|
|
const uint32_t SFB_TIME_AVOID_DUPLICATE = 2000;
|
|
|
|
enum SonoffBridgeCommands {
|
|
CMND_RFSYNC, CMND_RFLOW, CMND_RFHIGH, CMND_RFHOST, CMND_RFCODE };
|
|
|
|
const char kSonoffBridgeCommands[] PROGMEM = "|"
|
|
D_CMND_RFSYNC "|" D_CMND_RFLOW "|" D_CMND_RFHIGH "|" D_CMND_RFHOST "|" D_CMND_RFCODE "|" D_CMND_RFKEY "|" D_CMND_RFRAW;
|
|
|
|
void (* const SonoffBridgeCommand[])(void) PROGMEM = {
|
|
&CmndRfBridge, &CmndRfBridge, &CmndRfBridge, &CmndRfBridge, &CmndRfBridge, &CmndRfKey, &CmndRfRaw };
|
|
|
|
struct SONOFFBRIDGE {
|
|
uint32_t last_received_id = 0;
|
|
uint32_t last_send_code = 0;
|
|
uint32_t last_time = 0;
|
|
uint32_t last_learn_time = 0;
|
|
uint8_t receive_flag = 0;
|
|
uint8_t receive_raw_flag = 0;
|
|
uint8_t learn_key = 1;
|
|
uint8_t learn_active = 0;
|
|
uint8_t expected_bytes = 0;
|
|
} SnfBridge;
|
|
|
|
#ifdef USE_RF_FLASH
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "ihx.h"
|
|
#include "c2.h"
|
|
|
|
const ssize_t RF_RECORD_NO_START_FOUND = -1;
|
|
const ssize_t RF_RECORD_NO_END_FOUND = -2;
|
|
|
|
ssize_t rf_find_hex_record_start(uint8_t *buf, size_t size)
|
|
{
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (buf[i] == ':') {
|
|
return i;
|
|
}
|
|
}
|
|
return RF_RECORD_NO_START_FOUND;
|
|
}
|
|
|
|
ssize_t rf_find_hex_record_end(uint8_t *buf, size_t size)
|
|
{
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (buf[i] == '\n') {
|
|
return i;
|
|
}
|
|
}
|
|
return RF_RECORD_NO_END_FOUND;
|
|
}
|
|
|
|
ssize_t rf_glue_remnant_with_new_data_and_write(const uint8_t *remnant_data, uint8_t *new_data, size_t new_data_len)
|
|
{
|
|
ssize_t record_start;
|
|
ssize_t record_end;
|
|
ssize_t glue_record_sz;
|
|
uint8_t *glue_buf;
|
|
ssize_t result;
|
|
|
|
if (remnant_data[0] != ':') { return -8; }
|
|
|
|
|
|
record_end = rf_find_hex_record_end(new_data, new_data_len);
|
|
record_start = rf_find_hex_record_start(new_data, new_data_len);
|
|
|
|
|
|
|
|
|
|
if ((record_start != RF_RECORD_NO_START_FOUND) && (record_start < record_end)) {
|
|
return -8;
|
|
}
|
|
|
|
glue_record_sz = strlen((const char *) remnant_data) + record_end;
|
|
|
|
glue_buf = (uint8_t *) malloc(glue_record_sz);
|
|
if (glue_buf == nullptr) { return -2; }
|
|
|
|
|
|
memcpy(glue_buf, remnant_data, strlen((const char *) remnant_data));
|
|
memcpy(glue_buf + strlen((const char *) remnant_data), new_data, record_end);
|
|
|
|
result = rf_decode_and_write(glue_buf, glue_record_sz);
|
|
free(glue_buf);
|
|
return result;
|
|
}
|
|
|
|
ssize_t rf_decode_and_write(uint8_t *record, size_t size)
|
|
{
|
|
uint8_t err = ihx_decode(record, size);
|
|
if (err != IHX_SUCCESS) { return -13; }
|
|
|
|
ihx_t *h = (ihx_t *) record;
|
|
if (h->record_type == IHX_RT_DATA) {
|
|
int retries = 5;
|
|
uint16_t address = h->address_high * 0x100 + h->address_low;
|
|
|
|
do {
|
|
err = c2_programming_init();
|
|
err = c2_block_write(address, h->data, h->len);
|
|
} while (err != C2_SUCCESS && retries--);
|
|
} else if (h->record_type == IHX_RT_END_OF_FILE) {
|
|
|
|
err = c2_reset();
|
|
}
|
|
|
|
if (err != C2_SUCCESS) { return -12; }
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t rf_search_and_write(uint8_t *buf, size_t size)
|
|
{
|
|
|
|
ssize_t rec_end;
|
|
ssize_t rec_start;
|
|
ssize_t err;
|
|
|
|
for (size_t i = 0; i < size; i++) {
|
|
|
|
rec_start = rf_find_hex_record_start(buf + i, size - i);
|
|
if (rec_start == RF_RECORD_NO_START_FOUND) {
|
|
|
|
return -8;
|
|
}
|
|
|
|
|
|
rec_start += i;
|
|
rec_end = rf_find_hex_record_end(buf + rec_start, size - rec_start);
|
|
if (rec_end == RF_RECORD_NO_END_FOUND) {
|
|
|
|
return rec_start;
|
|
}
|
|
|
|
|
|
rec_end += rec_start;
|
|
|
|
err = rf_decode_and_write(buf + rec_start, rec_end - rec_start);
|
|
if (err < 0) { return err; }
|
|
i = rec_end;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t rf_erase_flash(void)
|
|
{
|
|
uint8_t err;
|
|
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
err = c2_programming_init();
|
|
if (err != C2_SUCCESS) {
|
|
return 10;
|
|
}
|
|
err = c2_device_erase();
|
|
if (err != C2_SUCCESS) {
|
|
if (i < 3) {
|
|
c2_reset();
|
|
} else {
|
|
return 11;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t SnfBrUpdateInit(void)
|
|
{
|
|
pinMode(PIN_C2CK, OUTPUT);
|
|
pinMode(PIN_C2D, INPUT);
|
|
|
|
return rf_erase_flash();
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void SonoffBridgeReceivedRaw(void)
|
|
{
|
|
|
|
uint8_t buckets = 0;
|
|
|
|
if (0xB1 == serial_in_buffer[1]) { buckets = serial_in_buffer[2] << 1; }
|
|
|
|
ResponseTime_P(PSTR(",\"" D_CMND_RFRAW "\":{\"" D_JSON_DATA "\":\""));
|
|
for (uint32_t i = 0; i < serial_in_byte_counter; i++) {
|
|
ResponseAppend_P(PSTR("%02X"), serial_in_buffer[i]);
|
|
if (0xB1 == serial_in_buffer[1]) {
|
|
if ((i > 3) && buckets) { buckets--; }
|
|
if ((i < 3) || (buckets % 2) || (i == serial_in_byte_counter -2)) {
|
|
ResponseAppend_P(PSTR(" "));
|
|
}
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("\"}}"));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_CMND_RFRAW));
|
|
|
|
XdrvRulesProcess();
|
|
}
|
|
|
|
|
|
|
|
void SonoffBridgeLearnFailed(void)
|
|
{
|
|
SnfBridge.learn_active = 0;
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, D_CMND_RFKEY, SnfBridge.learn_key, D_JSON_LEARN_FAILED);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RFKEY));
|
|
}
|
|
|
|
void SonoffBridgeReceived(void)
|
|
{
|
|
uint16_t sync_time = 0;
|
|
uint16_t low_time = 0;
|
|
uint16_t high_time = 0;
|
|
uint32_t received_id = 0;
|
|
char rfkey[8];
|
|
char stemp[16];
|
|
|
|
AddLogSerial(LOG_LEVEL_DEBUG);
|
|
|
|
if (0xA2 == serial_in_buffer[0]) {
|
|
SonoffBridgeLearnFailed();
|
|
}
|
|
else if (0xA3 == serial_in_buffer[0]) {
|
|
SnfBridge.learn_active = 0;
|
|
low_time = serial_in_buffer[3] << 8 | serial_in_buffer[4];
|
|
high_time = serial_in_buffer[5] << 8 | serial_in_buffer[6];
|
|
if (low_time && high_time) {
|
|
for (uint32_t i = 0; i < 9; i++) {
|
|
Settings.rf_code[SnfBridge.learn_key][i] = serial_in_buffer[i +1];
|
|
}
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, D_CMND_RFKEY, SnfBridge.learn_key, D_JSON_LEARNED);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_RFKEY));
|
|
} else {
|
|
SonoffBridgeLearnFailed();
|
|
}
|
|
}
|
|
else if (0xA4 == serial_in_buffer[0]) {
|
|
if (SnfBridge.learn_active) {
|
|
SonoffBridgeLearnFailed();
|
|
} else {
|
|
sync_time = serial_in_buffer[1] << 8 | serial_in_buffer[2];
|
|
low_time = serial_in_buffer[3] << 8 | serial_in_buffer[4];
|
|
high_time = serial_in_buffer[5] << 8 | serial_in_buffer[6];
|
|
received_id = serial_in_buffer[7] << 16 | serial_in_buffer[8] << 8 | serial_in_buffer[9];
|
|
|
|
unsigned long now = millis();
|
|
if (!((received_id == SnfBridge.last_received_id) && (now - SnfBridge.last_time < SFB_TIME_AVOID_DUPLICATE))) {
|
|
SnfBridge.last_received_id = received_id;
|
|
SnfBridge.last_time = now;
|
|
strncpy_P(rfkey, PSTR("\"" D_JSON_NONE "\""), sizeof(rfkey));
|
|
for (uint32_t i = 1; i <= 16; i++) {
|
|
if (Settings.rf_code[i][0]) {
|
|
uint32_t send_id = Settings.rf_code[i][6] << 16 | Settings.rf_code[i][7] << 8 | Settings.rf_code[i][8];
|
|
if (send_id == received_id) {
|
|
snprintf_P(rfkey, sizeof(rfkey), PSTR("%d"), i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (Settings.flag.rf_receive_decimal) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%u"), received_id);
|
|
} else {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("\"%06X\""), received_id);
|
|
}
|
|
ResponseTime_P(PSTR(",\"" D_JSON_RFRECEIVED "\":{\"" D_JSON_SYNC "\":%d,\"" D_JSON_LOW "\":%d,\"" D_JSON_HIGH "\":%d,\"" D_JSON_DATA "\":%s,\"" D_CMND_RFKEY "\":%s}}"),
|
|
sync_time, low_time, high_time, stemp, rfkey);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_RFRECEIVED));
|
|
XdrvRulesProcess();
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzSensor(DZ_COUNT, received_id);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SonoffBridgeSerialInput(void)
|
|
{
|
|
|
|
static int8_t receive_len = 0;
|
|
|
|
if (SnfBridge.receive_flag) {
|
|
if (SnfBridge.receive_raw_flag) {
|
|
if (!serial_in_byte_counter) {
|
|
serial_in_buffer[serial_in_byte_counter++] = 0xAA;
|
|
}
|
|
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
|
|
if (serial_in_byte_counter == 3) {
|
|
if ((0xA6 == serial_in_buffer[1]) || (0xAB == serial_in_buffer[1])) {
|
|
receive_len = serial_in_buffer[2] + 4;
|
|
}
|
|
}
|
|
if ((!receive_len && (0x55 == serial_in_byte)) || (receive_len && (serial_in_byte_counter == receive_len))) {
|
|
SonoffBridgeReceivedRaw();
|
|
SnfBridge.receive_flag = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
else if (!((0 == serial_in_byte_counter) && (0 == serial_in_byte))) {
|
|
if (0 == serial_in_byte_counter) {
|
|
SnfBridge.expected_bytes = 2;
|
|
if (serial_in_byte >= 0xA3) {
|
|
SnfBridge.expected_bytes = 11;
|
|
}
|
|
if (serial_in_byte == 0xA6) {
|
|
SnfBridge.expected_bytes = 0;
|
|
serial_in_buffer[serial_in_byte_counter++] = 0xAA;
|
|
SnfBridge.receive_raw_flag = 1;
|
|
}
|
|
}
|
|
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
|
|
if ((SnfBridge.expected_bytes == serial_in_byte_counter) && (0x55 == serial_in_byte)) {
|
|
SonoffBridgeReceived();
|
|
SnfBridge.receive_flag = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
serial_in_byte = 0;
|
|
}
|
|
if (0xAA == serial_in_byte) {
|
|
serial_in_byte_counter = 0;
|
|
serial_in_byte = 0;
|
|
SnfBridge.receive_flag = 1;
|
|
receive_len = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void SonoffBridgeSendCommand(uint8_t code)
|
|
{
|
|
Serial.write(0xAA);
|
|
Serial.write(code);
|
|
Serial.write(0x55);
|
|
}
|
|
|
|
void SonoffBridgeSendAck(void)
|
|
{
|
|
Serial.write(0xAA);
|
|
Serial.write(0xA0);
|
|
Serial.write(0x55);
|
|
}
|
|
|
|
void SonoffBridgeSendCode(uint32_t code)
|
|
{
|
|
Serial.write(0xAA);
|
|
Serial.write(0xA5);
|
|
for (uint32_t i = 0; i < 6; i++) {
|
|
Serial.write(Settings.rf_code[0][i]);
|
|
}
|
|
Serial.write((code >> 16) & 0xff);
|
|
Serial.write((code >> 8) & 0xff);
|
|
Serial.write(code & 0xff);
|
|
Serial.write(0x55);
|
|
Serial.flush();
|
|
}
|
|
|
|
void SonoffBridgeSend(uint8_t idx, uint8_t key)
|
|
{
|
|
uint8_t code;
|
|
|
|
key--;
|
|
Serial.write(0xAA);
|
|
Serial.write(0xA5);
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
Serial.write(Settings.rf_code[idx][i]);
|
|
}
|
|
if (0 == idx) {
|
|
code = (0x10 << (key >> 2)) | (1 << (key & 3));
|
|
} else {
|
|
code = Settings.rf_code[idx][8];
|
|
}
|
|
Serial.write(code);
|
|
Serial.write(0x55);
|
|
Serial.flush();
|
|
#ifdef USE_DOMOTICZ
|
|
|
|
|
|
#endif
|
|
}
|
|
|
|
void SonoffBridgeLearn(uint8_t key)
|
|
{
|
|
SnfBridge.learn_key = key;
|
|
SnfBridge.learn_active = 1;
|
|
SnfBridge.last_learn_time = millis();
|
|
Serial.write(0xAA);
|
|
Serial.write(0xA1);
|
|
Serial.write(0x55);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndRfBridge(void)
|
|
{
|
|
char *p;
|
|
char stemp [10];
|
|
uint32_t code = 0;
|
|
uint8_t radix = 10;
|
|
|
|
uint32_t set_index = XdrvMailbox.command_code *2;
|
|
|
|
if (XdrvMailbox.data[0] == '#') {
|
|
XdrvMailbox.data++;
|
|
XdrvMailbox.data_len--;
|
|
radix = 16;
|
|
}
|
|
|
|
if (XdrvMailbox.data_len) {
|
|
code = strtol(XdrvMailbox.data, &p, radix);
|
|
if (code) {
|
|
if (CMND_RFCODE == XdrvMailbox.command_code) {
|
|
SnfBridge.last_send_code = code;
|
|
SonoffBridgeSendCode(code);
|
|
} else {
|
|
if (1 == XdrvMailbox.payload) {
|
|
code = pgm_read_byte(kDefaultRfCode + set_index) << 8 | pgm_read_byte(kDefaultRfCode + set_index +1);
|
|
}
|
|
uint8_t msb = code >> 8;
|
|
uint8_t lsb = code & 0xFF;
|
|
if ((code > 0) && (code < 0x7FFF) && (msb != 0x55) && (lsb != 0x55)) {
|
|
Settings.rf_code[0][set_index] = msb;
|
|
Settings.rf_code[0][set_index +1] = lsb;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (CMND_RFCODE == XdrvMailbox.command_code) {
|
|
code = SnfBridge.last_send_code;
|
|
} else {
|
|
code = Settings.rf_code[0][set_index] << 8 | Settings.rf_code[0][set_index +1];
|
|
}
|
|
if (10 == radix) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), code);
|
|
} else {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("\"#%06X\""), code);
|
|
}
|
|
Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp);
|
|
}
|
|
|
|
void CmndRfKey(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 16)) {
|
|
unsigned long now = millis();
|
|
if ((!SnfBridge.learn_active) || (now - SnfBridge.last_learn_time > 60100)) {
|
|
SnfBridge.learn_active = 0;
|
|
if (2 == XdrvMailbox.payload) {
|
|
SonoffBridgeLearn(XdrvMailbox.index);
|
|
ResponseCmndIdxChar(D_JSON_START_LEARNING);
|
|
}
|
|
else if (3 == XdrvMailbox.payload) {
|
|
Settings.rf_code[XdrvMailbox.index][0] = 0;
|
|
ResponseCmndIdxChar(D_JSON_SET_TO_DEFAULT);
|
|
}
|
|
else if (4 == XdrvMailbox.payload) {
|
|
for (uint32_t i = 0; i < 6; i++) {
|
|
Settings.rf_code[XdrvMailbox.index][i] = Settings.rf_code[0][i];
|
|
}
|
|
Settings.rf_code[XdrvMailbox.index][6] = (SnfBridge.last_send_code >> 16) & 0xff;
|
|
Settings.rf_code[XdrvMailbox.index][7] = (SnfBridge.last_send_code >> 8) & 0xff;
|
|
Settings.rf_code[XdrvMailbox.index][8] = SnfBridge.last_send_code & 0xff;
|
|
ResponseCmndIdxChar(D_JSON_SAVED);
|
|
} else if (5 == XdrvMailbox.payload) {
|
|
uint8_t key = XdrvMailbox.index;
|
|
uint8_t index = (0 == Settings.rf_code[key][0]) ? 0 : key;
|
|
uint16_t sync_time = (Settings.rf_code[index][0] << 8) | Settings.rf_code[index][1];
|
|
uint16_t low_time = (Settings.rf_code[index][2] << 8) | Settings.rf_code[index][3];
|
|
uint16_t high_time = (Settings.rf_code[index][4] << 8) | Settings.rf_code[index][5];
|
|
uint32_t code = (Settings.rf_code[index][6] << 16) | (Settings.rf_code[index][7] << 8);
|
|
if (0 == index) {
|
|
key--;
|
|
code |= (uint8_t)((0x10 << (key >> 2)) | (1 << (key & 3)));
|
|
} else {
|
|
code |= Settings.rf_code[index][8];
|
|
}
|
|
Response_P(PSTR("{\"%s%d\":{\"" D_JSON_SYNC "\":%d,\"" D_JSON_LOW "\":%d,\"" D_JSON_HIGH "\":%d,\"" D_JSON_DATA "\":\"%06X\"}}"),
|
|
XdrvMailbox.command, XdrvMailbox.index, sync_time, low_time, high_time, code);
|
|
} else {
|
|
if ((1 == XdrvMailbox.payload) || (0 == Settings.rf_code[XdrvMailbox.index][0])) {
|
|
SonoffBridgeSend(0, XdrvMailbox.index);
|
|
ResponseCmndIdxChar(D_JSON_DEFAULT_SENT);
|
|
} else {
|
|
SonoffBridgeSend(XdrvMailbox.index, 0);
|
|
ResponseCmndIdxChar(D_JSON_LEARNED_SENT);
|
|
}
|
|
}
|
|
} else {
|
|
Response_P(S_JSON_COMMAND_INDEX_SVALUE, XdrvMailbox.command, SnfBridge.learn_key, D_JSON_LEARNING_ACTIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndRfRaw(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
if (XdrvMailbox.data_len < 6) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
SonoffBridgeSendCommand(0xA7);
|
|
case 1:
|
|
SnfBridge.receive_raw_flag = XdrvMailbox.payload;
|
|
break;
|
|
case 166:
|
|
case 167:
|
|
case 169:
|
|
case 176:
|
|
case 177:
|
|
case 255:
|
|
SonoffBridgeSendCommand(XdrvMailbox.payload);
|
|
SnfBridge.receive_raw_flag = 1;
|
|
break;
|
|
case 192:
|
|
char beep[] = "AAC000C055\0";
|
|
SerialSendRaw(beep);
|
|
break;
|
|
}
|
|
} else {
|
|
SerialSendRaw(RemoveSpace(XdrvMailbox.data));
|
|
SnfBridge.receive_raw_flag = 1;
|
|
}
|
|
}
|
|
ResponseCmndStateText(SnfBridge.receive_raw_flag);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (SONOFF_BRIDGE == my_module_type) {
|
|
switch (function) {
|
|
case FUNC_SERIAL:
|
|
result = SonoffBridgeSerialInput();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kSonoffBridgeCommands, SonoffBridgeCommand);
|
|
break;
|
|
case FUNC_INIT:
|
|
SnfBridge.receive_raw_flag = 0;
|
|
SonoffBridgeSendCommand(0xA7);
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
SetSerial(19200, TS_SERIAL_8N1);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_07_domoticz.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_07_domoticz.ino"
|
|
#ifdef USE_DOMOTICZ
|
|
|
|
#define XDRV_07 7
|
|
|
|
#define D_PRFX_DOMOTICZ "Domoticz"
|
|
#define D_CMND_IDX "Idx"
|
|
#define D_CMND_KEYIDX "KeyIdx"
|
|
#define D_CMND_SWITCHIDX "SwitchIdx"
|
|
#define D_CMND_SENSORIDX "SensorIdx"
|
|
#define D_CMND_UPDATETIMER "UpdateTimer"
|
|
|
|
const char kDomoticzCommands[] PROGMEM = D_PRFX_DOMOTICZ "|"
|
|
D_CMND_IDX "|" D_CMND_KEYIDX "|" D_CMND_SWITCHIDX "|" D_CMND_SENSORIDX "|" D_CMND_UPDATETIMER ;
|
|
|
|
void (* const DomoticzCommand[])(void) PROGMEM = {
|
|
&CmndDomoticzIdx, &CmndDomoticzKeyIdx, &CmndDomoticzSwitchIdx, &CmndDomoticzSensorIdx, &CmndDomoticzUpdateTimer };
|
|
|
|
const char DOMOTICZ_MESSAGE[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\",\"Battery\":%d,\"RSSI\":%d}";
|
|
|
|
#if MAX_DOMOTICZ_SNS_IDX < DZ_MAX_SENSORS
|
|
#error "Domoticz: Too many sensors or change settings.h layout"
|
|
#endif
|
|
|
|
const char kDomoticzSensors[] PROGMEM =
|
|
D_DOMOTICZ_TEMP "|" D_DOMOTICZ_TEMP_HUM "|" D_DOMOTICZ_TEMP_HUM_BARO "|" D_DOMOTICZ_POWER_ENERGY "|" D_DOMOTICZ_ILLUMINANCE "|"
|
|
D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER "|" D_DOMOTICZ_SHUTTER ;
|
|
|
|
char domoticz_in_topic[] = DOMOTICZ_IN_TOPIC;
|
|
|
|
int domoticz_update_timer = 0;
|
|
uint32_t domoticz_fan_debounce = 0;
|
|
bool domoticz_subscribe = false;
|
|
bool domoticz_update_flag = true;
|
|
|
|
#ifdef USE_SHUTTER
|
|
bool domoticz_is_shutter = false;
|
|
#endif
|
|
|
|
int DomoticzBatteryQuality(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
int quality = 100;
|
|
|
|
#ifdef USE_ADC_VCC
|
|
uint16_t voltage = ESP.getVcc();
|
|
if (voltage <= 2600) {
|
|
quality = 0;
|
|
} else if (voltage >= 4600) {
|
|
quality = 200;
|
|
} else {
|
|
quality = (voltage - 2600) / 10;
|
|
}
|
|
#endif
|
|
return quality;
|
|
}
|
|
|
|
int DomoticzRssiQuality(void)
|
|
{
|
|
|
|
|
|
return WifiGetRssiAsQuality(WiFi.RSSI()) / 10;
|
|
}
|
|
|
|
#ifdef USE_SONOFF_IFAN
|
|
void MqttPublishDomoticzFanState(void)
|
|
{
|
|
if (Settings.flag.mqtt_enabled && Settings.domoticz_relay_idx[1]) {
|
|
char svalue[8];
|
|
|
|
int fan_speed = GetFanspeed();
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fan_speed * 10);
|
|
Response_P(DOMOTICZ_MESSAGE, (int)Settings.domoticz_relay_idx[1], (0 == fan_speed) ? 0 : 2, svalue, DomoticzBatteryQuality(), DomoticzRssiQuality());
|
|
MqttPublish(domoticz_in_topic);
|
|
|
|
domoticz_fan_debounce = millis();
|
|
}
|
|
}
|
|
|
|
void DomoticzUpdateFanState(void)
|
|
{
|
|
if (domoticz_update_flag) {
|
|
MqttPublishDomoticzFanState();
|
|
}
|
|
domoticz_update_flag = true;
|
|
}
|
|
#endif
|
|
|
|
void MqttPublishDomoticzPowerState(uint8_t device)
|
|
{
|
|
if (Settings.flag.mqtt_enabled) {
|
|
if ((device < 1) || (device > devices_present)) { device = 1; }
|
|
if (Settings.domoticz_relay_idx[device -1]) {
|
|
#ifdef USE_SHUTTER
|
|
if (domoticz_is_shutter) {
|
|
|
|
} else {
|
|
#endif
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan() && (device > 1)) {
|
|
|
|
} else {
|
|
#endif
|
|
char svalue[8];
|
|
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%d"), Settings.light_dimmer);
|
|
Response_P(DOMOTICZ_MESSAGE, (int)Settings.domoticz_relay_idx[device -1], (power & (1 << (device -1))) ? 1 : 0, (light_type) ? svalue : "", DomoticzBatteryQuality(), DomoticzRssiQuality());
|
|
MqttPublish(domoticz_in_topic);
|
|
#ifdef USE_SONOFF_IFAN
|
|
}
|
|
#endif
|
|
#ifdef USE_SHUTTER
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void DomoticzUpdatePowerState(uint8_t device)
|
|
{
|
|
if (domoticz_update_flag) {
|
|
MqttPublishDomoticzPowerState(device);
|
|
}
|
|
domoticz_update_flag = true;
|
|
}
|
|
|
|
void DomoticzMqttUpdate(void)
|
|
{
|
|
if (domoticz_subscribe && (Settings.domoticz_update_timer || domoticz_update_timer)) {
|
|
domoticz_update_timer--;
|
|
if (domoticz_update_timer <= 0) {
|
|
domoticz_update_timer = Settings.domoticz_update_timer;
|
|
for (uint32_t i = 1; i <= devices_present; i++) {
|
|
#ifdef USE_SHUTTER
|
|
if (domoticz_is_shutter)
|
|
{
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan() && (i > 1)) {
|
|
MqttPublishDomoticzFanState();
|
|
break;
|
|
} else {
|
|
#endif
|
|
MqttPublishDomoticzPowerState(i);
|
|
#ifdef USE_SONOFF_IFAN
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DomoticzMqttSubscribe(void)
|
|
{
|
|
uint8_t maxdev = (devices_present > MAX_DOMOTICZ_IDX) ? MAX_DOMOTICZ_IDX : devices_present;
|
|
for (uint32_t i = 0; i < maxdev; i++) {
|
|
if (Settings.domoticz_relay_idx[i]) {
|
|
domoticz_subscribe = true;
|
|
}
|
|
}
|
|
|
|
if (domoticz_subscribe) {
|
|
char stopic[TOPSZ];
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(DOMOTICZ_OUT_TOPIC "/#"));
|
|
MqttSubscribe(stopic);
|
|
}
|
|
}
|
|
# 219 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_07_domoticz.ino"
|
|
bool DomoticzMqttData(void)
|
|
{
|
|
domoticz_update_flag = true;
|
|
|
|
if (strncasecmp_P(XdrvMailbox.topic, PSTR(DOMOTICZ_OUT_TOPIC), strlen(DOMOTICZ_OUT_TOPIC)) != 0) {
|
|
return false;
|
|
}
|
|
|
|
|
|
if (XdrvMailbox.data_len < 20) {
|
|
return true;
|
|
}
|
|
StaticJsonBuffer<400> jsonBuf;
|
|
JsonObject& domoticz = jsonBuf.parseObject(XdrvMailbox.data);
|
|
if (!domoticz.success()) {
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
uint32_t idx = domoticz["idx"];
|
|
int16_t nvalue = -1;
|
|
if (domoticz.containsKey("nvalue")) {
|
|
nvalue = domoticz["nvalue"];
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ "idx %d, nvalue %d"), idx, nvalue);
|
|
|
|
bool found = false;
|
|
if ((idx > 0) && (nvalue >= 0) && (nvalue <= 15)) {
|
|
uint8_t maxdev = (devices_present > MAX_DOMOTICZ_IDX) ? MAX_DOMOTICZ_IDX : devices_present;
|
|
for (uint32_t i = 0; i < maxdev; i++) {
|
|
if (idx == Settings.domoticz_relay_idx[i]) {
|
|
bool iscolordimmer = strcmp_P(domoticz["dtype"],PSTR("Color Switch")) == 0;
|
|
bool isShutter = strcmp_P(domoticz["dtype"],PSTR("Light/Switch")) == 0 & strncmp_P(domoticz["switchType"],PSTR("Blinds"), 6) == 0;
|
|
|
|
char stemp1[10];
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), i +1);
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan() && (1 == i)) {
|
|
uint8_t svalue = 0;
|
|
if (domoticz.containsKey("svalue1")) {
|
|
svalue = domoticz["svalue1"];
|
|
} else {
|
|
return true;
|
|
}
|
|
svalue = (nvalue == 2) ? svalue / 10 : 0;
|
|
if (GetFanspeed() == svalue) {
|
|
return true;
|
|
}
|
|
if (TimePassedSince(domoticz_fan_debounce) < 1000) {
|
|
return true;
|
|
}
|
|
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_FANSPEED));
|
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), svalue);
|
|
found = true;
|
|
} else
|
|
#endif
|
|
#ifdef USE_SHUTTER
|
|
if (isShutter)
|
|
{
|
|
if (domoticz.containsKey("nvalue")) {
|
|
nvalue = domoticz["nvalue"];
|
|
}
|
|
|
|
uint8_t position = 0;
|
|
if (domoticz.containsKey("svalue1")) {
|
|
position = domoticz["svalue1"];
|
|
}
|
|
if (nvalue != 2) {
|
|
position = nvalue == 0 ? 0 : 100;
|
|
}
|
|
|
|
snprintf_P(XdrvMailbox.topic, TOPSZ, PSTR("/" D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION));
|
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), position);
|
|
XdrvMailbox.data_len = position > 99 ? 3 : (position > 9 ? 2 : 1);
|
|
|
|
found = true;
|
|
} else
|
|
#endif
|
|
if (iscolordimmer && 10 == nvalue) {
|
|
|
|
JsonObject& color = domoticz["Color"];
|
|
uint16_t level = nvalue = domoticz["svalue1"];
|
|
uint16_t r = color["r"]; r = r * level / 100;
|
|
uint16_t g = color["g"]; g = g * level / 100;
|
|
uint16_t b = color["b"]; b = b * level / 100;
|
|
uint16_t cw = color["cw"]; cw = cw * level / 100;
|
|
uint16_t ww = color["ww"]; ww = ww * level / 100;
|
|
uint16_t m = 0;
|
|
uint16_t t = 0;
|
|
if (color.containsKey("m")) {
|
|
m = color["m"];
|
|
t = color["t"];
|
|
}
|
|
if (2 == m) {
|
|
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_BACKLOG));
|
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR(D_CMND_COLORTEMPERATURE " %d;" D_CMND_DIMMER " %d"), changeUIntScale(t, 0, 255, CT_MIN, CT_MAX), level);
|
|
} else {
|
|
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_COLOR));
|
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%02x%02x%02x%02x%02x"), r, g, b, cw, ww);
|
|
}
|
|
found = true;
|
|
}
|
|
else if ((!iscolordimmer && 2 == nvalue) ||
|
|
(iscolordimmer && 15 == nvalue)) {
|
|
if (domoticz.containsKey("svalue1")) {
|
|
nvalue = domoticz["svalue1"];
|
|
} else {
|
|
return true;
|
|
}
|
|
if (light_type && (Settings.light_dimmer == nvalue) && ((power >> i) &1)) {
|
|
return true;
|
|
}
|
|
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER));
|
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue);
|
|
found = true;
|
|
}
|
|
else if (1 == nvalue || 0 == nvalue) {
|
|
if (((power >> i) &1) == (power_t)nvalue) {
|
|
return true;
|
|
}
|
|
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_POWER "%s"), (devices_present > 1) ? stemp1 : "");
|
|
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue);
|
|
found = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) { return true; }
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ D_RECEIVED_TOPIC " %s, " D_DATA " %s"), XdrvMailbox.topic, XdrvMailbox.data);
|
|
|
|
domoticz_update_flag = false;
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
bool DomoticzSendKey(uint8_t key, uint8_t device, uint8_t state, uint8_t svalflg)
|
|
{
|
|
bool result = false;
|
|
|
|
if (device <= MAX_DOMOTICZ_IDX) {
|
|
if ((Settings.domoticz_key_idx[device -1] || Settings.domoticz_switch_idx[device -1]) && (svalflg)) {
|
|
Response_P(PSTR("{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"%s\"}"),
|
|
(key) ? Settings.domoticz_switch_idx[device -1] : Settings.domoticz_key_idx[device -1], (state) ? (POWER_TOGGLE == state) ? "Toggle" : "On" : "Off");
|
|
MqttPublish(domoticz_in_topic);
|
|
result = true;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
# 391 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_07_domoticz.ino"
|
|
uint8_t DomoticzHumidityState(char *hum)
|
|
{
|
|
uint8_t h = atoi(hum);
|
|
return (!h) ? 0 : (h < 40) ? 2 : (h > 70) ? 3 : 1;
|
|
}
|
|
|
|
void DomoticzSensor(uint8_t idx, char *data)
|
|
{
|
|
if (Settings.domoticz_sensor_idx[idx]) {
|
|
char dmess[128];
|
|
|
|
memcpy(dmess, mqtt_data, sizeof(dmess));
|
|
if (DZ_AIRQUALITY == idx) {
|
|
Response_P(PSTR("{\"idx\":%d,\"nvalue\":%s,\"Battery\":%d,\"RSSI\":%d}"),
|
|
Settings.domoticz_sensor_idx[idx], data, DomoticzBatteryQuality(), DomoticzRssiQuality());
|
|
} else {
|
|
uint8_t nvalue = 0;
|
|
#ifdef USE_SHUTTER
|
|
if (DZ_SHUTTER == idx) {
|
|
uint8_t position = atoi(data);
|
|
nvalue = position < 2 ? 0 : (position == 100 ? 1 : 2);
|
|
}
|
|
#endif
|
|
Response_P(DOMOTICZ_MESSAGE,
|
|
Settings.domoticz_sensor_idx[idx], nvalue, data, DomoticzBatteryQuality(), DomoticzRssiQuality());
|
|
}
|
|
MqttPublish(domoticz_in_topic);
|
|
memcpy(mqtt_data, dmess, sizeof(dmess));
|
|
}
|
|
}
|
|
|
|
void DomoticzSensor(uint8_t idx, uint32_t value)
|
|
{
|
|
char data[16];
|
|
snprintf_P(data, sizeof(data), PSTR("%d"), value);
|
|
DomoticzSensor(idx, data);
|
|
}
|
|
|
|
void DomoticzTempHumSensor(char *temp, char *hum)
|
|
{
|
|
char data[16];
|
|
snprintf_P(data, sizeof(data), PSTR("%s;%s;%d"), temp, hum, DomoticzHumidityState(hum));
|
|
DomoticzSensor(DZ_TEMP_HUM, data);
|
|
}
|
|
|
|
void DomoticzTempHumPressureSensor(char *temp, char *hum, char *baro)
|
|
{
|
|
char data[32];
|
|
snprintf_P(data, sizeof(data), PSTR("%s;%s;%d;%s;5"), temp, hum, DomoticzHumidityState(hum), baro);
|
|
DomoticzSensor(DZ_TEMP_HUM_BARO, data);
|
|
}
|
|
|
|
void DomoticzSensorPowerEnergy(int power, char *energy)
|
|
{
|
|
char data[16];
|
|
snprintf_P(data, sizeof(data), PSTR("%d;%s"), power, energy);
|
|
DomoticzSensor(DZ_POWER_ENERGY, data);
|
|
}
|
|
|
|
void DomoticzSensorP1SmartMeter(char *usage1, char *usage2, char *return1, char *return2, int power)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
int consumed = power;
|
|
int produced = 0;
|
|
if (power < 0) {
|
|
consumed = 0;
|
|
produced = -power;
|
|
}
|
|
char data[64];
|
|
snprintf_P(data, sizeof(data), PSTR("%s;%s;%s;%s;%d;%d"), usage1, usage2, return1, return2, consumed, produced);
|
|
DomoticzSensor(DZ_P1_SMART_METER, data);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndDomoticzIdx(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
Settings.domoticz_relay_idx[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.domoticz_relay_idx[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndDomoticzKeyIdx(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
Settings.domoticz_key_idx[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.domoticz_key_idx[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndDomoticzSwitchIdx(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
Settings.domoticz_switch_idx[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.domoticz_switch_idx[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndDomoticzSensorIdx(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= DZ_MAX_SENSORS)) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
Settings.domoticz_sensor_idx[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.domoticz_sensor_idx[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndDomoticzUpdateTimer(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
|
|
Settings.domoticz_update_timer = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.domoticz_update_timer);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
#define WEB_HANDLE_DOMOTICZ "dm"
|
|
|
|
const char S_CONFIGURE_DOMOTICZ[] PROGMEM = D_CONFIGURE_DOMOTICZ;
|
|
|
|
const char HTTP_BTN_MENU_DOMOTICZ[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_DOMOTICZ "' method='get'><button>" D_CONFIGURE_DOMOTICZ "</button></form></p>";
|
|
|
|
const char HTTP_FORM_DOMOTICZ[] PROGMEM =
|
|
"<fieldset><legend><b> " D_DOMOTICZ_PARAMETERS " </b></legend>"
|
|
"<form method='post' action='" WEB_HANDLE_DOMOTICZ "'>"
|
|
"<table>";
|
|
const char HTTP_FORM_DOMOTICZ_RELAY[] PROGMEM =
|
|
"<tr><td style='width:260px'><b>" D_DOMOTICZ_IDX " %d</b></td><td style='width:70px'><input id='r%d' placeholder='0' value='%d'></td></tr>"
|
|
"<tr><td style='width:260px'><b>" D_DOMOTICZ_KEY_IDX " %d</b></td><td style='width:70px'><input id='k%d' placeholder='0' value='%d'></td></tr>";
|
|
const char HTTP_FORM_DOMOTICZ_SWITCH[] PROGMEM =
|
|
"<tr><td style='width:260px'><b>" D_DOMOTICZ_SWITCH_IDX " %d</b></td><td style='width:70px'><input id='s%d' placeholder='0' value='%d'></td></tr>";
|
|
const char HTTP_FORM_DOMOTICZ_SENSOR[] PROGMEM =
|
|
"<tr><td style='width:260px'><b>" D_DOMOTICZ_SENSOR_IDX " %d</b> %s</td><td style='width:70px'><input id='l%d' placeholder='0' value='%d'></td></tr>";
|
|
const char HTTP_FORM_DOMOTICZ_TIMER[] PROGMEM =
|
|
"<tr><td style='width:260px'><b>" D_DOMOTICZ_UPDATE_TIMER "</b> (" STR(DOMOTICZ_UPDATE_TIMER) ")</td><td style='width:70px'><input id='ut' placeholder='" STR(DOMOTICZ_UPDATE_TIMER) "' value='%d'></td></tr>";
|
|
|
|
void HandleDomoticzConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_DOMOTICZ);
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
DomoticzSaveSettings();
|
|
WebRestart(1);
|
|
return;
|
|
}
|
|
|
|
char stemp[40];
|
|
|
|
WSContentStart_P(S_CONFIGURE_DOMOTICZ);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_DOMOTICZ);
|
|
for (uint32_t i = 0; i < MAX_DOMOTICZ_IDX; i++) {
|
|
if (i < devices_present) {
|
|
WSContentSend_P(HTTP_FORM_DOMOTICZ_RELAY,
|
|
i +1, i, Settings.domoticz_relay_idx[i],
|
|
i +1, i, Settings.domoticz_key_idx[i]);
|
|
}
|
|
if (pin[GPIO_SWT1 +i] < 99) {
|
|
WSContentSend_P(HTTP_FORM_DOMOTICZ_SWITCH,
|
|
i +1, i, Settings.domoticz_switch_idx[i]);
|
|
}
|
|
#ifdef USE_SONOFF_IFAN
|
|
if (IsModuleIfan() && (1 == i)) { break; }
|
|
#endif
|
|
}
|
|
for (uint32_t i = 0; i < DZ_MAX_SENSORS; i++) {
|
|
WSContentSend_P(HTTP_FORM_DOMOTICZ_SENSOR,
|
|
i +1, GetTextIndexed(stemp, sizeof(stemp), i, kDomoticzSensors), i, Settings.domoticz_sensor_idx[i]);
|
|
}
|
|
WSContentSend_P(HTTP_FORM_DOMOTICZ_TIMER, Settings.domoticz_update_timer);
|
|
WSContentSend_P(PSTR("</table>"));
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void DomoticzSaveSettings(void)
|
|
{
|
|
char stemp[20];
|
|
char ssensor_indices[6 * MAX_DOMOTICZ_SNS_IDX];
|
|
char tmp[100];
|
|
|
|
for (uint32_t i = 0; i < MAX_DOMOTICZ_IDX; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("r%d"), i);
|
|
WebGetArg(stemp, tmp, sizeof(tmp));
|
|
Settings.domoticz_relay_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("k%d"), i);
|
|
WebGetArg(stemp, tmp, sizeof(tmp));
|
|
Settings.domoticz_key_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("s%d"), i);
|
|
WebGetArg(stemp, tmp, sizeof(tmp));
|
|
Settings.domoticz_switch_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
}
|
|
ssensor_indices[0] = '\0';
|
|
for (uint32_t i = 0; i < DZ_MAX_SENSORS; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("l%d"), i);
|
|
WebGetArg(stemp, tmp, sizeof(tmp));
|
|
Settings.domoticz_sensor_idx[i] = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
snprintf_P(ssensor_indices, sizeof(ssensor_indices), PSTR("%s%s%d"), ssensor_indices, (strlen(ssensor_indices)) ? "," : "", Settings.domoticz_sensor_idx[i]);
|
|
}
|
|
WebGetArg("ut", tmp, sizeof(tmp));
|
|
Settings.domoticz_update_timer = (!strlen(tmp)) ? DOMOTICZ_UPDATE_TIMER : atoi(tmp);
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_DOMOTICZ D_CMND_IDX " %d,%d,%d,%d, " D_CMND_KEYIDX " %d,%d,%d,%d, " D_CMND_SWITCHIDX " %d,%d,%d,%d, " D_CMND_SENSORIDX " %s, " D_CMND_UPDATETIMER " %d"),
|
|
Settings.domoticz_relay_idx[0], Settings.domoticz_relay_idx[1], Settings.domoticz_relay_idx[2], Settings.domoticz_relay_idx[3],
|
|
Settings.domoticz_key_idx[0], Settings.domoticz_key_idx[1], Settings.domoticz_key_idx[2], Settings.domoticz_key_idx[3],
|
|
Settings.domoticz_switch_idx[0], Settings.domoticz_switch_idx[1], Settings.domoticz_switch_idx[2], Settings.domoticz_switch_idx[3],
|
|
ssensor_indices, Settings.domoticz_update_timer);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv07(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Settings.flag.mqtt_enabled) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DomoticzMqttUpdate();
|
|
break;
|
|
case FUNC_MQTT_DATA:
|
|
result = DomoticzMqttData();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_DOMOTICZ);
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/" WEB_HANDLE_DOMOTICZ, HandleDomoticzConfiguration);
|
|
break;
|
|
#endif
|
|
case FUNC_MQTT_SUBSCRIBE:
|
|
DomoticzMqttSubscribe();
|
|
#ifdef USE_SHUTTER
|
|
if (Settings.domoticz_sensor_idx[DZ_SHUTTER]) { domoticz_is_shutter = true; }
|
|
#endif
|
|
break;
|
|
case FUNC_MQTT_INIT:
|
|
domoticz_update_timer = 2;
|
|
break;
|
|
case FUNC_SHOW_SENSOR:
|
|
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kDomoticzCommands, DomoticzCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_08_serial_bridge.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_08_serial_bridge.ino"
|
|
#ifdef USE_SERIAL_BRIDGE
|
|
|
|
|
|
|
|
|
|
#define XDRV_08 8
|
|
|
|
const uint8_t SERIAL_BRIDGE_BUFFER_SIZE = 130;
|
|
|
|
const char kSerialBridgeCommands[] PROGMEM = "|"
|
|
D_CMND_SSERIALSEND "|" D_CMND_SBAUDRATE;
|
|
|
|
void (* const SerialBridgeCommand[])(void) PROGMEM = {
|
|
&CmndSSerialSend, &CmndSBaudrate };
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *SerialBridgeSerial = nullptr;
|
|
|
|
unsigned long serial_bridge_polling_window = 0;
|
|
char *serial_bridge_buffer = nullptr;
|
|
int serial_bridge_in_byte_counter = 0;
|
|
bool serial_bridge_active = true;
|
|
bool serial_bridge_raw = false;
|
|
|
|
void SerialBridgeInput(void)
|
|
{
|
|
while (SerialBridgeSerial->available()) {
|
|
yield();
|
|
uint8_t serial_in_byte = SerialBridgeSerial->read();
|
|
|
|
if ((serial_in_byte > 127) && !serial_bridge_raw) {
|
|
serial_bridge_in_byte_counter = 0;
|
|
SerialBridgeSerial->flush();
|
|
return;
|
|
}
|
|
if (serial_in_byte || serial_bridge_raw) {
|
|
|
|
if ((serial_bridge_in_byte_counter < SERIAL_BRIDGE_BUFFER_SIZE -1) &&
|
|
((isprint(serial_in_byte) && (128 == Settings.serial_delimiter)) ||
|
|
((serial_in_byte != Settings.serial_delimiter) && (128 != Settings.serial_delimiter)) ||
|
|
serial_bridge_raw)) {
|
|
serial_bridge_buffer[serial_bridge_in_byte_counter++] = serial_in_byte;
|
|
serial_bridge_polling_window = millis();
|
|
} else {
|
|
serial_bridge_polling_window = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (serial_bridge_in_byte_counter && (millis() > (serial_bridge_polling_window + SERIAL_POLLING))) {
|
|
serial_bridge_buffer[serial_bridge_in_byte_counter] = 0;
|
|
char hex_char[(serial_bridge_in_byte_counter * 2) + 2];
|
|
bool assume_json = (!serial_bridge_raw && (serial_bridge_buffer[0] == '{'));
|
|
Response_P(PSTR("{\"" D_JSON_SSERIALRECEIVED "\":%s%s%s}"),
|
|
(assume_json) ? "" : """",
|
|
(serial_bridge_raw) ? ToHex_P((unsigned char*)serial_bridge_buffer, serial_bridge_in_byte_counter, hex_char, sizeof(hex_char)) : serial_bridge_buffer,
|
|
(assume_json) ? "" : """");
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED));
|
|
XdrvRulesProcess();
|
|
serial_bridge_in_byte_counter = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SerialBridgeInit(void)
|
|
{
|
|
serial_bridge_active = false;
|
|
if ((pin[GPIO_SBR_RX] < 99) && (pin[GPIO_SBR_TX] < 99)) {
|
|
SerialBridgeSerial = new TasmotaSerial(pin[GPIO_SBR_RX], pin[GPIO_SBR_TX]);
|
|
if (SerialBridgeSerial->begin(Settings.sbaudrate * 300)) {
|
|
if (SerialBridgeSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
serial_bridge_buffer = serial_in_buffer;
|
|
} else {
|
|
serial_bridge_buffer = (char*)(malloc(SERIAL_BRIDGE_BUFFER_SIZE));
|
|
}
|
|
serial_bridge_active = true;
|
|
SerialBridgeSerial->flush();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndSSerialSend(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) {
|
|
serial_bridge_raw = (XdrvMailbox.index > 3);
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (1 == XdrvMailbox.index) {
|
|
SerialBridgeSerial->write(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
SerialBridgeSerial->write("\n");
|
|
}
|
|
else if ((2 == XdrvMailbox.index) || (4 == XdrvMailbox.index)) {
|
|
SerialBridgeSerial->write(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
}
|
|
else if (3 == XdrvMailbox.index) {
|
|
SerialBridgeSerial->write(Unescape(XdrvMailbox.data, &XdrvMailbox.data_len), XdrvMailbox.data_len);
|
|
}
|
|
else if (5 == XdrvMailbox.index) {
|
|
char *p;
|
|
char stemp[3];
|
|
uint8_t code;
|
|
|
|
char *codes = RemoveSpace(XdrvMailbox.data);
|
|
int size = strlen(XdrvMailbox.data);
|
|
|
|
while (size > 1) {
|
|
strlcpy(stemp, codes, sizeof(stemp));
|
|
code = strtol(stemp, &p, 16);
|
|
SerialBridgeSerial->write(code);
|
|
size -= 2;
|
|
codes += 2;
|
|
}
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndSBaudrate(void)
|
|
{
|
|
if (XdrvMailbox.payload >= 300) {
|
|
XdrvMailbox.payload /= 300;
|
|
Settings.sbaudrate = XdrvMailbox.payload;
|
|
SerialBridgeSerial->begin(Settings.sbaudrate * 300);
|
|
}
|
|
ResponseCmndNumber(Settings.sbaudrate * 300);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv08(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (serial_bridge_active) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (SerialBridgeSerial) { SerialBridgeInput(); }
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
SerialBridgeInit();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kSerialBridgeCommands, SerialBridgeCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_09_timers.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_09_timers.ino"
|
|
#ifdef USE_TIMERS
|
|
# 39 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_09_timers.ino"
|
|
#define XDRV_09 9
|
|
|
|
const char kTimerCommands[] PROGMEM = "|"
|
|
D_CMND_TIMER "|" D_CMND_TIMERS
|
|
#ifdef USE_SUNRISE
|
|
"|" D_CMND_LATITUDE "|" D_CMND_LONGITUDE
|
|
#endif
|
|
;
|
|
|
|
void (* const TimerCommand[])(void) PROGMEM = {
|
|
&CmndTimer, &CmndTimers
|
|
#ifdef USE_SUNRISE
|
|
, &CmndLatitude, &CmndLongitude
|
|
#endif
|
|
};
|
|
|
|
uint16_t timer_last_minute = 60;
|
|
int8_t timer_window[MAX_TIMERS] = { 0 };
|
|
|
|
#ifdef USE_SUNRISE
|
|
# 67 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_09_timers.ino"
|
|
const float pi2 = TWO_PI;
|
|
const float pi = PI;
|
|
const float RAD = DEG_TO_RAD;
|
|
|
|
float JulianischesDatum(void)
|
|
{
|
|
|
|
int Gregor;
|
|
int Jahr = RtcTime.year;
|
|
int Monat = RtcTime.month;
|
|
int Tag = RtcTime.day_of_month;
|
|
|
|
if (Monat <= 2) {
|
|
Monat += 12;
|
|
Jahr -= 1;
|
|
}
|
|
Gregor = (Jahr / 400) - (Jahr / 100) + (Jahr / 4);
|
|
return 2400000.5f + 365.0f*Jahr - 679004.0f + Gregor + (int)(30.6001f * (Monat +1)) + Tag + 0.5f;
|
|
}
|
|
|
|
float InPi(float x)
|
|
{
|
|
int n = (int)(x / pi2);
|
|
x = x - n*pi2;
|
|
if (x < 0) x += pi2;
|
|
return x;
|
|
}
|
|
|
|
float eps(float T)
|
|
{
|
|
|
|
return RAD * (23.43929111f + (-46.8150f*T - 0.00059f*T*T + 0.001813f*T*T*T)/3600.0f);
|
|
}
|
|
|
|
float BerechneZeitgleichung(float *DK,float T)
|
|
{
|
|
float RA_Mittel = 18.71506921f + 2400.0513369f*T +(2.5862e-5f - 1.72e-9f*T)*T*T;
|
|
float M = InPi(pi2 * (0.993133f + 99.997361f*T));
|
|
float L = InPi(pi2 * (0.7859453f + M/pi2 + (6893.0f*sinf(M)+72.0f*sinf(2.0f*M)+6191.2f*T) / 1296.0e3f));
|
|
float e = eps(T);
|
|
float RA = atanf(tanf(L)*cosf(e));
|
|
if (RA < 0.0) RA += pi;
|
|
if (L > pi) RA += pi;
|
|
RA = 24.0*RA/pi2;
|
|
*DK = asinf(sinf(e)*sinf(L));
|
|
|
|
RA_Mittel = 24.0f * InPi(pi2*RA_Mittel/24.0f)/pi2;
|
|
float dRA = RA_Mittel - RA;
|
|
if (dRA < -12.0f) dRA += 24.0f;
|
|
if (dRA > 12.0f) dRA -= 24.0f;
|
|
dRA = dRA * 1.0027379f;
|
|
return dRA;
|
|
}
|
|
|
|
void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down)
|
|
{
|
|
float JD2000 = 2451545.0f;
|
|
float JD = JulianischesDatum();
|
|
float T = (JD - JD2000) / 36525.0f;
|
|
float DK;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float h = SUNRISE_DAWN_ANGLE *RAD;
|
|
float B = (((float)Settings.latitude)/1000000) * RAD;
|
|
float GeographischeLaenge = ((float)Settings.longitude)/1000000;
|
|
|
|
|
|
|
|
float Zeitzone = ((float)Rtc.time_timezone) / 60;
|
|
float Zeitgleichung = BerechneZeitgleichung(&DK, T);
|
|
float Zeitdifferenz = 12.0f*acosf((sinf(h) - sinf(B)*sinf(DK)) / (cosf(B)*cosf(DK)))/pi;
|
|
float AufgangOrtszeit = 12.0f - Zeitdifferenz - Zeitgleichung;
|
|
float UntergangOrtszeit = 12.0f + Zeitdifferenz - Zeitgleichung;
|
|
float AufgangWeltzeit = AufgangOrtszeit - GeographischeLaenge / 15.0f;
|
|
float UntergangWeltzeit = UntergangOrtszeit - GeographischeLaenge / 15.0f;
|
|
float Aufgang = AufgangWeltzeit + Zeitzone;
|
|
if (Aufgang < 0.0f) {
|
|
Aufgang += 24.0f;
|
|
} else {
|
|
if (Aufgang >= 24.0f) Aufgang -= 24.0f;
|
|
}
|
|
float Untergang = UntergangWeltzeit + Zeitzone;
|
|
if (Untergang < 0.0f) {
|
|
Untergang += 24.0f;
|
|
} else {
|
|
if (Untergang >= 24.0f) Untergang -= 24.0f;
|
|
}
|
|
int AufgangMinuten = (int)(60.0f*(Aufgang - (int)Aufgang)+0.5f);
|
|
int AufgangStunden = (int)Aufgang;
|
|
if (AufgangMinuten >= 60.0f) {
|
|
AufgangMinuten -= 60.0f;
|
|
AufgangStunden++;
|
|
} else {
|
|
if (AufgangMinuten < 0.0f) {
|
|
AufgangMinuten += 60.0f;
|
|
AufgangStunden--;
|
|
if (AufgangStunden < 0.0f) AufgangStunden += 24.0f;
|
|
}
|
|
}
|
|
int UntergangMinuten = (int)(60.0f*(Untergang - (int)Untergang)+0.5f);
|
|
int UntergangStunden = (int)Untergang;
|
|
if (UntergangMinuten >= 60.0f) {
|
|
UntergangMinuten -= 60.0f;
|
|
UntergangStunden++;
|
|
} else {
|
|
if (UntergangMinuten<0) {
|
|
UntergangMinuten += 60.0f;
|
|
UntergangStunden--;
|
|
if (UntergangStunden < 0.0f) UntergangStunden += 24.0f;
|
|
}
|
|
}
|
|
*hour_up = AufgangStunden;
|
|
*minute_up = AufgangMinuten;
|
|
*hour_down = UntergangStunden;
|
|
*minute_down = UntergangMinuten;
|
|
}
|
|
|
|
void ApplyTimerOffsets(Timer *duskdawn)
|
|
{
|
|
uint8_t hour[2];
|
|
uint8_t minute[2];
|
|
Timer stored = (Timer)*duskdawn;
|
|
|
|
|
|
DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]);
|
|
uint8_t mode = (duskdawn->mode -1) &1;
|
|
duskdawn->time = (hour[mode] *60) + minute[mode];
|
|
|
|
|
|
uint16_t timeBuffer;
|
|
if ((uint16_t)stored.time > 719) {
|
|
|
|
timeBuffer = (uint16_t)stored.time - 720;
|
|
|
|
if (timeBuffer > (uint16_t)duskdawn->time) {
|
|
timeBuffer = 1440 - (timeBuffer - (uint16_t)duskdawn->time);
|
|
duskdawn->days = duskdawn->days >> 1;
|
|
duskdawn->days |= (stored.days << 6);
|
|
} else {
|
|
timeBuffer = (uint16_t)duskdawn->time - timeBuffer;
|
|
}
|
|
} else {
|
|
|
|
timeBuffer = (uint16_t)duskdawn->time + (uint16_t)stored.time;
|
|
|
|
if (timeBuffer > 1440) {
|
|
timeBuffer -= 1440;
|
|
duskdawn->days = duskdawn->days << 1;
|
|
duskdawn->days |= (stored.days >> 6);
|
|
}
|
|
}
|
|
duskdawn->time = timeBuffer;
|
|
}
|
|
|
|
String GetSun(uint32_t dawn)
|
|
{
|
|
char stime[6];
|
|
|
|
uint8_t hour[2];
|
|
uint8_t minute[2];
|
|
|
|
DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]);
|
|
dawn &= 1;
|
|
snprintf_P(stime, sizeof(stime), PSTR("%02d:%02d"), hour[dawn], minute[dawn]);
|
|
return String(stime);
|
|
}
|
|
|
|
uint16_t SunMinutes(uint32_t dawn)
|
|
{
|
|
uint8_t hour[2];
|
|
uint8_t minute[2];
|
|
|
|
DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]);
|
|
dawn &= 1;
|
|
return (hour[dawn] *60) + minute[dawn];
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void TimerSetRandomWindow(uint32_t index)
|
|
{
|
|
timer_window[index] = 0;
|
|
if (Settings.timer[index].window) {
|
|
timer_window[index] = (random(0, (Settings.timer[index].window << 1) +1)) - Settings.timer[index].window;
|
|
}
|
|
}
|
|
|
|
void TimerSetRandomWindows(void)
|
|
{
|
|
for (uint32_t i = 0; i < MAX_TIMERS; i++) { TimerSetRandomWindow(i); }
|
|
}
|
|
|
|
void TimerEverySecond(void)
|
|
{
|
|
if (RtcTime.valid) {
|
|
if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second) { TimerSetRandomWindows(); }
|
|
if (Settings.flag3.timers_enable &&
|
|
(uptime > 60) && (RtcTime.minute != timer_last_minute)) {
|
|
timer_last_minute = RtcTime.minute;
|
|
int32_t time = (RtcTime.hour *60) + RtcTime.minute;
|
|
uint8_t days = 1 << (RtcTime.day_of_week -1);
|
|
|
|
for (uint32_t i = 0; i < MAX_TIMERS; i++) {
|
|
|
|
Timer xtimer = Settings.timer[i];
|
|
#ifdef USE_SUNRISE
|
|
if ((1 == xtimer.mode) || (2 == xtimer.mode)) {
|
|
ApplyTimerOffsets(&xtimer);
|
|
}
|
|
#endif
|
|
if (xtimer.arm) {
|
|
int32_t set_time = xtimer.time + timer_window[i];
|
|
if (set_time < 0) {
|
|
set_time = abs(timer_window[i]);
|
|
}
|
|
if (set_time > 1439) {
|
|
set_time = xtimer.time - abs(timer_window[i]);
|
|
}
|
|
if (set_time > 1439) { set_time = 1439; }
|
|
|
|
DEBUG_DRIVER_LOG(PSTR("TIM: Timer %d, Time %d, Window %d, SetTime %d"), i +1, xtimer.time, timer_window[i], set_time);
|
|
|
|
if (time == set_time) {
|
|
if (xtimer.days & days) {
|
|
Settings.timer[i].arm = xtimer.repeat;
|
|
#if defined(USE_RULES) || defined(USE_SCRIPT)
|
|
if (POWER_BLINK == xtimer.power) {
|
|
Response_P(PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1);
|
|
XdrvRulesProcess();
|
|
} else
|
|
#endif
|
|
if (devices_present) { ExecuteCommandPower(xtimer.device +1, xtimer.power, SRC_TIMER); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PrepShowTimer(uint32_t index)
|
|
{
|
|
Timer xtimer = Settings.timer[index -1];
|
|
|
|
char days[8] = { 0 };
|
|
for (uint32_t i = 0; i < 7; i++) {
|
|
uint8_t mask = 1 << i;
|
|
snprintf(days, sizeof(days), "%s%d", days, ((xtimer.days & mask) > 0));
|
|
}
|
|
|
|
char soutput[80];
|
|
soutput[0] = '\0';
|
|
if (devices_present) {
|
|
snprintf_P(soutput, sizeof(soutput), PSTR(",\"" D_JSON_TIMER_OUTPUT "\":%d"), xtimer.device +1);
|
|
}
|
|
#ifdef USE_SUNRISE
|
|
char sign[2] = { 0 };
|
|
int16_t hour = xtimer.time / 60;
|
|
if ((1 == xtimer.mode) || (2 == xtimer.mode)) {
|
|
if (hour > 11) {
|
|
hour -= 12;
|
|
sign[0] = '-';
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_MODE "\":%d,\"" D_JSON_TIMER_TIME "\":\"%s%02d:%02d\",\"" D_JSON_TIMER_WINDOW "\":%d,\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d%s,\"" D_JSON_TIMER_ACTION "\":%d}"),
|
|
index, xtimer.arm, xtimer.mode, sign, hour, xtimer.time % 60, xtimer.window, days, xtimer.repeat, soutput, xtimer.power);
|
|
#else
|
|
ResponseAppend_P(PSTR("\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_WINDOW "\":%d,\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d%s,\"" D_JSON_TIMER_ACTION "\":%d}"),
|
|
index, xtimer.arm, xtimer.time / 60, xtimer.time % 60, xtimer.window, days, xtimer.repeat, soutput, xtimer.power);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndTimer(void)
|
|
{
|
|
uint32_t index = XdrvMailbox.index;
|
|
if ((index > 0) && (index <= MAX_TIMERS)) {
|
|
uint32_t error = 0;
|
|
if (XdrvMailbox.data_len) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= MAX_TIMERS)) {
|
|
if (XdrvMailbox.payload == 0) {
|
|
Settings.timer[index -1].data = 0;
|
|
} else {
|
|
Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data;
|
|
}
|
|
} else {
|
|
|
|
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
|
|
if (devices_present) {
|
|
#endif
|
|
char dataBufUc[XdrvMailbox.data_len + 1];
|
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
|
StaticJsonBuffer<256> jsonBuffer;
|
|
JsonObject& root = jsonBuffer.parseObject(dataBufUc);
|
|
if (!root.success()) {
|
|
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_INVALID_JSON "\"}"), index);
|
|
error = 1;
|
|
}
|
|
else {
|
|
char parm_uc[10];
|
|
index--;
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_ARM))].success()) {
|
|
Settings.timer[index].arm = (root[parm_uc] != 0);
|
|
}
|
|
#ifdef USE_SUNRISE
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_MODE))].success()) {
|
|
Settings.timer[index].mode = (uint8_t)root[parm_uc] & 0x03;
|
|
}
|
|
#endif
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_TIME))].success()) {
|
|
uint16_t itime = 0;
|
|
int8_t value = 0;
|
|
uint8_t sign = 0;
|
|
char time_str[10];
|
|
|
|
strlcpy(time_str, root[parm_uc], sizeof(time_str));
|
|
const char *substr = strtok(time_str, ":");
|
|
if (substr != nullptr) {
|
|
if (strchr(substr, '-')) {
|
|
sign = 1;
|
|
substr++;
|
|
}
|
|
value = atoi(substr);
|
|
if (sign) { value += 12; }
|
|
if (value > 23) { value = 23; }
|
|
itime = value * 60;
|
|
substr = strtok(nullptr, ":");
|
|
if (substr != nullptr) {
|
|
value = atoi(substr);
|
|
if (value < 0) { value = 0; }
|
|
if (value > 59) { value = 59; }
|
|
itime += value;
|
|
}
|
|
}
|
|
Settings.timer[index].time = itime;
|
|
}
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_WINDOW))].success()) {
|
|
Settings.timer[index].window = (uint8_t)root[parm_uc] & 0x0F;
|
|
TimerSetRandomWindow(index);
|
|
}
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_DAYS))].success()) {
|
|
|
|
Settings.timer[index].days = 0;
|
|
const char *tday = root[parm_uc];
|
|
uint8_t i = 0;
|
|
char ch = *tday++;
|
|
while ((ch != '\0') && (i < 7)) {
|
|
if (ch == '-') { ch = '0'; }
|
|
uint8_t mask = 1 << i++;
|
|
Settings.timer[index].days |= (ch == '0') ? 0 : mask;
|
|
ch = *tday++;
|
|
}
|
|
}
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_REPEAT))].success()) {
|
|
Settings.timer[index].repeat = (root[parm_uc] != 0);
|
|
}
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_OUTPUT))].success()) {
|
|
uint8_t device = ((uint8_t)root[parm_uc] -1) & 0x0F;
|
|
Settings.timer[index].device = (device < devices_present) ? device : 0;
|
|
}
|
|
if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_ACTION))].success()) {
|
|
uint8_t action = (uint8_t)root[parm_uc] & 0x03;
|
|
Settings.timer[index].power = (devices_present) ? action : 3;
|
|
}
|
|
|
|
index++;
|
|
}
|
|
|
|
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
|
|
} else {
|
|
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index);
|
|
error = 1;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
if (!error) {
|
|
Response_P(PSTR("{"));
|
|
PrepShowTimer(index);
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndTimers(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings.flag3.timers_enable = XdrvMailbox.payload;
|
|
}
|
|
if (XdrvMailbox.payload == 2) {
|
|
Settings.flag3.timers_enable = !Settings.flag3.timers_enable;
|
|
}
|
|
}
|
|
|
|
ResponseCmndStateText(Settings.flag3.timers_enable);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, XdrvMailbox.command);
|
|
|
|
uint32_t jsflg = 0;
|
|
uint32_t lines = 1;
|
|
for (uint32_t i = 0; i < MAX_TIMERS; i++) {
|
|
if (!jsflg) {
|
|
Response_P(PSTR("{\"" D_CMND_TIMERS "%d\":{"), lines++);
|
|
} else {
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
jsflg++;
|
|
PrepShowTimer(i +1);
|
|
if (jsflg > 3) {
|
|
ResponseJsonEndEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_TIMERS));
|
|
jsflg = 0;
|
|
}
|
|
}
|
|
mqtt_data[0] = '\0';
|
|
}
|
|
|
|
#ifdef USE_SUNRISE
|
|
void CmndLongitude(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
Settings.longitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
|
|
}
|
|
ResponseCmndFloat((float)(Settings.longitude) /1000000, 6);
|
|
}
|
|
|
|
void CmndLatitude(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
Settings.latitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
|
|
}
|
|
ResponseCmndFloat((float)(Settings.latitude) /1000000, 6);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef USE_TIMERS_WEB
|
|
|
|
#define WEB_HANDLE_TIMER "tm"
|
|
|
|
const char S_CONFIGURE_TIMER[] PROGMEM = D_CONFIGURE_TIMER;
|
|
|
|
const char HTTP_BTN_MENU_TIMER[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_TIMER "' method='get'><button>" D_CONFIGURE_TIMER "</button></form></p>";
|
|
|
|
const char HTTP_TIMER_SCRIPT1[] PROGMEM =
|
|
"var pt=[],ct=99;"
|
|
"function ce(i,q){"
|
|
"var o=document.createElement('option');"
|
|
"o.textContent=i;"
|
|
"q.appendChild(o);"
|
|
"}";
|
|
#ifdef USE_SUNRISE
|
|
const char HTTP_TIMER_SCRIPT2[] PROGMEM =
|
|
"function gt(){"
|
|
"var m,p,q;"
|
|
"m=qs('input[name=\"rd\"]:checked').value;"
|
|
"p=pt[ct]&0x7FF;"
|
|
"if(m==0){"
|
|
"so(0);"
|
|
"q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;"
|
|
"q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;"
|
|
"}"
|
|
"if((m==1)||(m==2)){"
|
|
"so(1);"
|
|
"q=Math.floor(p/60);"
|
|
"if(q>=12){q-=12;qs('#dr').selectedIndex=1;}"
|
|
"else{qs('#dr').selectedIndex=0;}"
|
|
"if(q<10){q='0'+q;}qs('#ho').value=q;"
|
|
"q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;"
|
|
"}"
|
|
"}"
|
|
"function so(b){"
|
|
"o=qs('#ho');"
|
|
"e=o.childElementCount;"
|
|
"if(b==1){"
|
|
"qs('#dr').style.visibility='';"
|
|
"if(e>12){for(i=12;i<=23;i++){o.removeChild(o.lastElementChild);}}"
|
|
"}else{"
|
|
"qs('#dr').style.visibility='hidden';"
|
|
"if(e<23){for(i=12;i<=23;i++){ce(i,o);}}"
|
|
"}"
|
|
"}";
|
|
#endif
|
|
const char HTTP_TIMER_SCRIPT3[] PROGMEM =
|
|
"function st(){"
|
|
"var i,l,m,n,p,s;"
|
|
"m=0;s=0;"
|
|
"n=1<<31;if(eb('a0').checked){s|=n;}"
|
|
"n=1<<15;if(eb('r0').checked){s|=n;}"
|
|
"for(i=0;i<7;i++){n=1<<(16+i);if(eb('w'+i).checked){s|=n;}}"
|
|
#ifdef USE_SUNRISE
|
|
"m=qs('input[name=\"rd\"]:checked').value;"
|
|
"s|=(qs('input[name=\"rd\"]:checked').value<<29);"
|
|
#endif
|
|
"if(%d>0){"
|
|
"i=qs('#d1').selectedIndex;if(i>=0){s|=(i<<23);}"
|
|
"s|=(qs('#p1').selectedIndex<<27);"
|
|
"}else{"
|
|
"s|=3<<27;"
|
|
"}"
|
|
"l=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;"
|
|
"if(m==0){s|=l;}"
|
|
#ifdef USE_SUNRISE
|
|
"if((m==1)||(m==2)){"
|
|
"if(qs('#dr').selectedIndex>0){if(l>0){l+=720;}}"
|
|
"s|=l&0x7FF;"
|
|
"}"
|
|
#endif
|
|
"s|=((qs('#mw').selectedIndex)&0x0F)<<11;"
|
|
"pt[ct]=s;"
|
|
"eb('t0').value=pt.join();"
|
|
"}";
|
|
const char HTTP_TIMER_SCRIPT4[] PROGMEM =
|
|
"function ot(t,e){"
|
|
"var i,n,o,p,q,s;"
|
|
"if(ct<99){st();}"
|
|
"ct=t;"
|
|
"o=document.getElementsByClassName('tl');"
|
|
"for(i=0;i<o.length;i++){o[i].style.cssText=\"background:#%06x;color:#%06x;font-weight:normal;\"}"
|
|
"e.style.cssText=\"background:#%06x;color:#%06x;font-weight:bold;\";"
|
|
"s=pt[ct];"
|
|
#ifdef USE_SUNRISE
|
|
"p=(s>>29)&3;eb('b'+p).checked=1;"
|
|
"gt();"
|
|
#else
|
|
"p=s&0x7FF;"
|
|
"q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;"
|
|
"q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;"
|
|
#endif
|
|
"q=(s>>11)&0xF;if(q<10){q='0'+q;}qs('#mw').value=q;"
|
|
"for(i=0;i<7;i++){p=(s>>(16+i))&1;eb('w'+i).checked=p;}"
|
|
"if(%d>0){"
|
|
"p=(s>>23)&0xF;qs('#d1').value=p+1;"
|
|
"p=(s>>27)&3;qs('#p1').selectedIndex=p;"
|
|
"}"
|
|
"p=(s>>15)&1;eb('r0').checked=p;"
|
|
"p=(s>>31)&1;eb('a0').checked=p;"
|
|
"}";
|
|
const char HTTP_TIMER_SCRIPT5[] PROGMEM =
|
|
"function it(){"
|
|
"var b,i,o,s;"
|
|
"pt=eb('t0').value.split(',').map(Number);"
|
|
"s='';"
|
|
"for(i=0;i<%d;i++){"
|
|
"b='';"
|
|
"if(0==i){b=\" id='dP'\";}"
|
|
"s+=\"<button type='button' class='tl' onclick='ot(\"+i+\",this)'\"+b+\">\"+(i+1)+\"</button>\""
|
|
"}"
|
|
"eb('bt').innerHTML=s;"
|
|
"if(%d>0){"
|
|
"eb('oa').innerHTML=\"<b>" D_TIMER_OUTPUT "</b> <span><select style='width:60px;' id='d1'></select></span> <b>" D_TIMER_ACTION "</b> <select style='width:99px;' id='p1'></select>\";"
|
|
"o=qs('#p1');ce('" D_OFF "',o);ce('" D_ON "',o);ce('" D_TOGGLE "',o);"
|
|
#if defined(USE_RULES) || defined(USE_SCRIPT)
|
|
"ce('" D_RULE "',o);"
|
|
#else
|
|
"ce('" D_BLINK "',o);"
|
|
#endif
|
|
"}else{"
|
|
"eb('oa').innerHTML=\"<b>" D_TIMER_ACTION "</b> " D_RULE "\";"
|
|
"}";
|
|
const char HTTP_TIMER_SCRIPT6[] PROGMEM =
|
|
#ifdef USE_SUNRISE
|
|
"o=qs('#dr');ce('+',o);ce('-',o);"
|
|
#endif
|
|
"o=qs('#ho');for(i=0;i<=23;i++){ce((i<10)?('0'+i):i,o);}"
|
|
"o=qs('#mi');for(i=0;i<=59;i++){ce((i<10)?('0'+i):i,o);}"
|
|
"o=qs('#mw');for(i=0;i<=15;i++){ce((i<10)?('0'+i):i,o);}"
|
|
"o=qs('#d1');for(i=0;i<%d;i++){ce(i+1,o);}"
|
|
"var a='" D_DAY3LIST "';"
|
|
"s='';for(i=0;i<7;i++){s+=\"<input id='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b> \"}"
|
|
"eb('ds').innerHTML=s;"
|
|
"eb('dP').click();"
|
|
"}"
|
|
"wl(it);";
|
|
const char HTTP_TIMER_STYLE[] PROGMEM =
|
|
".tl{float:left;border-radius:0;border:1px solid #%06x;padding:1px;width:6.25%%;}";
|
|
const char HTTP_FORM_TIMER1[] PROGMEM =
|
|
"<fieldset style='min-width:470px;text-align:center;'>"
|
|
"<legend style='text-align:left;'><b> " D_TIMER_PARAMETERS " </b></legend>"
|
|
"<form method='post' action='" WEB_HANDLE_TIMER "' onsubmit='return st();'>"
|
|
"<br><input id='e0' type='checkbox'%s><b>" D_TIMER_ENABLE "</b><br><br><hr>"
|
|
"<input id='t0' value='";
|
|
const char HTTP_FORM_TIMER2[] PROGMEM =
|
|
"' hidden><div id='bt'></div><br><br><br>"
|
|
"<div id='oa' name='oa'></div><br>"
|
|
"<div>"
|
|
"<input id='a0' type='checkbox'><b>" D_TIMER_ARM "</b> "
|
|
"<input id='r0' type='checkbox'><b>" D_TIMER_REPEAT "</b>"
|
|
"</div><br>"
|
|
"<div>";
|
|
#ifdef USE_SUNRISE
|
|
const char HTTP_FORM_TIMER3[] PROGMEM =
|
|
"<fieldset style='width:%dpx;margin:auto;text-align:left;border:0;'>"
|
|
"<input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b><br>"
|
|
"<input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b> (%s)<br>"
|
|
"<input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b> (%s)<br>"
|
|
"</fieldset>"
|
|
"<p></p>"
|
|
"<span><select style='width:46px;' id='dr'></select></span>"
|
|
" ";
|
|
#else
|
|
const char HTTP_FORM_TIMER3[] PROGMEM =
|
|
"<b>" D_TIMER_TIME "</b> ";
|
|
#endif
|
|
const char HTTP_FORM_TIMER4[] PROGMEM =
|
|
"<span><select style='width:60px;' id='ho'></select></span>"
|
|
" " D_HOUR_MINUTE_SEPARATOR " "
|
|
"<span><select style='width:60px;' id='mi'></select></span>"
|
|
" <b>+/-</b> "
|
|
"<span><select style='width:60px;' id='mw'></select></span>"
|
|
"</div><br>"
|
|
"<div id='ds' name='ds'></div>";
|
|
|
|
void HandleTimerConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_TIMER);
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
TimerSaveSettings();
|
|
HandleConfiguration();
|
|
return;
|
|
}
|
|
|
|
WSContentStart_P(S_CONFIGURE_TIMER);
|
|
WSContentSend_P(HTTP_TIMER_SCRIPT1);
|
|
#ifdef USE_SUNRISE
|
|
WSContentSend_P(HTTP_TIMER_SCRIPT2);
|
|
#endif
|
|
WSContentSend_P(HTTP_TIMER_SCRIPT3, devices_present);
|
|
WSContentSend_P(HTTP_TIMER_SCRIPT4, WebColor(COL_TIMER_TAB_BACKGROUND), WebColor(COL_TIMER_TAB_TEXT), WebColor(COL_FORM), WebColor(COL_TEXT), devices_present);
|
|
WSContentSend_P(HTTP_TIMER_SCRIPT5, MAX_TIMERS, devices_present);
|
|
WSContentSend_P(HTTP_TIMER_SCRIPT6, devices_present);
|
|
WSContentSendStyle_P(HTTP_TIMER_STYLE, WebColor(COL_FORM));
|
|
WSContentSend_P(HTTP_FORM_TIMER1, (Settings.flag3.timers_enable) ? " checked" : "");
|
|
for (uint32_t i = 0; i < MAX_TIMERS; i++) {
|
|
WSContentSend_P(PSTR("%s%u"), (i > 0) ? "," : "", Settings.timer[i].data);
|
|
}
|
|
WSContentSend_P(HTTP_FORM_TIMER2);
|
|
#ifdef USE_SUNRISE
|
|
WSContentSend_P(HTTP_FORM_TIMER3, 100 + (strlen(D_SUNSET) *12), GetSun(0).c_str(), GetSun(1).c_str());
|
|
#else
|
|
WSContentSend_P(HTTP_FORM_TIMER3);
|
|
#endif
|
|
WSContentSend_P(HTTP_FORM_TIMER4);
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void TimerSaveSettings(void)
|
|
{
|
|
char tmp[MAX_TIMERS *12];
|
|
char message[LOGSZ];
|
|
Timer timer;
|
|
|
|
Settings.flag3.timers_enable = WebServer->hasArg("e0");
|
|
WebGetArg("t0", tmp, sizeof(tmp));
|
|
char *p = tmp;
|
|
snprintf_P(message, sizeof(message), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable);
|
|
for (uint32_t i = 0; i < MAX_TIMERS; i++) {
|
|
timer.data = strtol(p, &p, 10);
|
|
p++;
|
|
if (timer.time < 1440) {
|
|
bool flag = (timer.window != Settings.timer[i].window);
|
|
Settings.timer[i].data = timer.data;
|
|
if (flag) TimerSetRandomWindow(i);
|
|
}
|
|
snprintf_P(message, sizeof(message), PSTR("%s,0x%08X"), message, Settings.timer[i].data);
|
|
}
|
|
AddLog_P(LOG_LEVEL_DEBUG, message);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv09(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
TimerSetRandomWindows();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef USE_TIMERS_WEB
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
#if defined(USE_RULES) || defined(USE_SCRIPT)
|
|
WSContentSend_P(HTTP_BTN_MENU_TIMER);
|
|
#else
|
|
if (devices_present) { WSContentSend_P(HTTP_BTN_MENU_TIMER); }
|
|
#endif
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/" WEB_HANDLE_TIMER, HandleTimerConfiguration);
|
|
break;
|
|
#endif
|
|
#endif
|
|
case FUNC_EVERY_SECOND:
|
|
TimerEverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kTimerCommands, TimerCommand);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
#ifdef USE_RULES
|
|
#ifndef USE_SCRIPT
|
|
# 67 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
#define XDRV_10 10
|
|
|
|
#define D_CMND_RULE "Rule"
|
|
#define D_CMND_RULETIMER "RuleTimer"
|
|
#define D_CMND_EVENT "Event"
|
|
#define D_CMND_VAR "Var"
|
|
#define D_CMND_MEM "Mem"
|
|
#define D_CMND_ADD "Add"
|
|
#define D_CMND_SUB "Sub"
|
|
#define D_CMND_MULT "Mult"
|
|
#define D_CMND_SCALE "Scale"
|
|
#define D_CMND_CALC_RESOLUTION "CalcRes"
|
|
#define D_CMND_SUBSCRIBE "Subscribe"
|
|
#define D_CMND_UNSUBSCRIBE "Unsubscribe"
|
|
#define D_CMND_IF "If"
|
|
|
|
#define D_JSON_INITIATED "Initiated"
|
|
|
|
#define COMPARE_OPERATOR_NONE -1
|
|
#define COMPARE_OPERATOR_EQUAL 0
|
|
#define COMPARE_OPERATOR_BIGGER 1
|
|
#define COMPARE_OPERATOR_SMALLER 2
|
|
#define COMPARE_OPERATOR_EXACT_DIVISION 3
|
|
#define COMPARE_OPERATOR_NUMBER_EQUAL 4
|
|
#define COMPARE_OPERATOR_NOT_EQUAL 5
|
|
#define COMPARE_OPERATOR_BIGGER_EQUAL 6
|
|
#define COMPARE_OPERATOR_SMALLER_EQUAL 7
|
|
#define MAXIMUM_COMPARE_OPERATOR COMPARE_OPERATOR_SMALLER_EQUAL
|
|
const char kCompareOperators[] PROGMEM = "=\0>\0<\0|\0==!=>=<=";
|
|
|
|
#ifdef USE_EXPRESSION
|
|
#include <LinkedList.h>
|
|
|
|
const char kExpressionOperators[] PROGMEM = "+-*/%^\0";
|
|
#define EXPRESSION_OPERATOR_ADD 0
|
|
#define EXPRESSION_OPERATOR_SUBTRACT 1
|
|
#define EXPRESSION_OPERATOR_MULTIPLY 2
|
|
#define EXPRESSION_OPERATOR_DIVIDEDBY 3
|
|
#define EXPRESSION_OPERATOR_MODULO 4
|
|
#define EXPRESSION_OPERATOR_POWER 5
|
|
|
|
const uint8_t kExpressionOperatorsPriorities[] PROGMEM = {1, 1, 2, 2, 3, 4};
|
|
#define MAX_EXPRESSION_OPERATOR_PRIORITY 4
|
|
|
|
|
|
#define LOGIC_OPERATOR_AND 1
|
|
#define LOGIC_OPERATOR_OR 2
|
|
|
|
#define IF_BLOCK_INVALID -1
|
|
#define IF_BLOCK_ANY 0
|
|
#define IF_BLOCK_ELSEIF 1
|
|
#define IF_BLOCK_ELSE 2
|
|
#define IF_BLOCK_ENDIF 3
|
|
#endif
|
|
|
|
const char kRulesCommands[] PROGMEM = "|"
|
|
D_CMND_RULE "|" D_CMND_RULETIMER "|" D_CMND_EVENT "|" D_CMND_VAR "|" D_CMND_MEM "|"
|
|
D_CMND_ADD "|" D_CMND_SUB "|" D_CMND_MULT "|" D_CMND_SCALE "|" D_CMND_CALC_RESOLUTION
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
"|" D_CMND_SUBSCRIBE "|" D_CMND_UNSUBSCRIBE
|
|
#endif
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
"|" D_CMND_IF
|
|
#endif
|
|
;
|
|
|
|
void (* const RulesCommand[])(void) PROGMEM = {
|
|
&CmndRule, &CmndRuleTimer, &CmndEvent, &CmndVariable, &CmndMemory,
|
|
&CmndAddition, &CmndSubtract, &CmndMultiply, &CmndScale, &CmndCalcResolution
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
, &CmndSubscribe, &CmndUnsubscribe
|
|
#endif
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
, &CmndIf
|
|
#endif
|
|
};
|
|
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
#include <LinkedList.h>
|
|
typedef struct {
|
|
String Event;
|
|
String Topic;
|
|
String Key;
|
|
} MQTT_Subscription;
|
|
LinkedList<MQTT_Subscription> subscriptions;
|
|
#endif
|
|
|
|
struct RULES {
|
|
String event_value;
|
|
unsigned long timer[MAX_RULE_TIMERS] = { 0 };
|
|
uint32_t triggers[MAX_RULE_SETS] = { 0 };
|
|
uint8_t trigger_count[MAX_RULE_SETS] = { 0 };
|
|
|
|
long new_power = -1;
|
|
long old_power = -1;
|
|
long old_dimm = -1;
|
|
|
|
uint16_t last_minute = 60;
|
|
uint16_t vars_event = 0;
|
|
uint8_t mems_event = 0;
|
|
bool teleperiod = false;
|
|
|
|
char event_data[100];
|
|
} Rules;
|
|
|
|
char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
|
|
|
|
#if (MAX_RULE_VARS>16)
|
|
#error MAX_RULE_VARS is bigger than 16
|
|
#endif
|
|
#if (MAX_RULE_MEMS>16)
|
|
#error MAX_RULE_MEMS is bigger than 16
|
|
#endif
|
|
|
|
|
|
|
|
bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
|
|
{
|
|
|
|
|
|
|
|
|
|
bool match = false;
|
|
char stemp[10];
|
|
|
|
|
|
int pos = rule.indexOf('#');
|
|
if (pos == -1) { return false; }
|
|
|
|
String rule_task = rule.substring(0, pos);
|
|
if (Rules.teleperiod) {
|
|
int ppos = rule_task.indexOf("TELE-");
|
|
if (ppos == -1) { return false; }
|
|
rule_task = rule.substring(5, pos);
|
|
}
|
|
|
|
String rule_expr = rule.substring(pos +1);
|
|
String rule_name, rule_param;
|
|
int8_t compareOperator = parseCompareExpression(rule_expr, rule_name, rule_param);
|
|
|
|
char rule_svalue[80] = { 0 };
|
|
float rule_value = 0;
|
|
if (compareOperator != COMPARE_OPERATOR_NONE) {
|
|
for (uint32_t i = 0; i < MAX_RULE_VARS; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%VAR%d%%"), i +1);
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = rules_vars[i];
|
|
break;
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = SettingsText(SET_MEM1 + i);
|
|
break;
|
|
}
|
|
}
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%TIME%%"));
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = String(MinutesPastMidnight());
|
|
}
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%UPTIME%%"));
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = String(MinutesUptime());
|
|
}
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%TIMESTAMP%%"));
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = GetDateAndTime(DT_LOCAL).c_str();
|
|
}
|
|
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%SUNRISE%%"));
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = String(SunMinutes(0));
|
|
}
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%SUNSET%%"));
|
|
if (rule_param.startsWith(stemp)) {
|
|
rule_param = String(SunMinutes(1));
|
|
}
|
|
#endif
|
|
rule_param.toUpperCase();
|
|
strlcpy(rule_svalue, rule_param.c_str(), sizeof(rule_svalue));
|
|
|
|
int temp_value = GetStateNumber(rule_svalue);
|
|
if (temp_value > -1) {
|
|
rule_value = temp_value;
|
|
} else {
|
|
rule_value = CharToFloat((char*)rule_svalue);
|
|
}
|
|
}
|
|
|
|
|
|
int rule_name_idx = 0;
|
|
if ((pos = rule_name.indexOf("[")) > 0) {
|
|
rule_name_idx = rule_name.substring(pos +1).toInt();
|
|
if ((rule_name_idx < 1) || (rule_name_idx > 6)) {
|
|
rule_name_idx = 1;
|
|
}
|
|
rule_name = rule_name.substring(0, pos);
|
|
}
|
|
|
|
StaticJsonBuffer<1024> jsonBuf;
|
|
JsonObject &root = jsonBuf.parseObject(event);
|
|
if (!root.success()) { return false; }
|
|
if (!root[rule_task].success()) { return false; }
|
|
|
|
JsonObject &obj1 = root[rule_task];
|
|
JsonObject *obj = &obj1;
|
|
String subtype;
|
|
uint32_t i = 0;
|
|
while ((pos = rule_name.indexOf("#")) > 0) {
|
|
subtype = rule_name.substring(0, pos);
|
|
if (!(*obj)[subtype].success()) { return false; }
|
|
JsonObject &obj2 = (*obj)[subtype];
|
|
obj = &obj2;
|
|
rule_name = rule_name.substring(pos +1);
|
|
if (i++ > 10) { return false; }
|
|
}
|
|
if (!(*obj)[rule_name].success()) { return false; }
|
|
const char* str_value;
|
|
if (rule_name_idx) {
|
|
str_value = (*obj)[rule_name][rule_name_idx -1];
|
|
} else {
|
|
str_value = (*obj)[rule_name];
|
|
}
|
|
|
|
|
|
|
|
|
|
Rules.event_value = str_value;
|
|
|
|
|
|
float value = 0;
|
|
if (str_value) {
|
|
value = CharToFloat((char*)str_value);
|
|
int int_value = int(value);
|
|
int int_rule_value = int(rule_value);
|
|
switch (compareOperator) {
|
|
case COMPARE_OPERATOR_EXACT_DIVISION:
|
|
match = (int_rule_value && (int_value % int_rule_value) == 0);
|
|
break;
|
|
case COMPARE_OPERATOR_EQUAL:
|
|
match = (!strcasecmp(str_value, rule_svalue));
|
|
break;
|
|
case COMPARE_OPERATOR_BIGGER:
|
|
match = (value > rule_value);
|
|
break;
|
|
case COMPARE_OPERATOR_SMALLER:
|
|
match = (value < rule_value);
|
|
break;
|
|
case COMPARE_OPERATOR_NUMBER_EQUAL:
|
|
match = (value == rule_value);
|
|
break;
|
|
case COMPARE_OPERATOR_NOT_EQUAL:
|
|
match = (value != rule_value);
|
|
break;
|
|
case COMPARE_OPERATOR_BIGGER_EQUAL:
|
|
match = (value >= rule_value);
|
|
break;
|
|
case COMPARE_OPERATOR_SMALLER_EQUAL:
|
|
match = (value <= rule_value);
|
|
break;
|
|
default:
|
|
match = true;
|
|
}
|
|
} else match = true;
|
|
|
|
if (bitRead(Settings.rule_once, rule_set)) {
|
|
if (match) {
|
|
if (!bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set])) {
|
|
bitSet(Rules.triggers[rule_set], Rules.trigger_count[rule_set]);
|
|
} else {
|
|
match = false;
|
|
}
|
|
} else {
|
|
bitClear(Rules.triggers[rule_set], Rules.trigger_count[rule_set]);
|
|
}
|
|
}
|
|
|
|
return match;
|
|
}
|
|
# 363 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
int8_t parseCompareExpression(String &expr, String &leftExpr, String &rightExpr)
|
|
{
|
|
char compare_operator[3];
|
|
int8_t compare = COMPARE_OPERATOR_NONE;
|
|
leftExpr = expr;
|
|
int position;
|
|
for (int8_t i = MAXIMUM_COMPARE_OPERATOR; i >= 0; i--) {
|
|
snprintf_P(compare_operator, sizeof(compare_operator), kCompareOperators + (i *2));
|
|
if ((position = expr.indexOf(compare_operator)) > 0) {
|
|
compare = i;
|
|
leftExpr = expr.substring(0, position);
|
|
leftExpr.trim();
|
|
rightExpr = expr.substring(position + strlen(compare_operator));
|
|
rightExpr.trim();
|
|
break;
|
|
}
|
|
}
|
|
return compare;
|
|
}
|
|
|
|
void RulesVarReplace(String &commands, const String &sfind, const String &replace)
|
|
{
|
|
|
|
|
|
|
|
char *find = (char*)sfind.c_str();
|
|
uint32_t flen = strlen(find);
|
|
|
|
String ucommand = commands;
|
|
ucommand.toUpperCase();
|
|
char *read_from = (char*)ucommand.c_str();
|
|
char *write_to = (char*)commands.c_str();
|
|
char *found_at;
|
|
while ((found_at = strstr(read_from, find)) != nullptr) {
|
|
write_to += (found_at - read_from);
|
|
memmove_P(write_to, find, flen);
|
|
write_to += flen;
|
|
read_from = found_at + flen;
|
|
}
|
|
|
|
commands.replace(find, replace);
|
|
}
|
|
|
|
|
|
|
|
bool RuleSetProcess(uint8_t rule_set, String &event_saved)
|
|
{
|
|
bool serviced = false;
|
|
char stemp[10];
|
|
|
|
delay(0);
|
|
|
|
|
|
|
|
String rules = Settings.rules[rule_set];
|
|
|
|
Rules.trigger_count[rule_set] = 0;
|
|
int plen = 0;
|
|
int plen2 = 0;
|
|
bool stop_all_rules = false;
|
|
while (true) {
|
|
rules = rules.substring(plen);
|
|
rules.trim();
|
|
if (!rules.length()) { return serviced; }
|
|
|
|
String rule = rules;
|
|
rule.toUpperCase();
|
|
if (!rule.startsWith("ON ")) { return serviced; }
|
|
|
|
int pevt = rule.indexOf(" DO ");
|
|
if (pevt == -1) { return serviced; }
|
|
String event_trigger = rule.substring(3, pevt);
|
|
|
|
plen = rule.indexOf(" ENDON");
|
|
plen2 = rule.indexOf(" BREAK");
|
|
if ((plen == -1) && (plen2 == -1)) { return serviced; }
|
|
|
|
if (plen == -1) { plen = 9999; }
|
|
if (plen2 == -1) { plen2 = 9999; }
|
|
plen = tmin(plen, plen2);
|
|
|
|
String commands = rules.substring(pevt +4, plen);
|
|
Rules.event_value = "";
|
|
String event = event_saved;
|
|
|
|
|
|
|
|
if (RulesRuleMatch(rule_set, event, event_trigger)) {
|
|
if (plen == plen2) { stop_all_rules = true; }
|
|
commands.trim();
|
|
String ucommand = commands;
|
|
ucommand.toUpperCase();
|
|
|
|
|
|
|
|
if ((ucommand.indexOf("IF ") == -1) &&
|
|
(ucommand.indexOf("EVENT ") != -1) &&
|
|
(ucommand.indexOf("BACKLOG ") == -1)) {
|
|
commands = "backlog " + commands;
|
|
}
|
|
|
|
RulesVarReplace(commands, F("%VALUE%"), Rules.event_value);
|
|
for (uint32_t i = 0; i < MAX_RULE_VARS; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%VAR%d%%"), i +1);
|
|
RulesVarReplace(commands, stemp, rules_vars[i]);
|
|
}
|
|
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
|
|
RulesVarReplace(commands, stemp, SettingsText(SET_MEM1 +i));
|
|
}
|
|
RulesVarReplace(commands, F("%TIME%"), String(MinutesPastMidnight()));
|
|
RulesVarReplace(commands, F("%UPTIME%"), String(MinutesUptime()));
|
|
RulesVarReplace(commands, F("%TIMESTAMP%"), GetDateAndTime(DT_LOCAL));
|
|
RulesVarReplace(commands, F("%TOPIC%"), SettingsText(SET_MQTT_TOPIC));
|
|
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
|
|
RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0)));
|
|
RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1)));
|
|
#endif
|
|
|
|
char command[commands.length() +1];
|
|
strlcpy(command, commands.c_str(), sizeof(command));
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("RUL: %s performs \"%s\""), event_trigger.c_str(), command);
|
|
|
|
|
|
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
char *pCmd = command;
|
|
RulesPreprocessCommand(pCmd);
|
|
#endif
|
|
ExecuteCommand(command, SRC_RULE);
|
|
serviced = true;
|
|
if (stop_all_rules) { return serviced; }
|
|
}
|
|
plen += 6;
|
|
Rules.trigger_count[rule_set]++;
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
bool RulesProcessEvent(char *json_event)
|
|
{
|
|
bool serviced = false;
|
|
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("RulesProcessEvent"));
|
|
#endif
|
|
|
|
String event_saved = json_event;
|
|
|
|
|
|
|
|
char *p = strchr(json_event, ':');
|
|
if ((p != NULL) && !(strchr(++p, ':'))) {
|
|
event_saved.replace(F(":"), F(":{\"Data\":"));
|
|
event_saved += F("}");
|
|
|
|
}
|
|
event_saved.toUpperCase();
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) {
|
|
if (strlen(Settings.rules[i]) && bitRead(Settings.rule_enabled, i)) {
|
|
if (RuleSetProcess(i, event_saved)) { serviced = true; }
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
bool RulesProcess(void)
|
|
{
|
|
return RulesProcessEvent(mqtt_data);
|
|
}
|
|
|
|
void RulesInit(void)
|
|
{
|
|
rules_flag.data = 0;
|
|
for (uint32_t i = 0; i < MAX_RULE_SETS; i++) {
|
|
if (Settings.rules[i][0] == '\0') {
|
|
bitWrite(Settings.rule_enabled, i, 0);
|
|
bitWrite(Settings.rule_once, i, 0);
|
|
}
|
|
}
|
|
Rules.teleperiod = false;
|
|
}
|
|
|
|
void RulesEvery50ms(void)
|
|
{
|
|
if (Settings.rule_enabled) {
|
|
char json_event[120];
|
|
|
|
if (-1 == Rules.new_power) { Rules.new_power = power; }
|
|
if (Rules.new_power != Rules.old_power) {
|
|
if (Rules.old_power != -1) {
|
|
for (uint32_t i = 0; i < devices_present; i++) {
|
|
uint8_t new_state = (Rules.new_power >> i) &1;
|
|
if (new_state != ((Rules.old_power >> i) &1)) {
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"State\":%d}}"), i +1, new_state);
|
|
RulesProcessEvent(json_event);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
for (uint32_t i = 0; i < devices_present; i++) {
|
|
uint8_t new_state = (Rules.new_power >> i) &1;
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Power%d\":{\"Boot\":%d}}"), i +1, new_state);
|
|
RulesProcessEvent(json_event);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
#ifdef USE_TM1638
|
|
if ((pin[GPIO_SWT1 +i] < 99) || ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99))) {
|
|
#else
|
|
if (pin[GPIO_SWT1 +i] < 99) {
|
|
#endif
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (SwitchState(i)));
|
|
RulesProcessEvent(json_event);
|
|
}
|
|
}
|
|
}
|
|
Rules.old_power = Rules.new_power;
|
|
}
|
|
else if (Rules.old_dimm != Settings.light_dimmer) {
|
|
if (Rules.old_dimm != -1) {
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Dimmer\":{\"State\":%d}}"), Settings.light_dimmer);
|
|
} else {
|
|
|
|
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Dimmer\":{\"Boot\":%d}}"), Settings.light_dimmer);
|
|
}
|
|
RulesProcessEvent(json_event);
|
|
Rules.old_dimm = Settings.light_dimmer;
|
|
}
|
|
else if (Rules.event_data[0]) {
|
|
char *event;
|
|
char *parameter;
|
|
event = strtok_r(Rules.event_data, "=", ¶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<float> object_values;
|
|
LinkedList<int8_t> operators;
|
|
int8_t op;
|
|
float va;
|
|
|
|
if (findNextObjectValue(scan_pointer, va)) {
|
|
object_values.add(va);
|
|
} else {
|
|
return 0;
|
|
}
|
|
while (*scan_pointer)
|
|
{
|
|
if (findNextOperator(scan_pointer, op)
|
|
&& *scan_pointer
|
|
&& findNextObjectValue(scan_pointer, va))
|
|
{
|
|
operators.add(op);
|
|
object_values.add(va);
|
|
} else {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for (int32_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY; priority>0; priority--) {
|
|
int index = 0;
|
|
while (index < operators.size()) {
|
|
if (priority == pgm_read_byte(kExpressionOperatorsPriorities + operators.get(index))) {
|
|
|
|
va = calculateTwoValues(object_values.get(index), object_values.remove(index + 1), operators.remove(index));
|
|
|
|
object_values.set(index, va);
|
|
} else {
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
return object_values.get(0);
|
|
}
|
|
#endif
|
|
|
|
#ifdef SUPPORT_IF_STATEMENT
|
|
void CmndIf(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char parameters[XdrvMailbox.data_len+1];
|
|
memcpy(parameters, XdrvMailbox.data, XdrvMailbox.data_len);
|
|
parameters[XdrvMailbox.data_len] = '\0';
|
|
ProcessIfStatement(parameters);
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
# 1273 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
bool evaluateComparisonExpression(const char *expression, int len)
|
|
{
|
|
bool bResult = true;
|
|
char expbuf[len + 1];
|
|
memcpy(expbuf, expression, len);
|
|
expbuf[len] = '\0';
|
|
String compare_expression = expbuf;
|
|
String leftExpr, rightExpr;
|
|
int8_t compareOp = parseCompareExpression(compare_expression, leftExpr, rightExpr);
|
|
|
|
double leftValue = evaluateExpression(leftExpr.c_str(), leftExpr.length());
|
|
double rightValue = evaluateExpression(rightExpr.c_str(), rightExpr.length());
|
|
switch (compareOp) {
|
|
case COMPARE_OPERATOR_EXACT_DIVISION:
|
|
bResult = (rightValue != 0 && leftValue == int(leftValue)
|
|
&& rightValue == int(rightValue) && (int(leftValue) % int(rightValue)) == 0);
|
|
break;
|
|
case COMPARE_OPERATOR_EQUAL:
|
|
bResult = leftExpr.equalsIgnoreCase(rightExpr);
|
|
break;
|
|
case COMPARE_OPERATOR_BIGGER:
|
|
bResult = (leftValue > rightValue);
|
|
break;
|
|
case COMPARE_OPERATOR_SMALLER:
|
|
bResult = (leftValue < rightValue);
|
|
break;
|
|
case COMPARE_OPERATOR_NUMBER_EQUAL:
|
|
bResult = (leftValue == rightValue);
|
|
break;
|
|
case COMPARE_OPERATOR_NOT_EQUAL:
|
|
bResult = (leftValue != rightValue);
|
|
break;
|
|
case COMPARE_OPERATOR_BIGGER_EQUAL:
|
|
bResult = (leftValue >= rightValue);
|
|
break;
|
|
case COMPARE_OPERATOR_SMALLER_EQUAL:
|
|
bResult = (leftValue <= rightValue);
|
|
break;
|
|
}
|
|
return bResult;
|
|
}
|
|
# 1329 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
bool findNextLogicOperator(char * &pointer, int8_t &op)
|
|
{
|
|
bool bSucceed = false;
|
|
while (*pointer && isspace(*pointer)) {
|
|
|
|
pointer++;
|
|
}
|
|
if (*pointer) {
|
|
if (strncasecmp_P(pointer, PSTR("AND "), 4) == 0) {
|
|
op = LOGIC_OPERATOR_AND;
|
|
pointer += 4;
|
|
bSucceed = true;
|
|
} else if (strncasecmp_P(pointer, PSTR("OR "), 3) == 0) {
|
|
op = LOGIC_OPERATOR_OR;
|
|
pointer += 3;
|
|
bSucceed = true;
|
|
}
|
|
}
|
|
return bSucceed;
|
|
}
|
|
# 1366 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
bool findNextLogicObjectValue(char * &pointer, bool &value)
|
|
{
|
|
bool bSucceed = false;
|
|
while (*pointer && isspace(*pointer)) {
|
|
|
|
pointer++;
|
|
}
|
|
char * pExpr = pointer;
|
|
while (*pointer) {
|
|
if (isalpha(*pointer)
|
|
&& (strncasecmp_P(pointer, PSTR("AND "), 4) == 0
|
|
|| strncasecmp_P(pointer, PSTR("OR "), 3) == 0))
|
|
{
|
|
value = evaluateComparisonExpression(pExpr, pointer - pExpr);
|
|
bSucceed = true;
|
|
break;
|
|
} else if (*pointer == '(') {
|
|
char * closureBracket = findClosureBracket(pointer);
|
|
if (closureBracket != nullptr) {
|
|
value = evaluateLogicalExpression(pointer+1, closureBracket - pointer - 1);
|
|
pointer = closureBracket + 1;
|
|
bSucceed = true;
|
|
}
|
|
break;
|
|
}
|
|
pointer++;
|
|
}
|
|
if (!bSucceed && pointer > pExpr) {
|
|
|
|
value = evaluateComparisonExpression(pExpr, pointer - pExpr);
|
|
bSucceed = true;
|
|
}
|
|
return bSucceed;
|
|
}
|
|
# 1415 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
bool evaluateLogicalExpression(const char * expression, int len)
|
|
{
|
|
bool bResult = false;
|
|
|
|
char expbuff[len + 1];
|
|
memcpy(expbuff, expression, len);
|
|
expbuff[len] = '\0';
|
|
|
|
|
|
char * pointer = expbuff;
|
|
LinkedList<bool> values;
|
|
LinkedList<int8_t> logicOperators;
|
|
|
|
bool bValue;
|
|
if (findNextLogicObjectValue(pointer, bValue)) {
|
|
values.add(bValue);
|
|
} else {
|
|
return false;
|
|
}
|
|
int8_t op;
|
|
while (*pointer) {
|
|
if (findNextLogicOperator(pointer, op)
|
|
&& (*pointer) && findNextLogicObjectValue(pointer, bValue))
|
|
{
|
|
logicOperators.add(op);
|
|
values.add(bValue);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
int index = 0;
|
|
while (index < logicOperators.size()) {
|
|
if (logicOperators.get(index) == LOGIC_OPERATOR_AND) {
|
|
values.set(index, values.get(index) && values.get(index+1));
|
|
values.remove(index + 1);
|
|
logicOperators.remove(index);
|
|
} else {
|
|
index++;
|
|
}
|
|
}
|
|
|
|
index = 0;
|
|
while (index < logicOperators.size()) {
|
|
if (logicOperators.get(index) == LOGIC_OPERATOR_OR) {
|
|
values.set(index, values.get(index) || values.get(index+1));
|
|
values.remove(index + 1);
|
|
logicOperators.remove(index);
|
|
} else {
|
|
index++;
|
|
}
|
|
}
|
|
return values.get(0);
|
|
}
|
|
# 1486 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
int8_t findIfBlock(char * &pointer, int &lenWord, int8_t block_type)
|
|
{
|
|
int8_t foundBlock = IF_BLOCK_INVALID;
|
|
|
|
const char * word;
|
|
while (*pointer) {
|
|
if (!isalpha(*pointer)) {
|
|
pointer++;
|
|
continue;
|
|
}
|
|
word = pointer;
|
|
while (*pointer && isalpha(*pointer)) {
|
|
pointer++;
|
|
}
|
|
lenWord = pointer - word;
|
|
|
|
if (2 == lenWord && 0 == strncasecmp_P(word, PSTR("IF"), 2)) {
|
|
|
|
|
|
if (findIfBlock(pointer, lenWord, IF_BLOCK_ENDIF) != IF_BLOCK_ENDIF) {
|
|
|
|
break;
|
|
}
|
|
} else if ( (IF_BLOCK_ENDIF == block_type || IF_BLOCK_ANY == block_type)
|
|
&& (5 == lenWord) && (0 == strncasecmp_P(word, PSTR("ENDIF"), 5)))
|
|
{
|
|
|
|
foundBlock = IF_BLOCK_ENDIF;
|
|
break;
|
|
} else if ( (IF_BLOCK_ELSEIF == block_type || IF_BLOCK_ANY == block_type)
|
|
&& (6 == lenWord) && (0 == strncasecmp_P(word, PSTR("ELSEIF"), 6)))
|
|
{
|
|
|
|
foundBlock = IF_BLOCK_ELSEIF;
|
|
break;
|
|
} else if ( (IF_BLOCK_ELSE == block_type || IF_BLOCK_ANY == block_type)
|
|
&& (4 == lenWord) && (0 == strncasecmp_P(word, PSTR("ELSE"), 4)))
|
|
{
|
|
|
|
foundBlock = IF_BLOCK_ELSE;
|
|
break;
|
|
}
|
|
}
|
|
return foundBlock;
|
|
}
|
|
# 1543 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
void ExecuteCommandBlock(const char * commands, int len)
|
|
{
|
|
char cmdbuff[len + 1];
|
|
memcpy(cmdbuff, commands, len);
|
|
cmdbuff[len] = '\0';
|
|
|
|
|
|
char oneCommand[len + 1];
|
|
int insertPosition = 0;
|
|
char * pos = cmdbuff;
|
|
int lenEndBlock = 0;
|
|
while (*pos) {
|
|
if (isspace(*pos) || '\x1e' == *pos || ';' == *pos) {
|
|
pos++;
|
|
continue;
|
|
}
|
|
if (strncasecmp_P(pos, PSTR("BACKLOG "), 8) == 0) {
|
|
|
|
pos += 8;
|
|
continue;
|
|
}
|
|
if (strncasecmp_P(pos, PSTR("IF "), 3) == 0) {
|
|
|
|
|
|
char *pEndif = pos + 3;
|
|
if (IF_BLOCK_ENDIF != findIfBlock(pEndif, lenEndBlock, IF_BLOCK_ENDIF)) {
|
|
|
|
break;
|
|
}
|
|
|
|
memcpy(oneCommand, pos, pEndif - pos);
|
|
oneCommand[pEndif - pos] = '\0';
|
|
pos = pEndif;
|
|
} else {
|
|
|
|
char *pEndOfCommand = strpbrk(pos, "\x1e;");
|
|
if (NULL == pEndOfCommand) {
|
|
pEndOfCommand = pos + strlen(pos);
|
|
}
|
|
memcpy(oneCommand, pos, pEndOfCommand - pos);
|
|
oneCommand[pEndOfCommand - pos] = '\0';
|
|
pos = pEndOfCommand;
|
|
}
|
|
|
|
|
|
String sCurrentCommand = oneCommand;
|
|
sCurrentCommand.trim();
|
|
if (sCurrentCommand.length() > 0
|
|
&& backlog.size() < MAX_BACKLOG && !backlog_mutex)
|
|
{
|
|
|
|
backlog_mutex = true;
|
|
backlog.add(insertPosition, sCurrentCommand);
|
|
backlog_mutex = false;
|
|
insertPosition++;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
# 1613 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
void ProcessIfStatement(const char* statements)
|
|
{
|
|
String conditionExpression;
|
|
int len = strlen(statements);
|
|
char statbuff[len + 1];
|
|
memcpy(statbuff, statements, len + 1);
|
|
char *pos = statbuff;
|
|
int lenEndBlock = 0;
|
|
while (true) {
|
|
|
|
|
|
while (*pos && *pos != '(') {
|
|
pos++;
|
|
}
|
|
if (0 == *pos) { break; }
|
|
char * posEnd = findClosureBracket(pos);
|
|
|
|
if (true == evaluateLogicalExpression(pos + 1, posEnd - (pos + 1))) {
|
|
|
|
char * cmdBlockStart = posEnd + 1;
|
|
char * cmdBlockEnd = cmdBlockStart;
|
|
int8_t nextBlock = findIfBlock(cmdBlockEnd, lenEndBlock, IF_BLOCK_ANY);
|
|
if (IF_BLOCK_INVALID == nextBlock) {
|
|
|
|
break;
|
|
}
|
|
ExecuteCommandBlock(cmdBlockStart, cmdBlockEnd - cmdBlockStart - lenEndBlock);
|
|
pos = cmdBlockEnd;
|
|
break;
|
|
} else {
|
|
pos = posEnd + 1;
|
|
int8_t nextBlock = findIfBlock(pos, lenEndBlock, IF_BLOCK_ANY);
|
|
if (IF_BLOCK_ELSEIF == nextBlock) {
|
|
|
|
continue;
|
|
} else if (IF_BLOCK_ELSE == nextBlock) {
|
|
|
|
char * cmdBlockEnd = pos;
|
|
int8_t nextBlock = findIfBlock(cmdBlockEnd, lenEndBlock, IF_BLOCK_ENDIF);
|
|
if (IF_BLOCK_ENDIF != nextBlock) {
|
|
|
|
break;
|
|
}
|
|
ExecuteCommandBlock(pos, cmdBlockEnd - pos - lenEndBlock);
|
|
break;
|
|
} else {
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
# 1677 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_rules.ino"
|
|
void RulesPreprocessCommand(char *pCommands)
|
|
{
|
|
char * cmd = pCommands;
|
|
int lenEndBlock = 0;
|
|
while (*cmd) {
|
|
|
|
if (';' == *cmd || isspace(*cmd)) {
|
|
cmd++;
|
|
}
|
|
else if (strncasecmp_P(cmd, PSTR("IF "), 3) == 0) {
|
|
|
|
char * pIfStart = cmd;
|
|
char * pIfEnd = pIfStart + 3;
|
|
|
|
if (IF_BLOCK_ENDIF == findIfBlock(pIfEnd, lenEndBlock, IF_BLOCK_ENDIF)) {
|
|
|
|
cmd = pIfEnd;
|
|
|
|
|
|
while (pIfStart < pIfEnd) {
|
|
if (';' == *pIfStart)
|
|
*pIfStart = '\x1e';
|
|
pIfStart++;
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
while (*cmd && ';' != *cmd) {
|
|
cmd++;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void CmndRule(void)
|
|
{
|
|
uint8_t index = XdrvMailbox.index;
|
|
if ((index > 0) && (index <= MAX_RULE_SETS)) {
|
|
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.rules[index -1]))) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 10)) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
case 1:
|
|
bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload);
|
|
break;
|
|
case 2:
|
|
bitWrite(Settings.rule_enabled, index -1, bitRead(Settings.rule_enabled, index -1) ^1);
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
bitWrite(Settings.rule_once, index -1, XdrvMailbox.payload &1);
|
|
break;
|
|
case 6:
|
|
bitWrite(Settings.rule_once, index -1, bitRead(Settings.rule_once, index -1) ^1);
|
|
break;
|
|
case 8:
|
|
case 9:
|
|
bitWrite(Settings.rule_stop, index -1, XdrvMailbox.payload &1);
|
|
break;
|
|
case 10:
|
|
bitWrite(Settings.rule_stop, index -1, bitRead(Settings.rule_stop, index -1) ^1);
|
|
break;
|
|
}
|
|
} else {
|
|
int offset = 0;
|
|
if ('+' == XdrvMailbox.data[0]) {
|
|
offset = strlen(Settings.rules[index -1]);
|
|
if (XdrvMailbox.data_len < (sizeof(Settings.rules[index -1]) - offset -1)) {
|
|
XdrvMailbox.data[0] = ' ';
|
|
} else {
|
|
offset = -1;
|
|
}
|
|
}
|
|
if (offset != -1) {
|
|
strlcpy(Settings.rules[index -1] + offset, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.rules[index -1]));
|
|
}
|
|
}
|
|
Rules.triggers[index -1] = 0;
|
|
}
|
|
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s%d\":\"%s\",\"Once\":\"%s\",\"StopOnError\":\"%s\",\"Free\":%d,\"Rules\":\"%s\"}"),
|
|
XdrvMailbox.command, index, GetStateText(bitRead(Settings.rule_enabled, index -1)), GetStateText(bitRead(Settings.rule_once, index -1)),
|
|
GetStateText(bitRead(Settings.rule_stop, index -1)), sizeof(Settings.rules[index -1]) - strlen(Settings.rules[index -1]) -1, Settings.rules[index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndRuleTimer(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_TIMERS)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
#ifdef USE_EXPRESSION
|
|
float timer_set = evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
Rules.timer[XdrvMailbox.index -1] = (timer_set > 0) ? millis() + (1000 * timer_set) : 0;
|
|
#else
|
|
Rules.timer[XdrvMailbox.index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0;
|
|
#endif
|
|
}
|
|
mqtt_data[0] = '\0';
|
|
for (uint32_t i = 0; i < MAX_RULE_TIMERS; i++) {
|
|
ResponseAppend_P(PSTR("%c\"T%d\":%d"), (i) ? ',' : '{', i +1, (Rules.timer[i]) ? (Rules.timer[i] - millis()) / 1000 : 0);
|
|
}
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
void CmndEvent(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
strlcpy(Rules.event_data, XdrvMailbox.data, sizeof(Rules.event_data));
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndVariable(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
mqtt_data[0] = '\0';
|
|
for (uint32_t i = 0; i < MAX_RULE_VARS; i++) {
|
|
ResponseAppend_P(PSTR("%c\"Var%d\":\"%s\""), (i) ? ',' : '{', i +1, rules_vars[i]);
|
|
}
|
|
ResponseJsonEnd();
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
#ifdef USE_EXPRESSION
|
|
if (XdrvMailbox.data[0] == '=') {
|
|
dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]);
|
|
} else {
|
|
strlcpy(rules_vars[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(rules_vars[XdrvMailbox.index -1]));
|
|
}
|
|
#else
|
|
strlcpy(rules_vars[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(rules_vars[XdrvMailbox.index -1]));
|
|
#endif
|
|
bitSet(Rules.vars_event, XdrvMailbox.index -1);
|
|
}
|
|
ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndMemory(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_MEMS)) {
|
|
if (!XdrvMailbox.usridx) {
|
|
ResponseCmndAll(SET_MEM1, MAX_RULE_MEMS);
|
|
} else {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
#ifdef USE_EXPRESSION
|
|
if (XdrvMailbox.data[0] == '=') {
|
|
dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, SettingsText(SET_MEM1 + XdrvMailbox.index -1));
|
|
} else {
|
|
SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
|
|
}
|
|
#else
|
|
SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
|
|
#endif
|
|
bitSet(Rules.mems_event, XdrvMailbox.index -1);
|
|
}
|
|
ResponseCmndIdxChar(SettingsText(SET_MEM1 + XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndCalcResolution(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 7)) {
|
|
Settings.flag2.calc_resolution = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.flag2.calc_resolution);
|
|
}
|
|
|
|
void CmndAddition(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
float tempvar = CharToFloat(rules_vars[XdrvMailbox.index -1]) + CharToFloat(XdrvMailbox.data);
|
|
dtostrfd(tempvar, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]);
|
|
bitSet(Rules.vars_event, XdrvMailbox.index -1);
|
|
}
|
|
ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndSubtract(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
float tempvar = CharToFloat(rules_vars[XdrvMailbox.index -1]) - CharToFloat(XdrvMailbox.data);
|
|
dtostrfd(tempvar, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]);
|
|
bitSet(Rules.vars_event, XdrvMailbox.index -1);
|
|
}
|
|
ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndMultiply(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
float tempvar = CharToFloat(rules_vars[XdrvMailbox.index -1]) * CharToFloat(XdrvMailbox.data);
|
|
dtostrfd(tempvar, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]);
|
|
bitSet(Rules.vars_event, XdrvMailbox.index -1);
|
|
}
|
|
ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndScale(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_VARS)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
char sub_string[XdrvMailbox.data_len +1];
|
|
|
|
float valueIN = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 1));
|
|
float fromLow = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
float fromHigh = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
float toLow = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4));
|
|
float toHigh = CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 5));
|
|
float value = map_double(valueIN, fromLow, fromHigh, toLow, toHigh);
|
|
dtostrfd(value, Settings.flag2.calc_resolution, rules_vars[XdrvMailbox.index -1]);
|
|
bitSet(Rules.vars_event, XdrvMailbox.index -1);
|
|
}
|
|
}
|
|
ResponseCmndIdxChar(rules_vars[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
float map_double(float x, float in_min, float in_max, float out_min, float out_max)
|
|
{
|
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv10(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
RulesEvery50ms();
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
RulesEvery100ms();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
RulesEverySecond();
|
|
break;
|
|
case FUNC_SET_POWER:
|
|
RulesSetPower();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kRulesCommands, RulesCommand);
|
|
break;
|
|
case FUNC_RULES_PROCESS:
|
|
result = RulesProcess();
|
|
break;
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
RulesSaveBeforeRestart();
|
|
break;
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
case FUNC_MQTT_DATA:
|
|
result = RulesMqttData();
|
|
break;
|
|
#endif
|
|
case FUNC_PRE_INIT:
|
|
RulesInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
#ifdef USE_SCRIPT
|
|
#ifndef USE_RULES
|
|
# 40 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
#define XDRV_10 10
|
|
#define XI2C_37 37
|
|
|
|
#define SCRIPT_DEBUG 0
|
|
|
|
|
|
#ifndef MAXVARS
|
|
#define MAXVARS 50
|
|
#endif
|
|
#ifndef MAXSVARS
|
|
#define MAXSVARS 5
|
|
#endif
|
|
#define MAXNVARS MAXVARS-MAXSVARS
|
|
|
|
#define MAXFILT 5
|
|
#define SCRIPT_SVARSIZE 20
|
|
#define SCRIPT_MAXSSIZE 48
|
|
#define SCRIPT_EOL '\n'
|
|
#define SCRIPT_FLOAT_PRECISION 2
|
|
#define PMEM_SIZE sizeof(Settings.script_pram)
|
|
#define SCRIPT_MAXPERM (PMEM_SIZE)-4/sizeof(float)
|
|
#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS
|
|
|
|
|
|
#define EPOCH_OFFSET 1546300800
|
|
|
|
enum {OPER_EQU=1,OPER_PLS,OPER_MIN,OPER_MUL,OPER_DIV,OPER_PLSEQU,OPER_MINEQU,OPER_MULEQU,OPER_DIVEQU,OPER_EQUEQU,OPER_NOTEQU,OPER_GRTEQU,OPER_LOWEQU,OPER_GRT,OPER_LOW,OPER_PERC,OPER_XOR,OPER_AND,OPER_OR,OPER_ANDEQU,OPER_OREQU,OPER_XOREQU,OPER_PERCEQU};
|
|
enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD};
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
#include <SPI.h>
|
|
#include <SD.h>
|
|
#ifndef FAT_SCRIPT_SIZE
|
|
#define FAT_SCRIPT_SIZE 4096
|
|
#endif
|
|
#define FAT_SCRIPT_NAME "script.txt"
|
|
#if USE_LONG_FILE_NAMES==1
|
|
#warning ("FATFS long filenames not supported");
|
|
#endif
|
|
#if USE_STANDARD_SPI_LIBRARY==0
|
|
#warning ("FATFS standard spi should be used");
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
#include <LinkedList.h>
|
|
typedef struct {
|
|
String Event;
|
|
String Topic;
|
|
String Key;
|
|
} MQTT_Subscription;
|
|
LinkedList<MQTT_Subscription> subscriptions;
|
|
#endif
|
|
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
#include <renderer.h>
|
|
extern VButton *buttons[MAXBUTTONS];
|
|
#endif
|
|
#endif
|
|
|
|
typedef union {
|
|
uint8_t data;
|
|
struct {
|
|
uint8_t is_string : 1;
|
|
uint8_t is_permanent : 1;
|
|
uint8_t is_timer : 1;
|
|
uint8_t is_autoinc : 1;
|
|
uint8_t changed : 1;
|
|
uint8_t settable : 1;
|
|
uint8_t is_filter : 1;
|
|
uint8_t constant : 1;
|
|
};
|
|
} SCRIPT_TYPE;
|
|
|
|
struct T_INDEX {
|
|
uint8_t index;
|
|
SCRIPT_TYPE bits;
|
|
};
|
|
|
|
struct M_FILT {
|
|
uint8_t numvals;
|
|
uint8_t index;
|
|
float maccu;
|
|
float rbuff[1];
|
|
};
|
|
|
|
typedef union {
|
|
uint8_t data;
|
|
struct {
|
|
uint8_t nutu8 : 1;
|
|
uint8_t nutu7 : 1;
|
|
uint8_t nutu6 : 1;
|
|
uint8_t nutu5 : 1;
|
|
uint8_t nutu4 : 1;
|
|
uint8_t nutu3 : 1;
|
|
uint8_t is_dir : 1;
|
|
uint8_t is_open : 1;
|
|
};
|
|
} FILE_FLAGS;
|
|
|
|
#define SFS_MAX 4
|
|
|
|
struct SCRIPT_MEM {
|
|
float *fvars;
|
|
float *s_fvars;
|
|
struct T_INDEX *type;
|
|
struct M_FILT *mfilt;
|
|
char *glob_vnp;
|
|
uint8_t *vnp_offset;
|
|
char *glob_snp;
|
|
char *scriptptr;
|
|
char *section_ptr;
|
|
char *scriptptr_bu;
|
|
char *script_ram;
|
|
uint16_t script_size;
|
|
uint8_t *script_pram;
|
|
uint16_t script_pram_size;
|
|
uint8_t numvars;
|
|
void *script_mem;
|
|
uint16_t script_mem_size;
|
|
uint8_t script_dprec;
|
|
uint8_t var_not_found;
|
|
uint8_t glob_error;
|
|
uint8_t max_ssize;
|
|
uint8_t script_loglevel;
|
|
uint8_t flags;
|
|
#ifdef USE_SCRIPT_FATFS
|
|
File files[SFS_MAX];
|
|
FILE_FLAGS file_flags[SFS_MAX];
|
|
uint8_t script_sd_found;
|
|
char flink[2][14];
|
|
#endif
|
|
} glob_script_mem;
|
|
|
|
|
|
int16_t last_findex;
|
|
uint8_t tasm_cmd_activ=0;
|
|
uint8_t fast_script=0;
|
|
uint32_t script_lastmillis;
|
|
|
|
|
|
#ifdef USE_BUTTON_EVENT
|
|
int8_t script_button[MAX_KEYS];
|
|
#endif
|
|
|
|
char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo);
|
|
char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo);
|
|
char *ForceStringVar(char *lp,char *dstr);
|
|
void send_download(void);
|
|
uint8_t reject(char *name);
|
|
|
|
void ScriptEverySecond(void) {
|
|
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
struct T_INDEX *vtp=glob_script_mem.type;
|
|
float delta=(millis()-script_lastmillis)/1000;
|
|
script_lastmillis=millis();
|
|
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
|
|
if (vtp[count].bits.is_timer) {
|
|
|
|
float *fp=&glob_script_mem.fvars[vtp[count].index];
|
|
if (*fp>0) {
|
|
|
|
*fp-=delta;
|
|
if (*fp<0) *fp=0;
|
|
}
|
|
}
|
|
if (vtp[count].bits.is_autoinc) {
|
|
|
|
float *fp=&glob_script_mem.fvars[vtp[count].index];
|
|
if (*fp>=0) {
|
|
*fp+=delta;
|
|
}
|
|
}
|
|
}
|
|
Run_Scripter(">S",2,0);
|
|
}
|
|
}
|
|
|
|
void RulesTeleperiod(void) {
|
|
if (bitRead(Settings.rule_enabled, 0) && mqtt_data[0]) Run_Scripter(">T",2, mqtt_data);
|
|
}
|
|
|
|
|
|
#ifdef USE_24C256
|
|
#ifndef USE_SCRIPT_FATFS
|
|
|
|
#include <Eeprom24C128_256.h>
|
|
#define EEPROM_ADDRESS 0x50
|
|
|
|
#ifndef EEP_SCRIPT_SIZE
|
|
#define EEP_SCRIPT_SIZE 4095
|
|
#endif
|
|
|
|
|
|
static Eeprom24C128_256 eeprom(EEPROM_ADDRESS);
|
|
|
|
#define EEP_WRITE(A,B,C) eeprom.writeBytes(A,B,(uint8_t*)C);
|
|
|
|
#define EEP_READ(A,B,C) eeprom.readBytes(A,B,(uint8_t*)C);
|
|
#endif
|
|
#endif
|
|
|
|
#define SCRIPT_SKIP_SPACES while (*lp==' ' || *lp=='\t') lp++;
|
|
#define SCRIPT_SKIP_EOL while (*lp==SCRIPT_EOL) lp++;
|
|
|
|
|
|
int16_t Init_Scripter(void) {
|
|
char *script;
|
|
|
|
script=glob_script_mem.script_ram;
|
|
|
|
|
|
uint16_t lines=0,nvars=0,svars=0,vars=0;
|
|
char *lp=script;
|
|
char vnames[MAXVARS*10];
|
|
char *vnames_p=vnames;
|
|
char *vnp[MAXVARS];
|
|
char **vnp_p=vnp;
|
|
char strings[MAXSVARS*SCRIPT_MAXSSIZE];
|
|
struct M_FILT mfilt[MAXFILT];
|
|
|
|
char *strings_p=strings;
|
|
char *snp[MAXSVARS];
|
|
char **snp_p=snp;
|
|
uint8_t numperm=0,numflt=0,count;
|
|
|
|
glob_script_mem.max_ssize=SCRIPT_SVARSIZE;
|
|
glob_script_mem.scriptptr=0;
|
|
|
|
if (!*script) return -999;
|
|
|
|
float fvalues[MAXVARS];
|
|
struct T_INDEX vtypes[MAXVARS];
|
|
char init=0;
|
|
while (1) {
|
|
|
|
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
if (*lp=='\n' || *lp=='\r') goto next_line;
|
|
|
|
if (*lp==';') goto next_line;
|
|
if (init) {
|
|
|
|
if (*lp=='>') {
|
|
init=0;
|
|
break;
|
|
}
|
|
char *op=strchr(lp,'=');
|
|
if (op) {
|
|
vtypes[vars].bits.data=0;
|
|
|
|
if (*lp=='p' && *(lp+1)==':') {
|
|
lp+=2;
|
|
if (numperm<SCRIPT_MAXPERM) {
|
|
vtypes[vars].bits.is_permanent=1;
|
|
numperm++;
|
|
}
|
|
} else {
|
|
vtypes[vars].bits.is_permanent=0;
|
|
}
|
|
if (*lp=='t' && *(lp+1)==':') {
|
|
lp+=2;
|
|
vtypes[vars].bits.is_timer=1;
|
|
} else {
|
|
vtypes[vars].bits.is_timer=0;
|
|
}
|
|
if (*lp=='i' && *(lp+1)==':') {
|
|
lp+=2;
|
|
vtypes[vars].bits.is_autoinc=1;
|
|
} else {
|
|
vtypes[vars].bits.is_autoinc=0;
|
|
}
|
|
if ((*lp=='m' || *lp=='M') && *(lp+1)==':') {
|
|
uint8_t flg=*lp;
|
|
lp+=2;
|
|
if (flg=='M') mfilt[numflt].numvals=8;
|
|
else mfilt[numflt].numvals=5;
|
|
vtypes[vars].bits.is_filter=1;
|
|
mfilt[numflt].index=0;
|
|
if (flg=='M') {
|
|
mfilt[numflt].numvals|=0x80;
|
|
}
|
|
vtypes[vars].index=numflt;
|
|
numflt++;
|
|
if (numflt>MAXFILT) {
|
|
return -6;
|
|
}
|
|
} else {
|
|
vtypes[vars].bits.is_filter=0;
|
|
}
|
|
*vnp_p++=vnames_p;
|
|
while (lp<op) {
|
|
*vnames_p++=*lp++;
|
|
}
|
|
*vnames_p++=0;
|
|
|
|
op++;
|
|
if (*op!='"') {
|
|
float fv;
|
|
if (*op=='0' && *(op+1)=='x') {
|
|
op+=2;
|
|
fv=strtol(op,&op,16);
|
|
} else {
|
|
fv=CharToFloat(op);
|
|
}
|
|
fvalues[nvars]=fv;
|
|
vtypes[vars].bits.is_string=0;
|
|
if (!vtypes[vars].bits.is_filter) vtypes[vars].index=nvars;
|
|
nvars++;
|
|
if (nvars>MAXNVARS) {
|
|
return -1;
|
|
}
|
|
if (vtypes[vars].bits.is_filter) {
|
|
while (isdigit(*op) || *op=='.' || *op=='-') {
|
|
op++;
|
|
}
|
|
while (*op==' ') op++;
|
|
if (isdigit(*op)) {
|
|
|
|
uint8_t flen=atoi(op);
|
|
mfilt[numflt-1].numvals&=0x80;
|
|
mfilt[numflt-1].numvals|=flen&0x7f;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
op++;
|
|
*snp_p++=strings_p;
|
|
while (*op!='\"') {
|
|
if (*op==SCRIPT_EOL) break;
|
|
*strings_p++=*op++;
|
|
}
|
|
*strings_p++=0;
|
|
vtypes[vars].bits.is_string=1;
|
|
vtypes[vars].index=svars;
|
|
svars++;
|
|
if (svars>MAXSVARS) {
|
|
return -2;
|
|
}
|
|
}
|
|
vars++;
|
|
if (vars>MAXVARS) {
|
|
return -3;
|
|
}
|
|
}
|
|
} else {
|
|
if (!strncmp(lp,">D",2)) {
|
|
lp+=2;
|
|
SCRIPT_SKIP_SPACES
|
|
if (isdigit(*lp)) {
|
|
uint8_t ssize=atoi(lp)+1;
|
|
if (ssize<10 || ssize>SCRIPT_MAXSSIZE) ssize=SCRIPT_MAXSSIZE;
|
|
glob_script_mem.max_ssize=ssize;
|
|
}
|
|
init=1;
|
|
}
|
|
}
|
|
|
|
next_line:
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) break;
|
|
lp++;
|
|
}
|
|
|
|
uint16_t fsize=0;
|
|
for (count=0; count<numflt; count++) {
|
|
fsize+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
|
|
|
|
uint16_t script_mem_size=
|
|
|
|
(sizeof(float)*nvars)+
|
|
(sizeof(float)*nvars)+
|
|
|
|
(vnames_p-vnames)+
|
|
|
|
(sizeof(uint8_t)*vars)+
|
|
|
|
(glob_script_mem.max_ssize*svars)+
|
|
|
|
(sizeof(struct T_INDEX)*vars)+
|
|
fsize;
|
|
|
|
script_mem_size+=16;
|
|
uint8_t *script_mem;
|
|
script_mem=(uint8_t*)calloc(script_mem_size,1);
|
|
if (!script_mem) {
|
|
return -4;
|
|
}
|
|
glob_script_mem.script_mem=script_mem;
|
|
glob_script_mem.script_mem_size=script_mem_size;
|
|
|
|
|
|
|
|
glob_script_mem.fvars=(float*)script_mem;
|
|
uint16_t size=sizeof(float)*nvars;
|
|
memcpy(script_mem,fvalues,size);
|
|
script_mem+=size;
|
|
glob_script_mem.s_fvars=(float*)script_mem;
|
|
size=sizeof(float)*nvars;
|
|
memcpy(script_mem,fvalues,size);
|
|
script_mem+=size;
|
|
|
|
glob_script_mem.mfilt=(struct M_FILT*)script_mem;
|
|
script_mem+=fsize;
|
|
|
|
|
|
glob_script_mem.type=(struct T_INDEX*)script_mem;
|
|
size=sizeof(struct T_INDEX)*vars;
|
|
memcpy(script_mem,vtypes,size);
|
|
script_mem+=size;
|
|
|
|
|
|
char *namep=(char*)script_mem;
|
|
glob_script_mem.glob_vnp=(char*)script_mem;
|
|
size=vnames_p-vnames;
|
|
memcpy(script_mem,vnames,size);
|
|
script_mem+=size;
|
|
|
|
glob_script_mem.vnp_offset=(uint8_t*)script_mem;
|
|
size=vars*sizeof(uint8_t);
|
|
script_mem+=size;
|
|
|
|
|
|
char *snamep=(char*)script_mem;
|
|
glob_script_mem.glob_snp=(char*)script_mem;
|
|
size=glob_script_mem.max_ssize*svars;
|
|
|
|
script_mem+=size;
|
|
|
|
|
|
uint16_t index=0;
|
|
uint8_t *cp=glob_script_mem.vnp_offset;
|
|
for (count=0;count<vars;count++) {
|
|
*cp++=index;
|
|
while (*namep) {
|
|
index++;
|
|
namep++;
|
|
}
|
|
namep++;
|
|
index++;
|
|
if (index>255) {
|
|
free(glob_script_mem.script_mem);
|
|
return -5;
|
|
}
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Script: nv=%d, tv=%d, vns=%d, ram=%d"), nvars, svars, index, glob_script_mem.script_mem_size);
|
|
|
|
|
|
char *cp1=glob_script_mem.glob_snp;
|
|
char *sp=strings;
|
|
for (count=0; count<svars;count++) {
|
|
strcpy(cp1,sp);
|
|
sp+=strlen(sp)+1;
|
|
cp1+=glob_script_mem.max_ssize;
|
|
}
|
|
|
|
|
|
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
|
|
for (count=0; count<numflt; count++) {
|
|
struct M_FILT *mflp=(struct M_FILT*)mp;
|
|
mflp->numvals=mfilt[count].numvals;
|
|
mp+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
|
|
glob_script_mem.numvars=vars;
|
|
glob_script_mem.script_dprec=SCRIPT_FLOAT_PRECISION;
|
|
glob_script_mem.script_loglevel=LOG_LEVEL_INFO;
|
|
|
|
|
|
#if SCRIPT_DEBUG>2
|
|
struct T_INDEX *dvtp=glob_script_mem.type;
|
|
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
|
|
if (dvtp[count].bits.is_string) {
|
|
|
|
} else {
|
|
char string[32];
|
|
dtostrfd(glob_script_mem.fvars[dvtp[count].index],glob_script_mem.script_dprec,string);
|
|
toLog(string);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
float *fp=(float*)glob_script_mem.script_pram;
|
|
struct T_INDEX *vtp=glob_script_mem.type;
|
|
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
|
|
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
|
|
uint8_t index=vtp[count].index;
|
|
if (!isnan(*fp)) {
|
|
glob_script_mem.fvars[index]=*fp;
|
|
} else {
|
|
*fp=glob_script_mem.fvars[index];
|
|
}
|
|
fp++;
|
|
}
|
|
}
|
|
sp=(char*)fp;
|
|
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
|
|
if (vtp[count].bits.is_permanent && vtp[count].bits.is_string) {
|
|
uint8_t index=vtp[count].index;
|
|
char *dp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
|
|
uint8_t slen=strlen(sp);
|
|
strlcpy(dp,sp,glob_script_mem.max_ssize);
|
|
sp+=slen+1;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
if (!glob_script_mem.script_sd_found) {
|
|
if (SD.begin(USE_SCRIPT_FATFS)) {
|
|
glob_script_mem.script_sd_found=1;
|
|
} else {
|
|
glob_script_mem.script_sd_found=0;
|
|
}
|
|
}
|
|
for (uint8_t cnt=0;cnt<SFS_MAX;cnt++) {
|
|
glob_script_mem.file_flags[cnt].is_open=0;
|
|
}
|
|
#endif
|
|
|
|
#if SCRIPT_DEBUG>0
|
|
ClaimSerial();
|
|
SetSerialBaudrate(9600);
|
|
#endif
|
|
|
|
|
|
glob_script_mem.scriptptr=lp-1;
|
|
glob_script_mem.scriptptr_bu=glob_script_mem.scriptptr;
|
|
return 0;
|
|
|
|
}
|
|
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_WS2812
|
|
void ws2812_set_array(float *array ,uint8_t len) {
|
|
|
|
Ws2812ForceSuspend();
|
|
for (uint8_t cnt=0;cnt<len;cnt++) {
|
|
if (cnt>Settings.light_pixels) break;
|
|
uint32_t col=array[cnt];
|
|
Ws2812SetColor(cnt+1,col>>16,col>>8,col,0);
|
|
}
|
|
Ws2812ForceUpdate();
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#define NUM_RES 0xfe
|
|
#define STR_RES 0xfd
|
|
#define VAR_NV 0xff
|
|
|
|
#define NTYPE 0
|
|
#define STYPE 0x80
|
|
|
|
#ifndef FLT_MAX
|
|
#define FLT_MAX 99999999
|
|
#endif
|
|
|
|
float median_array(float *array,uint8_t len) {
|
|
uint8_t ind[len];
|
|
uint8_t mind=0,index=0,flg;
|
|
float min=FLT_MAX;
|
|
|
|
for (uint8_t hcnt=0; hcnt<len/2+1; hcnt++) {
|
|
for (uint8_t mcnt=0; mcnt<len; mcnt++) {
|
|
flg=0;
|
|
for (uint8_t icnt=0; icnt<index; icnt++) {
|
|
if (ind[icnt]==mcnt) {
|
|
flg=1;
|
|
}
|
|
}
|
|
if (!flg) {
|
|
if (array[mcnt]<min) {
|
|
min=array[mcnt];
|
|
mind=mcnt;
|
|
}
|
|
}
|
|
}
|
|
ind[index]=mind;
|
|
index++;
|
|
min=FLT_MAX;
|
|
}
|
|
return array[ind[len/2]];
|
|
}
|
|
|
|
|
|
float *Get_MFAddr(uint8_t index,uint8_t *len) {
|
|
*len=0;
|
|
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
|
|
for (uint8_t count=0; count<MAXFILT; count++) {
|
|
struct M_FILT *mflp=(struct M_FILT*)mp;
|
|
if (count==index) {
|
|
*len=mflp->numvals&0x7f;
|
|
return mflp->rbuff;
|
|
}
|
|
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
float Get_MFVal(uint8_t index,uint8_t bind) {
|
|
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
|
|
for (uint8_t count=0; count<MAXFILT; count++) {
|
|
struct M_FILT *mflp=(struct M_FILT*)mp;
|
|
if (count==index) {
|
|
uint8_t maxind=mflp->numvals&0x7f;
|
|
if (!bind) {
|
|
return mflp->index;
|
|
}
|
|
if (bind<1 || bind>maxind) bind=maxind;
|
|
return mflp->rbuff[bind-1];
|
|
}
|
|
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Set_MFVal(uint8_t index,uint8_t bind,float val) {
|
|
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
|
|
for (uint8_t count=0; count<MAXFILT; count++) {
|
|
struct M_FILT *mflp=(struct M_FILT*)mp;
|
|
if (count==index) {
|
|
uint8_t maxind=mflp->numvals&0x7f;
|
|
if (!bind) {
|
|
mflp->index=val;
|
|
} else {
|
|
if (bind<1 || bind>maxind) bind=maxind;
|
|
mflp->rbuff[bind-1]=val;
|
|
}
|
|
return;
|
|
}
|
|
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
}
|
|
|
|
|
|
float Get_MFilter(uint8_t index) {
|
|
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
|
|
for (uint8_t count=0; count<MAXFILT; count++) {
|
|
struct M_FILT *mflp=(struct M_FILT*)mp;
|
|
if (count==index) {
|
|
if (mflp->numvals&0x80) {
|
|
|
|
return mflp->maccu/(mflp->numvals&0x7f);
|
|
} else {
|
|
|
|
return median_array(mflp->rbuff,mflp->numvals);
|
|
}
|
|
}
|
|
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Set_MFilter(uint8_t index, float invar) {
|
|
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
|
|
for (uint8_t count=0; count<MAXFILT; count++) {
|
|
struct M_FILT *mflp=(struct M_FILT*)mp;
|
|
if (count==index) {
|
|
if (mflp->numvals&0x80) {
|
|
|
|
mflp->maccu-=mflp->rbuff[mflp->index];
|
|
mflp->maccu+=invar;
|
|
mflp->rbuff[mflp->index]=invar;
|
|
mflp->index++;
|
|
if (mflp->index>=(mflp->numvals&0x7f)) mflp->index=0;
|
|
} else {
|
|
|
|
mflp->rbuff[mflp->index]=invar;
|
|
mflp->index++;
|
|
if (mflp->index>=mflp->numvals) mflp->index=0;
|
|
}
|
|
break;
|
|
}
|
|
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
|
|
}
|
|
}
|
|
|
|
#define MEDIAN_SIZE 5
|
|
#define MEDIAN_FILTER_NUM 2
|
|
|
|
struct MEDIAN_FILTER {
|
|
float buffer[MEDIAN_SIZE];
|
|
int8_t index;
|
|
} script_mf[MEDIAN_FILTER_NUM];
|
|
|
|
float DoMedian5(uint8_t index, float in) {
|
|
|
|
if (index>=MEDIAN_FILTER_NUM) index=0;
|
|
|
|
struct MEDIAN_FILTER* mf=&script_mf[index];
|
|
mf->buffer[mf->index]=in;
|
|
mf->index++;
|
|
if (mf->index>=MEDIAN_SIZE) mf->index=0;
|
|
return median_array(mf->buffer,MEDIAN_SIZE);
|
|
}
|
|
|
|
#ifdef USE_LIGHT
|
|
|
|
uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value) {
|
|
float r = 0, g = 0, b = 0;
|
|
struct HSV {
|
|
float H;
|
|
float S;
|
|
float V;
|
|
} hsv;
|
|
|
|
hsv.H=hue;
|
|
hsv.S=(float)saturation/100.0;
|
|
hsv.V=(float)value/100.0;
|
|
|
|
if (hsv.S == 0) {
|
|
r = hsv.V;
|
|
g = hsv.V;
|
|
b = hsv.V;
|
|
} else {
|
|
int i;
|
|
float f, p, q, t;
|
|
|
|
if (hsv.H == 360)
|
|
hsv.H = 0;
|
|
else
|
|
hsv.H = hsv.H / 60;
|
|
|
|
i = (int)trunc(hsv.H);
|
|
f = hsv.H - i;
|
|
|
|
p = hsv.V * (1.0 - hsv.S);
|
|
q = hsv.V * (1.0 - (hsv.S * f));
|
|
t = hsv.V * (1.0 - (hsv.S * (1.0 - f)));
|
|
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
r = hsv.V;
|
|
g = t;
|
|
b = p;
|
|
break;
|
|
|
|
case 1:
|
|
r = q;
|
|
g = hsv.V;
|
|
b = p;
|
|
break;
|
|
|
|
case 2:
|
|
r = p;
|
|
g = hsv.V;
|
|
b = t;
|
|
break;
|
|
|
|
case 3:
|
|
r = p;
|
|
g = q;
|
|
b = hsv.V;
|
|
break;
|
|
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
b = hsv.V;
|
|
break;
|
|
|
|
default:
|
|
r = hsv.V;
|
|
g = p;
|
|
b = q;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
uint8_t ir,ig,ib;
|
|
ir=r*255;
|
|
ig=g*255;
|
|
ib=b*255;
|
|
|
|
uint32_t rgb=(ir<<16)|(ig<<8)|ib;
|
|
return rgb;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) {
|
|
uint16_t count,len=0;
|
|
uint8_t nres=0;
|
|
char vname[32];
|
|
float fvar=0;
|
|
tind->index=0;
|
|
tind->bits.data=0;
|
|
|
|
if (isdigit(*lp) || (*lp=='-' && isdigit(*(lp+1))) || *lp=='.') {
|
|
|
|
if (fp) {
|
|
if (*lp=='0' && *(lp+1)=='x') {
|
|
lp+=2;
|
|
*fp=strtol(lp,0,16);
|
|
} else {
|
|
*fp=CharToFloat(lp);
|
|
}
|
|
}
|
|
if (*lp=='-') lp++;
|
|
while (isdigit(*lp) || *lp=='.') {
|
|
if (*lp==0 || *lp==SCRIPT_EOL) break;
|
|
lp++;
|
|
}
|
|
tind->bits.constant=1;
|
|
tind->bits.is_string=0;
|
|
*vtype=NUM_RES;
|
|
return lp;
|
|
}
|
|
if (*lp=='"') {
|
|
lp++;
|
|
while (*lp!='"') {
|
|
if (*lp==0 || *lp==SCRIPT_EOL) break;
|
|
uint8_t iob=*lp;
|
|
if (iob=='\\') {
|
|
lp++;
|
|
if (*lp=='t') {
|
|
iob='\t';
|
|
} else if (*lp=='n') {
|
|
iob='\n';
|
|
} else if (*lp=='r') {
|
|
iob='\r';
|
|
} else if (*lp=='\\') {
|
|
iob='\\';
|
|
} else {
|
|
lp--;
|
|
}
|
|
if (sp) *sp++=iob;
|
|
} else {
|
|
if (sp) *sp++=iob;
|
|
}
|
|
lp++;
|
|
}
|
|
if (sp) *sp=0;
|
|
*vtype=STR_RES;
|
|
tind->bits.constant=1;
|
|
tind->bits.is_string=1;
|
|
return lp+1;
|
|
}
|
|
|
|
if (*lp=='-') {
|
|
|
|
nres=1;
|
|
lp++;
|
|
}
|
|
|
|
const char *term="\n\r ])=+-/*%><!^&|}";
|
|
for (count=0; count<sizeof(vname); count++) {
|
|
char iob=lp[count];
|
|
if (!iob || strchr(term,iob)) {
|
|
vname[count]=0;
|
|
break;
|
|
}
|
|
vname[count]=iob;
|
|
len+=1;
|
|
}
|
|
|
|
if (!vname[0]) {
|
|
|
|
*vtype=VAR_NV;
|
|
tind->index=VAR_NV;
|
|
glob_script_mem.var_not_found=1;
|
|
return lp;
|
|
}
|
|
|
|
struct T_INDEX *vtp=glob_script_mem.type;
|
|
char dvnam[32];
|
|
strcpy (dvnam,vname);
|
|
uint8_t olen=len;
|
|
last_findex=-1;
|
|
char *ja=strchr(dvnam,'[');
|
|
if (ja) {
|
|
*ja=0;
|
|
ja++;
|
|
olen=strlen(dvnam);
|
|
}
|
|
for (count=0; count<glob_script_mem.numvars; count++) {
|
|
char *cp=glob_script_mem.glob_vnp+glob_script_mem.vnp_offset[count];
|
|
uint8_t slen=strlen(cp);
|
|
if (slen==olen && *cp==dvnam[0]) {
|
|
if (!strncmp(cp,dvnam,olen)) {
|
|
uint8_t index=vtp[count].index;
|
|
*tind=vtp[count];
|
|
tind->index=count;
|
|
if (vtp[count].bits.is_string==0) {
|
|
*vtype=NTYPE|index;
|
|
if (vtp[count].bits.is_filter) {
|
|
if (ja) {
|
|
lp+=olen+1;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
last_findex=fvar;
|
|
fvar=Get_MFVal(index,fvar);
|
|
len=1;
|
|
} else {
|
|
fvar=Get_MFilter(index);
|
|
}
|
|
} else {
|
|
fvar=glob_script_mem.fvars[index];
|
|
}
|
|
if (nres) fvar=-fvar;
|
|
if (fp) *fp=fvar;
|
|
} else {
|
|
*vtype=STYPE|index;
|
|
if (sp) strlcpy(sp,glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize),SCRIPT_MAXSSIZE);
|
|
}
|
|
return lp+len;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jo) {
|
|
|
|
const char* str_value;
|
|
uint8_t aindex;
|
|
String vn;
|
|
char *ja=strchr(vname,'[');
|
|
if (ja) {
|
|
|
|
*ja=0;
|
|
ja++;
|
|
|
|
float fvar;
|
|
GetNumericResult(ja,OPER_EQU,&fvar,0);
|
|
aindex=fvar;
|
|
if (aindex<1 || aindex>6) aindex=1;
|
|
aindex--;
|
|
}
|
|
if (jo->success()) {
|
|
char *subtype=strchr(vname,'#');
|
|
char *subtype2;
|
|
if (subtype) {
|
|
*subtype=0;
|
|
subtype++;
|
|
subtype2=strchr(subtype,'#');
|
|
if (subtype2) {
|
|
*subtype2=0;
|
|
*subtype2++;
|
|
}
|
|
}
|
|
vn=vname;
|
|
str_value = (*jo)[vn];
|
|
if ((*jo)[vn].success()) {
|
|
if (subtype) {
|
|
JsonObject &jobj1=(*jo)[vn];
|
|
if (jobj1.success()) {
|
|
vn=subtype;
|
|
jo=&jobj1;
|
|
str_value = (*jo)[vn];
|
|
if ((*jo)[vn].success()) {
|
|
|
|
if (subtype2) {
|
|
JsonObject &jobj2=(*jo)[vn];
|
|
if ((*jo)[vn].success()) {
|
|
vn=subtype2;
|
|
jo=&jobj2;
|
|
str_value = (*jo)[vn];
|
|
if ((*jo)[vn].success()) {
|
|
goto skip;
|
|
} else {
|
|
goto chknext;
|
|
}
|
|
} else {
|
|
goto chknext;
|
|
}
|
|
}
|
|
|
|
goto skip;
|
|
}
|
|
} else {
|
|
goto chknext;
|
|
}
|
|
}
|
|
skip:
|
|
if (ja) {
|
|
|
|
str_value = (*jo)[vn][aindex];
|
|
}
|
|
if (str_value && *str_value) {
|
|
if ((*jo).is<char*>(vn)) {
|
|
if (!strncmp(str_value,"ON",2)) {
|
|
if (fp) *fp=1;
|
|
goto nexit;
|
|
} else if (!strncmp(str_value,"OFF",3)) {
|
|
if (fp) *fp=0;
|
|
goto nexit;
|
|
} else {
|
|
*vtype=STR_RES;
|
|
tind->bits.constant=1;
|
|
tind->bits.is_string=1;
|
|
if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE);
|
|
return lp+len;
|
|
}
|
|
|
|
} else {
|
|
if (fp) {
|
|
if (!strncmp(vn.c_str(),"Epoch",5)) {
|
|
*fp=atoi(str_value)-(uint32_t)EPOCH_OFFSET;
|
|
} else {
|
|
*fp=CharToFloat((char*)str_value);
|
|
}
|
|
}
|
|
nexit:
|
|
*vtype=NUM_RES;
|
|
tind->bits.constant=1;
|
|
tind->bits.is_string=0;
|
|
return lp+len;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
chknext:
|
|
switch (vname[0]) {
|
|
case 'a':
|
|
#ifdef USE_ANGLE_FUNC
|
|
if (!strncmp(vname,"acos(",5)) {
|
|
lp+=5;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
fvar=acosf(fvar);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case 'b':
|
|
if (!strncmp(vname,"boot",4)) {
|
|
if (rules_flag.system_boot) {
|
|
rules_flag.system_boot=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
#ifdef USE_BUTTON_EVENT
|
|
if (!strncmp(vname,"bt[",3)) {
|
|
|
|
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
|
|
uint32_t index=fvar;
|
|
if (index<1 || index>MAX_KEYS) index=1;
|
|
fvar=script_button[index-1];
|
|
script_button[index-1]|=0x80;
|
|
len++;
|
|
goto exit;
|
|
}
|
|
#endif
|
|
break;
|
|
case 'c':
|
|
if (!strncmp(vname,"chg[",4)) {
|
|
|
|
struct T_INDEX ind;
|
|
uint8_t vtype;
|
|
isvar(vname+4,&vtype,&ind,0,0,0);
|
|
if (!ind.bits.constant) {
|
|
uint8_t index=glob_script_mem.type[ind.index].index;
|
|
if (glob_script_mem.fvars[index]!=glob_script_mem.s_fvars[index]) {
|
|
|
|
glob_script_mem.s_fvars[index]=glob_script_mem.fvars[index];
|
|
fvar=1;
|
|
len++;
|
|
goto exit;
|
|
} else {
|
|
fvar=0;
|
|
len++;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'd':
|
|
if (!strncmp(vname,"day",3)) {
|
|
fvar=RtcTime.day_of_month;
|
|
goto exit;
|
|
}
|
|
break;
|
|
case 'e':
|
|
if (!strncmp(vname,"epoch",5)) {
|
|
fvar=UtcTime()-(uint32_t)EPOCH_OFFSET;
|
|
goto exit;
|
|
}
|
|
break;
|
|
#ifdef USE_SCRIPT_FATFS
|
|
case 'f':
|
|
if (!strncmp(vname,"fo(",3)) {
|
|
lp+=3;
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
while (*lp==' ') lp++;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
uint8_t mode=fvar;
|
|
fvar=-1;
|
|
for (uint8_t cnt=0;cnt<SFS_MAX;cnt++) {
|
|
if (!glob_script_mem.file_flags[cnt].is_open) {
|
|
if (mode==0) {
|
|
glob_script_mem.files[cnt]=SD.open(str,FILE_READ);
|
|
if (glob_script_mem.files[cnt].isDirectory()) {
|
|
glob_script_mem.files[cnt].rewindDirectory();
|
|
glob_script_mem.file_flags[cnt].is_dir=1;
|
|
} else {
|
|
glob_script_mem.file_flags[cnt].is_dir=0;
|
|
}
|
|
}
|
|
else glob_script_mem.files[cnt]=SD.open(str,FILE_WRITE);
|
|
if (glob_script_mem.files[cnt]) {
|
|
fvar=cnt;
|
|
glob_script_mem.file_flags[cnt].is_open=1;
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_INFO,PSTR("file open failed"));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fc(",3)) {
|
|
lp+=3;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
uint8_t ind=fvar;
|
|
if (ind>=SFS_MAX) ind=SFS_MAX-1;
|
|
glob_script_mem.files[ind].close();
|
|
glob_script_mem.file_flags[ind].is_open=0;
|
|
fvar=0;
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"ff(",3)) {
|
|
lp+=3;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
uint8_t ind=fvar;
|
|
if (ind>=SFS_MAX) ind=SFS_MAX-1;
|
|
glob_script_mem.files[ind].flush();
|
|
fvar=0;
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fw(",3)) {
|
|
lp+=3;
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=ForceStringVar(lp,str);
|
|
while (*lp==' ') lp++;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
uint8_t ind=fvar;
|
|
if (ind>=SFS_MAX) ind=SFS_MAX-1;
|
|
if (glob_script_mem.file_flags[ind].is_open) {
|
|
fvar=glob_script_mem.files[ind].print(str);
|
|
} else {
|
|
fvar=0;
|
|
}
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fr(",3)) {
|
|
lp+=3;
|
|
struct T_INDEX ind;
|
|
uint8_t vtype;
|
|
uint8_t sindex=0;
|
|
lp=isvar(lp,&vtype,&ind,0,0,0);
|
|
if (vtype!=VAR_NV) {
|
|
|
|
if ((vtype&STYPE)==0) {
|
|
|
|
fvar=0;
|
|
goto exit;
|
|
} else {
|
|
|
|
sindex=glob_script_mem.type[ind.index].index;
|
|
}
|
|
} else {
|
|
|
|
fvar=0;
|
|
goto exit;
|
|
}
|
|
while (*lp==' ') lp++;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
uint8_t find=fvar;
|
|
if (find>=SFS_MAX) find=SFS_MAX-1;
|
|
uint8_t index=0;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
char *cp=str;
|
|
if (glob_script_mem.file_flags[find].is_open) {
|
|
if (glob_script_mem.file_flags[find].is_dir) {
|
|
while (true) {
|
|
File entry=glob_script_mem.files[find].openNextFile();
|
|
if (entry) {
|
|
if (!reject((char*)entry.name())) {
|
|
strcpy(str,entry.name());
|
|
entry.close();
|
|
break;
|
|
}
|
|
} else {
|
|
*cp=0;
|
|
break;
|
|
}
|
|
entry.close();
|
|
}
|
|
index=strlen(str);
|
|
} else {
|
|
while (glob_script_mem.files[find].available()) {
|
|
uint8_t buf[1];
|
|
glob_script_mem.files[find].read(buf,1);
|
|
if (buf[0]=='\t' || buf[0]==',' || buf[0]=='\n' || buf[0]=='\r') {
|
|
break;
|
|
} else {
|
|
*cp++=buf[0];
|
|
index++;
|
|
if (index>=glob_script_mem.max_ssize-1) break;
|
|
}
|
|
}
|
|
*cp=0;
|
|
}
|
|
} else {
|
|
strcpy(str,"file error");
|
|
}
|
|
lp++;
|
|
strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize);
|
|
fvar=index;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fd(",3)) {
|
|
lp+=3;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
SD.remove(str);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
#ifdef USE_SCRIPT_FATFS_EXT
|
|
if (!strncmp(vname,"fe(",3)) {
|
|
lp+=3;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
|
|
File ef=SD.open(str);
|
|
if (ef) {
|
|
uint16_t fsiz=ef.size();
|
|
if (fsiz<2048) {
|
|
char *script=(char*)calloc(fsiz+16,1);
|
|
if (script) {
|
|
ef.read((uint8_t*)script,fsiz);
|
|
execute_script(script);
|
|
free(script);
|
|
fvar=1;
|
|
}
|
|
}
|
|
ef.close();
|
|
}
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fmd(",4)) {
|
|
lp+=4;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
fvar=SD.mkdir(str);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"frd(",4)) {
|
|
lp+=4;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
fvar=SD.rmdir(str);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fx(",3)) {
|
|
lp+=3;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
if (SD.exists(str)) fvar=1;
|
|
else fvar=0;
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
#endif
|
|
if (!strncmp(vname,"fl1(",4) || !strncmp(vname,"fl2(",4) ) {
|
|
uint8_t lknum=*(lp+2)&3;
|
|
lp+=4;
|
|
char str[glob_script_mem.max_ssize+1];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
if (lknum<1 || lknum>2) lknum=1;
|
|
strlcpy(glob_script_mem.flink[lknum-1],str,14);
|
|
lp++;
|
|
fvar=0;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"fsm",3)) {
|
|
fvar=glob_script_mem.script_sd_found;
|
|
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
#endif
|
|
case 'g':
|
|
if (!strncmp(vname,"gtmp",4)) {
|
|
fvar=global_temperature;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"ghum",4)) {
|
|
fvar=global_humidity;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"gprs",4)) {
|
|
fvar=global_pressure;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"gtopic",6)) {
|
|
if (sp) strlcpy(sp,SettingsText(SET_MQTT_GRP_TOPIC),glob_script_mem.max_ssize);
|
|
goto strexit;
|
|
}
|
|
break;
|
|
case 'h':
|
|
if (!strncmp(vname,"hours",5)) {
|
|
fvar=RtcTime.hour;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"heap",4)) {
|
|
fvar=ESP.getFreeHeap();
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"hn(",3)) {
|
|
lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0);
|
|
if (fvar<0 || fvar>255) fvar=0;
|
|
lp++;
|
|
len=0;
|
|
if (sp) {
|
|
sprintf(sp,"%02x",(uint8_t)fvar);
|
|
}
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"hx(",3)) {
|
|
lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0);
|
|
lp++;
|
|
len=0;
|
|
if (sp) {
|
|
sprintf(sp,"%08x",(uint32_t)fvar);
|
|
}
|
|
goto strexit;
|
|
}
|
|
#ifdef USE_LIGHT
|
|
|
|
if (!strncmp(vname,"hsvrgb(",7)) {
|
|
lp=GetNumericResult(lp+7,OPER_EQU,&fvar,0);
|
|
if (fvar<0 || fvar>360) fvar=0;
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
float fvar2;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
|
|
if (fvar2<0 || fvar2>100) fvar2=0;
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
float fvar3;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar3,0);
|
|
if (fvar3<0 || fvar3>100) fvar3=0;
|
|
|
|
fvar=HSVToRGB(fvar,fvar2,fvar3);
|
|
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
|
|
#endif
|
|
break;
|
|
case 'i':
|
|
if (!strncmp(vname,"int(",4)) {
|
|
lp=GetNumericResult(lp+4,OPER_EQU,&fvar,0);
|
|
fvar=floor(fvar);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
break;
|
|
case 'l':
|
|
if (!strncmp(vname,"loglvl",6)) {
|
|
fvar=glob_script_mem.script_loglevel;
|
|
tind->index=SCRIPT_LOGLEVEL;
|
|
exit_settable:
|
|
if (fp) *fp=fvar;
|
|
*vtype=NTYPE;
|
|
tind->bits.settable=1;
|
|
tind->bits.is_string=0;
|
|
return lp+len;
|
|
}
|
|
break;
|
|
case 'm':
|
|
if (!strncmp(vname,"med(",4)) {
|
|
float fvar1;
|
|
lp=GetNumericResult(lp+4,OPER_EQU,&fvar1,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
float fvar2;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
|
|
fvar=DoMedian5(fvar1,fvar2);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"micros",6)) {
|
|
fvar=micros();
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"millis",6)) {
|
|
fvar=millis();
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"mins",4)) {
|
|
fvar=RtcTime.minute;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"month",5)) {
|
|
fvar=RtcTime.month;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"mqttc",5)) {
|
|
if (rules_flag.mqtt_connected) {
|
|
rules_flag.mqtt_connected=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"mqttd",5)) {
|
|
if (rules_flag.mqtt_disconnected) {
|
|
rules_flag.mqtt_disconnected=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"mqtts",5)) {
|
|
fvar=!global_state.mqtt_down;
|
|
goto exit;
|
|
}
|
|
break;
|
|
case 'p':
|
|
if (!strncmp(vname,"pin[",4)) {
|
|
|
|
GetNumericResult(vname+4,OPER_EQU,&fvar,0);
|
|
fvar=digitalRead((uint8_t)fvar);
|
|
|
|
len++;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"pn[",3)) {
|
|
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
|
|
fvar=pin[(uint8_t)fvar];
|
|
|
|
len++;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"pd[",3)) {
|
|
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
|
|
uint8_t gpiopin=fvar;
|
|
for (uint8_t i=0;i<GPIO_SENSOR_END;i++) {
|
|
if (pin[i]==gpiopin) {
|
|
fvar=i;
|
|
|
|
len++;
|
|
goto exit;
|
|
}
|
|
}
|
|
fvar=999;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"prefix1",7)) {
|
|
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX1),glob_script_mem.max_ssize);
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"prefix2",7)) {
|
|
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX2),glob_script_mem.max_ssize);
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"prefix3",7)) {
|
|
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX3),glob_script_mem.max_ssize);
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"pow(",4)) {
|
|
|
|
float fvar1;
|
|
lp=GetNumericResult(lp+4,OPER_EQU,&fvar1,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
float fvar2;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
|
|
lp++;
|
|
|
|
fvar=FastPrecisePow(fvar1,fvar2);
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"pwr[",4)) {
|
|
GetNumericResult(vname+4,OPER_EQU,&fvar,0);
|
|
uint8_t index=fvar;
|
|
if (index<=devices_present) {
|
|
fvar=bitRead(power,index-1);
|
|
} else {
|
|
fvar=-1;
|
|
}
|
|
len+=1;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"pc[",3)) {
|
|
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
|
|
uint8_t index=fvar;
|
|
if (index<1 || index>MAX_COUNTERS) index=1;
|
|
fvar=RtcSettings.pulse_counter[index-1];
|
|
len+=1;
|
|
goto exit;
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
if (!strncmp(vname,"ram",3)) {
|
|
fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(PMEM_SIZE);
|
|
goto exit;
|
|
}
|
|
break;
|
|
case 's':
|
|
if (!strncmp(vname,"secs",4)) {
|
|
fvar=RtcTime.second;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"sw[",3)) {
|
|
|
|
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
|
|
fvar=SwitchLastState((uint32_t)fvar);
|
|
|
|
len++;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"stack",5)) {
|
|
fvar=GetStack();
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"slen",4)) {
|
|
fvar=strlen(glob_script_mem.script_ram);
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"sl(",3)) {
|
|
lp+=3;
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
lp++;
|
|
len=0;
|
|
fvar=strlen(str);
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"sb(",3)) {
|
|
lp+=3;
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float fvar1;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar1,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float fvar2;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
|
|
lp++;
|
|
len=0;
|
|
if (fvar1<0) {
|
|
fvar1=strlen(str)+fvar1;
|
|
}
|
|
memcpy(sp,&str[(uint8_t)fvar1],(uint8_t)fvar2);
|
|
sp[(uint8_t)fvar2] = '\0';
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"st(",3)) {
|
|
lp+=3;
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
while (*lp==' ') lp++;
|
|
char token[2];
|
|
token[0]=*lp++;
|
|
token[1]=0;
|
|
while (*lp==' ') lp++;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
|
|
lp++;
|
|
len=0;
|
|
if (sp) {
|
|
|
|
char *st=strtok(str,token);
|
|
if (!st) {
|
|
*sp=0;
|
|
} else {
|
|
for (uint8_t cnt=1; cnt<=fvar; cnt++) {
|
|
if (cnt==fvar) {
|
|
strcpy(sp,st);
|
|
break;
|
|
}
|
|
st=strtok(NULL,token);
|
|
if (!st) {
|
|
*sp=0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"s(",2)) {
|
|
lp+=2;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
char str[glob_script_mem.max_ssize+1];
|
|
dtostrfd(fvar,glob_script_mem.script_dprec,str);
|
|
if (sp) strlcpy(sp,str,glob_script_mem.max_ssize);
|
|
lp++;
|
|
len=0;
|
|
goto strexit;
|
|
}
|
|
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
|
|
if (!strncmp(vname,"sunrise",7)) {
|
|
fvar=SunMinutes(0);
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"sunset",6)) {
|
|
fvar=SunMinutes(1);
|
|
goto exit;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SHUTTER
|
|
if (!strncmp(vname,"sht[",4)) {
|
|
GetNumericResult(vname+4,OPER_EQU,&fvar,0);
|
|
uint8_t index=fvar;
|
|
if (index<=shutters_present) {
|
|
fvar=Settings.shutter_position[index-1];
|
|
} else {
|
|
fvar=-1;
|
|
}
|
|
len+=1;
|
|
goto exit;
|
|
}
|
|
#endif
|
|
#ifdef USE_ANGLE_FUNC
|
|
if (!strncmp(vname,"sin(",4)) {
|
|
lp+=4;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
fvar=sinf(fvar);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"sqrt(",5)) {
|
|
lp+=5;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
fvar=sqrtf(fvar);
|
|
lp++;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
#endif
|
|
#ifdef USE_SML_SCRIPT_CMD
|
|
if (!strncmp(vname,"sml(",4)) {
|
|
lp+=4;
|
|
float fvar1;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar1,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float fvar2;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
|
|
SCRIPT_SKIP_SPACES
|
|
if (fvar2==0) {
|
|
float fvar3;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar3,0);
|
|
fvar=SML_SetBaud(fvar1,fvar3);
|
|
} else {
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
fvar=SML_Write(fvar1,str);
|
|
}
|
|
lp++;
|
|
fvar=0;
|
|
len=0;
|
|
goto exit;
|
|
}
|
|
#endif
|
|
break;
|
|
case 't':
|
|
if (!strncmp(vname,"time",4)) {
|
|
fvar=MinutesPastMidnight();
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"tper",4)) {
|
|
fvar=Settings.tele_period;
|
|
tind->index=SCRIPT_TELEPERIOD;
|
|
goto exit_settable;
|
|
}
|
|
if (!strncmp(vname,"tinit",5)) {
|
|
if (rules_flag.time_init) {
|
|
rules_flag.time_init=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"tset",4)) {
|
|
if (rules_flag.time_set) {
|
|
rules_flag.time_set=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"tstamp",6)) {
|
|
if (sp) strlcpy(sp,GetDateAndTime(DT_LOCAL).c_str(),glob_script_mem.max_ssize);
|
|
goto strexit;
|
|
}
|
|
if (!strncmp(vname,"topic",5)) {
|
|
if (sp) strlcpy(sp,SettingsText(SET_MQTT_TOPIC),glob_script_mem.max_ssize);
|
|
goto strexit;
|
|
}
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
if (!strncmp(vname,"tbut[",5)) {
|
|
GetNumericResult(vname+5,OPER_EQU,&fvar,0);
|
|
uint8_t index=fvar;
|
|
if (index<1 || index>MAXBUTTONS) index=1;
|
|
index--;
|
|
if (buttons[index]) {
|
|
fvar=buttons[index]->vpower&0x80;
|
|
} else {
|
|
fvar=-1;
|
|
}
|
|
len+=1;
|
|
goto exit;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
break;
|
|
case 'u':
|
|
if (!strncmp(vname,"uptime",6)) {
|
|
fvar=MinutesUptime();
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"upsecs",6)) {
|
|
fvar=uptime;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"upd[",4)) {
|
|
|
|
struct T_INDEX ind;
|
|
uint8_t vtype;
|
|
isvar(vname+4,&vtype,&ind,0,0,0);
|
|
if (!ind.bits.constant) {
|
|
if (!ind.bits.changed) {
|
|
fvar=0;
|
|
len++;
|
|
goto exit;
|
|
} else {
|
|
glob_script_mem.type[ind.index].bits.changed=0;
|
|
fvar=1;
|
|
len++;
|
|
goto exit;
|
|
}
|
|
}
|
|
goto notfound;
|
|
}
|
|
break;
|
|
|
|
case 'w':
|
|
if (!strncmp(vname,"wday",4)) {
|
|
fvar=RtcTime.day_of_week;
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"wific",5)) {
|
|
if (rules_flag.wifi_connected) {
|
|
rules_flag.wifi_connected=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"wifid",5)) {
|
|
if (rules_flag.wifi_disconnected) {
|
|
rules_flag.wifi_disconnected=0;
|
|
fvar=1;
|
|
}
|
|
goto exit;
|
|
}
|
|
if (!strncmp(vname,"wifis",5)) {
|
|
fvar=!global_state.wifi_down;
|
|
goto exit;
|
|
}
|
|
break;
|
|
case 'y':
|
|
if (!strncmp(vname,"year",4)) {
|
|
fvar=RtcTime.year;
|
|
goto exit;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
notfound:
|
|
if (fp) *fp=0;
|
|
*vtype=VAR_NV;
|
|
tind->index=VAR_NV;
|
|
glob_script_mem.var_not_found=1;
|
|
return lp;
|
|
|
|
exit:
|
|
if (fp) *fp=fvar;
|
|
*vtype=NUM_RES;
|
|
tind->bits.constant=1;
|
|
tind->bits.is_string=0;
|
|
return lp+len;
|
|
|
|
strexit:
|
|
*vtype=STYPE;
|
|
tind->bits.constant=1;
|
|
tind->bits.is_string=1;
|
|
return lp+len;
|
|
}
|
|
|
|
|
|
|
|
char *getop(char *lp, uint8_t *operand) {
|
|
switch (*lp) {
|
|
case '=':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_EQUEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_EQU;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '+':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_PLSEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_PLS;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '-':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_MINEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_MIN;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '*':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_MULEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_MUL;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '/':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_DIVEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_DIV;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '!':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_NOTEQU;
|
|
return lp+2;
|
|
}
|
|
break;
|
|
case '>':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_GRTEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_GRT;
|
|
return lp+1;
|
|
|
|
}
|
|
break;
|
|
case '<':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_LOWEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_LOW;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '%':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_PERCEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_PERC;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '^':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_XOREQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_XOR;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '&':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_ANDEQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_AND;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
case '|':
|
|
if (*(lp+1)=='=') {
|
|
*operand=OPER_OREQU;
|
|
return lp+2;
|
|
} else {
|
|
*operand=OPER_OR;
|
|
return lp+1;
|
|
}
|
|
break;
|
|
}
|
|
*operand=0;
|
|
return lp;
|
|
}
|
|
|
|
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1)
|
|
|
|
|
|
extern "C" {
|
|
#include <cont.h>
|
|
extern cont_t g_cont;
|
|
}
|
|
uint16_t GetStack(void) {
|
|
register uint32_t *sp asm("a1");
|
|
return (4 * (sp - g_cont.stack));
|
|
}
|
|
|
|
#else
|
|
extern "C" {
|
|
#include <cont.h>
|
|
extern cont_t* g_pcont;
|
|
}
|
|
uint16_t GetStack(void) {
|
|
register uint32_t *sp asm("a1");
|
|
return (4 * (sp - g_pcont->stack));
|
|
}
|
|
#endif
|
|
|
|
char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo) {
|
|
uint8_t operand=0;
|
|
uint8_t vtype;
|
|
char *slp;
|
|
struct T_INDEX ind;
|
|
char str[SCRIPT_MAXSSIZE],str1[SCRIPT_MAXSSIZE];
|
|
while (1) {
|
|
lp=isvar(lp,&vtype,&ind,0,str1,jo);
|
|
if (vtype!=STR_RES && !(vtype&STYPE)) {
|
|
|
|
glob_script_mem.glob_error=1;
|
|
return lp;
|
|
}
|
|
switch (lastop) {
|
|
case OPER_EQU:
|
|
strlcpy(str,str1,sizeof(str));
|
|
break;
|
|
case OPER_PLS:
|
|
strncat(str,str1,sizeof(str));
|
|
break;
|
|
}
|
|
slp=lp;
|
|
lp=getop(lp,&operand);
|
|
switch (operand) {
|
|
case OPER_EQUEQU:
|
|
case OPER_NOTEQU:
|
|
case OPER_LOW:
|
|
case OPER_LOWEQU:
|
|
case OPER_GRT:
|
|
case OPER_GRTEQU:
|
|
lp=slp;
|
|
strcpy(cp,str);
|
|
return lp;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
lastop=operand;
|
|
if (!operand) {
|
|
strcpy(cp,str);
|
|
return lp;
|
|
}
|
|
}
|
|
}
|
|
|
|
char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo) {
|
|
uint8_t operand=0;
|
|
float fvar1,fvar;
|
|
char *slp;
|
|
uint8_t vtype;
|
|
struct T_INDEX ind;
|
|
while (1) {
|
|
|
|
if (*lp=='(') {
|
|
lp++;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar1,jo);
|
|
lp++;
|
|
|
|
} else {
|
|
lp=isvar(lp,&vtype,&ind,&fvar1,0,jo);
|
|
if (vtype!=NUM_RES && vtype&STYPE) {
|
|
|
|
glob_script_mem.glob_error=1;
|
|
}
|
|
}
|
|
switch (lastop) {
|
|
case OPER_EQU:
|
|
fvar=fvar1;
|
|
break;
|
|
case OPER_PLS:
|
|
fvar+=fvar1;
|
|
break;
|
|
case OPER_MIN:
|
|
fvar-=fvar1;
|
|
break;
|
|
case OPER_MUL:
|
|
fvar*=fvar1;
|
|
break;
|
|
case OPER_DIV:
|
|
fvar/=fvar1;
|
|
break;
|
|
case OPER_PERC:
|
|
fvar=fmodf(fvar,fvar1);
|
|
break;
|
|
case OPER_XOR:
|
|
fvar=(uint32_t)fvar^(uint32_t)fvar1;
|
|
break;
|
|
case OPER_AND:
|
|
fvar=(uint32_t)fvar&(uint32_t)fvar1;
|
|
break;
|
|
case OPER_OR:
|
|
fvar=(uint32_t)fvar|(uint32_t)fvar1;
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
slp=lp;
|
|
lp=getop(lp,&operand);
|
|
switch (operand) {
|
|
case OPER_EQUEQU:
|
|
case OPER_NOTEQU:
|
|
case OPER_LOW:
|
|
case OPER_LOWEQU:
|
|
case OPER_GRT:
|
|
case OPER_GRTEQU:
|
|
lp=slp;
|
|
*fp=fvar;
|
|
return lp;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
lastop=operand;
|
|
if (!operand) {
|
|
*fp=fvar;
|
|
return lp;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
char *ForceStringVar(char *lp,char *dstr) {
|
|
float fvar;
|
|
char *slp=lp;
|
|
glob_script_mem.glob_error=0;
|
|
lp=GetStringResult(lp,OPER_EQU,dstr,0);
|
|
if (glob_script_mem.glob_error) {
|
|
|
|
lp=GetNumericResult(slp,OPER_EQU,&fvar,0);
|
|
dtostrfd(fvar,6,dstr);
|
|
glob_script_mem.glob_error=0;
|
|
}
|
|
return lp;
|
|
}
|
|
|
|
|
|
void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
|
|
char *cp;
|
|
uint16_t count;
|
|
uint8_t vtype;
|
|
uint8_t dprec=glob_script_mem.script_dprec;
|
|
float fvar;
|
|
cp=srcbuf;
|
|
struct T_INDEX ind;
|
|
char string[SCRIPT_MAXSSIZE];
|
|
dstsize-=2;
|
|
for (count=0;count<dstsize;count++) {
|
|
if (*cp=='%') {
|
|
cp++;
|
|
if (*cp=='%') {
|
|
dstbuf[count]=*cp++;
|
|
} else {
|
|
if (isdigit(*cp)) {
|
|
dprec=*cp&0xf;
|
|
cp++;
|
|
} else {
|
|
dprec=glob_script_mem.script_dprec;
|
|
}
|
|
cp=isvar(cp,&vtype,&ind,&fvar,string,0);
|
|
if (vtype!=VAR_NV) {
|
|
|
|
if (vtype==NUM_RES || (vtype&STYPE)==0) {
|
|
|
|
dtostrfd(fvar,dprec,string);
|
|
} else {
|
|
|
|
}
|
|
uint8_t slen=strlen(string);
|
|
if (count+slen<dstsize-1) {
|
|
strcpy(&dstbuf[count],string);
|
|
count+=slen-1;
|
|
}
|
|
cp++;
|
|
} else {
|
|
strcpy(&dstbuf[count],"???");
|
|
count+=2;
|
|
while (*cp!='%') {
|
|
if (*cp==0 || *cp==SCRIPT_EOL) {
|
|
dstbuf[count+1]=0;
|
|
return;
|
|
}
|
|
cp++;
|
|
}
|
|
cp++;
|
|
}
|
|
}
|
|
} else {
|
|
if (*cp=='\\') {
|
|
cp++;
|
|
if (*cp=='n') {
|
|
dstbuf[count]='\n';
|
|
} else if (*cp=='r') {
|
|
dstbuf[count]='\r';
|
|
} else if (*cp=='\\') {
|
|
dstbuf[count]='\\';
|
|
} else {
|
|
dstbuf[count]=*cp;
|
|
}
|
|
} else {
|
|
dstbuf[count]=*cp;
|
|
}
|
|
if (*cp==0) {
|
|
break;
|
|
}
|
|
cp++;
|
|
}
|
|
}
|
|
dstbuf[count]=0;
|
|
}
|
|
|
|
void toLog(const char *str) {
|
|
if (!str) return;
|
|
AddLog_P(LOG_LEVEL_INFO, str);
|
|
}
|
|
|
|
|
|
void toLogN(const char *cp,uint8_t len) {
|
|
if (!cp) return;
|
|
char str[32];
|
|
if (len>=sizeof(str)) len=len>=sizeof(str);
|
|
strlcpy(str,cp,len);
|
|
toSLog(str);
|
|
}
|
|
|
|
void toLogEOL(const char *s1,const char *str) {
|
|
if (!str) return;
|
|
uint8_t index=0;
|
|
char *cp=log_data;
|
|
strcpy(cp,s1);
|
|
cp+=strlen(s1);
|
|
while (*str) {
|
|
if (*str==SCRIPT_EOL) break;
|
|
*cp++=*str++;
|
|
}
|
|
*cp=0;
|
|
AddLog(LOG_LEVEL_INFO);
|
|
}
|
|
|
|
|
|
void toSLog(const char *str) {
|
|
if (!str) return;
|
|
#if SCRIPT_DEBUG>0
|
|
while (*str) {
|
|
Serial.write(*str);
|
|
str++;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
char *Evaluate_expression(char *lp,uint8_t and_or, uint8_t *result,JsonObject *jo) {
|
|
float fvar,*dfvar,fvar1;
|
|
uint8_t numeric;
|
|
struct T_INDEX ind;
|
|
uint8_t vtype=0,lastop;
|
|
uint8_t res=0;
|
|
char *llp=lp;
|
|
char *slp;
|
|
|
|
SCRIPT_SKIP_SPACES
|
|
if (*lp=='(') {
|
|
uint8_t res=0;
|
|
uint8_t xand_or=0;
|
|
lp++;
|
|
|
|
loop:
|
|
SCRIPT_SKIP_SPACES
|
|
lp=Evaluate_expression(lp,xand_or,&res,jo);
|
|
if (*lp==')') {
|
|
lp++;
|
|
goto exit0;
|
|
}
|
|
|
|
SCRIPT_SKIP_SPACES
|
|
if (!strncmp(lp,"or",2)) {
|
|
lp+=2;
|
|
xand_or=1;
|
|
goto loop;
|
|
} else if (!strncmp(lp,"and",3)) {
|
|
lp+=3;
|
|
xand_or=2;
|
|
goto loop;
|
|
}
|
|
exit0:
|
|
if (!and_or) {
|
|
*result=res;
|
|
} else if (and_or==1) {
|
|
*result|=res;
|
|
} else {
|
|
*result&=res;
|
|
}
|
|
goto exit10;
|
|
}
|
|
|
|
llp=lp;
|
|
|
|
dfvar=&fvar;
|
|
glob_script_mem.glob_error=0;
|
|
slp=lp;
|
|
numeric=1;
|
|
lp=GetNumericResult(lp,OPER_EQU,dfvar,0);
|
|
if (glob_script_mem.glob_error==1) {
|
|
|
|
char cmpstr[SCRIPT_MAXSSIZE];
|
|
lp=slp;
|
|
numeric=0;
|
|
|
|
lp=isvar(lp,&vtype,&ind,0,cmpstr,0);
|
|
lp=getop(lp,&lastop);
|
|
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,jo);
|
|
if (lastop==OPER_EQUEQU || lastop==OPER_NOTEQU) {
|
|
res=strcmp(cmpstr,str);
|
|
if (lastop==OPER_EQUEQU) res=!res;
|
|
goto exit;
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
lp=getop(lp,&lastop);
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar1,jo);
|
|
switch (lastop) {
|
|
case OPER_EQUEQU:
|
|
res=(*dfvar==fvar1);
|
|
break;
|
|
case OPER_NOTEQU:
|
|
res=(*dfvar!=fvar1);
|
|
break;
|
|
case OPER_LOW:
|
|
res=(*dfvar<fvar1);
|
|
break;
|
|
case OPER_LOWEQU:
|
|
res=(*dfvar<=fvar1);
|
|
break;
|
|
case OPER_GRT:
|
|
res=(*dfvar>fvar1);
|
|
break;
|
|
case OPER_GRTEQU:
|
|
res=(*dfvar>=fvar1);
|
|
break;
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
if (!and_or) {
|
|
*result=res;
|
|
} else if (and_or==1) {
|
|
*result|=res;
|
|
} else {
|
|
*result&=res;
|
|
}
|
|
}
|
|
|
|
|
|
exit10:
|
|
#if SCRIPT_DEBUG>0
|
|
char tbuff[128];
|
|
sprintf(tbuff,"p1=%d,p2=%d,cmpres=%d,and_or=%d line: ",(int32_t)*dfvar,(int32_t)fvar1,*result,and_or);
|
|
toLogEOL(tbuff,llp);
|
|
#endif
|
|
return lp;
|
|
}
|
|
|
|
|
|
|
|
#define IF_NEST 8
|
|
|
|
int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
|
|
|
|
if (tasm_cmd_activ && tlen>0) return 0;
|
|
|
|
uint8_t vtype=0,sindex,xflg,floop=0,globvindex,fromscriptcmd=0;
|
|
int8_t globaindex;
|
|
struct T_INDEX ind;
|
|
uint8_t operand,lastop,numeric=1,if_state[IF_NEST],if_exe[IF_NEST],if_result[IF_NEST],and_or,ifstck=0;
|
|
if_state[ifstck]=0;
|
|
if_result[ifstck]=0;
|
|
if_exe[ifstck]=1;
|
|
char cmpstr[SCRIPT_MAXSSIZE];
|
|
uint8_t check=0;
|
|
if (tlen<0) {
|
|
tlen=abs(tlen);
|
|
check=1;
|
|
}
|
|
|
|
float *dfvar,*cv_count,cv_max,cv_inc;
|
|
char *cv_ptr;
|
|
float fvar=0,fvar1,sysvar,swvar;
|
|
uint8_t section=0,sysv_type=0,swflg=0;
|
|
|
|
if (!glob_script_mem.scriptptr) {
|
|
return -99;
|
|
}
|
|
|
|
DynamicJsonBuffer jsonBuffer;
|
|
JsonObject &jobj=jsonBuffer.parseObject(js);
|
|
JsonObject *jo;
|
|
if (js) jo=&jobj;
|
|
else jo=0;
|
|
|
|
char *lp=glob_script_mem.scriptptr;
|
|
|
|
while (1) {
|
|
|
|
|
|
startline:
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
SCRIPT_SKIP_EOL
|
|
|
|
if (*lp==';') goto next_line;
|
|
if (!*lp) break;
|
|
|
|
if (section) {
|
|
|
|
if (*lp=='>') {
|
|
return 0;
|
|
}
|
|
if (*lp=='#') {
|
|
return 0;
|
|
}
|
|
glob_script_mem.var_not_found=0;
|
|
|
|
|
|
#ifdef IFTHEN_DEBUG
|
|
char tbuff[128];
|
|
sprintf(tbuff,"stack=%d,exe=%d,state=%d,cmpres=%d line: ",ifstck,if_exe[ifstck],if_state[ifstck],if_result[ifstck]);
|
|
toLogEOL(tbuff,lp);
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (!strncmp(lp,"if",2)) {
|
|
lp+=2;
|
|
if (ifstck<IF_NEST-1) ifstck++;
|
|
if_state[ifstck]=1;
|
|
if_result[ifstck]=0;
|
|
if (ifstck==1) if_exe[ifstck]=1;
|
|
else if_exe[ifstck]=if_exe[ifstck-1];
|
|
and_or=0;
|
|
} else if (!strncmp(lp,"then",4) && if_state[ifstck]==1) {
|
|
lp+=4;
|
|
if_state[ifstck]=2;
|
|
if (if_exe[ifstck-1]) if_exe[ifstck]=if_result[ifstck];
|
|
} else if (!strncmp(lp,"else",4) && if_state[ifstck]==2) {
|
|
lp+=4;
|
|
if_state[ifstck]=3;
|
|
if (if_exe[ifstck-1]) if_exe[ifstck]=!if_result[ifstck];
|
|
} else if (!strncmp(lp,"endif",5) && if_state[ifstck]>=2) {
|
|
lp+=5;
|
|
if (ifstck>0) {
|
|
if_state[ifstck]=0;
|
|
ifstck--;
|
|
}
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"or",2) && if_state[ifstck]==1) {
|
|
lp+=2;
|
|
and_or=1;
|
|
} else if (!strncmp(lp,"and",3) && if_state[ifstck]==1) {
|
|
lp+=3;
|
|
and_or=2;
|
|
}
|
|
|
|
if (*lp=='{' && if_state[ifstck]==1) {
|
|
lp+=1;
|
|
if_state[ifstck]=2;
|
|
if (if_exe[ifstck-1]) if_exe[ifstck]=if_result[ifstck];
|
|
} else if (*lp=='{' && if_state[ifstck]==3) {
|
|
lp+=1;
|
|
|
|
} else if (*lp=='}' && if_state[ifstck]>=2) {
|
|
lp++;
|
|
char *slp=lp;
|
|
uint8_t iselse=0;
|
|
for (uint8_t count=0; count<8;count++) {
|
|
if (*lp=='}') {
|
|
|
|
break;
|
|
}
|
|
if (!strncmp(lp,"else",4)) {
|
|
|
|
if_state[ifstck]=3;
|
|
if (if_exe[ifstck-1]) if_exe[ifstck]=!if_result[ifstck];
|
|
lp+=4;
|
|
iselse=1;
|
|
SCRIPT_SKIP_SPACES
|
|
if (*lp=='{') lp++;
|
|
break;
|
|
}
|
|
lp++;
|
|
}
|
|
if (!iselse) {
|
|
lp=slp;
|
|
|
|
if (ifstck>0) {
|
|
if_state[ifstck]=0;
|
|
ifstck--;
|
|
}
|
|
goto next_line;
|
|
}
|
|
}
|
|
|
|
if (!strncmp(lp,"for",3)) {
|
|
|
|
|
|
lp+=3;
|
|
SCRIPT_SKIP_SPACES
|
|
lp=isvar(lp,&vtype,&ind,0,0,0);
|
|
if ((vtype!=VAR_NV) && (vtype&STYPE)==0) {
|
|
|
|
uint8_t index=glob_script_mem.type[ind.index].index;
|
|
cv_count=&glob_script_mem.fvars[index];
|
|
SCRIPT_SKIP_SPACES
|
|
lp=GetNumericResult(lp,OPER_EQU,cv_count,0);
|
|
SCRIPT_SKIP_SPACES
|
|
lp=GetNumericResult(lp,OPER_EQU,&cv_max,0);
|
|
SCRIPT_SKIP_SPACES
|
|
lp=GetNumericResult(lp,OPER_EQU,&cv_inc,0);
|
|
|
|
cv_ptr=lp;
|
|
floop=1;
|
|
} else {
|
|
|
|
toLogEOL("for error",lp);
|
|
}
|
|
} else if (!strncmp(lp,"next",4) && floop>0) {
|
|
|
|
*cv_count+=cv_inc;
|
|
if (*cv_count<=cv_max) {
|
|
lp=cv_ptr;
|
|
} else {
|
|
lp+=4;
|
|
floop=0;
|
|
}
|
|
}
|
|
|
|
if (!strncmp(lp,"switch",6)) {
|
|
lp+=6;
|
|
SCRIPT_SKIP_SPACES
|
|
char *slp=lp;
|
|
lp=GetNumericResult(lp,OPER_EQU,&swvar,0);
|
|
if (glob_script_mem.glob_error==1) {
|
|
|
|
lp=slp;
|
|
|
|
lp=isvar(lp,&vtype,&ind,0,cmpstr,0);
|
|
swflg=0x81;
|
|
} else {
|
|
swflg=1;
|
|
}
|
|
} else if (!strncmp(lp,"case",4) && swflg>0) {
|
|
lp+=4;
|
|
SCRIPT_SKIP_SPACES
|
|
float cvar;
|
|
if (!(swflg&0x80)) {
|
|
lp=GetNumericResult(lp,OPER_EQU,&cvar,0);
|
|
if (swvar!=cvar) {
|
|
swflg=2;
|
|
} else {
|
|
swflg=1;
|
|
}
|
|
} else {
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,str,0);
|
|
if (!strcmp(cmpstr,str)) {
|
|
swflg=0x81;
|
|
} else {
|
|
swflg=0x82;
|
|
}
|
|
}
|
|
} else if (!strncmp(lp,"ends",4) && swflg>0) {
|
|
lp+=4;
|
|
swflg=0;
|
|
}
|
|
if ((swflg&3)==2) goto next_line;
|
|
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
if (*lp==SCRIPT_EOL) {
|
|
goto next_line;
|
|
}
|
|
|
|
|
|
if (!if_exe[ifstck] && if_state[ifstck]!=1) goto next_line;
|
|
|
|
#ifdef IFTHEN_DEBUG
|
|
sprintf(tbuff,"stack=%d,exe=%d,state=%d,cmpres=%d execute line: ",ifstck,if_exe[ifstck],if_state[ifstck],if_result[ifstck]);
|
|
toLogEOL(tbuff,lp);
|
|
#endif
|
|
|
|
if (!strncmp(lp,"break",5)) {
|
|
if (floop) {
|
|
|
|
floop=0;
|
|
} else {
|
|
section=0;
|
|
}
|
|
break;
|
|
} else if (!strncmp(lp,"dp",2) && isdigit(*(lp+2))) {
|
|
lp+=2;
|
|
|
|
glob_script_mem.script_dprec=atoi(lp);
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"delay(",6)) {
|
|
lp+=5;
|
|
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
delay(fvar);
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"spinm(",6)) {
|
|
lp+=6;
|
|
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
int8_t pinnr=fvar;
|
|
SCRIPT_SKIP_SPACES
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
int8_t mode=fvar;
|
|
pinMode(pinnr,mode&3);
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"spin(",5)) {
|
|
lp+=5;
|
|
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
int8_t pinnr=fvar;
|
|
SCRIPT_SKIP_SPACES
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
|
|
int8_t mode=fvar;
|
|
digitalWrite(pinnr,mode&1);
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"svars(",5)) {
|
|
lp+=5;
|
|
|
|
Scripter_save_pvars();
|
|
goto next_line;
|
|
}
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_WS2812
|
|
else if (!strncmp(lp,"ws2812(",7)) {
|
|
lp+=7;
|
|
lp=isvar(lp,&vtype,&ind,0,0,0);
|
|
if (vtype!=VAR_NV) {
|
|
|
|
uint8_t index=glob_script_mem.type[ind.index].index;
|
|
if ((vtype&STYPE)==0) {
|
|
|
|
if (glob_script_mem.type[index].bits.is_filter) {
|
|
uint8_t len=0;
|
|
float *fa=Get_MFAddr(index,&len);
|
|
|
|
if (fa && len) ws2812_set_array(fa,len);
|
|
}
|
|
}
|
|
}
|
|
goto next_line;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
else if (!strncmp(lp,"=>",2) || !strncmp(lp,"->",2) || !strncmp(lp,"+>",2) || !strncmp(lp,"print",5)) {
|
|
|
|
uint8_t sflag=0,pflg=0,svmqtt,swll;
|
|
if (*lp=='p') {
|
|
pflg=1;
|
|
lp+=5;
|
|
}
|
|
else {
|
|
if (*lp=='-') sflag=1;
|
|
if (*lp=='+') sflag=2;
|
|
lp+=2;
|
|
}
|
|
char *slp=lp;
|
|
SCRIPT_SKIP_SPACES
|
|
#define SCRIPT_CMDMEM 512
|
|
char *cmdmem=(char*)malloc(SCRIPT_CMDMEM);
|
|
if (cmdmem) {
|
|
char *cmd=cmdmem;
|
|
uint16_t count;
|
|
for (count=0; count<SCRIPT_CMDMEM/2-2; count++) {
|
|
|
|
if (!*lp || *lp=='\r' || *lp=='\n') {
|
|
cmd[count]=0;
|
|
break;
|
|
}
|
|
cmd[count]=*lp++;
|
|
}
|
|
|
|
|
|
char *tmp=cmdmem+SCRIPT_CMDMEM/2;
|
|
Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2);
|
|
|
|
|
|
if (!strncmp(tmp,"print",5) || pflg) {
|
|
if (pflg) toLog(tmp);
|
|
else toLog(&tmp[5]);
|
|
} else {
|
|
if (!sflag) {
|
|
tasm_cmd_activ=1;
|
|
AddLog_P2(glob_script_mem.script_loglevel&0x7f, PSTR("Script: performs \"%s\""), tmp);
|
|
} else if (sflag==2) {
|
|
|
|
} else {
|
|
tasm_cmd_activ=1;
|
|
svmqtt=Settings.flag.mqtt_enabled;
|
|
swll=Settings.weblog_level;
|
|
Settings.flag.mqtt_enabled=0;
|
|
Settings.weblog_level=0;
|
|
}
|
|
ExecuteCommand((char*)tmp, SRC_RULE);
|
|
tasm_cmd_activ=0;
|
|
if (sflag==1) {
|
|
Settings.flag.mqtt_enabled=svmqtt;
|
|
Settings.weblog_level=swll;
|
|
}
|
|
}
|
|
if (cmdmem) free(cmdmem);
|
|
}
|
|
lp=slp;
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"=#",2)) {
|
|
|
|
lp+=1;
|
|
char *slp=lp;
|
|
uint8_t plen=0;
|
|
while (*lp) {
|
|
if (*lp=='\n'|| *lp=='\r'|| *lp=='(') {
|
|
break;
|
|
}
|
|
lp++;
|
|
plen++;
|
|
}
|
|
if (fromscriptcmd) {
|
|
char *sp=glob_script_mem.scriptptr;
|
|
glob_script_mem.scriptptr=glob_script_mem.scriptptr_bu;
|
|
Run_Scripter(slp,plen,0);
|
|
glob_script_mem.scriptptr=sp;
|
|
} else {
|
|
Run_Scripter(slp,plen,0);
|
|
}
|
|
lp=slp;
|
|
goto next_line;
|
|
} else if (!strncmp(lp,"=(",2)) {
|
|
lp+=2;
|
|
char str[128];
|
|
str[0]='>';
|
|
lp=GetStringResult(lp,OPER_EQU,&str[1],0);
|
|
lp++;
|
|
execute_script(str);
|
|
}
|
|
|
|
|
|
if (if_state[ifstck]==1) {
|
|
|
|
lp=Evaluate_expression(lp,and_or,&if_result[ifstck],jo);
|
|
SCRIPT_SKIP_SPACES
|
|
if (*lp=='{' && if_state[ifstck]==1) {
|
|
lp+=1;
|
|
if_state[ifstck]=2;
|
|
if (if_exe[ifstck-1]) if_exe[ifstck]=if_result[ifstck];
|
|
}
|
|
goto next_line;
|
|
} else {
|
|
lp=isvar(lp,&vtype,&ind,&sysvar,0,0);
|
|
if (vtype!=VAR_NV) {
|
|
|
|
globvindex=ind.index;
|
|
globaindex=last_findex;
|
|
uint8_t index=glob_script_mem.type[ind.index].index;
|
|
if ((vtype&STYPE)==0) {
|
|
|
|
if (ind.bits.settable || ind.bits.is_filter) {
|
|
dfvar=&sysvar;
|
|
if (ind.bits.settable) {
|
|
sysv_type=ind.index;
|
|
} else {
|
|
sysv_type=0;
|
|
}
|
|
|
|
} else {
|
|
dfvar=&glob_script_mem.fvars[index];
|
|
sysv_type=0;
|
|
}
|
|
numeric=1;
|
|
lp=getop(lp,&lastop);
|
|
char *slp=lp;
|
|
glob_script_mem.glob_error=0;
|
|
lp=GetNumericResult(lp,OPER_EQU,&fvar,jo);
|
|
if (glob_script_mem.glob_error==1) {
|
|
|
|
|
|
lp=isvar(slp,&vtype,&ind,0,cmpstr,jo);
|
|
fvar=CharToFloat(cmpstr);
|
|
}
|
|
switch (lastop) {
|
|
case OPER_EQU:
|
|
if (glob_script_mem.var_not_found) {
|
|
if (!js) toLogEOL("var not found: ",lp);
|
|
goto next_line;
|
|
}
|
|
*dfvar=fvar;
|
|
break;
|
|
case OPER_PLSEQU:
|
|
*dfvar+=fvar;
|
|
break;
|
|
case OPER_MINEQU:
|
|
*dfvar-=fvar;
|
|
break;
|
|
case OPER_MULEQU:
|
|
*dfvar*=fvar;
|
|
break;
|
|
case OPER_DIVEQU:
|
|
*dfvar/=fvar;
|
|
break;
|
|
case OPER_PERCEQU:
|
|
*dfvar=fmodf(*dfvar,fvar);
|
|
break;
|
|
case OPER_ANDEQU:
|
|
*dfvar=(uint32_t)*dfvar&(uint32_t)fvar;
|
|
break;
|
|
case OPER_OREQU:
|
|
*dfvar=(uint32_t)*dfvar|(uint32_t)fvar;
|
|
break;
|
|
case OPER_XOREQU:
|
|
*dfvar=(uint32_t)*dfvar^(uint32_t)fvar;
|
|
break;
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
glob_script_mem.type[globvindex].bits.changed=1;
|
|
if (glob_script_mem.type[globvindex].bits.is_filter) {
|
|
if (globaindex>=0) {
|
|
Set_MFVal(glob_script_mem.type[globvindex].index,globaindex,*dfvar);
|
|
} else {
|
|
Set_MFilter(glob_script_mem.type[globvindex].index,*dfvar);
|
|
}
|
|
}
|
|
|
|
if (sysv_type) {
|
|
switch (sysv_type) {
|
|
case SCRIPT_LOGLEVEL:
|
|
glob_script_mem.script_loglevel=*dfvar;
|
|
break;
|
|
case SCRIPT_TELEPERIOD:
|
|
if (*dfvar<10) *dfvar=10;
|
|
if (*dfvar>300) *dfvar=300;
|
|
Settings.tele_period=*dfvar;
|
|
break;
|
|
}
|
|
sysv_type=0;
|
|
}
|
|
} else {
|
|
|
|
numeric=0;
|
|
sindex=index;
|
|
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=getop(lp,&lastop);
|
|
char *slp=lp;
|
|
glob_script_mem.glob_error=0;
|
|
lp=GetStringResult(lp,OPER_EQU,str,jo);
|
|
if (!js && glob_script_mem.glob_error) {
|
|
|
|
lp=GetNumericResult(slp,OPER_EQU,&fvar,0);
|
|
dtostrfd(fvar,6,str);
|
|
glob_script_mem.glob_error=0;
|
|
}
|
|
|
|
if (!glob_script_mem.var_not_found) {
|
|
|
|
glob_script_mem.type[globvindex].bits.changed=1;
|
|
if (lastop==OPER_EQU) {
|
|
strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize);
|
|
} else if (lastop==OPER_PLSEQU) {
|
|
strncat(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
SCRIPT_SKIP_SPACES
|
|
if (*lp=='{' && if_state[ifstck]==3) {
|
|
lp+=1;
|
|
|
|
}
|
|
goto next_line;
|
|
}
|
|
} else {
|
|
|
|
if (*lp=='>' && tlen==1) {
|
|
|
|
lp++;
|
|
section=1;
|
|
fromscriptcmd=1;
|
|
goto startline;
|
|
}
|
|
if (!strncmp(lp,type,tlen)) {
|
|
|
|
section=1;
|
|
glob_script_mem.section_ptr=lp;
|
|
if (check) {
|
|
return 99;
|
|
}
|
|
|
|
char *ctype=(char*)type;
|
|
if (*ctype=='#') {
|
|
|
|
ctype+=tlen;
|
|
if (*ctype=='(' && *(lp+tlen)=='(') {
|
|
float fparam;
|
|
numeric=1;
|
|
glob_script_mem.glob_error=0;
|
|
GetNumericResult((char*)ctype,OPER_EQU,&fparam,0);
|
|
if (glob_script_mem.glob_error==1) {
|
|
|
|
numeric=0;
|
|
|
|
GetStringResult((char*)ctype+1,OPER_EQU,cmpstr,0);
|
|
}
|
|
lp+=tlen;
|
|
if (*lp=='(') {
|
|
|
|
lp++;
|
|
lp=isvar(lp,&vtype,&ind,0,0,0);
|
|
if (vtype!=VAR_NV) {
|
|
|
|
uint8_t index=glob_script_mem.type[ind.index].index;
|
|
if ((vtype&STYPE)==0) {
|
|
|
|
dfvar=&glob_script_mem.fvars[index];
|
|
if (numeric) {
|
|
*dfvar=fparam;
|
|
} else {
|
|
|
|
*dfvar=CharToFloat(cmpstr);
|
|
}
|
|
} else {
|
|
|
|
sindex=index;
|
|
if (!numeric) {
|
|
strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),cmpstr,glob_script_mem.max_ssize);
|
|
} else {
|
|
|
|
dtostrfd(fparam,6,glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
lp+=tlen;
|
|
if (*ctype=='(' || (*lp!=SCRIPT_EOL && *lp!='?')) {
|
|
|
|
section=0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
next_line:
|
|
if (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
} else {
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) {
|
|
if (section) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
lp++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint8_t script_xsns_index = 0;
|
|
|
|
|
|
void ScripterEvery100ms(void) {
|
|
|
|
if (Settings.rule_enabled && (uptime > 4)) {
|
|
mqtt_data[0] = '\0';
|
|
uint16_t script_tele_period_save = tele_period;
|
|
tele_period = 2;
|
|
XsnsNextCall(FUNC_JSON_APPEND, script_xsns_index);
|
|
tele_period = script_tele_period_save;
|
|
if (strlen(mqtt_data)) {
|
|
mqtt_data[0] = '{';
|
|
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
|
|
Run_Scripter(">T",2, mqtt_data);
|
|
}
|
|
}
|
|
if (fast_script==99) Run_Scripter(">F",2,0);
|
|
}
|
|
|
|
|
|
|
|
|
|
void Scripter_save_pvars(void) {
|
|
int16_t mlen=0;
|
|
float *fp=(float*)glob_script_mem.script_pram;
|
|
mlen+=sizeof(float);
|
|
struct T_INDEX *vtp=glob_script_mem.type;
|
|
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
|
|
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
|
|
uint8_t index=vtp[count].index;
|
|
mlen+=sizeof(float);
|
|
if (mlen>PMEM_SIZE) {
|
|
vtp[count].bits.is_permanent=0;
|
|
return;
|
|
}
|
|
*fp++=glob_script_mem.fvars[index];
|
|
}
|
|
}
|
|
char *cp=(char*)fp;
|
|
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
|
|
if (vtp[count].bits.is_permanent && vtp[count].bits.is_string) {
|
|
uint8_t index=vtp[count].index;
|
|
char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
|
|
uint8_t slen=strlen(sp);
|
|
mlen+=slen+1;
|
|
if (mlen>PMEM_SIZE) {
|
|
vtp[count].bits.is_permanent=0;
|
|
return;
|
|
}
|
|
strcpy(cp,sp);
|
|
cp+=slen+1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
#define WEB_HANDLE_SCRIPT "s10"
|
|
|
|
const char S_CONFIGURE_SCRIPT[] PROGMEM = D_CONFIGURE_SCRIPT;
|
|
|
|
const char HTTP_BTN_MENU_RULES[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_SCRIPT "' method='get'><button>" D_CONFIGURE_SCRIPT "</button></form></p>";
|
|
|
|
|
|
const char HTTP_FORM_SCRIPT[] PROGMEM =
|
|
"<fieldset><legend><b> " D_SCRIPT " </b></legend>"
|
|
"<form method='post' action='" WEB_HANDLE_SCRIPT "'>";
|
|
|
|
const char HTTP_FORM_SCRIPT1[] PROGMEM =
|
|
"<div style='text-align:right' id='charNum'> </div>"
|
|
"<input style='width:3%%;' id='c%d' name='c%d' type='checkbox'%s><b>" D_SCRIPT_ENABLE "</b><br/>"
|
|
"<br><textarea id='t1' name='t1' rows='8' cols='80' maxlength='%d' style='font-size: 12pt' >";
|
|
|
|
const char HTTP_FORM_SCRIPT1b[] PROGMEM =
|
|
"</textarea>"
|
|
"<script type='text/javascript'>"
|
|
"eb('charNum').innerHTML='-';"
|
|
"var ta=eb('t1');"
|
|
"ta.addEventListener('keydown',function(e){"
|
|
"e = e || window.event;"
|
|
"var ml=this.getAttribute('maxlength');"
|
|
"var cl=this.value.length;"
|
|
"if(cl>=ml){"
|
|
"eb('charNum').innerHTML='" D_SCRIPT_CHARS_NO_MORE "';"
|
|
"}else{"
|
|
"eb('charNum').innerHTML=ml-cl+' " D_SCRIPT_CHARS_LEFT "';"
|
|
"}"
|
|
|
|
#if 0
|
|
|
|
"if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.which === 86) {"
|
|
|
|
"var paste = window.clipboardData.getData('Text');"
|
|
|
|
|
|
"var out=\"\";"
|
|
"var re=/\\r\\n|\\n\\r|\\n|\\r/g;"
|
|
"var allLines=paste.replace(re,\"\\n\").split(\"\\n\");"
|
|
"allLines.forEach((line) => {"
|
|
"if(line.length>0) {"
|
|
"if(line.charAt(0)!=';'){"
|
|
"out+=line+'\\n';"
|
|
"}"
|
|
"}"
|
|
"});"
|
|
"alert(out);"
|
|
"}"
|
|
#endif
|
|
|
|
"return true;"
|
|
"});"
|
|
|
|
|
|
|
|
#ifdef SCRIPT_STRIP_COMMENTS
|
|
"ta.addEventListener('paste',function(e){"
|
|
"let paste = (e.clipboardData || window.clipboardData).getData('text');"
|
|
"var ml=this.getAttribute('maxlength');"
|
|
"if(paste.length>=ml){"
|
|
"var out=\"\";"
|
|
"var re=/\\r\\n|\\n\\r|\\n|\\r/g;"
|
|
"var allLines=paste.replace(re,\"\\n\").split(\"\\n\");"
|
|
"allLines.forEach((line) => {"
|
|
"if(line.length>0) {"
|
|
"if(line.charAt(0)!=';'){"
|
|
"out+=line+'\\n';"
|
|
"}"
|
|
"}"
|
|
"});"
|
|
"event.preventDefault();"
|
|
"eb('t1').textContent=out;"
|
|
"}"
|
|
# 3145 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
"});"
|
|
|
|
|
|
#endif
|
|
|
|
"</script>";
|
|
|
|
const char HTTP_SCRIPT_FORM_END[] PROGMEM =
|
|
"<br/>"
|
|
"<button name='save' type='submit' formmethod='post' formenctype='multipart/form-data' formaction='/ta' class='button bgrn'>" D_SAVE "</button>"
|
|
"</form></fieldset>";
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
const char HTTP_FORM_SCRIPT1c[] PROGMEM =
|
|
"<button name='d%d' type='submit' class='button bgrn'>" D_SCRIPT_DOWNLOAD " '%s'</button>";
|
|
#ifdef SDCARD_DIR
|
|
const char HTTP_FORM_SCRIPT1d[] PROGMEM =
|
|
"<button method='post' name='upl' type='submit' class='button bgrn'>" D_SDCARD_DIR "</button>";
|
|
#else
|
|
const char HTTP_FORM_SCRIPT1d[] PROGMEM =
|
|
"<button method='post' name='upl' type='submit' class='button bgrn'>" D_SCRIPT_UPLOAD_FILES "</button>";
|
|
#endif
|
|
|
|
#ifdef SDCARD_DIR
|
|
const char S_SCRIPT_FILE_UPLOAD[] PROGMEM = D_SDCARD_DIR;
|
|
#else
|
|
const char S_SCRIPT_FILE_UPLOAD[] PROGMEM = D_SDCARD_UPLOAD;
|
|
#endif
|
|
|
|
const char HTTP_FORM_FILE_UPLOAD[] PROGMEM =
|
|
"<div id='f1' name='f1' style='display:block;'>"
|
|
"<fieldset><legend><b> %s" " </b></legend>";
|
|
const char HTTP_FORM_FILE_UPG[] PROGMEM =
|
|
"<form method='post' action='u3' enctype='multipart/form-data'>"
|
|
"<br/><input type='file' name='u3'><br/>"
|
|
"<br/><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>" D_START " %s</button></form>";
|
|
|
|
const char HTTP_FORM_FILE_UPGb[] PROGMEM =
|
|
"</fieldset>"
|
|
"</div>"
|
|
"<div id='f2' name='f2' style='display:none;text-align:center;'><b>" D_UPLOAD_STARTED " ...</b></div>";
|
|
|
|
const char HTTP_FORM_SDC_DIRa[] PROGMEM =
|
|
"<div style='text-align:left'>";
|
|
const char HTTP_FORM_SDC_DIRb[] PROGMEM =
|
|
"<pre><a href='%s' file='%s'>%s</a> %d</pre>";
|
|
const char HTTP_FORM_SDC_DIRd[] PROGMEM =
|
|
"<pre><a href='%s' file='%s'>%s</a></pre>";
|
|
const char HTTP_FORM_SDC_DIRc[] PROGMEM =
|
|
"</div>";
|
|
const char HTTP_FORM_SDC_HREF[] PROGMEM =
|
|
"http://%s/upl?download=%s/%s";
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
|
|
#if USE_LONG_FILE_NAMES>0
|
|
#undef REJCMPL
|
|
#define REJCMPL 6
|
|
#else
|
|
#undef REJCMPL
|
|
#define REJCMPL 8
|
|
#endif
|
|
|
|
uint8_t reject(char *name) {
|
|
|
|
if (*name=='_') return 1;
|
|
if (*name=='.') return 1;
|
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
if (!strncasecmp(name,"SPOTLI~1",REJCMPL)) return 1;
|
|
if (!strncasecmp(name,"TRASHE~1",REJCMPL)) return 1;
|
|
if (!strncasecmp(name,"FSEVEN~1",REJCMPL)) return 1;
|
|
if (!strncasecmp(name,"SYSTEM~1",REJCMPL)) return 1;
|
|
#else
|
|
if (!strcasecmp(name,"SPOTLI~1")) return 1;
|
|
if (!strcasecmp(name,"TRASHE~1")) return 1;
|
|
if (!strcasecmp(name,"FSEVEN~1")) return 1;
|
|
if (!strcasecmp(name,"SYSTEM~1")) return 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void ListDir(char *path, uint8_t depth) {
|
|
char name[32];
|
|
char npath[128];
|
|
char format[12];
|
|
sprintf(format,"%%-%ds",24-depth);
|
|
|
|
File dir=SD.open(path);
|
|
if (dir) {
|
|
dir.rewindDirectory();
|
|
if (strlen(path)>1) {
|
|
snprintf_P(npath,sizeof(npath),PSTR("http://%s/upl?download=%s"),WiFi.localIP().toString().c_str(),path);
|
|
for (uint8_t cnt=strlen(npath)-1;cnt>0;cnt--) {
|
|
if (npath[cnt]=='/') {
|
|
if (npath[cnt-1]=='=') npath[cnt+1]=0;
|
|
else npath[cnt]=0;
|
|
break;
|
|
}
|
|
}
|
|
WSContentSend_P(HTTP_FORM_SDC_DIRd,npath,path,"..");
|
|
}
|
|
while (true) {
|
|
File entry=dir.openNextFile();
|
|
if (!entry) {
|
|
break;
|
|
}
|
|
char *pp=path;
|
|
if (!*(pp+1)) pp++;
|
|
char *cp=name;
|
|
|
|
if (reject((char*)entry.name())) goto fclose;
|
|
|
|
for (uint8_t cnt=0;cnt<depth;cnt++) {
|
|
*cp++='-';
|
|
}
|
|
|
|
sprintf(cp,format,entry.name());
|
|
if (entry.isDirectory()) {
|
|
snprintf_P(npath,sizeof(npath),HTTP_FORM_SDC_HREF,WiFi.localIP().toString().c_str(),pp,entry.name());
|
|
WSContentSend_P(HTTP_FORM_SDC_DIRd,npath,entry.name(),name);
|
|
uint8_t plen=strlen(path);
|
|
if (plen>1) {
|
|
strcat(path,"/");
|
|
}
|
|
strcat(path,entry.name());
|
|
ListDir(path,depth+4);
|
|
path[plen]=0;
|
|
} else {
|
|
snprintf_P(npath,sizeof(npath),HTTP_FORM_SDC_HREF,WiFi.localIP().toString().c_str(),pp,entry.name());
|
|
WSContentSend_P(HTTP_FORM_SDC_DIRb,npath,entry.name(),name,entry.size());
|
|
}
|
|
fclose:
|
|
entry.close();
|
|
}
|
|
dir.close();
|
|
}
|
|
}
|
|
|
|
char path[48];
|
|
|
|
void Script_FileUploadConfiguration(void)
|
|
{
|
|
uint8_t depth=0;
|
|
strcpy(path,"/");
|
|
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
if (WebServer->hasArg("download")) {
|
|
String stmp = WebServer->arg("download");
|
|
char *cp=(char*)stmp.c_str();
|
|
if (DownloadFile(cp)) {
|
|
|
|
strcpy(path,cp);
|
|
}
|
|
}
|
|
|
|
WSContentStart_P(S_SCRIPT_FILE_UPLOAD);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_FILE_UPLOAD,D_SDCARD_DIR);
|
|
WSContentSend_P(HTTP_FORM_FILE_UPG, D_SCRIPT_UPLOAD);
|
|
#ifdef SDCARD_DIR
|
|
WSContentSend_P(HTTP_FORM_SDC_DIRa);
|
|
if (glob_script_mem.script_sd_found) {
|
|
ListDir(path,depth);
|
|
}
|
|
WSContentSend_P(HTTP_FORM_SDC_DIRc);
|
|
#endif
|
|
WSContentSend_P(HTTP_FORM_FILE_UPGb);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
Web.upload_error = 0;
|
|
}
|
|
|
|
File upload_file;
|
|
|
|
void ScriptFileUploadSuccess(void) {
|
|
WSContentStart_P(S_INFORMATION);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(PSTR("<div style='text-align:center;'><b>" D_UPLOAD " <font color='#"));
|
|
WSContentSend_P(PSTR("%06x'>" D_SUCCESSFUL "</font></b><br/>"), WebColor(COL_TEXT_SUCCESS));
|
|
WSContentSend_P(PSTR("</div><br/>"));
|
|
WSContentSend_P(PSTR("<p><form action='%s' method='get'><button>%s</button></form></p>"),"/upl",D_UPL_DONE);
|
|
|
|
WSContentStop();
|
|
}
|
|
|
|
|
|
|
|
void script_upload(void) {
|
|
|
|
|
|
|
|
HTTPUpload& upload = WebServer->upload();
|
|
if (upload.status == UPLOAD_FILE_START) {
|
|
char npath[48];
|
|
sprintf(npath,"%s/%s",path,upload.filename.c_str());
|
|
SD.remove(npath);
|
|
upload_file=SD.open(npath,FILE_WRITE);
|
|
if (!upload_file) Web.upload_error=1;
|
|
} else if(upload.status == UPLOAD_FILE_WRITE) {
|
|
if (upload_file) upload_file.write(upload.buf,upload.currentSize);
|
|
} else if(upload.status == UPLOAD_FILE_END) {
|
|
if (upload_file) upload_file.close();
|
|
if (Web.upload_error) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: upload error"));
|
|
}
|
|
} else {
|
|
Web.upload_error=1;
|
|
WebServer->send(500, "text/plain", "500: couldn't create file");
|
|
}
|
|
}
|
|
|
|
uint8_t DownloadFile(char *file) {
|
|
File download_file;
|
|
WiFiClient download_Client;
|
|
|
|
if (!SD.exists(file)) {
|
|
AddLog_P(LOG_LEVEL_INFO,PSTR("file not found"));
|
|
return 0;
|
|
}
|
|
|
|
download_file=SD.open(file,FILE_READ);
|
|
if (!download_file) {
|
|
AddLog_P(LOG_LEVEL_INFO,PSTR("could not open file"));
|
|
return 0;
|
|
}
|
|
|
|
if (download_file.isDirectory()) {
|
|
download_file.close();
|
|
return 1;
|
|
}
|
|
|
|
uint32_t flen=download_file.size();
|
|
|
|
download_Client = WebServer->client();
|
|
WebServer->setContentLength(flen);
|
|
|
|
char attachment[100];
|
|
char *cp;
|
|
for (uint8_t cnt=strlen(file); cnt>=0; cnt--) {
|
|
if (file[cnt]=='/') {
|
|
cp=&file[cnt+1];
|
|
break;
|
|
}
|
|
}
|
|
snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"),cp);
|
|
WebServer->sendHeader(F("Content-Disposition"), attachment);
|
|
WSSend(200, CT_STREAM, "");
|
|
|
|
uint8_t buff[512];
|
|
uint16_t bread;
|
|
|
|
|
|
uint8_t cnt=0;
|
|
while (download_file.available()) {
|
|
bread=download_file.read(buff,sizeof(buff));
|
|
uint16_t bw=download_Client.write((const char*)buff,bread);
|
|
if (!bw) break;
|
|
cnt++;
|
|
if (cnt>7) {
|
|
cnt=0;
|
|
if (glob_script_mem.script_loglevel&0x80) {
|
|
|
|
loop();
|
|
}
|
|
}
|
|
}
|
|
download_file.close();
|
|
download_Client.stop();
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void HandleScriptTextareaConfiguration(void) {
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
ScriptSaveSettings();
|
|
HandleConfiguration();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void HandleScriptConfiguration(void) {
|
|
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_SCRIPT);
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
if (WebServer->hasArg("d1")) {
|
|
DownloadFile(glob_script_mem.flink[0]);
|
|
}
|
|
if (WebServer->hasArg("d2")) {
|
|
DownloadFile(glob_script_mem.flink[1]);
|
|
}
|
|
if (WebServer->hasArg("upl")) {
|
|
Script_FileUploadConfiguration();
|
|
}
|
|
#endif
|
|
|
|
WSContentStart_P(S_CONFIGURE_SCRIPT);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_SCRIPT);
|
|
|
|
|
|
#ifdef xSCRIPT_STRIP_COMMENTS
|
|
uint16_t ssize=glob_script_mem.script_size;
|
|
if (bitRead(Settings.rule_enabled, 1)) ssize*=2;
|
|
WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",ssize);
|
|
#else
|
|
WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",glob_script_mem.script_size);
|
|
#endif
|
|
|
|
|
|
if (glob_script_mem.script_ram[0]) {
|
|
_WSContentSend(glob_script_mem.script_ram);
|
|
}
|
|
WSContentSend_P(HTTP_FORM_SCRIPT1b);
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
if (glob_script_mem.script_sd_found) {
|
|
WSContentSend_P(HTTP_FORM_SCRIPT1d);
|
|
if (glob_script_mem.flink[0][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,1,glob_script_mem.flink[0]);
|
|
if (glob_script_mem.flink[1][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,2,glob_script_mem.flink[1]);
|
|
}
|
|
#endif
|
|
|
|
WSContentSend_P(HTTP_SCRIPT_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
|
|
void ScriptSaveSettings(void) {
|
|
|
|
if (WebServer->hasArg("c1")) {
|
|
bitWrite(Settings.rule_enabled,0,1);
|
|
} else {
|
|
bitWrite(Settings.rule_enabled,0,0);
|
|
}
|
|
|
|
|
|
String str = WebServer->arg("t1");
|
|
|
|
if (*str.c_str()) {
|
|
|
|
str.replace("\r\n","\n");
|
|
str.replace("\r","\n");
|
|
|
|
#ifdef xSCRIPT_STRIP_COMMENTS
|
|
if (bitRead(Settings.rule_enabled, 1)) {
|
|
char *sp=(char*)str.c_str();
|
|
char *sp1=sp;
|
|
char *dp=sp;
|
|
uint8_t flg=0;
|
|
while (*sp) {
|
|
while (*sp==' ') sp++;
|
|
sp1=sp;
|
|
sp=strchr(sp,'\n');
|
|
if (!sp) {
|
|
flg=1;
|
|
} else {
|
|
*sp=0;
|
|
}
|
|
if (*sp1!=';') {
|
|
uint8_t slen=strlen(sp1);
|
|
if (slen) {
|
|
strcpy(dp,sp1);
|
|
dp+=slen;
|
|
*dp++='\n';
|
|
}
|
|
}
|
|
if (flg) {
|
|
*dp=0;
|
|
break;
|
|
}
|
|
sp++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
strlcpy(glob_script_mem.script_ram,str.c_str(), glob_script_mem.script_size);
|
|
|
|
#ifdef USE_24C256
|
|
#ifndef USE_SCRIPT_FATFS
|
|
if (glob_script_mem.flags&1) {
|
|
EEP_WRITE(0,EEP_SCRIPT_SIZE,glob_script_mem.script_ram);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
if (glob_script_mem.flags&1) {
|
|
SD.remove(FAT_SCRIPT_NAME);
|
|
File file=SD.open(FAT_SCRIPT_NAME,FILE_WRITE);
|
|
file.write(glob_script_mem.script_ram,FAT_SCRIPT_SIZE);
|
|
file.close();
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
if (glob_script_mem.script_mem) {
|
|
Scripter_save_pvars();
|
|
free(glob_script_mem.script_mem);
|
|
glob_script_mem.script_mem=0;
|
|
glob_script_mem.script_mem_size=0;
|
|
}
|
|
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
int16_t res=Init_Scripter();
|
|
if (res) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("script init error: %d"), res);
|
|
return;
|
|
}
|
|
Run_Scripter(">B",2,0);
|
|
fast_script=Run_Scripter(">F",-2,0);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
#if defined(USE_SCRIPT_HUE) && defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
|
|
|
|
|
|
#define HUE_DEV_MVNUM 5
|
|
#define HUE_DEV_NSIZE 16
|
|
struct HUE_SCRIPT {
|
|
char name[HUE_DEV_NSIZE];
|
|
uint8_t type;
|
|
uint8_t index[HUE_DEV_MVNUM];
|
|
uint8_t vindex[HUE_DEV_MVNUM];
|
|
} hue_script[32];
|
|
|
|
|
|
const char SCRIPT_HUE_LIGHTS_STATUS_JSON1[] PROGMEM =
|
|
"{\"state\":"
|
|
"{\"on\":{state},"
|
|
"{light_status}"
|
|
"\"alert\":\"none\","
|
|
"\"effect\":\"none\","
|
|
"\"reachable\":true}"
|
|
",\"type\":\"{type}\","
|
|
"\"name\":\"{j1\","
|
|
"\"modelid\":\"{m1}\","
|
|
"\"uniqueid\":\"{j2\","
|
|
"\"swversion\":\"5.50.1.19085\"}";
|
|
# 3624 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
const char SCRIPT_HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
|
|
"{\"state\":{"
|
|
"\"presence\":{state},"
|
|
"\"lastupdated\":\"2017-10-01T12:37:30\""
|
|
"},"
|
|
"\"swupdate\":{"
|
|
"\"state\":\"noupdates\","
|
|
"\"lastinstall\": null"
|
|
"},"
|
|
"\"config\":{"
|
|
"\"on\":true,"
|
|
"\"battery\":100,"
|
|
"\"reachable\":true,"
|
|
"\"alert\":\"none\","
|
|
"\"ledindication\":false,"
|
|
"\"usertest\":false,"
|
|
"\"sensitivity\":2,"
|
|
"\"sensitivitymax\":2,"
|
|
"\"pending\":[]"
|
|
"},"
|
|
"\"name\":\"{j1\","
|
|
"\"type\":\"ZLLPresence\","
|
|
"\"modelid\":\"SML001\","
|
|
"\"manufacturername\":\"Philips\","
|
|
"\"swversion\":\"6.1.0.18912\","
|
|
"\"uniqueid\":\"{j2\""
|
|
"}";
|
|
# 3705 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
void Script_HueStatus(String *response, uint16_t hue_devs) {
|
|
|
|
if (hue_script[hue_devs].type=='p') {
|
|
*response+=FPSTR(SCRIPT_HUE_LIGHTS_STATUS_JSON2);
|
|
response->replace("{j1",hue_script[hue_devs].name);
|
|
response->replace("{j2", GetHueDeviceId(hue_devs));
|
|
uint8_t pwr=glob_script_mem.fvars[hue_script[hue_devs].index[0]-1];
|
|
response->replace("{state}", (pwr ? "true" : "false"));
|
|
return;
|
|
}
|
|
|
|
*response+=FPSTR(SCRIPT_HUE_LIGHTS_STATUS_JSON1);
|
|
uint8_t pwr=glob_script_mem.fvars[hue_script[hue_devs].index[0]-1];
|
|
response->replace("{state}", (pwr ? "true" : "false"));
|
|
String light_status = "";
|
|
if (hue_script[hue_devs].index[1]>0) {
|
|
|
|
light_status += "\"bri\":";
|
|
uint32_t bri=glob_script_mem.fvars[hue_script[hue_devs].index[1]-1];
|
|
if (bri > 254) bri = 254;
|
|
if (bri < 1) bri = 1;
|
|
light_status += String(bri);
|
|
light_status += ",";
|
|
}
|
|
if (hue_script[hue_devs].index[2]>0) {
|
|
|
|
uint32_t hue=glob_script_mem.fvars[hue_script[hue_devs].index[2]-1];
|
|
|
|
light_status += "\"hue\":";
|
|
light_status += String(hue);
|
|
light_status += ",";
|
|
}
|
|
if (hue_script[hue_devs].index[3]>0) {
|
|
|
|
uint32_t sat=glob_script_mem.fvars[hue_script[hue_devs].index[3]-1] ;
|
|
if (sat > 254) sat = 254;
|
|
if (sat < 1) sat = 1;
|
|
light_status += "\"sat\":";
|
|
light_status += String(sat);
|
|
light_status += ",";
|
|
}
|
|
if (hue_script[hue_devs].index[4]>0) {
|
|
|
|
uint32_t ct=glob_script_mem.fvars[hue_script[hue_devs].index[4]-1];
|
|
light_status += "\"ct\":";
|
|
light_status += String(ct);
|
|
light_status += ",";
|
|
}
|
|
|
|
float temp;
|
|
switch (hue_script[hue_devs].type) {
|
|
case 'C':
|
|
response->replace("{type}","Color Ligh");
|
|
response->replace("{m1","LST001");
|
|
break;
|
|
case 'D':
|
|
response->replace("{type}","Dimmable Light");
|
|
response->replace("{m1","LWB004");
|
|
break;
|
|
case 'T':
|
|
response->replace("{type}","Color Temperature Light");
|
|
response->replace("{m1","LTW011");
|
|
break;
|
|
case 'E':
|
|
response->replace("{type}","Extended color light");
|
|
response->replace("{m1","LCT007");
|
|
break;
|
|
case 'S':
|
|
response->replace("{type}","On/Off light");
|
|
response->replace("{m1","LCT007");
|
|
break;
|
|
default:
|
|
response->replace("{type}","color light");
|
|
response->replace("{m1","LST001");
|
|
break;
|
|
}
|
|
|
|
response->replace("{light_status}", light_status);
|
|
response->replace("{j1",hue_script[hue_devs].name);
|
|
response->replace("{j2", GetHueDeviceId(hue_devs));
|
|
|
|
}
|
|
|
|
void Script_Check_Hue(String *response) {
|
|
if (!bitRead(Settings.rule_enabled, 0)) return;
|
|
|
|
uint8_t hue_script_found=Run_Scripter(">H",-2,0);
|
|
if (hue_script_found!=99) return;
|
|
|
|
char line[128];
|
|
char tmp[128];
|
|
uint8_t hue_devs=0;
|
|
uint8_t vindex=0;
|
|
char *cp;
|
|
char *lp=glob_script_mem.section_ptr+2;
|
|
while (lp) {
|
|
SCRIPT_SKIP_SPACES
|
|
while (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
}
|
|
if (!*lp || *lp=='#' || *lp=='>') {
|
|
break;
|
|
}
|
|
if (*lp!=';') {
|
|
|
|
memcpy(line,lp,sizeof(line));
|
|
line[sizeof(line)-1]=0;
|
|
cp=line;
|
|
for (uint32_t i=0; i<sizeof(line); i++) {
|
|
if (!*cp || *cp=='\n' || *cp=='\r') {
|
|
*cp=0;
|
|
break;
|
|
}
|
|
cp++;
|
|
}
|
|
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
|
|
|
|
|
|
cp=tmp;
|
|
cp=strchr(cp,',');
|
|
if (!cp) break;
|
|
*cp=0;
|
|
|
|
strlcpy(hue_script[hue_devs].name,tmp,HUE_DEV_NSIZE);
|
|
cp++;
|
|
while (*cp==' ') cp++;
|
|
|
|
hue_script[hue_devs].type=*cp;
|
|
|
|
for (vindex=0;vindex<HUE_DEV_MVNUM;vindex++) {
|
|
hue_script[hue_devs].index[vindex]=0;
|
|
}
|
|
vindex=0;
|
|
while (1) {
|
|
cp=strchr(cp,',');
|
|
if (!cp) break;
|
|
|
|
cp++;
|
|
while (*cp==' ') cp++;
|
|
|
|
vindex==0xff;
|
|
if (!strncmp(cp,"on=",3)) {
|
|
cp+=3;
|
|
vindex=0;
|
|
} else if (!strncmp(cp,"bri=",4)) {
|
|
cp+=4;
|
|
vindex=1;
|
|
} else if (!strncmp(cp,"hue=",4)) {
|
|
cp+=4;
|
|
vindex=2;
|
|
} else if (!strncmp(cp,"sat=",4)) {
|
|
cp+=4;
|
|
vindex=3;
|
|
} else if (!strncmp(cp,"ct=",3)) {
|
|
cp+=3;
|
|
vindex=4;
|
|
} else {
|
|
|
|
vindex==0xff;
|
|
break;
|
|
}
|
|
if (vindex!=0xff) {
|
|
struct T_INDEX ind;
|
|
uint8_t vtype;
|
|
char vname[16];
|
|
for (uint32_t cnt=0;cnt<sizeof(vname)-1;cnt++) {
|
|
if (*cp==',' || *cp==0) {
|
|
vname[cnt]=0;
|
|
break;
|
|
}
|
|
vname[cnt]=*cp++;
|
|
}
|
|
isvar(vname,&vtype,&ind,0,0,0);
|
|
if (vtype!=VAR_NV) {
|
|
|
|
if (vtype==NUM_RES || (vtype&STYPE)==0) {
|
|
hue_script[hue_devs].vindex[vindex]=ind.index;
|
|
hue_script[hue_devs].index[vindex]=glob_script_mem.type[ind.index].index+1;
|
|
} else {
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (response) {
|
|
if (devices_present) {
|
|
*response+=",\"";
|
|
}
|
|
else {
|
|
if (hue_devs>0) *response+=",\"";
|
|
}
|
|
*response+=String(EncodeLightId(hue_devs+devices_present+1))+"\":";
|
|
Script_HueStatus(response,hue_devs);
|
|
}
|
|
|
|
hue_devs++;
|
|
}
|
|
if (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
} else {
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) break;
|
|
lp++;
|
|
}
|
|
}
|
|
#if 0
|
|
if (response) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Hue: %d"), hue_devs);
|
|
toLog(">>>>");
|
|
toLog(response->c_str());
|
|
toLog(response->c_str()+LOGSZ);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const char sHUE_LIGHT_RESPONSE_JSON[] PROGMEM =
|
|
"{\"success\":{\"/lights/{id/state/{cm\":{re}}";
|
|
|
|
const char sHUE_SENSOR_RESPONSE_JSON[] PROGMEM =
|
|
"{\"success\":{\"/lights/{id/state/{cm\":{re}}";
|
|
|
|
const char sHUE_ERROR_JSON[] PROGMEM =
|
|
"[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
|
|
|
|
|
|
|
|
void Script_Handle_Hue(String *path) {
|
|
String response;
|
|
int code = 200;
|
|
uint16_t tmp = 0;
|
|
uint16_t hue = 0;
|
|
uint8_t sat = 0;
|
|
uint8_t bri = 254;
|
|
uint16_t ct = 0;
|
|
bool resp = false;
|
|
|
|
uint8_t device = DecodeLightId(atoi(path->c_str()));
|
|
uint8_t index = device-devices_present-1;
|
|
|
|
if (WebServer->args()) {
|
|
response = "[";
|
|
|
|
StaticJsonBuffer<400> jsonBuffer;
|
|
JsonObject &hue_json = jsonBuffer.parseObject(WebServer->arg((WebServer->args())-1));
|
|
if (hue_json.containsKey("on")) {
|
|
|
|
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(EncodeLightId(device)));
|
|
response.replace("{cm", "on");
|
|
|
|
bool on = hue_json["on"];
|
|
switch(on)
|
|
{
|
|
case false : glob_script_mem.fvars[hue_script[index].index[0]-1]=0;
|
|
response.replace("{re", "false");
|
|
break;
|
|
case true : glob_script_mem.fvars[hue_script[index].index[0]-1]=1;
|
|
response.replace("{re", "true");
|
|
break;
|
|
}
|
|
glob_script_mem.type[hue_script[index].vindex[0]].bits.changed=1;
|
|
resp = true;
|
|
}
|
|
if (hue_json.containsKey("bri")) {
|
|
tmp = hue_json["bri"];
|
|
bri=tmp;
|
|
if (254 <= bri) { bri = 255; }
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(EncodeLightId(device)));
|
|
response.replace("{cm", "bri");
|
|
response.replace("{re", String(tmp));
|
|
glob_script_mem.fvars[hue_script[index].index[1]-1]=bri;
|
|
glob_script_mem.type[hue_script[index].vindex[1]].bits.changed=1;
|
|
resp = true;
|
|
}
|
|
if (hue_json.containsKey("xy")) {
|
|
float x, y;
|
|
x = hue_json["xy"][0];
|
|
y = hue_json["xy"][1];
|
|
const String &x_str = hue_json["xy"][0];
|
|
const String &y_str = hue_json["xy"][1];
|
|
uint8_t rr,gg,bb;
|
|
LightStateClass::XyToRgb(x, y, &rr, &gg, &bb);
|
|
LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(device));
|
|
response.replace("{cm", "xy");
|
|
response.replace("{re", "[" + x_str + "," + y_str + "]");
|
|
glob_script_mem.fvars[hue_script[index].index[2]-1]=hue;
|
|
glob_script_mem.type[hue_script[index].vindex[2]].bits.changed=1;
|
|
glob_script_mem.fvars[hue_script[index].index[3]-1]=sat;
|
|
glob_script_mem.type[hue_script[index].vindex[3]].bits.changed=1;
|
|
resp = true;
|
|
}
|
|
|
|
if (hue_json.containsKey("hue")) {
|
|
tmp = hue_json["hue"];
|
|
|
|
|
|
hue=tmp;
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(EncodeLightId(device)));
|
|
response.replace("{cm", "hue");
|
|
response.replace("{re", String(tmp));
|
|
glob_script_mem.fvars[hue_script[index].index[2]-1]=hue;
|
|
glob_script_mem.type[hue_script[index].vindex[2]].bits.changed=1;
|
|
resp = true;
|
|
}
|
|
if (hue_json.containsKey("sat")) {
|
|
tmp = hue_json["sat"];
|
|
sat=tmp;
|
|
if (254 <= sat) { sat = 255; }
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(EncodeLightId(device)));
|
|
response.replace("{cm", "sat");
|
|
response.replace("{re", String(tmp));
|
|
glob_script_mem.fvars[hue_script[index].index[3]-1]=sat;
|
|
glob_script_mem.type[hue_script[index].vindex[3]].bits.changed=1;
|
|
resp = true;
|
|
}
|
|
if (hue_json.containsKey("ct")) {
|
|
ct = hue_json["ct"];
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(sHUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(EncodeLightId(device)));
|
|
response.replace("{cm", "ct");
|
|
response.replace("{re", String(ct));
|
|
glob_script_mem.fvars[hue_script[index].index[4]-1]=ct;
|
|
glob_script_mem.type[hue_script[index].vindex[4]].bits.changed=1;
|
|
resp = true;
|
|
}
|
|
response += "]";
|
|
|
|
} else {
|
|
response = FPSTR(sHUE_ERROR_JSON);
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
|
|
WSSend(code, CT_JSON, response);
|
|
if (resp) {
|
|
Run_Scripter(">E",2,0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef USE_SCRIPT_SUB_COMMAND
|
|
bool Script_SubCmd(void) {
|
|
if (!bitRead(Settings.rule_enabled, 0)) return false;
|
|
|
|
if (tasm_cmd_activ) return false;
|
|
|
|
char command[CMDSZ];
|
|
strlcpy(command,XdrvMailbox.topic,CMDSZ);
|
|
uint32_t pl=XdrvMailbox.payload;
|
|
char pld[64];
|
|
strlcpy(pld,XdrvMailbox.data,sizeof(pld));
|
|
|
|
char cmdbuff[128];
|
|
char *cp=cmdbuff;
|
|
*cp++='#';
|
|
strcpy(cp,XdrvMailbox.topic);
|
|
uint8_t tlen=strlen(XdrvMailbox.topic);
|
|
cp+=tlen;
|
|
if (XdrvMailbox.index > 0) {
|
|
*cp++=XdrvMailbox.index|0x30;
|
|
tlen++;
|
|
}
|
|
if ((XdrvMailbox.payload>0) || (XdrvMailbox.data_len>0)) {
|
|
*cp++='(';
|
|
strncpy(cp,XdrvMailbox.data,XdrvMailbox.data_len);
|
|
cp+=XdrvMailbox.data_len;
|
|
*cp++=')';
|
|
*cp=0;
|
|
}
|
|
|
|
uint32_t res=Run_Scripter(cmdbuff,tlen+1,0);
|
|
|
|
if (res) return false;
|
|
else {
|
|
if (pl>=0) {
|
|
Response_P(S_JSON_COMMAND_NVALUE, command, pl);
|
|
} else {
|
|
Response_P(S_JSON_COMMAND_SVALUE, command, pld);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
void execute_script(char *script) {
|
|
char *svd_sp=glob_script_mem.scriptptr;
|
|
strcat(script,"\n#");
|
|
glob_script_mem.scriptptr=script;
|
|
Run_Scripter(">",1,0);
|
|
glob_script_mem.scriptptr=svd_sp;
|
|
}
|
|
#define D_CMND_SCRIPT "Script"
|
|
#define D_CMND_SUBSCRIBE "Subscribe"
|
|
#define D_CMND_UNSUBSCRIBE "Unsubscribe"
|
|
|
|
enum ScriptCommands { CMND_SCRIPT,CMND_SUBSCRIBE, CMND_UNSUBSCRIBE };
|
|
const char kScriptCommands[] PROGMEM = D_CMND_SCRIPT "|" D_CMND_SUBSCRIBE "|" D_CMND_UNSUBSCRIBE;
|
|
|
|
bool ScriptCommand(void) {
|
|
char command[CMDSZ];
|
|
bool serviced = true;
|
|
uint8_t index = XdrvMailbox.index;
|
|
|
|
if (tasm_cmd_activ) return false;
|
|
|
|
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kScriptCommands);
|
|
if (-1 == command_code) {
|
|
serviced = false;
|
|
}
|
|
else if ((CMND_SCRIPT == command_code) && (index > 0)) {
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 4)) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
case 1:
|
|
bitWrite(Settings.rule_enabled, index -1, XdrvMailbox.payload);
|
|
break;
|
|
#ifdef xSCRIPT_STRIP_COMMENTS
|
|
case 2:
|
|
bitWrite(Settings.rule_enabled, 1,0);
|
|
break;
|
|
case 3:
|
|
bitWrite(Settings.rule_enabled, 1,1);
|
|
break;
|
|
#endif
|
|
}
|
|
} else {
|
|
if ('>' == XdrvMailbox.data[0]) {
|
|
|
|
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\"}"),command,XdrvMailbox.data);
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
for (uint8_t count=0; count<XdrvMailbox.data_len; count++) {
|
|
if (XdrvMailbox.data[count]==';') XdrvMailbox.data[count]='\n';
|
|
}
|
|
execute_script(XdrvMailbox.data);
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"%s\",\"Free\":%d}"),command, GetStateText(bitRead(Settings.rule_enabled,0)),glob_script_mem.script_size-strlen(glob_script_mem.script_ram));
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
} else if (CMND_SUBSCRIBE == command_code) {
|
|
String result = ScriptSubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
|
|
} else if (CMND_UNSUBSCRIBE == command_code) {
|
|
String result = ScriptUnsubscribe(XdrvMailbox.data, XdrvMailbox.data_len);
|
|
Response_P(S_JSON_COMMAND_SVALUE, command, result.c_str());
|
|
#endif
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
|
|
uint16_t xFAT_DATE(uint16_t year, uint8_t month, uint8_t day) {
|
|
return (year - 1980) << 9 | month << 5 | day;
|
|
}
|
|
uint16_t xFAT_TIME(uint8_t hour, uint8_t minute, uint8_t second) {
|
|
return hour << 11 | minute << 5 | second >> 1;
|
|
}
|
|
|
|
void dateTime(uint16_t* date, uint16_t* time) {
|
|
|
|
*date = xFAT_DATE(RtcTime.year,RtcTime.month, RtcTime.day_of_month);
|
|
|
|
*time = xFAT_TIME(RtcTime.hour,RtcTime.minute,RtcTime.second);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
# 4199 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
bool ScriptMqttData(void)
|
|
{
|
|
bool serviced = false;
|
|
|
|
toLog(XdrvMailbox.data);
|
|
if (XdrvMailbox.data_len < 1 || XdrvMailbox.data_len > 256) {
|
|
return false;
|
|
}
|
|
String sTopic = XdrvMailbox.topic;
|
|
String sData = XdrvMailbox.data;
|
|
|
|
MQTT_Subscription event_item;
|
|
|
|
for (uint32_t index = 0; index < subscriptions.size(); index++) {
|
|
event_item = subscriptions.get(index);
|
|
|
|
|
|
if (sTopic.startsWith(event_item.Topic)) {
|
|
|
|
serviced = true;
|
|
String value;
|
|
String lkey;
|
|
if (event_item.Key.length() == 0) {
|
|
value = sData;
|
|
} else {
|
|
StaticJsonBuffer<400> jsonBuf;
|
|
JsonObject& jsonData = jsonBuf.parseObject(sData);
|
|
String key1 = event_item.Key;
|
|
String key2;
|
|
if (!jsonData.success()) break;
|
|
int dot;
|
|
if ((dot = key1.indexOf('.')) > 0) {
|
|
key2 = key1.substring(dot+1);
|
|
key1 = key1.substring(0, dot);
|
|
lkey=key2;
|
|
if (!jsonData[key1][key2].success()) break;
|
|
value = (const char *)jsonData[key1][key2];
|
|
} else {
|
|
if (!jsonData[key1].success()) break;
|
|
value = (const char *)jsonData[key1];
|
|
lkey=key1;
|
|
}
|
|
}
|
|
value.trim();
|
|
char sbuffer[128];
|
|
|
|
if (!strncmp(lkey.c_str(),"Epoch",5)) {
|
|
uint32_t ep=atoi(value.c_str())-(uint32_t)EPOCH_OFFSET;
|
|
snprintf_P(sbuffer, sizeof(sbuffer), PSTR(">%s=%d\n"), event_item.Event.c_str(),ep);
|
|
} else {
|
|
snprintf_P(sbuffer, sizeof(sbuffer), PSTR(">%s=\"%s\"\n"), event_item.Event.c_str(), value.c_str());
|
|
}
|
|
|
|
execute_script(sbuffer);
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
# 4274 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
String ScriptSubscribe(const char *data, int data_len)
|
|
{
|
|
MQTT_Subscription subscription_item;
|
|
String events;
|
|
if (data_len > 0) {
|
|
char parameters[data_len+1];
|
|
memcpy(parameters, data, data_len);
|
|
parameters[data_len] = '\0';
|
|
String event_name, topic, key;
|
|
|
|
char * pos = strtok(parameters, ",");
|
|
if (pos) {
|
|
event_name = Trim(pos);
|
|
pos = strtok(nullptr, ",");
|
|
if (pos) {
|
|
topic = Trim(pos);
|
|
pos = strtok(nullptr, ",");
|
|
if (pos) {
|
|
key = Trim(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (event_name.length() > 0 && topic.length() > 0) {
|
|
|
|
for (uint32_t index=0; index < subscriptions.size(); index++) {
|
|
if (subscriptions.get(index).Event.equals(event_name)) {
|
|
|
|
String stopic = subscriptions.get(index).Topic + "/#";
|
|
MqttUnsubscribe(stopic.c_str());
|
|
subscriptions.remove(index);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!topic.endsWith("#")) {
|
|
if (topic.endsWith("/")) {
|
|
topic.concat("#");
|
|
} else {
|
|
topic.concat("/#");
|
|
}
|
|
}
|
|
|
|
|
|
subscription_item.Event = event_name;
|
|
subscription_item.Topic = topic.substring(0, topic.length() - 2);
|
|
subscription_item.Key = key;
|
|
subscriptions.add(subscription_item);
|
|
|
|
MqttSubscribe(topic.c_str());
|
|
events.concat(event_name + "," + topic
|
|
+ (key.length()>0 ? "," : "")
|
|
+ key);
|
|
} else {
|
|
events = D_JSON_WRONG_PARAMETERS;
|
|
}
|
|
} else {
|
|
|
|
for (uint32_t index=0; index < subscriptions.size(); index++) {
|
|
subscription_item = subscriptions.get(index);
|
|
events.concat(subscription_item.Event + "," + subscription_item.Topic
|
|
+ (subscription_item.Key.length()>0 ? "," : "")
|
|
+ subscription_item.Key + "; ");
|
|
}
|
|
}
|
|
return events;
|
|
}
|
|
# 4354 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_10_scripter.ino"
|
|
String ScriptUnsubscribe(const char * data, int data_len)
|
|
{
|
|
MQTT_Subscription subscription_item;
|
|
String events;
|
|
if (data_len > 0) {
|
|
for (uint32_t index = 0; index < subscriptions.size(); index++) {
|
|
subscription_item = subscriptions.get(index);
|
|
if (subscription_item.Event.equalsIgnoreCase(data)) {
|
|
String stopic = subscription_item.Topic + "/#";
|
|
MqttUnsubscribe(stopic.c_str());
|
|
events = subscription_item.Event;
|
|
subscriptions.remove(index);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
String stopic;
|
|
while (subscriptions.size() > 0) {
|
|
events.concat(subscriptions.get(0).Event + "; ");
|
|
stopic = subscriptions.get(0).Topic + "/#";
|
|
MqttUnsubscribe(stopic.c_str());
|
|
subscriptions.remove(0);
|
|
}
|
|
}
|
|
return events;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef USE_SCRIPT_WEB_DISPLAY
|
|
|
|
void Script_Check_HTML_Setvars(void) {
|
|
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
if (WebServer->hasArg("sv")) {
|
|
String stmp = WebServer->arg("sv");
|
|
char cmdbuf[64];
|
|
memset(cmdbuf,0,sizeof(cmdbuf));
|
|
char *cp=cmdbuf;
|
|
*cp++='>';
|
|
strncpy(cp,stmp.c_str(),sizeof(cmdbuf)-1);
|
|
char *cp1=strchr(cp,'_');
|
|
if (!cp1) return;
|
|
*cp1=0;
|
|
char vname[32];
|
|
strncpy(vname,cp,sizeof(vname));
|
|
*cp1='=';
|
|
cp1++;
|
|
|
|
struct T_INDEX ind;
|
|
uint8_t vtype;
|
|
isvar(vname,&vtype,&ind,0,0,0);
|
|
if (vtype!=NUM_RES && vtype&STYPE) {
|
|
|
|
uint8_t tlen=strlen(cp1);
|
|
memmove(cp1+1,cp1,tlen);
|
|
*cp1='\"';
|
|
*(cp1+tlen+1)='\"';
|
|
}
|
|
|
|
|
|
execute_script(cmdbuf);
|
|
Run_Scripter(">E",2,0);
|
|
}
|
|
}
|
|
|
|
|
|
const char SCRIPT_MSG_BUTTONa[] PROGMEM =
|
|
"<button type='submit' style=\"width:%d%%\" onclick='seva(%d,\"%s\")'>%s</button>";
|
|
|
|
const char SCRIPT_MSG_BUTTONa_TBL[] PROGMEM =
|
|
"<td style=\"width:%d%%\"><button type='submit' onclick='seva(%d,\"%s\")'>%s</button></td>";
|
|
|
|
const char SCRIPT_MSG_BUTTONb[] PROGMEM =
|
|
"<img width=\"%d%%\"></img>";
|
|
|
|
const char SCRIPT_MSG_BUT_START[] PROGMEM =
|
|
"<div>";
|
|
const char SCRIPT_MSG_BUT_START_TBL[] PROGMEM =
|
|
"<table style='width:100%%'><tr>";
|
|
|
|
const char SCRIPT_MSG_BUT_STOP[] PROGMEM =
|
|
"</div>";
|
|
const char SCRIPT_MSG_BUT_STOP_TBL[] PROGMEM =
|
|
"</tr></table>";
|
|
|
|
const char SCRIPT_MSG_SLIDER[] PROGMEM =
|
|
"<div><span class='p'>%s</span><center><b>%s</b><span class='q'>%s</span></div>"
|
|
"<div><input type='range' min='%d' max='%d' value='%d' onchange='seva(value,\"%s\")'></div>";
|
|
|
|
const char SCRIPT_MSG_CHKBOX[] PROGMEM =
|
|
"<div><center><label><b>%s</b><input type='checkbox' %s onchange='seva(%d,\"%s\")'></label></div>";
|
|
|
|
const char SCRIPT_MSG_TEXTINP[] PROGMEM =
|
|
"<div><center><label><b>%s</b><input type='text' value='%s' style='width:200px' onfocusin='pr(0)' onfocusout='pr(1)' onchange='siva(value,\"%s\")'></label></div>";
|
|
|
|
const char SCRIPT_MSG_NUMINP[] PROGMEM =
|
|
"<div><center><label><b>%s</b><input min='%s' max='%s' step='%s' value='%s' type='number' style='width:200px' onfocusin='pr(0)' onfocusout='pr(1)' onchange='siva(value,\"%s\")'></label></div>";
|
|
|
|
|
|
void ScriptGetVarname(char *nbuf,char *sp, uint32_t blen) {
|
|
uint32_t cnt;
|
|
for (cnt=0;cnt<blen-1;cnt++) {
|
|
if (*sp==' ' || *sp==')') {
|
|
break;
|
|
}
|
|
nbuf[cnt]=*sp++;
|
|
}
|
|
nbuf[cnt]=0;
|
|
}
|
|
|
|
void ScriptWebShow(void) {
|
|
uint8_t web_script=Run_Scripter(">W",-2,0);
|
|
if (web_script==99) {
|
|
char line[128];
|
|
char tmp[128];
|
|
uint8_t optflg=0;
|
|
char *lp=glob_script_mem.section_ptr+2;
|
|
while (lp) {
|
|
while (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
}
|
|
if (!*lp || *lp=='#' || *lp=='>') {
|
|
break;
|
|
}
|
|
if (*lp!=';') {
|
|
|
|
memcpy(line,lp,sizeof(line));
|
|
line[sizeof(line)-1]=0;
|
|
char *cp=line;
|
|
for (uint32_t i=0; i<sizeof(line); i++) {
|
|
if (!*cp || *cp=='\n' || *cp=='\r') {
|
|
*cp=0;
|
|
break;
|
|
}
|
|
cp++;
|
|
}
|
|
char *lin=line;
|
|
if (*lin=='@') {
|
|
lin++;
|
|
optflg=1;
|
|
} else {
|
|
optflg=0;
|
|
}
|
|
|
|
if (!strncmp(lin,"sl(",3)) {
|
|
|
|
char *lp=lin;
|
|
float min;
|
|
lp=GetNumericResult(lp+3,OPER_EQU,&min,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
float max;
|
|
lp=GetNumericResult(lp,OPER_EQU,&max,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float val;
|
|
char *slp=lp;
|
|
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
char vname[16];
|
|
ScriptGetVarname(vname,slp,sizeof(vname));
|
|
|
|
char left[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,left,0);
|
|
SCRIPT_SKIP_SPACES
|
|
char mid[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,mid,0);
|
|
SCRIPT_SKIP_SPACES
|
|
char right[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,right,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
WSContentSend_PD(SCRIPT_MSG_SLIDER,left,mid,right,(uint32_t)min,(uint32_t)max,(uint32_t)val,vname);
|
|
|
|
|
|
} else if (!strncmp(lin,"ck(",3)) {
|
|
char *lp=lin+3;
|
|
char *slp=lp;
|
|
float val;
|
|
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
char vname[16];
|
|
ScriptGetVarname(vname,slp,sizeof(vname));
|
|
|
|
char label[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,label,0);
|
|
const char *cp;
|
|
uint8_t uval;
|
|
if (val>0) {
|
|
cp="checked='checked'";
|
|
uval=0;
|
|
} else {
|
|
cp="";
|
|
uval=1;
|
|
}
|
|
WSContentSend_PD(SCRIPT_MSG_CHKBOX,label,(char*)cp,uval,vname);
|
|
|
|
} else if (!strncmp(lin,"bu(",3)) {
|
|
char *lp=lin+3;
|
|
uint8_t bcnt=0;
|
|
char *found=lin;
|
|
while (bcnt<4) {
|
|
found=strstr(found,"bu(");
|
|
if (!found) break;
|
|
found+=3;
|
|
bcnt++;
|
|
}
|
|
uint8_t proz=100/bcnt;
|
|
if (!optflg && bcnt>1) proz-=2;
|
|
if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_START_TBL);
|
|
else WSContentSend_PD(SCRIPT_MSG_BUT_START);
|
|
for (uint32_t cnt=0;cnt<bcnt;cnt++) {
|
|
float val;
|
|
char *slp=lp;
|
|
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
|
SCRIPT_SKIP_SPACES
|
|
|
|
char vname[16];
|
|
ScriptGetVarname(vname,slp,sizeof(vname));
|
|
|
|
SCRIPT_SKIP_SPACES
|
|
char ontxt[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,ontxt,0);
|
|
SCRIPT_SKIP_SPACES
|
|
char offtxt[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,offtxt,0);
|
|
|
|
char *cp;
|
|
uint8_t uval;
|
|
if (val>0) {
|
|
cp=ontxt;
|
|
uval=0;
|
|
} else {
|
|
cp=offtxt;
|
|
uval=1;
|
|
}
|
|
if (bcnt>1 && cnt==bcnt-1) {
|
|
if (!optflg) proz+=2;
|
|
}
|
|
if (!optflg) {
|
|
WSContentSend_PD(SCRIPT_MSG_BUTTONa,proz,uval,vname,cp);
|
|
} else {
|
|
WSContentSend_PD(SCRIPT_MSG_BUTTONa_TBL,proz,uval,vname,cp);
|
|
}
|
|
if (bcnt>1 && cnt<bcnt-1) {
|
|
if (!optflg) WSContentSend_PD(SCRIPT_MSG_BUTTONb,2);
|
|
}
|
|
lp+=4;
|
|
}
|
|
if (optflg) WSContentSend_PD(SCRIPT_MSG_BUT_STOP_TBL);
|
|
else WSContentSend_PD(SCRIPT_MSG_BUT_STOP);
|
|
} else if (!strncmp(lin,"tx(",3)) {
|
|
char *lp=lin+3;
|
|
char *slp=lp;
|
|
char str[SCRIPT_MAXSSIZE];
|
|
lp=ForceStringVar(lp,str);
|
|
SCRIPT_SKIP_SPACES
|
|
char label[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,label,0);
|
|
|
|
char vname[16];
|
|
ScriptGetVarname(vname,slp,sizeof(vname));
|
|
|
|
WSContentSend_PD(SCRIPT_MSG_TEXTINP,label,str,vname);
|
|
|
|
} else if (!strncmp(lin,"nm(",3)) {
|
|
char *lp=lin;
|
|
float min;
|
|
lp=GetNumericResult(lp+3,OPER_EQU,&min,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float max;
|
|
lp=GetNumericResult(lp,OPER_EQU,&max,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float step;
|
|
lp=GetNumericResult(lp,OPER_EQU,&step,0);
|
|
SCRIPT_SKIP_SPACES
|
|
float val;
|
|
char *slp=lp;
|
|
lp=GetNumericResult(lp,OPER_EQU,&val,0);
|
|
SCRIPT_SKIP_SPACES
|
|
char vname[16];
|
|
ScriptGetVarname(vname,slp,sizeof(vname));
|
|
|
|
char label[SCRIPT_MAXSSIZE];
|
|
lp=GetStringResult(lp,OPER_EQU,label,0);
|
|
|
|
char vstr[16],minstr[16],maxstr[16],stepstr[16];
|
|
dtostrfd(val,4,vstr);
|
|
dtostrfd(min,4,minstr);
|
|
dtostrfd(max,4,maxstr);
|
|
dtostrfd(step,4,stepstr);
|
|
WSContentSend_PD(SCRIPT_MSG_NUMINP,label,minstr,maxstr,stepstr,vstr,vname);
|
|
|
|
} else {
|
|
Replace_Cmd_Vars(lin,tmp,sizeof(tmp));
|
|
if (optflg) {
|
|
WSContentSend_PD(PSTR("<div>%s</div>"),tmp);
|
|
} else {
|
|
WSContentSend_PD(PSTR("{s}%s{e}"),tmp);
|
|
}
|
|
}
|
|
}
|
|
if (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
} else {
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) break;
|
|
lp++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef USE_SENDMAIL
|
|
void script_send_email_body(BearSSL::WiFiClientSecure_light *client) {
|
|
uint8_t msect=Run_Scripter(">m",-2,0);
|
|
if (msect==99) {
|
|
char line[128];
|
|
char tmp[128];
|
|
char *lp=glob_script_mem.section_ptr+2;
|
|
while (lp) {
|
|
while (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
}
|
|
if (!*lp || *lp=='#' || *lp=='>') {
|
|
break;
|
|
}
|
|
if (*lp!=';') {
|
|
|
|
memcpy(line,lp,sizeof(line));
|
|
line[sizeof(line)-1]=0;
|
|
char *cp=line;
|
|
for (uint32_t i=0; i<sizeof(line); i++) {
|
|
if (!*cp || *cp=='\n' || *cp=='\r') {
|
|
*cp=0;
|
|
break;
|
|
}
|
|
cp++;
|
|
}
|
|
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
|
|
client->println(tmp);
|
|
}
|
|
if (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
} else {
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) break;
|
|
lp++;
|
|
}
|
|
}
|
|
} else {
|
|
client->println("*");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SCRIPT_JSON_EXPORT
|
|
void ScriptJsonAppend(void) {
|
|
uint8_t web_script=Run_Scripter(">J",-2,0);
|
|
if (web_script==99) {
|
|
char line[128];
|
|
char tmp[128];
|
|
char *lp=glob_script_mem.section_ptr+2;
|
|
while (lp) {
|
|
while (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
}
|
|
if (!*lp || *lp=='#' || *lp=='>') {
|
|
break;
|
|
}
|
|
if (*lp!=';') {
|
|
|
|
memcpy(line,lp,sizeof(line));
|
|
line[sizeof(line)-1]=0;
|
|
char *cp=line;
|
|
for (uint32_t i=0; i<sizeof(line); i++) {
|
|
if (!*cp || *cp=='\n' || *cp=='\r') {
|
|
*cp=0;
|
|
break;
|
|
}
|
|
cp++;
|
|
}
|
|
Replace_Cmd_Vars(line,tmp,sizeof(tmp));
|
|
ResponseAppend_P(PSTR("%s"),tmp);
|
|
}
|
|
if (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
} else {
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) break;
|
|
lp++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv10(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
|
|
glob_script_mem.script_ram=Settings.rules[0];
|
|
glob_script_mem.script_size=MAX_SCRIPT_SIZE;
|
|
glob_script_mem.flags=0;
|
|
glob_script_mem.script_pram=(uint8_t*)Settings.script_pram[0];
|
|
glob_script_mem.script_pram_size=PMEM_SIZE;
|
|
|
|
#ifdef USE_BUTTON_EVENT
|
|
for (uint32_t cnt=0;cnt<MAX_KEYS;cnt++) {
|
|
script_button[cnt]=-1;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_24C256
|
|
#ifndef USE_SCRIPT_FATFS
|
|
if (I2cEnabled(XI2C_37)) {
|
|
if (I2cSetDevice(EEPROM_ADDRESS)) {
|
|
|
|
char *script;
|
|
script=(char*)calloc(EEP_SCRIPT_SIZE+4,1);
|
|
if (!script) break;
|
|
glob_script_mem.script_ram=script;
|
|
glob_script_mem.script_size=EEP_SCRIPT_SIZE;
|
|
EEP_READ(0,EEP_SCRIPT_SIZE,script);
|
|
if (*script==0xff) {
|
|
memset(script,EEP_SCRIPT_SIZE,0);
|
|
}
|
|
script[EEP_SCRIPT_SIZE-1]=0;
|
|
|
|
glob_script_mem.script_pram=(uint8_t*)Settings.rules[0];
|
|
glob_script_mem.script_pram_size=MAX_SCRIPT_SIZE;
|
|
|
|
glob_script_mem.flags=1;
|
|
I2cSetActiveFound(EEPROM_ADDRESS, "EEPROM");
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
if (SD.begin(USE_SCRIPT_FATFS)) {
|
|
glob_script_mem.script_sd_found=1;
|
|
char *script;
|
|
script=(char*)calloc(FAT_SCRIPT_SIZE+4,1);
|
|
if (!script) break;
|
|
glob_script_mem.script_ram=script;
|
|
glob_script_mem.script_size=FAT_SCRIPT_SIZE;
|
|
if (SD.exists(FAT_SCRIPT_NAME)) {
|
|
File file=SD.open(FAT_SCRIPT_NAME,FILE_READ);
|
|
file.read((uint8_t*)script,FAT_SCRIPT_SIZE);
|
|
file.close();
|
|
}
|
|
script[FAT_SCRIPT_SIZE-1]=0;
|
|
|
|
glob_script_mem.script_pram=(uint8_t*)Settings.rules[0];
|
|
glob_script_mem.script_pram_size=MAX_SCRIPT_SIZE;
|
|
|
|
glob_script_mem.flags=1;
|
|
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_2)
|
|
|
|
SdFile::dateTimeCallback(dateTime);
|
|
#endif
|
|
|
|
} else {
|
|
glob_script_mem.script_sd_found=0;
|
|
}
|
|
#endif
|
|
|
|
|
|
{ uint32_t ptr=(uint32_t)glob_script_mem.script_pram;
|
|
ptr&=0xfffffffc;
|
|
ptr+=4;
|
|
glob_script_mem.script_pram=(uint8_t*)ptr;
|
|
glob_script_mem.script_pram_size-=4;
|
|
}
|
|
|
|
if (bitRead(Settings.rule_enabled, 0)) Init_Scripter();
|
|
break;
|
|
case FUNC_INIT:
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
Run_Scripter(">B",2,0);
|
|
fast_script=Run_Scripter(">F",-2,0);
|
|
#if defined(USE_SCRIPT_HUE) && defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
|
|
Script_Check_Hue(0);
|
|
#endif
|
|
}
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
ScripterEvery100ms();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
ScriptEverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = ScriptCommand();
|
|
break;
|
|
case FUNC_SET_POWER:
|
|
#ifdef SCRIPT_POWER_SECTION
|
|
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">P",2,0);
|
|
#else
|
|
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,0);
|
|
#endif
|
|
break;
|
|
case FUNC_RULES_PROCESS:
|
|
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,mqtt_data);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_RULES);
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/" WEB_HANDLE_SCRIPT, HandleScriptConfiguration);
|
|
WebServer->on("/ta",HTTP_POST, HandleScriptTextareaConfiguration);
|
|
|
|
#ifdef USE_SCRIPT_FATFS
|
|
WebServer->on("/u3", HTTP_POST,[]() { WebServer->sendHeader("Location","/u3");WebServer->send(303);},script_upload);
|
|
WebServer->on("/u3", HTTP_GET,ScriptFileUploadSuccess);
|
|
WebServer->on("/upl", HTTP_GET,Script_FileUploadConfiguration);
|
|
#endif
|
|
break;
|
|
#endif
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
Run_Scripter(">R",2,0);
|
|
Scripter_save_pvars();
|
|
}
|
|
break;
|
|
#ifdef SUPPORT_MQTT_EVENT
|
|
case FUNC_MQTT_DATA:
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
result = ScriptMqttData();
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef USE_SCRIPT_WEB_DISPLAY
|
|
case FUNC_WEB_SENSOR:
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
ScriptWebShow();
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_SCRIPT_JSON_EXPORT
|
|
case FUNC_JSON_APPEND:
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
ScriptJsonAppend();
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_BUTTON_EVENT
|
|
case FUNC_BUTTON_PRESSED:
|
|
if (bitRead(Settings.rule_enabled, 0)) {
|
|
if ((script_button[XdrvMailbox.index]&1)!=(XdrvMailbox.payload&1)) {
|
|
script_button[XdrvMailbox.index]=XdrvMailbox.payload;
|
|
Run_Scripter(">b",2,0);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
|
|
#ifdef USE_KNX
|
|
# 51 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
|
|
#define XDRV_11 11
|
|
|
|
#include <esp-knx-ip.h>
|
|
|
|
address_t KNX_physs_addr;
|
|
address_t KNX_addr;
|
|
|
|
#define KNX_Empty 255
|
|
|
|
#define TOGGLE_INHIBIT_TIME 15
|
|
|
|
float last_temp;
|
|
float last_hum;
|
|
uint8_t toggle_inhibit;
|
|
|
|
typedef struct __device_parameters
|
|
{
|
|
uint8_t type;
|
|
|
|
|
|
|
|
|
|
bool show;
|
|
|
|
bool last_state;
|
|
|
|
callback_id_t CB_id;
|
|
|
|
|
|
|
|
|
|
|
|
} device_parameters_t;
|
|
|
|
|
|
device_parameters_t device_param[] = {
|
|
{ 1, false, false, KNX_Empty },
|
|
{ 2, false, false, KNX_Empty },
|
|
{ 3, false, false, KNX_Empty },
|
|
{ 4, false, false, KNX_Empty },
|
|
{ 5, false, false, KNX_Empty },
|
|
{ 6, false, false, KNX_Empty },
|
|
{ 7, false, false, KNX_Empty },
|
|
{ 8, false, false, KNX_Empty },
|
|
{ 9, false, false, KNX_Empty },
|
|
{ 10, false, false, KNX_Empty },
|
|
{ 11, false, false, KNX_Empty },
|
|
{ 12, false, false, KNX_Empty },
|
|
{ 13, false, false, KNX_Empty },
|
|
{ 14, false, false, KNX_Empty },
|
|
{ 15, false, false, KNX_Empty },
|
|
{ 16, false, false, KNX_Empty },
|
|
{ KNX_TEMPERATURE, false, false, KNX_Empty },
|
|
{ KNX_HUMIDITY , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_VOLTAGE , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_CURRENT , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_POWER , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_POWERFACTOR , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_DAILY , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_START , false, false, KNX_Empty },
|
|
{ KNX_ENERGY_TOTAL , false, false, KNX_Empty },
|
|
{ KNX_SLOT1 , false, false, KNX_Empty },
|
|
{ KNX_SLOT2 , false, false, KNX_Empty },
|
|
{ KNX_SLOT3 , false, false, KNX_Empty },
|
|
{ KNX_SLOT4 , false, false, KNX_Empty },
|
|
{ KNX_SLOT5 , false, false, KNX_Empty },
|
|
{ KNX_Empty, false, false, KNX_Empty}
|
|
};
|
|
|
|
|
|
const char * device_param_ga[] = {
|
|
D_TIMER_OUTPUT " 1",
|
|
D_TIMER_OUTPUT " 2",
|
|
D_TIMER_OUTPUT " 3",
|
|
D_TIMER_OUTPUT " 4",
|
|
D_TIMER_OUTPUT " 5",
|
|
D_TIMER_OUTPUT " 6",
|
|
D_TIMER_OUTPUT " 7",
|
|
D_TIMER_OUTPUT " 8",
|
|
D_SENSOR_BUTTON " 1",
|
|
D_SENSOR_BUTTON " 2",
|
|
D_SENSOR_BUTTON " 3",
|
|
D_SENSOR_BUTTON " 4",
|
|
D_SENSOR_BUTTON " 5",
|
|
D_SENSOR_BUTTON " 6",
|
|
D_SENSOR_BUTTON " 7",
|
|
D_SENSOR_BUTTON " 8",
|
|
D_TEMPERATURE ,
|
|
D_HUMIDITY ,
|
|
D_VOLTAGE ,
|
|
D_CURRENT ,
|
|
D_POWERUSAGE ,
|
|
D_POWER_FACTOR ,
|
|
D_ENERGY_TODAY ,
|
|
D_ENERGY_YESTERDAY ,
|
|
D_ENERGY_TOTAL ,
|
|
D_KNX_TX_SLOT " 1",
|
|
D_KNX_TX_SLOT " 2",
|
|
D_KNX_TX_SLOT " 3",
|
|
D_KNX_TX_SLOT " 4",
|
|
D_KNX_TX_SLOT " 5",
|
|
nullptr
|
|
};
|
|
|
|
|
|
const char *device_param_cb[] = {
|
|
D_TIMER_OUTPUT " 1",
|
|
D_TIMER_OUTPUT " 2",
|
|
D_TIMER_OUTPUT " 3",
|
|
D_TIMER_OUTPUT " 4",
|
|
D_TIMER_OUTPUT " 5",
|
|
D_TIMER_OUTPUT " 6",
|
|
D_TIMER_OUTPUT " 7",
|
|
D_TIMER_OUTPUT " 8",
|
|
D_TIMER_OUTPUT " 1 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 2 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 3 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 4 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 5 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 6 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 7 " D_BUTTON_TOGGLE,
|
|
D_TIMER_OUTPUT " 8 " D_BUTTON_TOGGLE,
|
|
D_REPLY " " D_TEMPERATURE,
|
|
D_REPLY " " D_HUMIDITY,
|
|
D_REPLY " " D_VOLTAGE ,
|
|
D_REPLY " " D_CURRENT ,
|
|
D_REPLY " " D_POWERUSAGE ,
|
|
D_REPLY " " D_POWER_FACTOR ,
|
|
D_REPLY " " D_ENERGY_TODAY ,
|
|
D_REPLY " " D_ENERGY_YESTERDAY ,
|
|
D_REPLY " " D_ENERGY_TOTAL ,
|
|
D_KNX_RX_SLOT " 1",
|
|
D_KNX_RX_SLOT " 2",
|
|
D_KNX_RX_SLOT " 3",
|
|
D_KNX_RX_SLOT " 4",
|
|
D_KNX_RX_SLOT " 5",
|
|
nullptr
|
|
};
|
|
|
|
|
|
#define D_PRFX_KNX "Knx"
|
|
#define D_CMND_KNXTXCMND "Tx_Cmnd"
|
|
#define D_CMND_KNXTXVAL "Tx_Val"
|
|
#define D_CMND_KNX_ENABLED "_Enabled"
|
|
#define D_CMND_KNX_ENHANCED "_Enhanced"
|
|
#define D_CMND_KNX_PA "_PA"
|
|
#define D_CMND_KNX_GA "_GA"
|
|
#define D_CMND_KNX_CB "_CB"
|
|
|
|
const char kKnxCommands[] PROGMEM = D_PRFX_KNX "|"
|
|
D_CMND_KNXTXCMND "|" D_CMND_KNXTXVAL "|" D_CMND_KNX_ENABLED "|" D_CMND_KNX_ENHANCED "|" D_CMND_KNX_PA "|" D_CMND_KNX_GA "|" D_CMND_KNX_CB ;
|
|
|
|
void (* const KnxCommand[])(void) PROGMEM = {
|
|
&CmndKnxTxCmnd, &CmndKnxTxVal, &CmndKnxEnabled, &CmndKnxEnhanced, &CmndKnxPa, &CmndKnxGa, &CmndKnxCb };
|
|
|
|
uint8_t KNX_GA_Search( uint8_t param, uint8_t start = 0 )
|
|
{
|
|
for (uint32_t i = start; i < Settings.knx_GA_registered; ++i)
|
|
{
|
|
if ( Settings.knx_GA_param[i] == param )
|
|
{
|
|
if ( Settings.knx_GA_addr[i] != 0 )
|
|
{
|
|
if ( i >= start ) { return i; }
|
|
}
|
|
}
|
|
}
|
|
return KNX_Empty;
|
|
}
|
|
|
|
|
|
uint8_t KNX_CB_Search( uint8_t param, uint8_t start = 0 )
|
|
{
|
|
for (uint32_t i = start; i < Settings.knx_CB_registered; ++i)
|
|
{
|
|
if ( Settings.knx_CB_param[i] == param )
|
|
{
|
|
if ( Settings.knx_CB_addr[i] != 0 )
|
|
{
|
|
if ( i >= start ) { return i; }
|
|
}
|
|
}
|
|
}
|
|
return KNX_Empty;
|
|
}
|
|
|
|
|
|
void KNX_ADD_GA( uint8_t GAop, uint8_t GA_FNUM, uint8_t GA_AREA, uint8_t GA_FDEF )
|
|
{
|
|
|
|
if ( Settings.knx_GA_registered >= MAX_KNX_GA ) { return; }
|
|
if ( GA_FNUM == 0 && GA_AREA == 0 && GA_FDEF == 0 ) { return; }
|
|
|
|
|
|
Settings.knx_GA_param[Settings.knx_GA_registered] = GAop;
|
|
KNX_addr.ga.area = GA_FNUM;
|
|
KNX_addr.ga.line = GA_AREA;
|
|
KNX_addr.ga.member = GA_FDEF;
|
|
Settings.knx_GA_addr[Settings.knx_GA_registered] = KNX_addr.value;
|
|
|
|
Settings.knx_GA_registered++;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_ADD " GA #%d: %s " D_TO " %d/%d/%d"),
|
|
Settings.knx_GA_registered,
|
|
device_param_ga[GAop-1],
|
|
GA_FNUM, GA_AREA, GA_FDEF );
|
|
}
|
|
|
|
|
|
void KNX_DEL_GA( uint8_t GAnum )
|
|
{
|
|
|
|
uint8_t dest_offset = 0;
|
|
uint8_t src_offset = 0;
|
|
uint8_t len = 0;
|
|
|
|
|
|
Settings.knx_GA_param[GAnum-1] = 0;
|
|
|
|
if (GAnum == 1)
|
|
{
|
|
|
|
src_offset = 1;
|
|
|
|
|
|
|
|
len = (Settings.knx_GA_registered - 1);
|
|
}
|
|
else if (GAnum == Settings.knx_GA_registered)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
|
|
|
|
dest_offset = GAnum -1 ;
|
|
src_offset = dest_offset + 1;
|
|
len = (Settings.knx_GA_registered - GAnum);
|
|
}
|
|
|
|
if (len > 0)
|
|
{
|
|
memmove(Settings.knx_GA_param + dest_offset, Settings.knx_GA_param + src_offset, len * sizeof(uint8_t));
|
|
memmove(Settings.knx_GA_addr + dest_offset, Settings.knx_GA_addr + src_offset, len * sizeof(uint16_t));
|
|
}
|
|
|
|
Settings.knx_GA_registered--;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_DELETE " GA #%d"),
|
|
GAnum );
|
|
}
|
|
|
|
|
|
void KNX_ADD_CB( uint8_t CBop, uint8_t CB_FNUM, uint8_t CB_AREA, uint8_t CB_FDEF )
|
|
{
|
|
|
|
if ( Settings.knx_CB_registered >= MAX_KNX_CB ) { return; }
|
|
if ( CB_FNUM == 0 && CB_AREA == 0 && CB_FDEF == 0 ) { return; }
|
|
|
|
|
|
if ( device_param[CBop-1].CB_id == KNX_Empty )
|
|
{
|
|
|
|
device_param[CBop-1].CB_id = knx.callback_register("", KNX_CB_Action, &device_param[CBop-1]);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
Settings.knx_CB_param[Settings.knx_CB_registered] = CBop;
|
|
KNX_addr.ga.area = CB_FNUM;
|
|
KNX_addr.ga.line = CB_AREA;
|
|
KNX_addr.ga.member = CB_FDEF;
|
|
Settings.knx_CB_addr[Settings.knx_CB_registered] = KNX_addr.value;
|
|
|
|
knx.callback_assign( device_param[CBop-1].CB_id, KNX_addr );
|
|
|
|
Settings.knx_CB_registered++;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_ADD " CB #%d: %d/%d/%d " D_TO " %s"),
|
|
Settings.knx_CB_registered,
|
|
CB_FNUM, CB_AREA, CB_FDEF,
|
|
device_param_cb[CBop-1] );
|
|
}
|
|
|
|
|
|
void KNX_DEL_CB( uint8_t CBnum )
|
|
{
|
|
uint8_t oldparam = Settings.knx_CB_param[CBnum-1];
|
|
uint8_t dest_offset = 0;
|
|
uint8_t src_offset = 0;
|
|
uint8_t len = 0;
|
|
|
|
|
|
knx.callback_unassign(CBnum-1);
|
|
Settings.knx_CB_param[CBnum-1] = 0;
|
|
|
|
if (CBnum == 1)
|
|
{
|
|
|
|
src_offset = 1;
|
|
|
|
|
|
|
|
len = (Settings.knx_CB_registered - 1);
|
|
}
|
|
else if (CBnum == Settings.knx_CB_registered)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
|
|
|
|
dest_offset = CBnum -1 ;
|
|
src_offset = dest_offset + 1;
|
|
len = (Settings.knx_CB_registered - CBnum);
|
|
}
|
|
|
|
if (len > 0)
|
|
{
|
|
memmove(Settings.knx_CB_param + dest_offset, Settings.knx_CB_param + src_offset, len * sizeof(uint8_t));
|
|
memmove(Settings.knx_CB_addr + dest_offset, Settings.knx_CB_addr + src_offset, len * sizeof(uint16_t));
|
|
}
|
|
|
|
Settings.knx_CB_registered--;
|
|
|
|
|
|
if ( KNX_CB_Search( oldparam ) == KNX_Empty ) {
|
|
knx.callback_deregister( device_param[oldparam-1].CB_id );
|
|
device_param[oldparam-1].CB_id = KNX_Empty;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_DELETE " CB #%d"), CBnum );
|
|
}
|
|
|
|
|
|
bool KNX_CONFIG_NOT_MATCH(void)
|
|
{
|
|
|
|
for (uint32_t i = 0; i < KNX_MAX_device_param; ++i)
|
|
{
|
|
if ( !device_param[i].show ) {
|
|
|
|
|
|
|
|
if ( KNX_GA_Search(i+1) != KNX_Empty ) { return true; }
|
|
|
|
if ( i < 8 )
|
|
{
|
|
if ( KNX_CB_Search(i+1) != KNX_Empty ) { return true; }
|
|
if ( KNX_CB_Search(i+9) != KNX_Empty ) { return true; }
|
|
}
|
|
|
|
if ( i > 15 )
|
|
{
|
|
if ( KNX_CB_Search(i+1) != KNX_Empty ) { return true; }
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (uint32_t i = 0; i < Settings.knx_GA_registered; ++i)
|
|
{
|
|
if ( Settings.knx_GA_param[i] != 0 )
|
|
{
|
|
if ( Settings.knx_GA_addr[i] == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < Settings.knx_CB_registered; ++i)
|
|
{
|
|
if ( Settings.knx_CB_param[i] != 0 )
|
|
{
|
|
if ( Settings.knx_CB_addr[i] == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void KNXStart(void)
|
|
{
|
|
knx.start(nullptr);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_START));
|
|
}
|
|
|
|
|
|
void KNX_INIT(void)
|
|
{
|
|
|
|
if (Settings.knx_GA_registered > MAX_KNX_GA) { Settings.knx_GA_registered = MAX_KNX_GA; }
|
|
if (Settings.knx_CB_registered > MAX_KNX_CB) { Settings.knx_CB_registered = MAX_KNX_CB; }
|
|
|
|
|
|
KNX_physs_addr.value = Settings.knx_physsical_addr;
|
|
knx.physical_address_set( KNX_physs_addr );
|
|
# 472 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
|
|
for (uint32_t i = 0; i < devices_present; ++i)
|
|
{
|
|
device_param[i].show = true;
|
|
}
|
|
for (uint32_t i = GPIO_SWT1; i < GPIO_SWT4 + 1; ++i)
|
|
{
|
|
if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_SWT1 + 8].show = true; }
|
|
}
|
|
for (uint32_t i = GPIO_KEY1; i < GPIO_KEY4 + 1; ++i)
|
|
{
|
|
if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_KEY1 + 8].show = true; }
|
|
}
|
|
for (uint32_t i = GPIO_SWT1_NP; i < GPIO_SWT4_NP + 1; ++i)
|
|
{
|
|
if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_SWT1_NP + 8].show = true; }
|
|
}
|
|
for (uint32_t i = GPIO_KEY1_NP; i < GPIO_KEY4_NP + 1; ++i)
|
|
{
|
|
if (GetUsedInModule(i, my_module.io)) { device_param[i - GPIO_KEY1_NP + 8].show = true; }
|
|
}
|
|
if (GetUsedInModule(GPIO_DHT11, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
|
|
if (GetUsedInModule(GPIO_DHT22, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
|
|
if (GetUsedInModule(GPIO_SI7021, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
|
|
#ifdef USE_DS18x20
|
|
if (GetUsedInModule(GPIO_DSB, my_module.io)) { device_param[KNX_TEMPERATURE-1].show = true; }
|
|
#endif
|
|
if (GetUsedInModule(GPIO_DHT11, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; }
|
|
if (GetUsedInModule(GPIO_DHT22, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; }
|
|
if (GetUsedInModule(GPIO_SI7021, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; }
|
|
|
|
#if defined(USE_ENERGY_SENSOR)
|
|
|
|
if ( energy_flg != ENERGY_NONE ) {
|
|
device_param[KNX_ENERGY_POWER-1].show = true;
|
|
device_param[KNX_ENERGY_DAILY-1].show = true;
|
|
device_param[KNX_ENERGY_START-1].show = true;
|
|
device_param[KNX_ENERGY_TOTAL-1].show = true;
|
|
device_param[KNX_ENERGY_VOLTAGE-1].show = true;
|
|
device_param[KNX_ENERGY_CURRENT-1].show = true;
|
|
device_param[KNX_ENERGY_POWERFACTOR-1].show = true;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_RULES
|
|
device_param[KNX_SLOT1-1].show = true;
|
|
device_param[KNX_SLOT2-1].show = true;
|
|
device_param[KNX_SLOT3-1].show = true;
|
|
device_param[KNX_SLOT4-1].show = true;
|
|
device_param[KNX_SLOT5-1].show = true;
|
|
#endif
|
|
|
|
|
|
if (KNX_CONFIG_NOT_MATCH()) {
|
|
Settings.knx_GA_registered = 0;
|
|
Settings.knx_CB_registered = 0;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_DELETE " " D_KNX_PARAMETERS));
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t j;
|
|
for (uint32_t i = 0; i < Settings.knx_CB_registered; ++i)
|
|
{
|
|
j = Settings.knx_CB_param[i];
|
|
if ( j > 0 )
|
|
{
|
|
device_param[j-1].CB_id = knx.callback_register("", KNX_CB_Action, &device_param[j-1]);
|
|
|
|
|
|
|
|
KNX_addr.value = Settings.knx_CB_addr[i];
|
|
knx.callback_assign( device_param[j-1].CB_id, KNX_addr );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void KNX_CB_Action(message_t const &msg, void *arg)
|
|
{
|
|
device_parameters_t *chan = (device_parameters_t *)arg;
|
|
if (!(Settings.flag.knx_enabled)) { return; }
|
|
|
|
char tempchar[33];
|
|
|
|
if (msg.data_len == 1) {
|
|
|
|
tempchar[0] = msg.data[0];
|
|
tempchar[1] = '\0';
|
|
} else {
|
|
|
|
float tempvar = knx.data_to_2byte_float(msg.data);
|
|
dtostrfd(tempvar,2,tempchar);
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX D_RECEIVED_FROM " %d.%d.%d " D_COMMAND " %s: %s " D_TO " %s"),
|
|
msg.received_on.ga.area, msg.received_on.ga.line, msg.received_on.ga.member,
|
|
(msg.ct == KNX_CT_WRITE) ? D_KNX_COMMAND_WRITE : (msg.ct == KNX_CT_READ) ? D_KNX_COMMAND_READ : D_KNX_COMMAND_OTHER,
|
|
tempchar,
|
|
device_param_cb[(chan->type)-1]);
|
|
|
|
switch (msg.ct)
|
|
{
|
|
case KNX_CT_WRITE:
|
|
if (chan->type < 9)
|
|
{
|
|
ExecuteCommandPower(chan->type, msg.data[0], SRC_KNX);
|
|
}
|
|
else if (chan->type < 17)
|
|
{
|
|
if (!toggle_inhibit) {
|
|
ExecuteCommandPower((chan->type) -8, POWER_TOGGLE, SRC_KNX);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
toggle_inhibit = TOGGLE_INHIBIT_TIME;
|
|
}
|
|
}
|
|
}
|
|
#ifdef USE_RULES
|
|
else if ((chan->type >= KNX_SLOT1) && (chan->type <= KNX_SLOT5))
|
|
{
|
|
if (!toggle_inhibit) {
|
|
char command[25];
|
|
if (msg.data_len == 1) {
|
|
|
|
snprintf_P(command, sizeof(command), PSTR("event KNXRX_CMND%d=%d"), ((chan->type) - KNX_SLOT1 + 1 ), msg.data[0]);
|
|
} else {
|
|
|
|
snprintf_P(command, sizeof(command), PSTR("event KNXRX_VAL%d=%s"), ((chan->type) - KNX_SLOT1 + 1 ), tempchar);
|
|
}
|
|
ExecuteCommand(command, SRC_KNX);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
toggle_inhibit = TOGGLE_INHIBIT_TIME;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case KNX_CT_READ:
|
|
if (chan->type < 9)
|
|
{
|
|
knx.answer_1bit(msg.received_on, chan->last_state);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.answer_1bit(msg.received_on, chan->last_state);
|
|
knx.answer_1bit(msg.received_on, chan->last_state);
|
|
}
|
|
}
|
|
else if (chan->type == KNX_TEMPERATURE)
|
|
{
|
|
knx.answer_2byte_float(msg.received_on, last_temp);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.answer_2byte_float(msg.received_on, last_temp);
|
|
knx.answer_2byte_float(msg.received_on, last_temp);
|
|
}
|
|
}
|
|
else if (chan->type == KNX_HUMIDITY)
|
|
{
|
|
knx.answer_2byte_float(msg.received_on, last_hum);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.answer_2byte_float(msg.received_on, last_hum);
|
|
knx.answer_2byte_float(msg.received_on, last_hum);
|
|
}
|
|
}
|
|
#ifdef USE_RULES
|
|
else if ((chan->type >= KNX_SLOT1) && (chan->type <= KNX_SLOT5))
|
|
{
|
|
if (!toggle_inhibit) {
|
|
char command[25];
|
|
snprintf_P(command, sizeof(command), PSTR("event KNXRX_REQ%d"), ((chan->type) - KNX_SLOT1 + 1 ) );
|
|
ExecuteCommand(command, SRC_KNX);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
toggle_inhibit = TOGGLE_INHIBIT_TIME;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void KnxUpdatePowerState(uint8_t device, power_t state)
|
|
{
|
|
if (!(Settings.flag.knx_enabled)) { return; }
|
|
|
|
device_param[device -1].last_state = bitRead(state, device -1);
|
|
|
|
|
|
uint8_t i = KNX_GA_Search(device);
|
|
while ( i != KNX_Empty ) {
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
knx.write_1bit(KNX_addr, device_param[device -1].last_state);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.write_1bit(KNX_addr, device_param[device -1].last_state);
|
|
knx.write_1bit(KNX_addr, device_param[device -1].last_state);
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"),
|
|
device_param_ga[device -1], device_param[device -1].last_state,
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
|
|
|
|
i = KNX_GA_Search(device, i + 1);
|
|
}
|
|
}
|
|
|
|
|
|
void KnxSendButtonPower(void)
|
|
{
|
|
if (!(Settings.flag.knx_enabled)) { return; }
|
|
|
|
uint32_t key = (XdrvMailbox.payload >> 16) & 0xFF;
|
|
uint32_t device = XdrvMailbox.payload & 0xFF;
|
|
uint32_t state = (XdrvMailbox.payload >> 8) & 0xFF;
|
|
# 693 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_11_knx.ino"
|
|
uint8_t i = KNX_GA_Search(device + 8);
|
|
while ( i != KNX_Empty ) {
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
knx.write_1bit(KNX_addr, !(state == 0));
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.write_1bit(KNX_addr, !(state == 0));
|
|
knx.write_1bit(KNX_addr, !(state == 0));
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"),
|
|
device_param_ga[device + 7], !(state == 0),
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
|
|
|
|
i = KNX_GA_Search(device + 8, i + 1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void KnxSensor(uint8_t sensor_type, float value)
|
|
{
|
|
if (sensor_type == KNX_TEMPERATURE)
|
|
{
|
|
last_temp = value;
|
|
} else if (sensor_type == KNX_HUMIDITY)
|
|
{
|
|
last_hum = value;
|
|
}
|
|
|
|
if (!(Settings.flag.knx_enabled)) { return; }
|
|
|
|
uint8_t i = KNX_GA_Search(sensor_type);
|
|
while ( i != KNX_Empty ) {
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
knx.write_2byte_float(KNX_addr, value);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.write_2byte_float(KNX_addr, value);
|
|
knx.write_2byte_float(KNX_addr, value);
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s " D_SENT_TO " %d.%d.%d "),
|
|
device_param_ga[sensor_type -1],
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
|
|
|
|
i = KNX_GA_Search(sensor_type, i+1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef USE_KNX_WEB_MENU
|
|
const char S_CONFIGURE_KNX[] PROGMEM = D_CONFIGURE_KNX;
|
|
|
|
const char HTTP_BTN_MENU_KNX[] PROGMEM =
|
|
"<p><form action='kn' method='get'><button>" D_CONFIGURE_KNX "</button></form></p>";
|
|
|
|
const char HTTP_FORM_KNX[] PROGMEM =
|
|
"<fieldset style='min-width:530px;'>"
|
|
"<legend style='text-align:left;'><b> " D_KNX_PARAMETERS " </b></legend>"
|
|
"<form method='post' action='kn'>"
|
|
"<br><center>"
|
|
"<b>" D_KNX_PHYSICAL_ADDRESS " </b>"
|
|
"<input style='width:12%%;' type='number' name='area' min='0' max='15' value='%d'> . "
|
|
"<input style='width:12%%;' type='number' name='line' min='0' max='15' value='%d'> . "
|
|
"<input style='width:12%%;' type='number' name='member' min='0' max='255' value='%d'>"
|
|
"<br><br>" D_KNX_PHYSICAL_ADDRESS_NOTE "<br><br>"
|
|
"<input id='b1' type='checkbox'";
|
|
|
|
const char HTTP_FORM_KNX1[] PROGMEM =
|
|
"><b>" D_KNX_ENABLE "</b> <input id='b2' type='checkbox'";
|
|
|
|
const char HTTP_FORM_KNX2[] PROGMEM =
|
|
"><b>" D_KNX_ENHANCEMENT "</b><br></center><br>"
|
|
|
|
"<fieldset><center>"
|
|
"<b>" D_KNX_GROUP_ADDRESS_TO_WRITE "</b><hr>"
|
|
|
|
"<select name='GAop' style='width:25%%;'>";
|
|
|
|
const char HTTP_FORM_KNX_OPT[] PROGMEM =
|
|
"<option value='%d'>%s</option>";
|
|
|
|
const char HTTP_FORM_KNX_GA[] PROGMEM =
|
|
"<input style='width:12%%;' type='number' id='%s' min='0' max='31' value='0'> / "
|
|
"<input style='width:12%%;' type='number' id='%s' min='0' max='7' value='0'> / "
|
|
"<input style='width:12%%;' type='number' id='%s' min='0' max='255' value='0'> ";
|
|
|
|
const char HTTP_FORM_KNX_ADD_BTN[] PROGMEM =
|
|
"<button type='submit' onclick='%s()' %s name='btn_add' value='%d' style='width:18%%;'>" D_ADD "</button><br><br>"
|
|
"<table style='width:80%%; font-size: 14px;'><col width='250'><col width='30'>";
|
|
|
|
const char HTTP_FORM_KNX_ADD_TABLE_ROW[] PROGMEM =
|
|
"<tr><td><b>%s -> %d / %d / %d </b></td>"
|
|
"<td><button type='submit' name='btn_del_ga' value='%d' class='button bred'> " D_DELETE " </button></td></tr>";
|
|
|
|
const char HTTP_FORM_KNX3[] PROGMEM =
|
|
"</table></center></fieldset><br>"
|
|
"<fieldset><form method='post' action='kn'><center>"
|
|
"<b>" D_KNX_GROUP_ADDRESS_TO_READ "</b><hr>";
|
|
|
|
const char HTTP_FORM_KNX4[] PROGMEM =
|
|
"-> <select name='CBop' style='width:25%%;'>";
|
|
|
|
const char HTTP_FORM_KNX_ADD_TABLE_ROW2[] PROGMEM =
|
|
"<tr><td><b>%d / %d / %d -> %s</b></td>"
|
|
"<td><button type='submit' name='btn_del_cb' value='%d' class='button bred'> " D_DELETE " </button></td></tr>";
|
|
|
|
void HandleKNXConfiguration(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_KNX);
|
|
|
|
char tmp[100];
|
|
String stmp;
|
|
|
|
if ( WebServer->hasArg("save") ) {
|
|
KNX_Save_Settings();
|
|
HandleConfiguration();
|
|
}
|
|
else
|
|
{
|
|
if ( WebServer->hasArg("btn_add") ) {
|
|
if ( WebServer->arg("btn_add") == "1" ) {
|
|
|
|
stmp = WebServer->arg("GAop");
|
|
uint8_t GAop = stmp.toInt();
|
|
stmp = WebServer->arg("GA_FNUM");
|
|
uint8_t GA_FNUM = stmp.toInt();
|
|
stmp = WebServer->arg("GA_AREA");
|
|
uint8_t GA_AREA = stmp.toInt();
|
|
stmp = WebServer->arg("GA_FDEF");
|
|
uint8_t GA_FDEF = stmp.toInt();
|
|
|
|
if (GAop) {
|
|
KNX_ADD_GA( GAop, GA_FNUM, GA_AREA, GA_FDEF );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
stmp = WebServer->arg("CBop");
|
|
uint8_t CBop = stmp.toInt();
|
|
stmp = WebServer->arg("CB_FNUM");
|
|
uint8_t CB_FNUM = stmp.toInt();
|
|
stmp = WebServer->arg("CB_AREA");
|
|
uint8_t CB_AREA = stmp.toInt();
|
|
stmp = WebServer->arg("CB_FDEF");
|
|
uint8_t CB_FDEF = stmp.toInt();
|
|
|
|
if (CBop) {
|
|
KNX_ADD_CB( CBop, CB_FNUM, CB_AREA, CB_FDEF );
|
|
}
|
|
}
|
|
}
|
|
else if ( WebServer->hasArg("btn_del_ga") )
|
|
{
|
|
|
|
stmp = WebServer->arg("btn_del_ga");
|
|
uint8_t GA_NUM = stmp.toInt();
|
|
|
|
KNX_DEL_GA(GA_NUM);
|
|
|
|
}
|
|
else if ( WebServer->hasArg("btn_del_cb") )
|
|
{
|
|
|
|
stmp = WebServer->arg("btn_del_cb");
|
|
uint8_t CB_NUM = stmp.toInt();
|
|
|
|
KNX_DEL_CB(CB_NUM);
|
|
|
|
}
|
|
|
|
WSContentStart_P(S_CONFIGURE_KNX);
|
|
WSContentSend_P(
|
|
PSTR("function GAwarning()"
|
|
"{"
|
|
"var GA_FNUM=eb('GA_FNUM');"
|
|
"var GA_AREA=eb('GA_AREA');"
|
|
"var GA_FDEF=eb('GA_FDEF');"
|
|
"if(GA_FNUM!=null&&GA_FNUM.value=='0'&&GA_AREA.value=='0'&&GA_FDEF.value=='0'){"
|
|
"alert('" D_KNX_WARNING "');"
|
|
"}"
|
|
"}"
|
|
"function CBwarning()"
|
|
"{"
|
|
"var CB_FNUM=eb('CB_FNUM');"
|
|
"var CB_AREA=eb('CB_AREA');"
|
|
"var CB_FDEF=eb('CB_FDEF');"
|
|
"if(CB_FNUM!=null&&CB_FNUM.value=='0'&&CB_AREA.value=='0'&&CB_FDEF.value=='0'){"
|
|
"alert('" D_KNX_WARNING "');"
|
|
"}"
|
|
"}"));
|
|
WSContentSendStyle();
|
|
KNX_physs_addr.value = Settings.knx_physsical_addr;
|
|
WSContentSend_P(HTTP_FORM_KNX, KNX_physs_addr.pa.area, KNX_physs_addr.pa.line, KNX_physs_addr.pa.member);
|
|
if ( Settings.flag.knx_enabled ) { WSContentSend_P(PSTR(" checked")); }
|
|
WSContentSend_P(HTTP_FORM_KNX1);
|
|
if ( Settings.flag.knx_enable_enhancement ) { WSContentSend_P(PSTR(" checked")); }
|
|
|
|
WSContentSend_P(HTTP_FORM_KNX2);
|
|
for (uint32_t i = 0; i < KNX_MAX_device_param ; i++)
|
|
{
|
|
if ( device_param[i].show )
|
|
{
|
|
WSContentSend_P(HTTP_FORM_KNX_OPT, device_param[i].type, device_param_ga[i]);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("</select> -> "));
|
|
WSContentSend_P(HTTP_FORM_KNX_GA, "GA_FNUM", "GA_AREA", "GA_FDEF");
|
|
WSContentSend_P(HTTP_FORM_KNX_ADD_BTN, "GAwarning", (Settings.knx_GA_registered < MAX_KNX_GA) ? "" : "disabled", 1);
|
|
for (uint32_t i = 0; i < Settings.knx_GA_registered ; ++i)
|
|
{
|
|
if ( Settings.knx_GA_param[i] )
|
|
{
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
WSContentSend_P(HTTP_FORM_KNX_ADD_TABLE_ROW, device_param_ga[Settings.knx_GA_param[i]-1], KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member, i +1);
|
|
}
|
|
}
|
|
|
|
WSContentSend_P(HTTP_FORM_KNX3);
|
|
WSContentSend_P(HTTP_FORM_KNX_GA, "CB_FNUM", "CB_AREA", "CB_FDEF");
|
|
WSContentSend_P(HTTP_FORM_KNX4);
|
|
|
|
uint8_t j;
|
|
for (uint32_t i = 0; i < KNX_MAX_device_param ; i++)
|
|
{
|
|
|
|
if ( (i > 8) && (i < 16) ) { j=i-8; } else { j=i; }
|
|
if ( i == 8 ) { j = 0; }
|
|
if ( device_param[j].show )
|
|
{
|
|
WSContentSend_P(HTTP_FORM_KNX_OPT, device_param[i].type, device_param_cb[i]);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("</select> "));
|
|
WSContentSend_P(HTTP_FORM_KNX_ADD_BTN, "CBwarning", (Settings.knx_CB_registered < MAX_KNX_CB) ? "" : "disabled", 2);
|
|
|
|
for (uint32_t i = 0; i < Settings.knx_CB_registered ; ++i)
|
|
{
|
|
if ( Settings.knx_CB_param[i] )
|
|
{
|
|
KNX_addr.value = Settings.knx_CB_addr[i];
|
|
WSContentSend_P(HTTP_FORM_KNX_ADD_TABLE_ROW2, KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member, device_param_cb[Settings.knx_CB_param[i]-1], i +1);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("</table></center></fieldset>"));
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void KNX_Save_Settings(void)
|
|
{
|
|
String stmp;
|
|
address_t KNX_addr;
|
|
|
|
Settings.flag.knx_enabled = WebServer->hasArg("b1");
|
|
Settings.flag.knx_enable_enhancement = WebServer->hasArg("b2");
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_ENABLED ": %d, " D_KNX_ENHANCEMENT ": %d"),
|
|
Settings.flag.knx_enabled, Settings.flag.knx_enable_enhancement );
|
|
|
|
stmp = WebServer->arg("area");
|
|
KNX_addr.pa.area = stmp.toInt();
|
|
stmp = WebServer->arg("line");
|
|
KNX_addr.pa.line = stmp.toInt();
|
|
stmp = WebServer->arg("member");
|
|
KNX_addr.pa.member = stmp.toInt();
|
|
Settings.knx_physsical_addr = KNX_addr.value;
|
|
knx.physical_address_set( KNX_addr );
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX D_KNX_PHYSICAL_ADDRESS ": %d.%d.%d "),
|
|
KNX_addr.pa.area, KNX_addr.pa.line, KNX_addr.pa.member );
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "GA: %d"),
|
|
Settings.knx_GA_registered );
|
|
for (uint32_t i = 0; i < Settings.knx_GA_registered ; ++i)
|
|
{
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "GA #%d: %s " D_TO " %d/%d/%d"),
|
|
i+1, device_param_ga[Settings.knx_GA_param[i]-1],
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member );
|
|
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "CB: %d"),
|
|
Settings.knx_CB_registered );
|
|
for (uint32_t i = 0; i < Settings.knx_CB_registered ; ++i)
|
|
{
|
|
KNX_addr.value = Settings.knx_CB_addr[i];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_KNX "CB #%d: %d/%d/%d " D_TO " %s"),
|
|
i+1,
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member,
|
|
device_param_cb[Settings.knx_CB_param[i]-1] );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void CmndKnxTxCmnd(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNXTX_CMNDS) && (XdrvMailbox.data_len > 0) && Settings.flag.knx_enabled) {
|
|
|
|
|
|
|
|
uint8_t i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1);
|
|
while ( i != KNX_Empty ) {
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
knx.write_1bit(KNX_addr, !(XdrvMailbox.payload == 0));
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.write_1bit(KNX_addr, !(XdrvMailbox.payload == 0));
|
|
knx.write_1bit(KNX_addr, !(XdrvMailbox.payload == 0));
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %d " D_SENT_TO " %d.%d.%d"),
|
|
device_param_ga[XdrvMailbox.index + KNX_SLOT1 -2], !(XdrvMailbox.payload == 0),
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
|
|
|
|
i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1, i + 1);
|
|
}
|
|
ResponseCmndIdxChar (XdrvMailbox.data );
|
|
}
|
|
}
|
|
|
|
void CmndKnxTxVal(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNXTX_CMNDS) && (XdrvMailbox.data_len > 0) && Settings.flag.knx_enabled) {
|
|
|
|
|
|
|
|
uint8_t i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1);
|
|
while ( i != KNX_Empty ) {
|
|
KNX_addr.value = Settings.knx_GA_addr[i];
|
|
|
|
float tempvar = CharToFloat(XdrvMailbox.data);
|
|
dtostrfd(tempvar,2,XdrvMailbox.data);
|
|
|
|
knx.write_2byte_float(KNX_addr, tempvar);
|
|
if (Settings.flag.knx_enable_enhancement) {
|
|
knx.write_2byte_float(KNX_addr, tempvar);
|
|
knx.write_2byte_float(KNX_addr, tempvar);
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_KNX "%s = %s " D_SENT_TO " %d.%d.%d"),
|
|
device_param_ga[XdrvMailbox.index + KNX_SLOT1 -2], XdrvMailbox.data,
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member);
|
|
|
|
i = KNX_GA_Search(XdrvMailbox.index + KNX_SLOT1 -1, i + 1);
|
|
}
|
|
ResponseCmndIdxChar (XdrvMailbox.data );
|
|
}
|
|
}
|
|
|
|
void CmndKnxEnabled(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings.flag.knx_enabled = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndChar (GetStateText(Settings.flag.knx_enabled) );
|
|
}
|
|
|
|
void CmndKnxEnhanced(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
|
|
Settings.flag.knx_enable_enhancement = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndChar (GetStateText(Settings.flag.knx_enable_enhancement) );
|
|
}
|
|
|
|
void CmndKnxPa(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
if (strstr(XdrvMailbox.data, ".") != nullptr) {
|
|
char sub_string[XdrvMailbox.data_len];
|
|
|
|
int pa_area = atoi(subStr(sub_string, XdrvMailbox.data, ".", 1));
|
|
int pa_line = atoi(subStr(sub_string, XdrvMailbox.data, ".", 2));
|
|
int pa_member = atoi(subStr(sub_string, XdrvMailbox.data, ".", 3));
|
|
|
|
if ( ((pa_area == 0) && (pa_line == 0) && (pa_member == 0))
|
|
|| (pa_area > 15) || (pa_line > 15) || (pa_member > 255) ) {
|
|
Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command );
|
|
return;
|
|
}
|
|
|
|
KNX_addr.pa.area = pa_area;
|
|
KNX_addr.pa.line = pa_line;
|
|
KNX_addr.pa.member = pa_member;
|
|
Settings.knx_physsical_addr = KNX_addr.value;
|
|
}
|
|
}
|
|
KNX_addr.value = Settings.knx_physsical_addr;
|
|
Response_P (PSTR("{\"%s\":\"%d.%d.%d\"}"),
|
|
XdrvMailbox.command, KNX_addr.pa.area, KNX_addr.pa.line, KNX_addr.pa.member );
|
|
}
|
|
|
|
void CmndKnxGa(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNX_GA)) {
|
|
if (XdrvMailbox.data_len) {
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
char sub_string[XdrvMailbox.data_len];
|
|
|
|
int ga_option = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1));
|
|
int ga_area = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
int ga_line = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
int ga_member = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4));
|
|
|
|
if ( ((ga_area == 0) && (ga_line == 0) && (ga_member == 0))
|
|
|| (ga_area > 31) || (ga_line > 7) || (ga_member > 255)
|
|
|| (ga_option < 0) || ((ga_option > KNX_MAX_device_param ) && (ga_option != KNX_Empty))
|
|
|| (!device_param[ga_option-1].show) ) {
|
|
Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command );
|
|
return;
|
|
}
|
|
|
|
KNX_addr.ga.area = ga_area;
|
|
KNX_addr.ga.line = ga_line;
|
|
KNX_addr.ga.member = ga_member;
|
|
|
|
if ( XdrvMailbox.index > Settings.knx_GA_registered ) {
|
|
Settings.knx_GA_registered ++;
|
|
XdrvMailbox.index = Settings.knx_GA_registered;
|
|
}
|
|
|
|
Settings.knx_GA_addr[XdrvMailbox.index -1] = KNX_addr.value;
|
|
Settings.knx_GA_param[XdrvMailbox.index -1] = ga_option;
|
|
} else {
|
|
if ( (XdrvMailbox.payload <= Settings.knx_GA_registered) && (XdrvMailbox.payload > 0) ) {
|
|
XdrvMailbox.index = XdrvMailbox.payload;
|
|
} else {
|
|
Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command );
|
|
return;
|
|
}
|
|
}
|
|
if ( XdrvMailbox.index <= Settings.knx_GA_registered ) {
|
|
KNX_addr.value = Settings.knx_GA_addr[XdrvMailbox.index -1];
|
|
Response_P (PSTR("{\"%s%d\":\"%s, %d/%d/%d\"}"),
|
|
XdrvMailbox.command, XdrvMailbox.index, device_param_ga[Settings.knx_GA_param[XdrvMailbox.index-1]-1],
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member );
|
|
}
|
|
} else {
|
|
ResponseCmndNumber (Settings.knx_GA_registered );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndKnxCb(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_KNX_CB)) {
|
|
if (XdrvMailbox.data_len) {
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
char sub_string[XdrvMailbox.data_len];
|
|
|
|
int cb_option = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1));
|
|
int cb_area = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
int cb_line = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
int cb_member = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4));
|
|
|
|
if ( ((cb_area == 0) && (cb_line == 0) && (cb_member == 0))
|
|
|| (cb_area > 31) || (cb_line > 7) || (cb_member > 255)
|
|
|| (cb_option < 0) || ((cb_option > KNX_MAX_device_param ) && (cb_option != KNX_Empty))
|
|
|| (!device_param[cb_option-1].show) ) {
|
|
Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command );
|
|
return;
|
|
}
|
|
|
|
KNX_addr.ga.area = cb_area;
|
|
KNX_addr.ga.line = cb_line;
|
|
KNX_addr.ga.member = cb_member;
|
|
|
|
if ( XdrvMailbox.index > Settings.knx_CB_registered ) {
|
|
Settings.knx_CB_registered ++;
|
|
XdrvMailbox.index = Settings.knx_CB_registered;
|
|
}
|
|
|
|
Settings.knx_CB_addr[XdrvMailbox.index -1] = KNX_addr.value;
|
|
Settings.knx_CB_param[XdrvMailbox.index -1] = cb_option;
|
|
} else {
|
|
if ( (XdrvMailbox.payload <= Settings.knx_CB_registered) && (XdrvMailbox.payload > 0) ) {
|
|
XdrvMailbox.index = XdrvMailbox.payload;
|
|
} else {
|
|
Response_P (PSTR("{\"%s\":\"" D_ERROR "\"}"), XdrvMailbox.command );
|
|
return;
|
|
}
|
|
}
|
|
if ( XdrvMailbox.index <= Settings.knx_CB_registered ) {
|
|
KNX_addr.value = Settings.knx_CB_addr[XdrvMailbox.index -1];
|
|
Response_P (PSTR("{\"%s%d\":\"%s, %d/%d/%d\"}"),
|
|
XdrvMailbox.command, XdrvMailbox.index, device_param_cb[Settings.knx_CB_param[XdrvMailbox.index-1]-1],
|
|
KNX_addr.ga.area, KNX_addr.ga.line, KNX_addr.ga.member );
|
|
}
|
|
} else {
|
|
ResponseCmndNumber (Settings.knx_CB_registered );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv11(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (!global_state.wifi_down) { knx.loop(); }
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (toggle_inhibit) {
|
|
toggle_inhibit--;
|
|
}
|
|
break;
|
|
case FUNC_ANY_KEY:
|
|
KnxSendButtonPower();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef USE_KNX_WEB_MENU
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_KNX);
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/kn", HandleKNXConfiguration);
|
|
break;
|
|
#endif
|
|
#endif
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kKnxCommands, KnxCommand);
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
KNX_INIT();
|
|
break;
|
|
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_12_home_assistant.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_12_home_assistant.ino"
|
|
#ifdef USE_HOME_ASSISTANT
|
|
|
|
#define XDRV_12 12
|
|
|
|
|
|
const char kHAssJsonSensorTypes[] PROGMEM =
|
|
D_JSON_TEMPERATURE "|" D_JSON_PRESSURE "|" D_JSON_PRESSUREATSEALEVEL "|"
|
|
D_JSON_APPARENT_POWERUSAGE "|Battery|" D_JSON_CURRENT "|" D_JSON_DISTANCE "|" D_JSON_FREQUENCY "|" D_JSON_HUMIDITY "|" D_JSON_ILLUMINANCE "|"
|
|
D_JSON_MOISTURE "|PB0.3|PB0.5|PB1|PB2.5|PB5|PB10|PM1|PM2.5|PM10|" D_JSON_POWERFACTOR "|" D_JSON_POWERUSAGE "|"
|
|
D_JSON_REACTIVE_POWERUSAGE "|" D_JSON_TODAY "|" D_JSON_TOTAL "|" D_JSON_VOLTAGE "|" D_JSON_WEIGHT "|" D_JSON_YESTERDAY;
|
|
const char kHAssJsonSensorUnits[] PROGMEM =
|
|
"|||"
|
|
"W|%|A|Cm|Hz|%|LX|"
|
|
"%|ppd|ppd|ppd|ppd|ppd|ppd|µg/m³|µg/m³|µg/m³||W|"
|
|
"W|KWh|KWh|V|Kg|KWh";
|
|
const char kHAssJsonSensorDevCla[] PROGMEM =
|
|
"dev_cla\":\"temperature|dev_cla\":\"pressure|dev_cla\":\"pressure|"
|
|
"dev_cla\":\"power|dev_cla\":\"battery|ic\":\"mdi:alpha-a-circle-outline|ic\":\"mdi:leak|ic\":\"mdi:current-ac|dev_cla\":\"humidity|dev_cla\":\"illuminance|"
|
|
"ic\":\"mdi:cup-water|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|ic\":\"mdi:flask|"
|
|
"ic\":\"mdi:air-filter|ic\":\"mdi:air-filter|ic\":\"mdi:air-filter|ic\":\"mdi:alpha-f-circle-outline|dev_cla\":\"power|"
|
|
"dev_cla\":\"power|dev_cla\":\"power|dev_cla\":\"power|ic\":\"mdi:alpha-v-circle-outline|ic\":\"mdi:scale|dev_cla\":\"power";
|
|
|
|
|
|
const char HASS_DISCOVER_SENSOR[] PROGMEM =
|
|
",\"unit_of_meas\":\"%s\",\"%s\","
|
|
"\"frc_upd\":true,"
|
|
"\"val_tpl\":\"{{value_json['%s']['%s']";
|
|
|
|
const char HASS_DISCOVER_BASE[] PROGMEM =
|
|
"{\"name\":\"%s\","
|
|
"\"stat_t\":\"%s\","
|
|
"\"avty_t\":\"%s\","
|
|
"\"pl_avail\":\"" D_ONLINE "\","
|
|
"\"pl_not_avail\":\"" D_OFFLINE "\"";
|
|
|
|
const char HASS_DISCOVER_RELAY[] PROGMEM =
|
|
",\"cmd_t\":\"%s\","
|
|
"\"val_tpl\":\"{{value_json.%s}}\","
|
|
"\"pl_off\":\"%s\","
|
|
"\"pl_on\":\"%s\"";
|
|
|
|
const char HASS_DISCOVER_BUTTON_TOGGLE[] PROGMEM =
|
|
",\"value_template\":\"{%%if is_state(entity_id,\\\"off\\\")-%%}ON{%%-endif%%}\","
|
|
"\"off_delay\":1";
|
|
|
|
const char HASS_DISCOVER_BUTTON_SWITCH_ONOFF[] PROGMEM =
|
|
",\"value_template\":\"{{value_json.%s}}\","
|
|
"\"frc_upd\":true,"
|
|
"\"pl_on\":\"%s\","
|
|
"\"pl_off\":\"%s\"";
|
|
|
|
const char HASS_DISCOVER_LIGHT_DIMMER[] PROGMEM =
|
|
",\"bri_cmd_t\":\"%s\","
|
|
"\"bri_stat_t\":\"%s\","
|
|
"\"bri_scl\":100,"
|
|
"\"on_cmd_type\":\"%s\","
|
|
"\"bri_val_tpl\":\"{{value_json." D_CMND_DIMMER "}}\"";
|
|
|
|
const char HASS_DISCOVER_LIGHT_COLOR[] PROGMEM =
|
|
",\"rgb_cmd_t\":\"%s2\","
|
|
"\"rgb_stat_t\":\"%s\","
|
|
"\"rgb_val_tpl\":\"{{value_json." D_CMND_COLOR ".split(',')[0:3]|join(',')}}\"";
|
|
|
|
const char HASS_DISCOVER_LIGHT_WHITE[] PROGMEM =
|
|
",\"whit_val_cmd_t\":\"%s\","
|
|
"\"whit_val_stat_t\":\"%s\","
|
|
"\"white_value_scale\":100,"
|
|
"\"whit_val_tpl\":\"{{value_json.Channel[3]}}\"";
|
|
|
|
const char HASS_DISCOVER_LIGHT_CT[] PROGMEM =
|
|
",\"clr_temp_cmd_t\":\"%s\","
|
|
"\"clr_temp_stat_t\":\"%s\","
|
|
"\"clr_temp_val_tpl\":\"{{value_json." D_CMND_COLORTEMPERATURE "}}\"";
|
|
|
|
const char HASS_DISCOVER_LIGHT_SCHEME[] PROGMEM =
|
|
",\"fx_cmd_t\":\"%s\","
|
|
"\"fx_stat_t\":\"%s\","
|
|
"\"fx_val_tpl\":\"{{value_json." D_CMND_SCHEME "}}\","
|
|
"\"fx_list\":[\"0\",\"1\",\"2\",\"3\",\"4\"]";
|
|
|
|
const char HASS_DISCOVER_SENSOR_HASS_STATUS[] PROGMEM =
|
|
",\"json_attributes_topic\":\"%s\","
|
|
"\"unit_of_meas\":\" \","
|
|
"\"val_tpl\":\"{{value_json['" D_JSON_RSSI "']}}\","
|
|
"\"ic\":\"mdi:information-outline\"";
|
|
|
|
const char HASS_DISCOVER_DEVICE_INFO[] PROGMEM =
|
|
",\"uniq_id\":\"%s\","
|
|
"\"device\":{\"identifiers\":[\"%06X\"],"
|
|
"\"connections\":[[\"mac\",\"%s\"]],"
|
|
"\"name\":\"%s\","
|
|
"\"model\":\"%s\","
|
|
"\"sw_version\":\"%s%s\","
|
|
"\"manufacturer\":\"Tasmota\"}";
|
|
|
|
const char HASS_DISCOVER_DEVICE_INFO_SHORT[] PROGMEM =
|
|
",\"uniq_id\":\"%s\","
|
|
"\"device\":{\"identifiers\":[\"%06X\"],"
|
|
"\"connections\":[[\"mac\",\"%s\"]]}";
|
|
|
|
uint8_t hass_init_step = 0;
|
|
uint8_t hass_mode = 0;
|
|
int hass_tele_period = 0;
|
|
|
|
void TryResponseAppend_P(const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
char dummy[2];
|
|
int dlen = vsnprintf_P(dummy, 1, format, args);
|
|
|
|
int mlen = strlen(mqtt_data);
|
|
int slen = sizeof(mqtt_data) - 1 - mlen;
|
|
if (dlen >= slen)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: MQTT discovery failed due to too long topic or friendly name. "
|
|
"Please shorten topic and friendly name. Failed to format(%u/%u):"),
|
|
dlen, slen);
|
|
va_start(args, format);
|
|
vsnprintf_P(log_data, sizeof(log_data), format, args);
|
|
AddLog(LOG_LEVEL_ERROR);
|
|
}
|
|
else
|
|
{
|
|
va_start(args, format);
|
|
vsnprintf_P(mqtt_data + mlen, slen, format, args);
|
|
}
|
|
va_end(args);
|
|
}
|
|
|
|
void HAssAnnounceRelayLight(void)
|
|
{
|
|
char stopic[TOPSZ];
|
|
char stemp1[TOPSZ];
|
|
char stemp2[TOPSZ];
|
|
char stemp3[TOPSZ];
|
|
char unique_id[30];
|
|
bool is_light = false;
|
|
bool is_topic_light = false;
|
|
|
|
for (uint32_t i = 1; i <= MAX_RELAYS; i++)
|
|
{
|
|
is_light = ((i == devices_present) && (light_type));
|
|
is_topic_light = Settings.flag.hass_light || is_light;
|
|
|
|
mqtt_data[0] = '\0';
|
|
|
|
|
|
snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), (is_topic_light) ? "RL" : "LI", i);
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s/config"),
|
|
(is_topic_light) ? "switch" : "light", unique_id);
|
|
MqttPublish(stopic, true);
|
|
|
|
snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), (is_topic_light) ? "LI" : "RL", i);
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/%s/%s/config"),
|
|
(is_topic_light) ? "light" : "switch", unique_id);
|
|
|
|
if (Settings.flag.hass_discovery && (i <= devices_present))
|
|
{
|
|
char name[33 + 2];
|
|
char value_template[33];
|
|
char prefix[TOPSZ];
|
|
char *command_topic = stemp1;
|
|
char *state_topic = stemp2;
|
|
char *availability_topic = stemp3;
|
|
|
|
if (i > MAX_FRIENDLYNAMES)
|
|
{
|
|
snprintf_P(name, sizeof(name), PSTR("%s %d"), SettingsText(SET_FRIENDLYNAME1), i);
|
|
}
|
|
else
|
|
{
|
|
snprintf_P(name, sizeof(name), SettingsText(SET_FRIENDLYNAME1 + i - 1));
|
|
}
|
|
GetPowerDevice(value_template, i, sizeof(value_template), Settings.flag.device_index_enable);
|
|
GetTopic_P(command_topic, CMND, mqtt_topic, value_template);
|
|
GetTopic_P(state_topic, TELE, mqtt_topic, D_RSLT_STATE);
|
|
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
|
|
|
|
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
|
|
TryResponseAppend_P(HASS_DISCOVER_RELAY, command_topic, value_template, SettingsText(SET_STATE_TXT1), SettingsText(SET_STATE_TXT2));
|
|
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
|
|
|
|
#ifdef USE_LIGHT
|
|
if (is_light)
|
|
{
|
|
char *brightness_command_topic = stemp1;
|
|
|
|
GetTopic_P(brightness_command_topic, CMND, mqtt_topic, D_CMND_DIMMER);
|
|
strncpy_P(stemp3, Settings.flag.not_power_linked ? PSTR("last") : PSTR("brightness"), sizeof(stemp3));
|
|
TryResponseAppend_P(HASS_DISCOVER_LIGHT_DIMMER, brightness_command_topic, state_topic, stemp3);
|
|
|
|
if (Light.subtype >= LST_RGB)
|
|
{
|
|
char *rgb_command_topic = stemp1;
|
|
|
|
GetTopic_P(rgb_command_topic, CMND, mqtt_topic, D_CMND_COLOR);
|
|
TryResponseAppend_P(HASS_DISCOVER_LIGHT_COLOR, rgb_command_topic, state_topic);
|
|
|
|
char *effect_command_topic = stemp1;
|
|
GetTopic_P(effect_command_topic, CMND, mqtt_topic, D_CMND_SCHEME);
|
|
TryResponseAppend_P(HASS_DISCOVER_LIGHT_SCHEME, effect_command_topic, state_topic);
|
|
}
|
|
if (LST_RGBW == Light.subtype)
|
|
{
|
|
char *white_temp_command_topic = stemp1;
|
|
|
|
GetTopic_P(white_temp_command_topic, CMND, mqtt_topic, D_CMND_WHITE);
|
|
TryResponseAppend_P(HASS_DISCOVER_LIGHT_WHITE, white_temp_command_topic, state_topic);
|
|
}
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype))
|
|
{
|
|
char *color_temp_command_topic = stemp1;
|
|
|
|
GetTopic_P(color_temp_command_topic, CMND, mqtt_topic, D_CMND_COLORTEMPERATURE);
|
|
TryResponseAppend_P(HASS_DISCOVER_LIGHT_CT, color_temp_command_topic, state_topic);
|
|
}
|
|
}
|
|
#endif
|
|
TryResponseAppend_P(PSTR("}"));
|
|
}
|
|
MqttPublish(stopic, true);
|
|
}
|
|
}
|
|
|
|
void HAssAnnounceButtonSwitch(uint8_t device, char *topic, uint8_t present, uint8_t key, uint8_t toggle)
|
|
{
|
|
|
|
|
|
char stopic[TOPSZ];
|
|
char stemp1[TOPSZ];
|
|
char stemp2[TOPSZ];
|
|
char unique_id[30];
|
|
|
|
mqtt_data[0] = '\0';
|
|
|
|
|
|
snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%d"), ESP.getChipId(), key ? "SW" : "BTN", device + 1);
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/binary_sensor/%s/config"), unique_id);
|
|
|
|
if (Settings.flag.hass_discovery && present)
|
|
{
|
|
char name[33 + 6];
|
|
char value_template[33];
|
|
char prefix[TOPSZ];
|
|
char *state_topic = stemp1;
|
|
char *availability_topic = stemp2;
|
|
char jsoname[8];
|
|
|
|
snprintf_P(name, sizeof(name), PSTR("%s %s%d"), SettingsText(SET_FRIENDLYNAME1), key ? "Switch" : "Button", device + 1);
|
|
snprintf_P(jsoname, sizeof(jsoname), PSTR("%s%d"), key ? "SWITCH" : "BUTTON", device + 1);
|
|
GetPowerDevice(value_template, device + 1, sizeof(value_template),
|
|
key + Settings.flag.device_index_enable);
|
|
GetTopic_P(state_topic, STAT, mqtt_topic, (PSTR("/'%s'"), jsoname));
|
|
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
|
|
|
|
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
|
|
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
|
|
|
|
if (toggle) {
|
|
if (!key) {
|
|
TryResponseAppend_P(HASS_DISCOVER_BUTTON_TOGGLE);
|
|
} else {
|
|
TryResponseAppend_P(",\"value_template\":\"{%%if is_state(entity_id,\\\"on\\\")-%%}OFF{%%-else-%%}ON{%%-endif%%}\"");
|
|
}
|
|
} else {
|
|
TryResponseAppend_P(HASS_DISCOVER_BUTTON_SWITCH_ONOFF, PSTR(D_RSLT_STATE), SettingsText(SET_STATE_TXT2), SettingsText(SET_STATE_TXT1));
|
|
}
|
|
TryResponseAppend_P(PSTR("}"));
|
|
}
|
|
MqttPublish(stopic, true);
|
|
}
|
|
|
|
void HAssAnnounceSwitches(void)
|
|
{
|
|
char sw_topic[TOPSZ];
|
|
|
|
|
|
char *tmp = SettingsText(SET_MQTT_SWITCH_TOPIC);
|
|
Format(sw_topic, tmp, sizeof(sw_topic));
|
|
if (!strcmp_P(sw_topic, "0") || strlen(sw_topic) == 0)
|
|
{
|
|
for (uint32_t switch_index = 0; switch_index < MAX_SWITCHES; switch_index++)
|
|
{
|
|
uint8_t switch_present = 0;
|
|
uint8_t toggle = 1;
|
|
|
|
if (pin[GPIO_SWT1 + switch_index] < 99)
|
|
{
|
|
switch_present = 1;
|
|
}
|
|
|
|
|
|
if (Settings.switchmode[switch_index] == FOLLOW || Settings.switchmode[switch_index] == FOLLOW_INV ||
|
|
Settings.flag3.button_switch_force_local ||
|
|
!strcmp(mqtt_topic, sw_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), sw_topic))
|
|
{
|
|
toggle = 0;
|
|
}
|
|
HAssAnnounceButtonSwitch(switch_index, sw_topic, switch_present, 1, toggle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HAssAnnounceButtons(void)
|
|
{
|
|
char key_topic[TOPSZ];
|
|
|
|
|
|
char *tmp = SettingsText(SET_MQTT_BUTTON_TOPIC);
|
|
Format(key_topic, tmp, sizeof(key_topic));
|
|
if (!strcmp_P(key_topic, "0") || strlen(key_topic) == 0)
|
|
{
|
|
for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++)
|
|
{
|
|
uint8_t button_present = 0;
|
|
uint8_t toggle = 1;
|
|
|
|
if (!button_index && ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)))
|
|
{
|
|
button_present = 1;
|
|
}
|
|
else
|
|
{
|
|
if (pin[GPIO_KEY1 + button_index] < 99)
|
|
{
|
|
button_present = 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (Settings.flag3.button_switch_force_local ||
|
|
!strcmp(mqtt_topic, key_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic))
|
|
{
|
|
toggle = 0;
|
|
}
|
|
HAssAnnounceButtonSwitch(button_index, key_topic, button_present, 0, toggle);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HAssAnnounceSensor(const char *sensorname, const char *subsensortype, const char *MultiSubName, uint8_t subqty, uint8_t subidx)
|
|
{
|
|
char stopic[TOPSZ];
|
|
char stemp1[TOPSZ];
|
|
char stemp2[TOPSZ];
|
|
char unique_id[30];
|
|
|
|
mqtt_data[0] = '\0';
|
|
|
|
char subname[20];
|
|
NoAlNumToUnderscore(subname, MultiSubName);
|
|
snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_%s_%s"), ESP.getChipId(), sensorname, subname);
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id);;
|
|
|
|
if (Settings.flag.hass_discovery)
|
|
{
|
|
char name[33 + 42];
|
|
char prefix[TOPSZ];
|
|
char *state_topic = stemp1;
|
|
char *availability_topic = stemp2;
|
|
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id);
|
|
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR));
|
|
snprintf_P(name, sizeof(name), PSTR("%s %s %s"), SettingsText(SET_FRIENDLYNAME1), sensorname, MultiSubName);
|
|
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
|
|
|
|
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
|
|
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
|
|
|
|
char jname[32];
|
|
int sensor_index = GetCommandCode(jname, sizeof(jname), subsensortype, kHAssJsonSensorTypes);
|
|
if (sensor_index > -1) {
|
|
char param1[20];
|
|
GetTextIndexed(param1, sizeof(param1), sensor_index, kHAssJsonSensorUnits);
|
|
switch (sensor_index) {
|
|
case 0:
|
|
snprintf_P(param1, sizeof(param1), PSTR("°%c"),TempUnit());
|
|
break;
|
|
case 1:
|
|
case 2:
|
|
snprintf_P(param1, sizeof(param1), PSTR("%s"), PressureUnit().c_str());
|
|
break;
|
|
}
|
|
char param2[50];
|
|
GetTextIndexed(param2, sizeof(param2), sensor_index, kHAssJsonSensorDevCla);
|
|
TryResponseAppend_P(HASS_DISCOVER_SENSOR, param1, param2, sensorname, subsensortype);
|
|
if (subidx) {
|
|
TryResponseAppend_P(PSTR("[%d]"), subqty -1);
|
|
}
|
|
} else {
|
|
TryResponseAppend_P(HASS_DISCOVER_SENSOR, " ", "ic\":\"mdi:eye", sensorname, subsensortype);
|
|
}
|
|
TryResponseAppend_P(PSTR("}}\"}"));
|
|
}
|
|
MqttPublish(stopic, true);
|
|
}
|
|
|
|
void HAssAnnounceSensors(void)
|
|
{
|
|
uint8_t hass_xsns_index = 0;
|
|
bool is_sensor = true;
|
|
uint8_t subqty = 0;
|
|
do
|
|
{
|
|
mqtt_data[0] = '\0';
|
|
int tele_period_save = tele_period;
|
|
tele_period = 2;
|
|
XsnsNextCall(FUNC_JSON_APPEND, hass_xsns_index);
|
|
tele_period = tele_period_save;
|
|
|
|
char sensordata[512];
|
|
strlcpy(sensordata, mqtt_data, sizeof(sensordata));
|
|
|
|
if (strlen(sensordata))
|
|
{
|
|
sensordata[0] = '{';
|
|
snprintf_P(sensordata, sizeof(sensordata), PSTR("%s}"), sensordata);
|
|
|
|
|
|
|
|
StaticJsonBuffer<500> jsonBuffer;
|
|
JsonObject &root = jsonBuffer.parseObject(sensordata);
|
|
if (!root.success())
|
|
{
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: jsonBuffer failed to parse '%s'"), sensordata);
|
|
continue;
|
|
}
|
|
for (auto sensor : root)
|
|
{
|
|
const char *sensorname = sensor.key;
|
|
JsonObject &sensors = sensor.value.as<JsonObject>();
|
|
if (!sensors.success())
|
|
{
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("HASS: JsonObject failed to parse '%s'"), sensordata);
|
|
continue;
|
|
}
|
|
for (auto subsensor : sensors)
|
|
{
|
|
|
|
if (subsensor.value.is<JsonArray&>()) {
|
|
JsonArray& subsensors = subsensor.value.as<JsonArray&>();
|
|
subqty = subsensors.size();
|
|
char MultiSubName[20];
|
|
for (int i = 1; i <= subqty; i++) {
|
|
snprintf_P(MultiSubName, sizeof(MultiSubName), PSTR("%s %d"), subsensor.key, i);
|
|
HAssAnnounceSensor(sensorname, subsensor.key, MultiSubName, i, 1);
|
|
}
|
|
} else { HAssAnnounceSensor(sensorname, subsensor.key, subsensor.key, 0, 0);}
|
|
}
|
|
}
|
|
}
|
|
yield();
|
|
} while (hass_xsns_index != 0);
|
|
}
|
|
|
|
void HAssAnnounceStatusSensor(void)
|
|
{
|
|
char stopic[TOPSZ];
|
|
char stemp1[TOPSZ];
|
|
char stemp2[TOPSZ];
|
|
char unique_id[30];
|
|
|
|
|
|
mqtt_data[0] = '\0';
|
|
|
|
|
|
snprintf_P(unique_id, sizeof(unique_id), PSTR("%06X_status"), ESP.getChipId());
|
|
snprintf_P(stopic, sizeof(stopic), PSTR(HOME_ASSISTANT_DISCOVERY_PREFIX "/sensor/%s/config"), unique_id);
|
|
|
|
if (Settings.flag.hass_discovery)
|
|
{
|
|
char name[33 + 7];
|
|
char prefix[TOPSZ];
|
|
char *state_topic = stemp1;
|
|
char *availability_topic = stemp2;
|
|
|
|
snprintf_P(name, sizeof(name), PSTR("%s status"), SettingsText(SET_FRIENDLYNAME1));
|
|
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_HASS_STATE));
|
|
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
|
|
|
|
Response_P(HASS_DISCOVER_BASE, name, state_topic, availability_topic);
|
|
TryResponseAppend_P(HASS_DISCOVER_SENSOR_HASS_STATUS, state_topic);
|
|
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP.getChipId(), WiFi.macAddress().c_str(),
|
|
SettingsText(SET_FRIENDLYNAME1), ModuleName().c_str(), my_version, my_image);
|
|
|
|
TryResponseAppend_P(PSTR("}"));
|
|
}
|
|
MqttPublish(stopic, true);
|
|
}
|
|
|
|
void HAssPublishStatus(void)
|
|
{
|
|
Response_P(PSTR("{\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_BUILDDATETIME "\":\"%s\","
|
|
"\"" D_JSON_COREVERSION "\":\"" ARDUINO_ESP8266_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\","
|
|
"\"" D_CMND_MODULE "\":\"%s\",\"" D_JSON_RESTARTREASON "\":\"%s\",\"" D_JSON_UPTIME "\":\"%s\","
|
|
"\"WiFi " D_JSON_LINK_COUNT "\":%d,\"WiFi " D_JSON_DOWNTIME "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,"
|
|
"\"" D_JSON_BOOTCOUNT "\":%d,\"" D_JSON_SAVECOUNT "\":%d,\"" D_CMND_IPADDRESS "\":\"%s\","
|
|
"\"" D_JSON_RSSI "\":\"%d\",\"LoadAvg\":%lu}"),
|
|
my_version, my_image, GetBuildDateAndTime().c_str(), ESP.getSdkVersion(), ModuleName().c_str(),
|
|
GetResetReason().c_str(), GetUptime().c_str(), WifiLinkCount(), WifiDowntime().c_str(), MqttConnectCount(),
|
|
Settings.bootcount, Settings.save_flag, WiFi.localIP().toString().c_str(),
|
|
WifiGetRssiAsQuality(WiFi.RSSI()), loop_load_avg);
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_HASS_STATE));
|
|
}
|
|
|
|
void HAssDiscovery(void)
|
|
{
|
|
|
|
if (Settings.flag.hass_discovery)
|
|
{
|
|
Settings.flag.mqtt_response = 0;
|
|
Settings.flag.decimal_text = 1;
|
|
Settings.flag3.hass_tele_on_power = 1;
|
|
Settings.light_scheme = 0;
|
|
}
|
|
|
|
if (Settings.flag.hass_discovery || (1 == hass_mode))
|
|
{
|
|
|
|
HAssAnnounceRelayLight();
|
|
|
|
|
|
HAssAnnounceButtons();
|
|
|
|
|
|
HAssAnnounceSwitches();
|
|
|
|
|
|
HAssAnnounceSensors();
|
|
|
|
|
|
HAssAnnounceStatusSensor();
|
|
}
|
|
}
|
|
|
|
void HAssDiscover(void)
|
|
{
|
|
hass_mode = 1;
|
|
hass_init_step = 1;
|
|
}
|
|
|
|
void HAssAnyKey(void)
|
|
{
|
|
if (!Settings.flag.hass_discovery)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32_t key = (XdrvMailbox.payload >> 16) & 0xFF;
|
|
uint32_t device = XdrvMailbox.payload & 0xFF;
|
|
uint32_t state = (XdrvMailbox.payload >> 8) & 0xFF;
|
|
|
|
char scommand[CMDSZ];
|
|
snprintf_P(scommand, sizeof(scommand), PSTR("%s%d"), (key) ? "SWITCH" : "BUTTON", device);
|
|
char stopic[TOPSZ];
|
|
|
|
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
|
|
Response_P(S_JSON_COMMAND_SVALUE, PSTR(D_RSLT_STATE), GetStateText(state));
|
|
MqttPublish(stopic);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv12(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Settings.flag.mqtt_enabled)
|
|
{
|
|
switch (function)
|
|
{
|
|
case FUNC_EVERY_SECOND:
|
|
if (hass_init_step)
|
|
{
|
|
hass_init_step--;
|
|
if (!hass_init_step)
|
|
{
|
|
HAssDiscovery();
|
|
}
|
|
}
|
|
else if (Settings.flag.hass_discovery && Settings.tele_period)
|
|
{
|
|
hass_tele_period++;
|
|
if (hass_tele_period >= Settings.tele_period)
|
|
{
|
|
hass_tele_period = 0;
|
|
|
|
mqtt_data[0] = '\0';
|
|
HAssPublishStatus();
|
|
}
|
|
}
|
|
break;
|
|
case FUNC_ANY_KEY:
|
|
HAssAnyKey();
|
|
break;
|
|
case FUNC_MQTT_INIT:
|
|
hass_mode = 0;
|
|
hass_init_step = 2;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_13_display.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_13_display.ino"
|
|
#if defined(USE_I2C) || defined(USE_SPI)
|
|
#ifdef USE_DISPLAY
|
|
|
|
#define XDRV_13 13
|
|
|
|
#include <renderer.h>
|
|
#include <FT6236.h>
|
|
|
|
Renderer *renderer;
|
|
|
|
enum ColorType { COLOR_BW, COLOR_COLOR };
|
|
|
|
#ifndef MAXBUTTONS
|
|
#define MAXBUTTONS 16
|
|
#endif
|
|
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
VButton *buttons[MAXBUTTONS];
|
|
#endif
|
|
|
|
|
|
|
|
uint16_t fg_color = 1;
|
|
uint16_t bg_color = 0;
|
|
uint8_t color_type = COLOR_BW;
|
|
uint8_t auto_draw=1;
|
|
|
|
const uint8_t DISPLAY_MAX_DRIVERS = 16;
|
|
const uint8_t DISPLAY_MAX_COLS = 44;
|
|
const uint8_t DISPLAY_MAX_ROWS = 32;
|
|
|
|
const uint8_t DISPLAY_LOG_ROWS = 32;
|
|
|
|
#define D_PRFX_DISPLAY "Display"
|
|
#define D_CMND_DISP_ADDRESS "Address"
|
|
#define D_CMND_DISP_COLS "Cols"
|
|
#define D_CMND_DISP_DIMMER "Dimmer"
|
|
#define D_CMND_DISP_MODE "Mode"
|
|
#define D_CMND_DISP_MODEL "Model"
|
|
#define D_CMND_DISP_REFRESH "Refresh"
|
|
#define D_CMND_DISP_ROWS "Rows"
|
|
#define D_CMND_DISP_SIZE "Size"
|
|
#define D_CMND_DISP_FONT "Font"
|
|
#define D_CMND_DISP_ROTATE "Rotate"
|
|
#define D_CMND_DISP_TEXT "Text"
|
|
#define D_CMND_DISP_WIDTH "Width"
|
|
#define D_CMND_DISP_HEIGHT "Height"
|
|
|
|
enum XdspFunctions { FUNC_DISPLAY_INIT_DRIVER, FUNC_DISPLAY_INIT, FUNC_DISPLAY_EVERY_50_MSECOND, FUNC_DISPLAY_EVERY_SECOND,
|
|
FUNC_DISPLAY_MODEL, FUNC_DISPLAY_MODE, FUNC_DISPLAY_POWER,
|
|
FUNC_DISPLAY_CLEAR, FUNC_DISPLAY_DRAW_FRAME,
|
|
FUNC_DISPLAY_DRAW_HLINE, FUNC_DISPLAY_DRAW_VLINE, FUNC_DISPLAY_DRAW_LINE,
|
|
FUNC_DISPLAY_DRAW_CIRCLE, FUNC_DISPLAY_FILL_CIRCLE,
|
|
FUNC_DISPLAY_DRAW_RECTANGLE, FUNC_DISPLAY_FILL_RECTANGLE,
|
|
FUNC_DISPLAY_TEXT_SIZE, FUNC_DISPLAY_FONT_SIZE, FUNC_DISPLAY_ROTATION, FUNC_DISPLAY_DRAW_STRING, FUNC_DISPLAY_ONOFF };
|
|
|
|
enum DisplayInitModes { DISPLAY_INIT_MODE, DISPLAY_INIT_PARTIAL, DISPLAY_INIT_FULL };
|
|
|
|
const char kDisplayCommands[] PROGMEM = D_PRFX_DISPLAY "|"
|
|
"|" D_CMND_DISP_MODEL "|" D_CMND_DISP_WIDTH "|" D_CMND_DISP_HEIGHT "|" D_CMND_DISP_MODE "|" D_CMND_DISP_REFRESH "|"
|
|
D_CMND_DISP_DIMMER "|" D_CMND_DISP_COLS "|" D_CMND_DISP_ROWS "|" D_CMND_DISP_SIZE "|" D_CMND_DISP_FONT "|"
|
|
D_CMND_DISP_ROTATE "|" D_CMND_DISP_TEXT "|" D_CMND_DISP_ADDRESS ;
|
|
|
|
void (* const DisplayCommand[])(void) PROGMEM = {
|
|
&CmndDisplay, &CmndDisplayModel, &CmndDisplayWidth, &CmndDisplayHeight, &CmndDisplayMode, &CmndDisplayRefresh,
|
|
&CmndDisplayDimmer, &CmndDisplayColumns, &CmndDisplayRows, &CmndDisplaySize, &CmndDisplayFont,
|
|
&CmndDisplayRotate, &CmndDisplayText, &CmndDisplayAddress };
|
|
|
|
char *dsp_str;
|
|
|
|
uint16_t dsp_x;
|
|
uint16_t dsp_y;
|
|
uint16_t dsp_x2;
|
|
uint16_t dsp_y2;
|
|
uint16_t dsp_rad;
|
|
uint16_t dsp_color;
|
|
int16_t dsp_len;
|
|
int16_t disp_xpos = 0;
|
|
int16_t disp_ypos = 0;
|
|
|
|
uint8_t disp_power = 0;
|
|
uint8_t disp_device = 0;
|
|
uint8_t disp_refresh = 1;
|
|
uint8_t disp_autodraw = 1;
|
|
uint8_t dsp_init;
|
|
uint8_t dsp_font;
|
|
uint8_t dsp_flag;
|
|
uint8_t dsp_on;
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
char **disp_log_buffer;
|
|
char **disp_screen_buffer;
|
|
char disp_temp[2];
|
|
char disp_pres[5];
|
|
|
|
uint8_t disp_log_buffer_cols = 0;
|
|
uint8_t disp_log_buffer_idx = 0;
|
|
uint8_t disp_log_buffer_ptr = 0;
|
|
uint8_t disp_screen_buffer_cols = 0;
|
|
uint8_t disp_screen_buffer_rows = 0;
|
|
bool disp_subscribed = false;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void DisplayInit(uint8_t mode)
|
|
{
|
|
if (renderer) {
|
|
renderer->DisplayInit(mode, Settings.display_size, Settings.display_rotate, Settings.display_font);
|
|
}
|
|
else {
|
|
dsp_init = mode;
|
|
XdspCall(FUNC_DISPLAY_INIT);
|
|
}
|
|
}
|
|
|
|
void DisplayClear(void)
|
|
{
|
|
XdspCall(FUNC_DISPLAY_CLEAR);
|
|
}
|
|
|
|
void DisplayDrawHLine(uint16_t x, uint16_t y, int16_t len, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_len = len;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_DRAW_HLINE);
|
|
}
|
|
|
|
void DisplayDrawVLine(uint16_t x, uint16_t y, int16_t len, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_len = len;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_DRAW_VLINE);
|
|
}
|
|
|
|
void DisplayDrawLine(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_x2 = x2;
|
|
dsp_y2 = y2;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_DRAW_LINE);
|
|
}
|
|
|
|
void DisplayDrawCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_rad = rad;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_DRAW_CIRCLE);
|
|
}
|
|
|
|
void DisplayDrawFilledCircle(uint16_t x, uint16_t y, uint16_t rad, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_rad = rad;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_FILL_CIRCLE);
|
|
}
|
|
|
|
void DisplayDrawRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_x2 = x2;
|
|
dsp_y2 = y2;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_DRAW_RECTANGLE);
|
|
}
|
|
|
|
void DisplayDrawFilledRectangle(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2, uint16_t color)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_x2 = x2;
|
|
dsp_y2 = y2;
|
|
dsp_color = color;
|
|
XdspCall(FUNC_DISPLAY_FILL_RECTANGLE);
|
|
}
|
|
|
|
void DisplayDrawFrame(void)
|
|
{
|
|
XdspCall(FUNC_DISPLAY_DRAW_FRAME);
|
|
}
|
|
|
|
void DisplaySetSize(uint8_t size)
|
|
{
|
|
Settings.display_size = size &3;
|
|
XdspCall(FUNC_DISPLAY_TEXT_SIZE);
|
|
}
|
|
|
|
void DisplaySetFont(uint8_t font)
|
|
{
|
|
Settings.display_font = font &3;
|
|
XdspCall(FUNC_DISPLAY_FONT_SIZE);
|
|
}
|
|
|
|
void DisplaySetRotation(uint8_t rotation)
|
|
{
|
|
Settings.display_rotate = rotation &3;
|
|
XdspCall(FUNC_DISPLAY_ROTATION);
|
|
}
|
|
|
|
void DisplayDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag)
|
|
{
|
|
dsp_x = x;
|
|
dsp_y = y;
|
|
dsp_str = str;
|
|
dsp_color = color;
|
|
dsp_flag = flag;
|
|
XdspCall(FUNC_DISPLAY_DRAW_STRING);
|
|
}
|
|
|
|
void DisplayOnOff(uint8_t on)
|
|
{
|
|
dsp_on = on;
|
|
XdspCall(FUNC_DISPLAY_ONOFF);
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t fatoiv(char *cp,float *res) {
|
|
uint8_t index=0;
|
|
*res=CharToFloat(cp);
|
|
while (*cp) {
|
|
if ((*cp>='0' && *cp<='9') || (*cp=='-') || (*cp=='.')) {
|
|
cp++;
|
|
index++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
|
|
uint8_t atoiv(char *cp, int16_t *res)
|
|
{
|
|
uint8_t index = 0;
|
|
*res = atoi(cp);
|
|
while (*cp) {
|
|
if ((*cp>='0' && *cp<='9') || (*cp=='-')) {
|
|
cp++;
|
|
index++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
|
|
uint8_t atoiV(char *cp, uint16_t *res)
|
|
{
|
|
uint8_t index = 0;
|
|
*res = atoi(cp);
|
|
while (*cp) {
|
|
if (*cp>='0' && *cp<='9') {
|
|
cp++;
|
|
index++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
|
|
void alignright(char *string) {
|
|
uint16_t slen=strlen(string);
|
|
uint16_t len=slen;
|
|
while (len) {
|
|
|
|
if (string[len-1]!=' ') {
|
|
break;
|
|
}
|
|
len--;
|
|
}
|
|
uint16_t diff=slen-len;
|
|
if (diff>0) {
|
|
|
|
memmove(&string[diff],string,len);
|
|
memset(string,' ',diff);
|
|
}
|
|
}
|
|
|
|
char *get_string(char *buff,uint8_t len,char *cp) {
|
|
uint8_t index=0;
|
|
while (*cp!=':') {
|
|
buff[index]=*cp++;
|
|
index++;
|
|
if (index>=len) break;
|
|
}
|
|
buff[index]=0;
|
|
cp++;
|
|
return cp;
|
|
}
|
|
|
|
#define ESCAPE_CHAR '~'
|
|
|
|
|
|
uint32_t decode_te(char *line) {
|
|
uint32_t skip = 0;
|
|
char sbuf[3],*cp;
|
|
while (*line) {
|
|
if (*line==ESCAPE_CHAR) {
|
|
cp=line+1;
|
|
if (*cp!=0 && *cp==ESCAPE_CHAR) {
|
|
|
|
memmove(cp,cp+1,strlen(cp));
|
|
skip++;
|
|
} else {
|
|
|
|
if (strlen(cp)<2) {
|
|
|
|
return skip;
|
|
}
|
|
|
|
sbuf[0]=*(cp);
|
|
sbuf[1]=*(cp+1);
|
|
sbuf[2]=0;
|
|
*line=strtol(sbuf,0,16);
|
|
|
|
memmove(cp,cp+2,strlen(cp)-1);
|
|
skip += 2;
|
|
}
|
|
}
|
|
line++;
|
|
}
|
|
return skip;
|
|
}
|
|
|
|
|
|
|
|
#define DISPLAY_BUFFER_COLS 128
|
|
|
|
void DisplayText(void)
|
|
{
|
|
uint8_t lpos;
|
|
uint8_t escape = 0;
|
|
uint8_t var;
|
|
int16_t lin = 0;
|
|
int16_t col = 0;
|
|
int16_t fill = 0;
|
|
int16_t temp;
|
|
int16_t temp1;
|
|
float ftemp;
|
|
|
|
char linebuf[DISPLAY_BUFFER_COLS];
|
|
char *dp = linebuf;
|
|
char *cp = XdrvMailbox.data;
|
|
|
|
memset(linebuf, ' ', sizeof(linebuf));
|
|
linebuf[sizeof(linebuf)-1] = 0;
|
|
*dp = 0;
|
|
|
|
while (*cp) {
|
|
if (!escape) {
|
|
|
|
if (*cp == '[') {
|
|
escape = 1;
|
|
cp++;
|
|
|
|
if ((uint32_t)dp - (uint32_t)linebuf) {
|
|
if (!fill) { *dp = 0; }
|
|
if (col > 0 && lin > 0) {
|
|
|
|
if (!renderer) DisplayDrawStringAt(col, lin, linebuf, fg_color, 1);
|
|
else renderer->DrawStringAt(col, lin, linebuf, fg_color, 1);
|
|
} else {
|
|
|
|
if (!renderer) DisplayDrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0);
|
|
else renderer->DrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0);
|
|
}
|
|
memset(linebuf, ' ', sizeof(linebuf));
|
|
linebuf[sizeof(linebuf)-1] = 0;
|
|
dp = linebuf;
|
|
}
|
|
} else {
|
|
|
|
if (dp < (linebuf + DISPLAY_BUFFER_COLS)) { *dp++ = *cp++; }
|
|
}
|
|
} else {
|
|
|
|
if (*cp == ']') {
|
|
escape = 0;
|
|
cp++;
|
|
} else {
|
|
|
|
switch (*cp++) {
|
|
case 'z':
|
|
|
|
if (!renderer) DisplayClear();
|
|
else renderer->fillScreen(bg_color);
|
|
disp_xpos = 0;
|
|
disp_ypos = 0;
|
|
col = 0;
|
|
lin = 0;
|
|
break;
|
|
case 'i':
|
|
|
|
DisplayInit(DISPLAY_INIT_PARTIAL);
|
|
break;
|
|
case 'I':
|
|
|
|
DisplayInit(DISPLAY_INIT_FULL);
|
|
break;
|
|
case 'o':
|
|
if (!renderer) {
|
|
DisplayOnOff(0);
|
|
} else {
|
|
renderer->DisplayOnff(0);
|
|
}
|
|
break;
|
|
case 'O':
|
|
if (!renderer) {
|
|
DisplayOnOff(1);
|
|
} else {
|
|
renderer->DisplayOnff(1);
|
|
}
|
|
break;
|
|
case 'x':
|
|
|
|
var = atoiv(cp, &disp_xpos);
|
|
cp += var;
|
|
break;
|
|
case 'y':
|
|
|
|
var = atoiv(cp, &disp_ypos);
|
|
cp += var;
|
|
break;
|
|
case 'l':
|
|
|
|
var = atoiv(cp, &lin);
|
|
cp += var;
|
|
|
|
break;
|
|
case 'c':
|
|
|
|
var = atoiv(cp, &col);
|
|
cp += var;
|
|
|
|
break;
|
|
case 'C':
|
|
|
|
if (*cp=='i') {
|
|
|
|
cp++;
|
|
var = atoiv(cp, &temp);
|
|
if (renderer) ftemp=renderer->GetColorFromIndex(temp);
|
|
} else {
|
|
|
|
var = fatoiv(cp,&ftemp);
|
|
}
|
|
fg_color=ftemp;
|
|
cp += var;
|
|
if (renderer) renderer->setTextColor(fg_color,bg_color);
|
|
break;
|
|
case 'B':
|
|
|
|
if (*cp=='i') {
|
|
|
|
cp++;
|
|
var = atoiv(cp, &temp);
|
|
if (renderer) ftemp=renderer->GetColorFromIndex(temp);
|
|
} else {
|
|
var = fatoiv(cp,&ftemp);
|
|
}
|
|
bg_color=ftemp;
|
|
cp += var;
|
|
if (renderer) renderer->setTextColor(fg_color,bg_color);
|
|
break;
|
|
case 'p':
|
|
|
|
var = atoiv(cp, &fill);
|
|
cp += var;
|
|
linebuf[fill] = 0;
|
|
break;
|
|
#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT)
|
|
case 'P':
|
|
{ char *ep=strchr(cp,':');
|
|
if (ep) {
|
|
*ep=0;
|
|
ep++;
|
|
Draw_RGB_Bitmap(cp,disp_xpos,disp_ypos);
|
|
cp=ep;
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
case 'h':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
if (temp < 0) {
|
|
if (renderer) renderer->writeFastHLine(disp_xpos + temp, disp_ypos, -temp, fg_color);
|
|
else DisplayDrawHLine(disp_xpos + temp, disp_ypos, -temp, fg_color);
|
|
} else {
|
|
if (renderer) renderer->writeFastHLine(disp_xpos, disp_ypos, temp, fg_color);
|
|
else DisplayDrawHLine(disp_xpos, disp_ypos, temp, fg_color);
|
|
}
|
|
disp_xpos += temp;
|
|
break;
|
|
case 'v':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
if (temp < 0) {
|
|
if (renderer) renderer->writeFastVLine(disp_xpos, disp_ypos + temp, -temp, fg_color);
|
|
else DisplayDrawVLine(disp_xpos, disp_ypos + temp, -temp, fg_color);
|
|
} else {
|
|
if (renderer) renderer->writeFastVLine(disp_xpos, disp_ypos, temp, fg_color);
|
|
else DisplayDrawVLine(disp_xpos, disp_ypos, temp, fg_color);
|
|
}
|
|
disp_ypos += temp;
|
|
break;
|
|
case 'L':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &temp1);
|
|
cp += var;
|
|
if (renderer) renderer->writeLine(disp_xpos, disp_ypos, temp, temp1, fg_color);
|
|
else DisplayDrawLine(disp_xpos, disp_ypos, temp, temp1, fg_color);
|
|
disp_xpos += temp;
|
|
disp_ypos += temp1;
|
|
break;
|
|
case 'k':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
if (renderer) renderer->drawCircle(disp_xpos, disp_ypos, temp, fg_color);
|
|
else DisplayDrawCircle(disp_xpos, disp_ypos, temp, fg_color);
|
|
break;
|
|
case 'K':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
if (renderer) renderer->fillCircle(disp_xpos, disp_ypos, temp, fg_color);
|
|
else DisplayDrawFilledCircle(disp_xpos, disp_ypos, temp, fg_color);
|
|
break;
|
|
case 'r':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &temp1);
|
|
cp += var;
|
|
if (renderer) renderer->drawRect(disp_xpos, disp_ypos, temp, temp1, fg_color);
|
|
else DisplayDrawRectangle(disp_xpos, disp_ypos, temp, temp1, fg_color);
|
|
break;
|
|
case 'R':
|
|
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &temp1);
|
|
cp += var;
|
|
if (renderer) renderer->fillRect(disp_xpos, disp_ypos, temp, temp1, fg_color);
|
|
else DisplayDrawFilledRectangle(disp_xpos, disp_ypos, temp, temp1, fg_color);
|
|
break;
|
|
case 'u':
|
|
|
|
{ int16_t rad;
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &temp1);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &rad);
|
|
cp += var;
|
|
if (renderer) renderer->drawRoundRect(disp_xpos, disp_ypos, temp, temp1, rad, fg_color);
|
|
|
|
}
|
|
break;
|
|
case 'U':
|
|
|
|
{ int16_t rad;
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &temp1);
|
|
cp += var;
|
|
cp++;
|
|
var = atoiv(cp, &rad);
|
|
cp += var;
|
|
if (renderer) renderer->fillRoundRect(disp_xpos, disp_ypos, temp, temp1, rad, fg_color);
|
|
|
|
}
|
|
break;
|
|
|
|
case 't':
|
|
if (*cp=='S') {
|
|
cp++;
|
|
if (dp < (linebuf + DISPLAY_BUFFER_COLS) -8) {
|
|
snprintf_P(dp, 9, PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
dp += 8;
|
|
}
|
|
} else {
|
|
if (dp < (linebuf + DISPLAY_BUFFER_COLS) -5) {
|
|
snprintf_P(dp, 6, PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute);
|
|
dp += 5;
|
|
}
|
|
}
|
|
break;
|
|
case 'T':
|
|
if (dp < (linebuf + DISPLAY_BUFFER_COLS) -8) {
|
|
snprintf_P(dp, 9, PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year%2000);
|
|
dp += 8;
|
|
}
|
|
break;
|
|
case 'd':
|
|
|
|
if (renderer) renderer->Updateframe();
|
|
else DisplayDrawFrame();
|
|
break;
|
|
case 'D':
|
|
|
|
auto_draw=*cp&3;
|
|
if (renderer) renderer->setDrawMode(auto_draw>>1);
|
|
cp += 1;
|
|
break;
|
|
case 's':
|
|
|
|
if (renderer) renderer->setTextSize(*cp&7);
|
|
else DisplaySetSize(*cp&3);
|
|
cp += 1;
|
|
break;
|
|
case 'f':
|
|
|
|
if (renderer) renderer->setTextFont(*cp&7);
|
|
else DisplaySetFont(*cp&7);
|
|
cp += 1;
|
|
break;
|
|
case 'a':
|
|
|
|
if (renderer) renderer->setRotation(*cp&3);
|
|
else DisplaySetRotation(*cp&3);
|
|
cp+=1;
|
|
break;
|
|
|
|
#ifdef USE_GRAPH
|
|
case 'G':
|
|
|
|
if (*cp=='d') {
|
|
cp++;
|
|
var=atoiv(cp,&temp);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&temp1);
|
|
cp+=var;
|
|
RedrawGraph(temp,temp1);
|
|
break;
|
|
}
|
|
#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT)
|
|
if (*cp=='s') {
|
|
cp++;
|
|
var=atoiv(cp,&temp);
|
|
cp+=var;
|
|
cp++;
|
|
|
|
char bbuff[128];
|
|
cp=get_string(bbuff,sizeof(bbuff),cp);
|
|
Save_graph(temp,bbuff);
|
|
break;
|
|
}
|
|
if (*cp=='r') {
|
|
cp++;
|
|
var=atoiv(cp,&temp);
|
|
cp+=var;
|
|
cp++;
|
|
|
|
char bbuff[128];
|
|
cp=get_string(bbuff,sizeof(bbuff),cp);
|
|
Restore_graph(temp,bbuff);
|
|
break;
|
|
}
|
|
#endif
|
|
{ int16_t num,gxp,gyp,gxs,gys,dec,icol;
|
|
float ymin,ymax;
|
|
var=atoiv(cp,&num);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gxp);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gyp);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gxs);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gys);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&dec);
|
|
cp+=var;
|
|
cp++;
|
|
var=fatoiv(cp,&ymin);
|
|
cp+=var;
|
|
cp++;
|
|
var=fatoiv(cp,&ymax);
|
|
cp+=var;
|
|
if (color_type==COLOR_COLOR) {
|
|
|
|
cp++;
|
|
var=atoiv(cp,&icol);
|
|
cp+=var;
|
|
} else {
|
|
icol=0;
|
|
}
|
|
DefineGraph(num,gxp,gyp,gxs,gys,dec,ymin,ymax,icol);
|
|
}
|
|
break;
|
|
case 'g':
|
|
{ float temp;
|
|
int16_t num;
|
|
var=atoiv(cp,&num);
|
|
cp+=var;
|
|
cp++;
|
|
var=fatoiv(cp,&temp);
|
|
cp+=var;
|
|
AddValue(num,temp);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_AWATCH
|
|
case 'w':
|
|
var = atoiv(cp, &temp);
|
|
cp += var;
|
|
DrawAClock(temp);
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
case 'b':
|
|
{ int16_t num,gxp,gyp,gxs,gys,outline,fill,textcolor,textsize;
|
|
var=atoiv(cp,&num);
|
|
cp+=var;
|
|
cp++;
|
|
uint8_t bflags=num>>8;
|
|
num=num%MAXBUTTONS;
|
|
var=atoiv(cp,&gxp);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gyp);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gxs);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&gys);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&outline);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&fill);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&textcolor);
|
|
cp+=var;
|
|
cp++;
|
|
var=atoiv(cp,&textsize);
|
|
cp+=var;
|
|
cp++;
|
|
|
|
char bbuff[32];
|
|
cp=get_string(bbuff,sizeof(bbuff),cp);
|
|
|
|
if (buttons[num]) {
|
|
delete buttons[num];
|
|
}
|
|
if (renderer) {
|
|
buttons[num]= new VButton();
|
|
if (buttons[num]) {
|
|
buttons[num]->vpower=bflags;
|
|
buttons[num]->initButtonUL(renderer,gxp,gyp,gxs,gys,renderer->GetColorFromIndex(outline),\
|
|
renderer->GetColorFromIndex(fill),renderer->GetColorFromIndex(textcolor),bbuff,textsize);
|
|
if (!bflags) {
|
|
|
|
buttons[num]->xdrawButton(bitRead(power,num));
|
|
} else {
|
|
|
|
buttons[num]->vpower&=0x7f;
|
|
buttons[num]->xdrawButton(buttons[num]->vpower&0x80);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
|
|
Response_P(PSTR("Unknown Escape"));
|
|
goto exit;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exit:
|
|
|
|
dp -= decode_te(linebuf);
|
|
if ((uint32_t)dp - (uint32_t)linebuf) {
|
|
if (!fill) {
|
|
*dp = 0;
|
|
} else {
|
|
linebuf[abs(fill)] = 0;
|
|
}
|
|
if (fill<0) {
|
|
|
|
alignright(linebuf);
|
|
}
|
|
if (col > 0 && lin > 0) {
|
|
|
|
if (!renderer) DisplayDrawStringAt(col, lin, linebuf, fg_color, 1);
|
|
else renderer->DrawStringAt(col, lin, linebuf, fg_color, 1);
|
|
} else {
|
|
|
|
if (!renderer) DisplayDrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0);
|
|
else renderer->DrawStringAt(disp_xpos, disp_ypos, linebuf, fg_color, 0);
|
|
}
|
|
}
|
|
|
|
if (auto_draw&1) {
|
|
if (renderer) renderer->Updateframe();
|
|
else DisplayDrawFrame();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void DisplayClearScreenBuffer(void)
|
|
{
|
|
if (disp_screen_buffer_cols) {
|
|
for (uint32_t i = 0; i < disp_screen_buffer_rows; i++) {
|
|
memset(disp_screen_buffer[i], 0, disp_screen_buffer_cols);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisplayFreeScreenBuffer(void)
|
|
{
|
|
if (disp_screen_buffer != nullptr) {
|
|
for (uint32_t i = 0; i < disp_screen_buffer_rows; i++) {
|
|
if (disp_screen_buffer[i] != nullptr) { free(disp_screen_buffer[i]); }
|
|
}
|
|
free(disp_screen_buffer);
|
|
disp_screen_buffer_cols = 0;
|
|
disp_screen_buffer_rows = 0;
|
|
}
|
|
}
|
|
|
|
void DisplayAllocScreenBuffer(void)
|
|
{
|
|
if (!disp_screen_buffer_cols) {
|
|
disp_screen_buffer_rows = Settings.display_rows;
|
|
disp_screen_buffer = (char**)malloc(sizeof(*disp_screen_buffer) * disp_screen_buffer_rows);
|
|
if (disp_screen_buffer != nullptr) {
|
|
for (uint32_t i = 0; i < disp_screen_buffer_rows; i++) {
|
|
disp_screen_buffer[i] = (char*)malloc(sizeof(*disp_screen_buffer[i]) * (Settings.display_cols[0] +1));
|
|
if (disp_screen_buffer[i] == nullptr) {
|
|
DisplayFreeScreenBuffer();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (disp_screen_buffer != nullptr) {
|
|
disp_screen_buffer_cols = Settings.display_cols[0] +1;
|
|
DisplayClearScreenBuffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisplayReAllocScreenBuffer(void)
|
|
{
|
|
DisplayFreeScreenBuffer();
|
|
DisplayAllocScreenBuffer();
|
|
}
|
|
|
|
void DisplayFillScreen(uint32_t line)
|
|
{
|
|
uint32_t len = disp_screen_buffer_cols - strlen(disp_screen_buffer[line]);
|
|
if (len) {
|
|
memset(disp_screen_buffer[line] + strlen(disp_screen_buffer[line]), 0x20, len);
|
|
disp_screen_buffer[line][disp_screen_buffer_cols -1] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DisplayClearLogBuffer(void)
|
|
{
|
|
if (disp_log_buffer_cols) {
|
|
for (uint32_t i = 0; i < DISPLAY_LOG_ROWS; i++) {
|
|
memset(disp_log_buffer[i], 0, disp_log_buffer_cols);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisplayFreeLogBuffer(void)
|
|
{
|
|
if (disp_log_buffer != nullptr) {
|
|
for (uint32_t i = 0; i < DISPLAY_LOG_ROWS; i++) {
|
|
if (disp_log_buffer[i] != nullptr) { free(disp_log_buffer[i]); }
|
|
}
|
|
free(disp_log_buffer);
|
|
disp_log_buffer_cols = 0;
|
|
}
|
|
}
|
|
|
|
void DisplayAllocLogBuffer(void)
|
|
{
|
|
if (!disp_log_buffer_cols) {
|
|
disp_log_buffer = (char**)malloc(sizeof(*disp_log_buffer) * DISPLAY_LOG_ROWS);
|
|
if (disp_log_buffer != nullptr) {
|
|
for (uint32_t i = 0; i < DISPLAY_LOG_ROWS; i++) {
|
|
disp_log_buffer[i] = (char*)malloc(sizeof(*disp_log_buffer[i]) * (Settings.display_cols[0] +1));
|
|
if (disp_log_buffer[i] == nullptr) {
|
|
DisplayFreeLogBuffer();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (disp_log_buffer != nullptr) {
|
|
disp_log_buffer_cols = Settings.display_cols[0] +1;
|
|
DisplayClearLogBuffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisplayReAllocLogBuffer(void)
|
|
{
|
|
DisplayFreeLogBuffer();
|
|
DisplayAllocLogBuffer();
|
|
}
|
|
|
|
void DisplayLogBufferAdd(char* txt)
|
|
{
|
|
if (disp_log_buffer_cols) {
|
|
strlcpy(disp_log_buffer[disp_log_buffer_idx], txt, disp_log_buffer_cols);
|
|
disp_log_buffer_idx++;
|
|
if (DISPLAY_LOG_ROWS == disp_log_buffer_idx) { disp_log_buffer_idx = 0; }
|
|
}
|
|
}
|
|
|
|
char* DisplayLogBuffer(char temp_code)
|
|
{
|
|
char* result = nullptr;
|
|
if (disp_log_buffer_cols) {
|
|
if (disp_log_buffer_idx != disp_log_buffer_ptr) {
|
|
result = disp_log_buffer[disp_log_buffer_ptr];
|
|
disp_log_buffer_ptr++;
|
|
if (DISPLAY_LOG_ROWS == disp_log_buffer_ptr) { disp_log_buffer_ptr = 0; }
|
|
|
|
char *pch = strchr(result, '~');
|
|
if (pch != nullptr) { result[pch - result] = temp_code; }
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void DisplayLogBufferInit(void)
|
|
{
|
|
if (Settings.display_mode) {
|
|
disp_log_buffer_idx = 0;
|
|
disp_log_buffer_ptr = 0;
|
|
disp_refresh = Settings.display_refresh;
|
|
|
|
snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%c"), TempUnit());
|
|
snprintf_P(disp_pres, sizeof(disp_pres), PressureUnit().c_str());
|
|
|
|
DisplayReAllocLogBuffer();
|
|
|
|
char buffer[40];
|
|
snprintf_P(buffer, sizeof(buffer), PSTR(D_VERSION " %s%s"), my_version, my_image);
|
|
DisplayLogBufferAdd(buffer);
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("Display mode %d"), Settings.display_mode);
|
|
DisplayLogBufferAdd(buffer);
|
|
|
|
snprintf_P(buffer, sizeof(buffer), PSTR(D_CMND_HOSTNAME " %s"), my_hostname);
|
|
DisplayLogBufferAdd(buffer);
|
|
snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_SSID " %s"), SettingsText(SET_STASSID1 + Settings.sta_active));
|
|
DisplayLogBufferAdd(buffer);
|
|
snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_MAC " %s"), WiFi.macAddress().c_str());
|
|
DisplayLogBufferAdd(buffer);
|
|
if (!global_state.wifi_down) {
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("IP %s"), WiFi.localIP().toString().c_str());
|
|
DisplayLogBufferAdd(buffer);
|
|
snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_RSSI " %d%%"), WifiGetRssiAsQuality(WiFi.RSSI()));
|
|
DisplayLogBufferAdd(buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum SensorQuantity {
|
|
JSON_TEMPERATURE,
|
|
JSON_HUMIDITY, JSON_LIGHT, JSON_NOISE, JSON_AIRQUALITY,
|
|
JSON_PRESSURE, JSON_PRESSUREATSEALEVEL,
|
|
JSON_ILLUMINANCE,
|
|
JSON_GAS,
|
|
JSON_YESTERDAY, JSON_TOTAL, JSON_TODAY,
|
|
JSON_PERIOD,
|
|
JSON_POWERFACTOR, JSON_COUNTER, JSON_ANALOG_INPUT, JSON_UV_LEVEL,
|
|
JSON_CURRENT,
|
|
JSON_VOLTAGE,
|
|
JSON_POWERUSAGE,
|
|
JSON_CO2,
|
|
JSON_FREQUENCY };
|
|
const char kSensorQuantity[] PROGMEM =
|
|
D_JSON_TEMPERATURE "|"
|
|
D_JSON_HUMIDITY "|" D_JSON_LIGHT "|" D_JSON_NOISE "|" D_JSON_AIRQUALITY "|"
|
|
D_JSON_PRESSURE "|" D_JSON_PRESSUREATSEALEVEL "|"
|
|
D_JSON_ILLUMINANCE "|"
|
|
D_JSON_GAS "|"
|
|
D_JSON_YESTERDAY "|" D_JSON_TOTAL "|" D_JSON_TODAY "|"
|
|
D_JSON_PERIOD "|"
|
|
D_JSON_POWERFACTOR "|" D_JSON_COUNTER "|" D_JSON_ANALOG_INPUT "|" D_JSON_UV_LEVEL "|"
|
|
D_JSON_CURRENT "|"
|
|
D_JSON_VOLTAGE "|"
|
|
D_JSON_POWERUSAGE "|"
|
|
D_JSON_CO2 "|"
|
|
D_JSON_FREQUENCY ;
|
|
|
|
void DisplayJsonValue(const char* topic, const char* device, const char* mkey, const char* value)
|
|
{
|
|
char quantity[TOPSZ];
|
|
char buffer[Settings.display_cols[0] +1];
|
|
char spaces[Settings.display_cols[0]];
|
|
char source[Settings.display_cols[0] - Settings.display_cols[1]];
|
|
char svalue[Settings.display_cols[1] +1];
|
|
|
|
#ifdef USE_DEBUG_DRIVER
|
|
ShowFreeMem(PSTR("DisplayJsonValue"));
|
|
#endif
|
|
|
|
memset(spaces, 0x20, sizeof(spaces));
|
|
spaces[sizeof(spaces) -1] = '\0';
|
|
snprintf_P(source, sizeof(source), PSTR("%s%s%s%s"), topic, (strlen(topic))?"/":"", mkey, spaces);
|
|
|
|
int quantity_code = GetCommandCode(quantity, sizeof(quantity), mkey, kSensorQuantity);
|
|
if ((-1 == quantity_code) || !strcmp_P(mkey, S_RSLT_POWER)) {
|
|
return;
|
|
}
|
|
if (JSON_TEMPERATURE == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s~%s"), value, disp_temp);
|
|
}
|
|
else if ((quantity_code >= JSON_HUMIDITY) && (quantity_code <= JSON_AIRQUALITY)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s%%"), value);
|
|
}
|
|
else if ((quantity_code >= JSON_PRESSURE) && (quantity_code <= JSON_PRESSUREATSEALEVEL)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s%s"), value, disp_pres);
|
|
}
|
|
else if (JSON_ILLUMINANCE == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_LUX), value);
|
|
}
|
|
else if (JSON_GAS == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_KILOOHM), value);
|
|
}
|
|
else if ((quantity_code >= JSON_YESTERDAY) && (quantity_code <= JSON_TODAY)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_KILOWATTHOUR), value);
|
|
}
|
|
else if (JSON_PERIOD == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_WATTHOUR), value);
|
|
}
|
|
else if ((quantity_code >= JSON_POWERFACTOR) && (quantity_code <= JSON_UV_LEVEL)) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s"), value);
|
|
}
|
|
else if (JSON_CURRENT == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_AMPERE), value);
|
|
}
|
|
else if (JSON_VOLTAGE == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_VOLT), value);
|
|
}
|
|
else if (JSON_POWERUSAGE == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_WATT), value);
|
|
}
|
|
else if (JSON_CO2 == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_PARTS_PER_MILLION), value);
|
|
}
|
|
else if (JSON_FREQUENCY == quantity_code) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s" D_UNIT_HERTZ), value);
|
|
}
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%s %s"), source, svalue);
|
|
|
|
|
|
|
|
DisplayLogBufferAdd(buffer);
|
|
}
|
|
|
|
void DisplayAnalyzeJson(char *topic, char *json)
|
|
{
|
|
# 1145 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_13_display.ino"
|
|
String jsonStr = json;
|
|
|
|
StaticJsonBuffer<1024> jsonBuf;
|
|
JsonObject &root = jsonBuf.parseObject(jsonStr);
|
|
if (root.success()) {
|
|
|
|
const char *unit;
|
|
unit = root[D_JSON_TEMPERATURE_UNIT];
|
|
if (unit) {
|
|
snprintf_P(disp_temp, sizeof(disp_temp), PSTR("%s"), unit);
|
|
}
|
|
unit = root[D_JSON_PRESSURE_UNIT];
|
|
if (unit) {
|
|
snprintf_P(disp_pres, sizeof(disp_pres), PSTR("%s"), unit);
|
|
}
|
|
|
|
for (JsonObject::iterator it = root.begin(); it != root.end(); ++it) {
|
|
JsonVariant value = it->value;
|
|
if (value.is<JsonObject>()) {
|
|
JsonObject& Object2 = value;
|
|
for (JsonObject::iterator it2 = Object2.begin(); it2 != Object2.end(); ++it2) {
|
|
JsonVariant value2 = it2->value;
|
|
if (value2.is<JsonObject>()) {
|
|
JsonObject& Object3 = value2;
|
|
for (JsonObject::iterator it3 = Object3.begin(); it3 != Object3.end(); ++it3) {
|
|
const char* value = it3->value;
|
|
if (value != nullptr) {
|
|
DisplayJsonValue(topic, it->key, it3->key, value);
|
|
}
|
|
}
|
|
} else {
|
|
const char* value = it2->value;
|
|
if (value != nullptr) {
|
|
DisplayJsonValue(topic, it->key, it2->key, value);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const char* value = it->value;
|
|
if (value != nullptr) {
|
|
DisplayJsonValue(topic, it->key, it->key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisplayMqttSubscribe(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Settings.display_model && (Settings.display_mode &0x04)) {
|
|
|
|
char stopic[TOPSZ];
|
|
char ntopic[TOPSZ];
|
|
|
|
ntopic[0] = '\0';
|
|
strlcpy(stopic, SettingsText(SET_MQTT_FULLTOPIC), sizeof(stopic));
|
|
char *tp = strtok(stopic, "/");
|
|
while (tp != nullptr) {
|
|
if (!strcmp_P(tp, MQTT_TOKEN_PREFIX)) {
|
|
break;
|
|
}
|
|
strncat_P(ntopic, PSTR("+/"), sizeof(ntopic) - strlen(ntopic) -1);
|
|
tp = strtok(nullptr, "/");
|
|
}
|
|
strncat(ntopic, SettingsText(SET_MQTTPREFIX3), sizeof(ntopic) - strlen(ntopic) -1);
|
|
strncat_P(ntopic, PSTR("/#"), sizeof(ntopic) - strlen(ntopic) -1);
|
|
MqttSubscribe(ntopic);
|
|
disp_subscribed = true;
|
|
} else {
|
|
disp_subscribed = false;
|
|
}
|
|
}
|
|
|
|
bool DisplayMqttData(void)
|
|
{
|
|
if (disp_subscribed) {
|
|
char stopic[TOPSZ];
|
|
|
|
snprintf_P(stopic, sizeof(stopic) , PSTR("%s/"), SettingsText(SET_MQTTPREFIX3));
|
|
char *tp = strstr(XdrvMailbox.topic, stopic);
|
|
if (tp) {
|
|
if (Settings.display_mode &0x04) {
|
|
tp = tp + strlen(stopic);
|
|
char *topic = strtok(tp, "/");
|
|
DisplayAnalyzeJson(topic, XdrvMailbox.data);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DisplayLocalSensor(void)
|
|
{
|
|
if ((Settings.display_mode &0x02) && (0 == tele_period)) {
|
|
char no_topic[1] = { 0 };
|
|
|
|
DisplayAnalyzeJson(no_topic, mqtt_data);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void DisplayInitDriver(void)
|
|
{
|
|
XdspCall(FUNC_DISPLAY_INIT_DRIVER);
|
|
|
|
if (renderer) {
|
|
renderer->setTextFont(Settings.display_font);
|
|
renderer->setTextSize(Settings.display_size);
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Settings.display_model) {
|
|
devices_present++;
|
|
disp_device = devices_present;
|
|
|
|
#ifndef USE_DISPLAY_MODES1TO5
|
|
Settings.display_mode = 0;
|
|
#else
|
|
DisplayLogBufferInit();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void DisplaySetPower(void)
|
|
{
|
|
disp_power = bitRead(XdrvMailbox.index, disp_device -1);
|
|
|
|
|
|
|
|
if (Settings.display_model) {
|
|
if (!renderer) {
|
|
XdspCall(FUNC_DISPLAY_POWER);
|
|
} else {
|
|
renderer->DisplayOnff(disp_power);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndDisplay(void)
|
|
{
|
|
Response_P(PSTR("{\"" D_PRFX_DISPLAY "\":{\"" D_CMND_DISP_MODEL "\":%d,\"" D_CMND_DISP_WIDTH "\":%d,\"" D_CMND_DISP_HEIGHT "\":%d,\""
|
|
D_CMND_DISP_MODE "\":%d,\"" D_CMND_DISP_DIMMER "\":%d,\"" D_CMND_DISP_SIZE "\":%d,\"" D_CMND_DISP_FONT "\":%d,\""
|
|
D_CMND_DISP_ROTATE "\":%d,\"" D_CMND_DISP_REFRESH "\":%d,\"" D_CMND_DISP_COLS "\":[%d,%d],\"" D_CMND_DISP_ROWS "\":%d}}"),
|
|
Settings.display_model, Settings.display_width, Settings.display_height,
|
|
Settings.display_mode, Settings.display_dimmer, Settings.display_size, Settings.display_font,
|
|
Settings.display_rotate, Settings.display_refresh, Settings.display_cols[0], Settings.display_cols[1], Settings.display_rows);
|
|
}
|
|
|
|
void CmndDisplayModel(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < DISPLAY_MAX_DRIVERS)) {
|
|
uint32_t last_display_model = Settings.display_model;
|
|
Settings.display_model = XdrvMailbox.payload;
|
|
if (XdspCall(FUNC_DISPLAY_MODEL)) {
|
|
restart_flag = 2;
|
|
} else {
|
|
Settings.display_model = last_display_model;
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.display_model);
|
|
}
|
|
|
|
void CmndDisplayWidth(void)
|
|
{
|
|
if (XdrvMailbox.payload > 0) {
|
|
if (XdrvMailbox.payload != Settings.display_width) {
|
|
Settings.display_width = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.display_width);
|
|
}
|
|
|
|
void CmndDisplayHeight(void)
|
|
{
|
|
if (XdrvMailbox.payload > 0) {
|
|
if (XdrvMailbox.payload != Settings.display_height) {
|
|
Settings.display_height = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.display_height);
|
|
}
|
|
|
|
void CmndDisplayMode(void)
|
|
{
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) {
|
|
uint32_t last_display_mode = Settings.display_mode;
|
|
Settings.display_mode = XdrvMailbox.payload;
|
|
|
|
if (disp_subscribed != (Settings.display_mode &0x04)) {
|
|
restart_flag = 2;
|
|
} else {
|
|
if (last_display_mode && !Settings.display_mode) {
|
|
DisplayInit(DISPLAY_INIT_MODE);
|
|
if (renderer) renderer->fillScreen(bg_color);
|
|
else DisplayClear();
|
|
} else {
|
|
DisplayLogBufferInit();
|
|
DisplayInit(DISPLAY_INIT_MODE);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
ResponseCmndNumber(Settings.display_mode);
|
|
}
|
|
|
|
void CmndDisplayDimmer(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
Settings.display_dimmer = ((XdrvMailbox.payload +1) * 100) / 666;
|
|
if (Settings.display_dimmer && !(disp_power)) {
|
|
ExecuteCommandPower(disp_device, POWER_ON, SRC_DISPLAY);
|
|
}
|
|
else if (!Settings.display_dimmer && disp_power) {
|
|
ExecuteCommandPower(disp_device, POWER_OFF, SRC_DISPLAY);
|
|
}
|
|
if (renderer) renderer->dim(Settings.display_dimmer);
|
|
}
|
|
ResponseCmndNumber(Settings.display_dimmer);
|
|
}
|
|
|
|
void CmndDisplaySize(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 4)) {
|
|
Settings.display_size = XdrvMailbox.payload;
|
|
if (renderer) renderer->setTextSize(Settings.display_size);
|
|
else DisplaySetSize(Settings.display_size);
|
|
}
|
|
ResponseCmndNumber(Settings.display_size);
|
|
}
|
|
|
|
void CmndDisplayFont(void)
|
|
{
|
|
if ((XdrvMailbox.payload >=0) && (XdrvMailbox.payload <= 4)) {
|
|
Settings.display_font = XdrvMailbox.payload;
|
|
if (renderer) renderer->setTextFont(Settings.display_font);
|
|
else DisplaySetFont(Settings.display_font);
|
|
}
|
|
ResponseCmndNumber(Settings.display_font);
|
|
}
|
|
|
|
void CmndDisplayRotate(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 4)) {
|
|
if (Settings.display_rotate != XdrvMailbox.payload) {
|
|
# 1428 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_13_display.ino"
|
|
Settings.display_rotate = XdrvMailbox.payload;
|
|
DisplayInit(DISPLAY_INIT_MODE);
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
DisplayLogBufferInit();
|
|
#endif
|
|
}
|
|
}
|
|
ResponseCmndNumber(Settings.display_rotate);
|
|
}
|
|
|
|
void CmndDisplayText(void)
|
|
{
|
|
if (disp_device && XdrvMailbox.data_len > 0) {
|
|
#ifndef USE_DISPLAY_MODES1TO5
|
|
DisplayText();
|
|
#else
|
|
if (!Settings.display_mode) {
|
|
DisplayText();
|
|
} else {
|
|
DisplayLogBufferAdd(XdrvMailbox.data);
|
|
}
|
|
#endif
|
|
ResponseCmndChar(XdrvMailbox.data);
|
|
}
|
|
}
|
|
|
|
void CmndDisplayAddress(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
|
|
Settings.display_address[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.display_address[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndDisplayRefresh(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 7)) {
|
|
Settings.display_refresh = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.display_refresh);
|
|
}
|
|
|
|
void CmndDisplayColumns(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_COLS)) {
|
|
Settings.display_cols[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
if (1 == XdrvMailbox.index) {
|
|
DisplayLogBufferInit();
|
|
DisplayReAllocScreenBuffer();
|
|
}
|
|
#endif
|
|
}
|
|
ResponseCmndIdxNumber(Settings.display_cols[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndDisplayRows(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_ROWS)) {
|
|
Settings.display_rows = XdrvMailbox.payload;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
DisplayLogBufferInit();
|
|
DisplayReAllocScreenBuffer();
|
|
#endif
|
|
}
|
|
ResponseCmndNumber(Settings.display_rows);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT)
|
|
void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp) {
|
|
if (!renderer) return;
|
|
|
|
|
|
File fp;
|
|
fp=SD.open(file,FILE_READ);
|
|
if (!fp) return;
|
|
uint16_t xsize;
|
|
fp.read((uint8_t*)&xsize,2);
|
|
uint16_t ysize;
|
|
fp.read((uint8_t*)&ysize,2);
|
|
|
|
#if 1
|
|
#define XBUFF 128
|
|
uint16_t xdiv=xsize/XBUFF;
|
|
renderer->setAddrWindow(xp,yp,xp+xsize,yp+ysize);
|
|
for(int16_t j=0; j<ysize; j++) {
|
|
for(int16_t i=0; i<xsize; i+=XBUFF) {
|
|
uint16_t rgb[XBUFF];
|
|
uint16_t len=fp.read((uint8_t*)rgb,XBUFF*2);
|
|
if (len>=2) renderer->pushColors(rgb,len/2,true);
|
|
}
|
|
OsWatchLoop();
|
|
}
|
|
renderer->setAddrWindow(0,0,0,0);
|
|
#else
|
|
for(int16_t j=0; j<ysize; j++) {
|
|
for(int16_t i=0; i<xsize; i++ ) {
|
|
uint16_t rgb;
|
|
uint8_t res=fp.read((uint8_t*)&rgb,2);
|
|
if (!res) break;
|
|
renderer->writePixel(xp+i,yp,rgb);
|
|
}
|
|
delay(0);
|
|
OsWatchLoop();
|
|
yp++;
|
|
}
|
|
#endif
|
|
fp.close();
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_AWATCH
|
|
#define MINUTE_REDUCT 4
|
|
|
|
#ifndef pi
|
|
#define pi 3.14159265359
|
|
#endif
|
|
|
|
|
|
void DrawAClock(uint16_t rad) {
|
|
if (!renderer) return;
|
|
float frad=rad;
|
|
uint16_t hred=frad/3.0;
|
|
renderer->fillCircle(disp_xpos, disp_ypos, rad, bg_color);
|
|
renderer->drawCircle(disp_xpos, disp_ypos, rad, fg_color);
|
|
renderer->fillCircle(disp_xpos, disp_ypos, 4, fg_color);
|
|
for (uint8_t count=0; count<60; count+=5) {
|
|
float p1=((float)count*(pi/30)-(pi/2));
|
|
uint8_t len;
|
|
if ((count%15)==0) {
|
|
len=4;
|
|
} else {
|
|
len=2;
|
|
}
|
|
renderer->writeLine(disp_xpos+((float)(rad-len)*cosf(p1)), disp_ypos+((float)(rad-len)*sinf(p1)), disp_xpos+(frad*cosf(p1)), disp_ypos+(frad*sinf(p1)), fg_color);
|
|
}
|
|
|
|
|
|
float hour=((float)RtcTime.hour*60.0+(float)RtcTime.minute)/60.0;
|
|
float temp=(hour*(pi/6.0)-(pi/2.0));
|
|
renderer->writeLine(disp_xpos, disp_ypos,disp_xpos+(frad-hred)*cosf(temp),disp_ypos+(frad-hred)*sinf(temp), fg_color);
|
|
|
|
|
|
temp=((float)RtcTime.minute*(pi/30.0)-(pi/2.0));
|
|
renderer->writeLine(disp_xpos, disp_ypos,disp_xpos+(frad-MINUTE_REDUCT)*cosf(temp),disp_ypos+(frad-MINUTE_REDUCT)*sinf(temp), fg_color);
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef USE_GRAPH
|
|
|
|
typedef union {
|
|
uint8_t data;
|
|
struct {
|
|
uint8_t overlay : 1;
|
|
uint8_t draw : 1;
|
|
uint8_t nu3 : 1;
|
|
uint8_t nu4 : 1;
|
|
uint8_t nu5 : 1;
|
|
uint8_t nu6 : 1;
|
|
uint8_t nu7 : 1;
|
|
uint8_t nu8 : 1;
|
|
};
|
|
} GFLAGS;
|
|
|
|
struct GRAPH {
|
|
uint16_t xp;
|
|
uint16_t yp;
|
|
uint16_t xs;
|
|
uint16_t ys;
|
|
float ymin;
|
|
float ymax;
|
|
float range;
|
|
uint32_t x_time;
|
|
uint32_t last_ms;
|
|
uint32_t last_ms_redrawn;
|
|
int16_t decimation;
|
|
uint16_t dcnt;
|
|
uint32_t summ;
|
|
uint16_t xcnt;
|
|
uint8_t *values;
|
|
uint8_t xticks;
|
|
uint8_t yticks;
|
|
uint8_t last_val;
|
|
uint8_t color_index;
|
|
GFLAGS flags;
|
|
};
|
|
|
|
|
|
struct GRAPH *graph[NUM_GRAPHS];
|
|
|
|
#define TICKLEN 4
|
|
void ClrGraph(uint16_t num) {
|
|
struct GRAPH *gp=graph[num];
|
|
|
|
uint16_t xticks=gp->xticks;
|
|
uint16_t yticks=gp->yticks;
|
|
uint16_t count;
|
|
|
|
|
|
if (gp->flags.overlay) return;
|
|
|
|
renderer->fillRect(gp->xp+1,gp->yp+1,gp->xs-2,gp->ys-2,bg_color);
|
|
|
|
if (xticks) {
|
|
float cxp=gp->xp,xd=(float)gp->xs/(float)xticks;
|
|
for (count=0; count<xticks; count++) {
|
|
renderer->writeFastVLine(cxp,gp->yp+gp->ys-TICKLEN,TICKLEN,fg_color);
|
|
cxp+=xd;
|
|
}
|
|
}
|
|
if (yticks) {
|
|
if (gp->ymin<0 && gp->ymax>0) {
|
|
|
|
float cxp=0;
|
|
float czp=gp->yp+(gp->ymax/gp->range);
|
|
while (cxp<gp->xs) {
|
|
renderer->writeFastHLine(gp->xp+cxp,czp,2,fg_color);
|
|
cxp+=6.0;
|
|
}
|
|
|
|
float cyp=0,yd=gp->ys/yticks;
|
|
for (count=0; count<yticks; count++) {
|
|
if ((czp-cyp)>gp->yp) {
|
|
renderer->writeFastHLine(gp->xp,czp-cyp,TICKLEN,fg_color);
|
|
renderer->writeFastHLine(gp->xp+gp->xs-TICKLEN,czp-cyp,TICKLEN,fg_color);
|
|
}
|
|
if ((czp+cyp)<(gp->yp+gp->ys)) {
|
|
renderer->writeFastHLine(gp->xp,czp+cyp,TICKLEN,fg_color);
|
|
renderer->writeFastHLine(gp->xp+gp->xs-TICKLEN,czp+cyp,TICKLEN,fg_color);
|
|
}
|
|
cyp+=yd;
|
|
}
|
|
} else {
|
|
float cyp=gp->yp,yd=gp->ys/yticks;
|
|
for (count=0; count<yticks; count++) {
|
|
renderer->writeFastHLine(gp->xp,cyp,TICKLEN,fg_color);
|
|
renderer->writeFastHLine(gp->xp+gp->xs-TICKLEN,cyp,TICKLEN,fg_color);
|
|
cyp+=yd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DefineGraph(uint16_t num,uint16_t xp,uint16_t yp,int16_t xs,uint16_t ys,int16_t dec,float ymin, float ymax,uint8_t icol) {
|
|
if (!renderer) return;
|
|
uint8_t rflg=0;
|
|
if (xs<0) {
|
|
rflg=1;
|
|
xs=abs(xs);
|
|
}
|
|
struct GRAPH *gp;
|
|
uint16_t count;
|
|
uint16_t index=num%NUM_GRAPHS;
|
|
if (!graph[index]) {
|
|
gp=(struct GRAPH*)calloc(sizeof(struct GRAPH),1);
|
|
if (!gp) return;
|
|
graph[index]=gp;
|
|
} else {
|
|
gp=graph[index];
|
|
if (rflg) {
|
|
RedrawGraph(index,1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
gp->xticks=(num>>4)&0x3f;
|
|
gp->yticks=(num>>10)&0x3f;
|
|
gp->xp=xp;
|
|
gp->yp=yp;
|
|
gp->xs=xs;
|
|
gp->ys=ys;
|
|
if (!dec) dec=1;
|
|
gp->decimation=dec;
|
|
if (dec>0) {
|
|
|
|
gp->x_time=((float)dec*60000.0)/(float)xs;
|
|
gp->last_ms=millis()+gp->x_time;
|
|
}
|
|
gp->ymin=ymin;
|
|
gp->ymax=ymax;
|
|
gp->range=(ymax-ymin)/ys;
|
|
gp->xcnt=0;
|
|
gp->dcnt=0;
|
|
gp->summ=0;
|
|
if (gp->values) free(gp->values);
|
|
gp->values=(uint8_t*) calloc(1,xs+2);
|
|
if (!gp->values) {
|
|
free(gp);
|
|
graph[index]=0;
|
|
return;
|
|
}
|
|
|
|
gp->values[0]=0;
|
|
|
|
gp->last_ms_redrawn=millis();
|
|
|
|
if (!icol) icol=1;
|
|
gp->color_index=icol;
|
|
gp->flags.overlay=0;
|
|
gp->flags.draw=1;
|
|
|
|
|
|
if (index>0) {
|
|
for (uint8_t count=0; count<index; count++) {
|
|
if (graph[count]) {
|
|
|
|
|
|
struct GRAPH *gp1=graph[count];
|
|
if ((gp->xp==gp1->xp) && (gp->yp==gp1->yp)) {
|
|
gp->flags.overlay=1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
renderer->drawRect(xp,yp,xs,ys,fg_color);
|
|
|
|
ClrGraph(index);
|
|
|
|
}
|
|
|
|
|
|
void DisplayCheckGraph() {
|
|
int16_t count;
|
|
struct GRAPH *gp;
|
|
for (count=0;count<NUM_GRAPHS;count++) {
|
|
gp=graph[count];
|
|
if (gp) {
|
|
if (gp->decimation>0) {
|
|
|
|
while (millis()>gp->last_ms) {
|
|
gp->last_ms+=gp->x_time;
|
|
uint8_t val;
|
|
if (gp->dcnt) {
|
|
val=gp->summ/gp->dcnt;
|
|
gp->dcnt=0;
|
|
gp->summ=0;
|
|
gp->last_val=val;
|
|
} else {
|
|
val=gp->last_val;
|
|
}
|
|
AddGraph(count,val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT)
|
|
#include <SD.h>
|
|
|
|
void Save_graph(uint8_t num, char *path) {
|
|
if (!renderer) return;
|
|
uint16_t index=num%NUM_GRAPHS;
|
|
struct GRAPH *gp=graph[index];
|
|
if (!gp) return;
|
|
File fp;
|
|
SD.remove(path);
|
|
fp=SD.open(path,FILE_WRITE);
|
|
if (!fp) return;
|
|
char str[32];
|
|
sprintf_P(str,PSTR("%d\t%d\t%d\t"),gp->xcnt,gp->xs,gp->ys);
|
|
fp.print(str);
|
|
dtostrfd(gp->ymin,2,str);
|
|
fp.print(str);
|
|
fp.print("\t");
|
|
dtostrfd(gp->ymax,2,str);
|
|
fp.print(str);
|
|
fp.print("\t");
|
|
for (uint32_t count=0;count<gp->xs;count++) {
|
|
dtostrfd(gp->values[count],0,str);
|
|
fp.print(str);
|
|
fp.print("\t");
|
|
}
|
|
fp.print("\n");
|
|
fp.close();
|
|
}
|
|
void Restore_graph(uint8_t num, char *path) {
|
|
if (!renderer) return;
|
|
uint16_t index=num%NUM_GRAPHS;
|
|
struct GRAPH *gp=graph[index];
|
|
if (!gp) return;
|
|
File fp;
|
|
fp=SD.open(path,FILE_READ);
|
|
if (!fp) return;
|
|
char vbuff[32];
|
|
char *cp=vbuff;
|
|
uint8_t buf[2];
|
|
uint8_t findex=0;
|
|
|
|
for (uint32_t count=0;count<=gp->xs+4;count++) {
|
|
cp=vbuff;
|
|
findex=0;
|
|
while (fp.available()) {
|
|
fp.read(buf,1);
|
|
if (buf[0]=='\t' || buf[0]==',' || buf[0]=='\n' || buf[0]=='\r') {
|
|
break;
|
|
} else {
|
|
*cp++=buf[0];
|
|
findex++;
|
|
if (findex>=sizeof(vbuff)-1) break;
|
|
}
|
|
}
|
|
*cp=0;
|
|
if (count<=4) {
|
|
if (count==0) gp->xcnt=atoi(vbuff);
|
|
} else {
|
|
gp->values[count-5]=atoi(vbuff);
|
|
}
|
|
}
|
|
fp.close();
|
|
RedrawGraph(num,1);
|
|
}
|
|
#endif
|
|
|
|
void RedrawGraph(uint8_t num, uint8_t flags) {
|
|
uint16_t index=num%NUM_GRAPHS;
|
|
struct GRAPH *gp=graph[index];
|
|
if (!gp) return;
|
|
if (!flags) {
|
|
gp->flags.draw=0;
|
|
return;
|
|
}
|
|
if (!renderer) return;
|
|
|
|
gp->flags.draw=1;
|
|
uint16_t linecol=fg_color;
|
|
|
|
if (color_type==COLOR_COLOR) {
|
|
linecol=renderer->GetColorFromIndex(gp->color_index);
|
|
}
|
|
|
|
if (!gp->flags.overlay) {
|
|
|
|
renderer->drawRect(gp->xp,gp->yp,gp->xs,gp->ys,fg_color);
|
|
|
|
ClrGraph(index);
|
|
}
|
|
|
|
for (uint16_t count=0;count<gp->xs-1;count++) {
|
|
renderer->writeLine(gp->xp+count,gp->yp+gp->ys-gp->values[count]-1,gp->xp+count+1,gp->yp+gp->ys-gp->values[count+1]-1,linecol);
|
|
}
|
|
}
|
|
|
|
|
|
void AddGraph(uint8_t num,uint8_t val) {
|
|
struct GRAPH *gp=graph[num];
|
|
if (!renderer) return;
|
|
|
|
uint16_t linecol=fg_color;
|
|
if (color_type==COLOR_COLOR) {
|
|
linecol=renderer->GetColorFromIndex(gp->color_index);
|
|
}
|
|
gp->xcnt++;
|
|
if (gp->xcnt>gp->xs) {
|
|
gp->xcnt=gp->xs;
|
|
int16_t count;
|
|
|
|
for (count=0;count<gp->xs-1;count++) {
|
|
gp->values[count]=gp->values[count+1];
|
|
}
|
|
gp->values[gp->xcnt-1]=val;
|
|
|
|
if (!gp->flags.draw) return;
|
|
|
|
|
|
if (millis()-gp->last_ms_redrawn>1000) {
|
|
gp->last_ms_redrawn=millis();
|
|
|
|
if (!gp->flags.overlay) {
|
|
|
|
renderer->drawRect(gp->xp,gp->yp,gp->xs,gp->ys,fg_color);
|
|
|
|
ClrGraph(num);
|
|
}
|
|
|
|
for (count=0;count<gp->xs-1;count++) {
|
|
renderer->writeLine(gp->xp+count,gp->yp+gp->ys-gp->values[count]-1,gp->xp+count+1,gp->yp+gp->ys-gp->values[count+1]-1,linecol);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
gp->values[gp->xcnt]=val;
|
|
if (!gp->flags.draw) return;
|
|
renderer->writeLine(gp->xp+gp->xcnt-1,gp->yp+gp->ys-gp->values[gp->xcnt-1]-1,gp->xp+gp->xcnt,gp->yp+gp->ys-gp->values[gp->xcnt]-1,linecol);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void AddValue(uint8_t num,float fval) {
|
|
|
|
num=num%NUM_GRAPHS;
|
|
struct GRAPH *gp=graph[num];
|
|
if (!gp) return;
|
|
|
|
if (fval>gp->ymax) fval=gp->ymax;
|
|
if (fval<gp->ymin) fval=gp->ymin;
|
|
|
|
int16_t val;
|
|
val=(fval-gp->ymin)/gp->range;
|
|
|
|
if (val>gp->ys-1) val=gp->ys-1;
|
|
if (val<0) val=0;
|
|
|
|
|
|
gp->summ+=val;
|
|
gp->dcnt++;
|
|
|
|
|
|
if (gp->decimation<0) {
|
|
if (gp->dcnt>=-gp->decimation) {
|
|
gp->dcnt=0;
|
|
|
|
val=gp->summ/-gp->decimation;
|
|
gp->summ=0;
|
|
|
|
AddGraph(num,val);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv13(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if ((i2c_flg || spi_flg || soft_spi_flg) && XdspPresent()) {
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
DisplayInitDriver();
|
|
#ifdef USE_GRAPH
|
|
for (uint8_t count=0;count<NUM_GRAPHS;count++) {
|
|
graph[count]=0;
|
|
}
|
|
#endif
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (Settings.display_model) { XdspCall(FUNC_DISPLAY_EVERY_50_MSECOND); }
|
|
break;
|
|
case FUNC_SET_POWER:
|
|
DisplaySetPower();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
#ifdef USE_GRAPH
|
|
DisplayCheckGraph();
|
|
#endif
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
if (Settings.display_model && Settings.display_mode) { XdspCall(FUNC_DISPLAY_EVERY_SECOND); }
|
|
#endif
|
|
break;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_MQTT_SUBSCRIBE:
|
|
DisplayMqttSubscribe();
|
|
break;
|
|
case FUNC_MQTT_DATA:
|
|
result = DisplayMqttData();
|
|
break;
|
|
case FUNC_SHOW_SENSOR:
|
|
DisplayLocalSensor();
|
|
break;
|
|
#endif
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kDisplayCommands, DisplayCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_14_mp3.ino"
|
|
# 64 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_14_mp3.ino"
|
|
#ifdef USE_MP3_PLAYER
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_14 14
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *MP3Player;
|
|
|
|
|
|
|
|
|
|
|
|
#define D_CMND_MP3 "MP3"
|
|
|
|
const char S_JSON_MP3_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MP3 "%s\":%d}";
|
|
const char S_JSON_MP3_COMMAND[] PROGMEM = "{\"" D_CMND_MP3 "%s\"}";
|
|
const char kMP3_Commands[] PROGMEM = "Track|Play|Pause|Stop|Volume|EQ|Device|Reset|DAC";
|
|
|
|
|
|
|
|
|
|
|
|
enum MP3_Commands {
|
|
CMND_MP3_TRACK,
|
|
CMND_MP3_PLAY,
|
|
CMND_MP3_PAUSE,
|
|
CMND_MP3_STOP,
|
|
CMND_MP3_VOLUME,
|
|
CMND_MP3_EQ,
|
|
CMND_MP3_DEVICE,
|
|
CMND_MP3_RESET,
|
|
CMND_MP3_DAC };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define MP3_CMD_RESET_VALUE 0
|
|
|
|
#define MP3_CMD_TRACK 0x03
|
|
#define MP3_CMD_PLAY 0x0d
|
|
#define MP3_CMD_PAUSE 0x0e
|
|
#define MP3_CMD_STOP 0x16
|
|
#define MP3_CMD_VOLUME 0x06
|
|
#define MP3_CMD_EQ 0x07
|
|
#define MP3_CMD_DEVICE 0x09
|
|
#define MP3_CMD_RESET 0x0C
|
|
#define MP3_CMD_DAC 0x1A
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t MP3_Checksum(uint8_t *array)
|
|
{
|
|
uint16_t checksum = 0;
|
|
for (uint32_t i = 0; i < 6; i++) {
|
|
checksum += array[i];
|
|
}
|
|
checksum = checksum^0xffff;
|
|
return (checksum+1);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MP3PlayerInit(void) {
|
|
MP3Player = new TasmotaSerial(-1, pin[GPIO_MP3_DFR562]);
|
|
|
|
if (MP3Player->begin(9600)) {
|
|
MP3Player->flush();
|
|
delay(1000);
|
|
MP3_CMD(MP3_CMD_RESET, MP3_CMD_RESET_VALUE);
|
|
delay(3000);
|
|
MP3_CMD(MP3_CMD_VOLUME, MP3_VOLUME);
|
|
}
|
|
return;
|
|
}
|
|
# 159 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_14_mp3.ino"
|
|
void MP3_CMD(uint8_t mp3cmd,uint16_t val) {
|
|
uint8_t i = 0;
|
|
uint8_t cmd[10] = {0x7e,0xff,6,0,0,0,0,0,0,0xef};
|
|
cmd[3] = mp3cmd;
|
|
cmd[4] = 0;
|
|
cmd[5] = val>>8;
|
|
cmd[6] = val;
|
|
uint16_t chks = MP3_Checksum(&cmd[1]);
|
|
cmd[7] = chks>>8;
|
|
cmd[8] = chks;
|
|
MP3Player->write(cmd, sizeof(cmd));
|
|
delay(1000);
|
|
if (mp3cmd == MP3_CMD_RESET) {
|
|
MP3_CMD(MP3_CMD_VOLUME, MP3_VOLUME);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool MP3PlayerCmd(void) {
|
|
char command[CMDSZ];
|
|
bool serviced = true;
|
|
uint8_t disp_len = strlen(D_CMND_MP3);
|
|
|
|
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_MP3), disp_len)) {
|
|
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kMP3_Commands);
|
|
|
|
switch (command_code) {
|
|
case CMND_MP3_TRACK:
|
|
case CMND_MP3_VOLUME:
|
|
case CMND_MP3_EQ:
|
|
case CMND_MP3_DEVICE:
|
|
case CMND_MP3_DAC:
|
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (command_code == CMND_MP3_TRACK) { MP3_CMD(MP3_CMD_TRACK, XdrvMailbox.payload); }
|
|
if (command_code == CMND_MP3_VOLUME) { MP3_CMD(MP3_CMD_VOLUME, XdrvMailbox.payload * 30 / 100); }
|
|
if (command_code == CMND_MP3_EQ) { MP3_CMD(MP3_CMD_EQ, XdrvMailbox.payload); }
|
|
if (command_code == CMND_MP3_DEVICE) { MP3_CMD(MP3_CMD_DEVICE, XdrvMailbox.payload); }
|
|
if (command_code == CMND_MP3_DAC) { MP3_CMD(MP3_CMD_DAC, XdrvMailbox.payload); }
|
|
}
|
|
Response_P(S_JSON_MP3_COMMAND_NVALUE, command, XdrvMailbox.payload);
|
|
break;
|
|
case CMND_MP3_PLAY:
|
|
case CMND_MP3_PAUSE:
|
|
case CMND_MP3_STOP:
|
|
case CMND_MP3_RESET:
|
|
|
|
if (command_code == CMND_MP3_PLAY) { MP3_CMD(MP3_CMD_PLAY, 0); }
|
|
if (command_code == CMND_MP3_PAUSE) { MP3_CMD(MP3_CMD_PAUSE, 0); }
|
|
if (command_code == CMND_MP3_STOP) { MP3_CMD(MP3_CMD_STOP, 0); }
|
|
if (command_code == CMND_MP3_RESET) { MP3_CMD(MP3_CMD_RESET, 0); }
|
|
Response_P(S_JSON_MP3_COMMAND, command, XdrvMailbox.payload);
|
|
break;
|
|
default:
|
|
|
|
serviced = false;
|
|
break;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv14(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (pin[GPIO_MP3_DFR562] < 99) {
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
MP3PlayerInit();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = MP3PlayerCmd();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_15_pca9685.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_15_pca9685.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_PCA9685
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_15 15
|
|
#define XI2C_01 1
|
|
|
|
#define PCA9685_REG_MODE1 0x00
|
|
#define PCA9685_REG_LED0_ON_L 0x06
|
|
#define PCA9685_REG_PRE_SCALE 0xFE
|
|
|
|
#ifndef USE_PCA9685_ADDR
|
|
#define USE_PCA9685_ADDR 0x40
|
|
#endif
|
|
#ifndef USE_PCA9685_FREQ
|
|
#define USE_PCA9685_FREQ 50
|
|
#endif
|
|
|
|
bool pca9685_detected = false;
|
|
uint16_t pca9685_freq = USE_PCA9685_FREQ;
|
|
uint16_t pca9685_pin_pwm_value[16];
|
|
|
|
void PCA9685_Detect(void)
|
|
{
|
|
if (I2cActive(USE_PCA9685_ADDR)) { return; }
|
|
|
|
uint8_t buffer;
|
|
if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) {
|
|
I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x20);
|
|
if (I2cValidRead8(&buffer, USE_PCA9685_ADDR, PCA9685_REG_MODE1)) {
|
|
if (0x20 == buffer) {
|
|
pca9685_detected = true;
|
|
I2cSetActiveFound(USE_PCA9685_ADDR, "PCA9685");
|
|
PCA9685_Reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PCA9685_Reset(void)
|
|
{
|
|
I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, 0x80);
|
|
PCA9685_SetPWMfreq(USE_PCA9685_FREQ);
|
|
for (uint32_t pin=0;pin<16;pin++) {
|
|
PCA9685_SetPWM(pin,0,false);
|
|
pca9685_pin_pwm_value[pin] = 0;
|
|
}
|
|
Response_P(PSTR("{\"PCA9685\":{\"RESET\":\"OK\"}}"));
|
|
}
|
|
|
|
void PCA9685_SetPWMfreq(double freq) {
|
|
|
|
|
|
|
|
|
|
if (freq > 23 && freq < 1527) {
|
|
pca9685_freq=freq;
|
|
} else {
|
|
pca9685_freq=50;
|
|
}
|
|
uint8_t pre_scale_osc = round(25000000/(4096*pca9685_freq))-1;
|
|
if (1526 == pca9685_freq) pre_scale_osc=0xFF;
|
|
uint8_t current_mode1 = I2cRead8(USE_PCA9685_ADDR, PCA9685_REG_MODE1);
|
|
uint8_t sleep_mode1 = (current_mode1&0x7F) | 0x10;
|
|
I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, sleep_mode1);
|
|
I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_PRE_SCALE, pre_scale_osc);
|
|
I2cWrite8(USE_PCA9685_ADDR, PCA9685_REG_MODE1, current_mode1 | 0xA0);
|
|
}
|
|
|
|
void PCA9685_SetPWM_Reg(uint8_t pin, uint16_t on, uint16_t off) {
|
|
uint8_t led_reg = PCA9685_REG_LED0_ON_L + 4 * pin;
|
|
uint32_t led_data = 0;
|
|
I2cWrite8(USE_PCA9685_ADDR, led_reg, on);
|
|
I2cWrite8(USE_PCA9685_ADDR, led_reg+1, (on >> 8));
|
|
I2cWrite8(USE_PCA9685_ADDR, led_reg+2, off);
|
|
I2cWrite8(USE_PCA9685_ADDR, led_reg+3, (off >> 8));
|
|
}
|
|
|
|
void PCA9685_SetPWM(uint8_t pin, uint16_t pwm, bool inverted) {
|
|
if (4096 == pwm) {
|
|
PCA9685_SetPWM_Reg(pin, 4096, 0);
|
|
} else {
|
|
PCA9685_SetPWM_Reg(pin, 0, pwm);
|
|
}
|
|
pca9685_pin_pwm_value[pin] = pwm;
|
|
}
|
|
|
|
bool PCA9685_Command(void)
|
|
{
|
|
bool serviced = true;
|
|
bool validpin = false;
|
|
uint8_t paramcount = 0;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
paramcount=1;
|
|
} else {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
char sub_string[XdrvMailbox.data_len];
|
|
for (uint32_t ca=0;ca<XdrvMailbox.data_len;ca++) {
|
|
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
|
|
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
|
|
}
|
|
UpperCase(XdrvMailbox.data,XdrvMailbox.data);
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET")) { PCA9685_Reset(); return serviced; }
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"STATUS")) { PCA9685_OutputTelemetry(false); return serviced; }
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"PWMF")) {
|
|
if (paramcount > 1) {
|
|
uint16_t new_freq = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if ((new_freq >= 24) && (new_freq <= 1526)) {
|
|
PCA9685_SetPWMfreq(new_freq);
|
|
Response_P(PSTR("{\"PCA9685\":{\"PWMF\":%i, \"Result\":\"OK\"}}"),new_freq);
|
|
return serviced;
|
|
}
|
|
} else {
|
|
Response_P(PSTR("{\"PCA9685\":{\"PWMF\":%i}}"),pca9685_freq);
|
|
return serviced;
|
|
}
|
|
}
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"PWM")) {
|
|
if (paramcount > 1) {
|
|
uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if (paramcount > 2) {
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 3), "ON")) {
|
|
PCA9685_SetPWM(pin, 4096, false);
|
|
Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,4096);
|
|
serviced = true;
|
|
return serviced;
|
|
}
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 3), "OFF")) {
|
|
PCA9685_SetPWM(pin, 0, false);
|
|
Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,0);
|
|
serviced = true;
|
|
return serviced;
|
|
}
|
|
uint16_t pwm = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
if ((pin >= 0 && pin <= 15) && (pwm >= 0 && pwm <= 4096)) {
|
|
PCA9685_SetPWM(pin, pwm, false);
|
|
Response_P(PSTR("{\"PCA9685\":{\"PIN\":%i,\"PWM\":%i}}"),pin,pwm);
|
|
serviced = true;
|
|
return serviced;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
void PCA9685_OutputTelemetry(bool telemetry)
|
|
{
|
|
ResponseTime_P(PSTR(",\"PCA9685\":{\"PWM_FREQ\":%i,"),pca9685_freq);
|
|
for (uint32_t pin=0;pin<16;pin++) {
|
|
ResponseAppend_P(PSTR("\"PWM%i\":%i,"),pin,pca9685_pin_pwm_value[pin]);
|
|
}
|
|
ResponseAppend_P(PSTR("\"END\":1}}"));
|
|
if (telemetry) {
|
|
MqttPublishTeleSensor();
|
|
}
|
|
}
|
|
|
|
bool Xdrv15(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_01)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
PCA9685_Detect();
|
|
}
|
|
else if (pca9685_detected) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
if (tele_period == 0) {
|
|
PCA9685_OutputTelemetry(true);
|
|
}
|
|
break;
|
|
case FUNC_COMMAND_DRIVER:
|
|
if (XDRV_15 == XdrvMailbox.index) {
|
|
result = PCA9685_Command();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_16_tuyamcu.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_16_tuyamcu.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_TUYA_MCU
|
|
|
|
#define XDRV_16 16
|
|
#define XNRG_16 16
|
|
|
|
#ifndef TUYA_DIMMER_ID
|
|
#define TUYA_DIMMER_ID 0
|
|
#endif
|
|
|
|
#define TUYA_CMD_HEARTBEAT 0x00
|
|
#define TUYA_CMD_QUERY_PRODUCT 0x01
|
|
#define TUYA_CMD_MCU_CONF 0x02
|
|
#define TUYA_CMD_WIFI_STATE 0x03
|
|
#define TUYA_CMD_WIFI_RESET 0x04
|
|
#define TUYA_CMD_WIFI_SELECT 0x05
|
|
#define TUYA_CMD_SET_DP 0x06
|
|
#define TUYA_CMD_STATE 0x07
|
|
#define TUYA_CMD_QUERY_STATE 0x08
|
|
|
|
#define TUYA_LOW_POWER_CMD_WIFI_STATE 0x02
|
|
#define TUYA_LOW_POWER_CMD_WIFI_RESET 0x03
|
|
#define TUYA_LOW_POWER_CMD_WIFI_CONFIG 0x04
|
|
#define TUYA_LOW_POWER_CMD_STATE 0x05
|
|
|
|
#define TUYA_TYPE_BOOL 0x01
|
|
#define TUYA_TYPE_VALUE 0x02
|
|
#define TUYA_TYPE_STRING 0x03
|
|
#define TUYA_TYPE_ENUM 0x04
|
|
|
|
#define TUYA_BUFFER_SIZE 256
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *TuyaSerial = nullptr;
|
|
|
|
struct TUYA {
|
|
uint16_t new_dim = 0;
|
|
bool ignore_dim = false;
|
|
uint8_t cmd_status = 0;
|
|
uint8_t cmd_checksum = 0;
|
|
uint8_t data_len = 0;
|
|
uint8_t wifi_state = -2;
|
|
uint8_t heartbeat_timer = 0;
|
|
#ifdef USE_ENERGY_SENSOR
|
|
uint32_t lastPowerCheckTime = 0;
|
|
#endif
|
|
char *buffer = nullptr;
|
|
int byte_counter = 0;
|
|
bool low_power_mode = false;
|
|
bool send_success_next_second = false;
|
|
} Tuya;
|
|
|
|
|
|
enum TuyaSupportedFunctions {
|
|
TUYA_MCU_FUNC_NONE,
|
|
TUYA_MCU_FUNC_SWT1 = 1,
|
|
TUYA_MCU_FUNC_SWT2,
|
|
TUYA_MCU_FUNC_SWT3,
|
|
TUYA_MCU_FUNC_SWT4,
|
|
TUYA_MCU_FUNC_REL1 = 11,
|
|
TUYA_MCU_FUNC_REL2,
|
|
TUYA_MCU_FUNC_REL3,
|
|
TUYA_MCU_FUNC_REL4,
|
|
TUYA_MCU_FUNC_REL5,
|
|
TUYA_MCU_FUNC_REL6,
|
|
TUYA_MCU_FUNC_REL7,
|
|
TUYA_MCU_FUNC_REL8,
|
|
TUYA_MCU_FUNC_DIMMER = 21,
|
|
TUYA_MCU_FUNC_POWER = 31,
|
|
TUYA_MCU_FUNC_CURRENT,
|
|
TUYA_MCU_FUNC_VOLTAGE,
|
|
TUYA_MCU_FUNC_BATTERY_STATE,
|
|
TUYA_MCU_FUNC_BATTERY_PERCENTAGE,
|
|
TUYA_MCU_FUNC_REL1_INV = 41,
|
|
TUYA_MCU_FUNC_REL2_INV,
|
|
TUYA_MCU_FUNC_REL3_INV,
|
|
TUYA_MCU_FUNC_REL4_INV,
|
|
TUYA_MCU_FUNC_REL5_INV,
|
|
TUYA_MCU_FUNC_REL6_INV,
|
|
TUYA_MCU_FUNC_REL7_INV,
|
|
TUYA_MCU_FUNC_REL8_INV,
|
|
TUYA_MCU_FUNC_LOWPOWER_MODE = 51,
|
|
TUYA_MCU_FUNC_LAST = 255
|
|
};
|
|
|
|
const char kTuyaCommand[] PROGMEM = "|"
|
|
D_CMND_TUYA_MCU "|" D_CMND_TUYA_MCU_SEND_STATE;
|
|
|
|
void (* const TuyaCommand[])(void) PROGMEM = {
|
|
&CmndTuyaMcu, &CmndTuyaSend
|
|
};
|
|
# 126 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_16_tuyamcu.ino"
|
|
void CmndTuyaSend(void) {
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
|
|
char *p;
|
|
char *data;
|
|
uint8_t i = 0;
|
|
uint8_t dpId = 0;
|
|
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) {
|
|
if ( i == 0) {
|
|
dpId = strtoul(str, nullptr, 0);
|
|
} else {
|
|
data = str;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (1 == XdrvMailbox.index) {
|
|
TuyaSendBool(dpId, strtoul(data, nullptr, 0));
|
|
} else if (2 == XdrvMailbox.index) {
|
|
TuyaSendValue(dpId, strtoull(data, nullptr, 0));
|
|
} else if (3 == XdrvMailbox.index) {
|
|
TuyaSendString(dpId, data);
|
|
} else if (4 == XdrvMailbox.index) {
|
|
TuyaSendEnum(dpId, strtoul(data, nullptr, 0));
|
|
}
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CmndTuyaMcu(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char *p;
|
|
uint8_t i = 0;
|
|
uint8_t parm[3] = { 0 };
|
|
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) {
|
|
parm[i] = strtoul(str, nullptr, 0);
|
|
i++;
|
|
}
|
|
|
|
if (TuyaFuncIdValid(parm[0])) {
|
|
TuyaAddMcuFunc(parm[0], parm[1]);
|
|
restart_flag = 2;
|
|
} else {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("TYA: TuyaMcu Invalid function id=%d"), parm[0]);
|
|
}
|
|
|
|
}
|
|
|
|
Response_P(PSTR("{\"" D_CMND_TUYA_MCU "\":["));
|
|
bool added = false;
|
|
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
if (Settings.tuya_fnid_map[i].fnid != 0) {
|
|
if (added) {
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
ResponseAppend_P(PSTR("{\"fnId\":%d,\"dpId\":%d}" ), Settings.tuya_fnid_map[i].fnid, Settings.tuya_fnid_map[i].dpid);
|
|
added = true;
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("]}"));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TuyaAddMcuFunc(uint8_t fnId, uint8_t dpId) {
|
|
bool added = false;
|
|
|
|
if (fnId == 0 || dpId == 0) {
|
|
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
if ((dpId > 0 && Settings.tuya_fnid_map[i].dpid == dpId) || (fnId > TUYA_MCU_FUNC_NONE && Settings.tuya_fnid_map[i].fnid == fnId)) {
|
|
Settings.tuya_fnid_map[i].fnid = TUYA_MCU_FUNC_NONE;
|
|
Settings.tuya_fnid_map[i].dpid = 0;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
if (Settings.tuya_fnid_map[i].dpid == dpId || Settings.tuya_fnid_map[i].dpid == 0 || Settings.tuya_fnid_map[i].fnid == fnId || Settings.tuya_fnid_map[i].fnid == 0) {
|
|
if (!added) {
|
|
Settings.tuya_fnid_map[i].fnid = fnId;
|
|
Settings.tuya_fnid_map[i].dpid = dpId;
|
|
added = true;
|
|
} else if (Settings.tuya_fnid_map[i].dpid == dpId || Settings.tuya_fnid_map[i].fnid == fnId) {
|
|
Settings.tuya_fnid_map[i].fnid = TUYA_MCU_FUNC_NONE;
|
|
Settings.tuya_fnid_map[i].dpid = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
UpdateDevices();
|
|
}
|
|
|
|
void UpdateDevices() {
|
|
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
uint8_t fnId = Settings.tuya_fnid_map[i].fnid;
|
|
if (fnId > TUYA_MCU_FUNC_NONE && Settings.tuya_fnid_map[i].dpid > 0) {
|
|
|
|
if (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) {
|
|
bitClear(rel_inverted, fnId - TUYA_MCU_FUNC_REL1);
|
|
} else if (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) {
|
|
bitSet(rel_inverted, fnId - TUYA_MCU_FUNC_REL1_INV);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
inline bool TuyaFuncIdValid(uint8_t fnId) {
|
|
return (fnId >= TUYA_MCU_FUNC_SWT1 && fnId <= TUYA_MCU_FUNC_SWT4) ||
|
|
(fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) ||
|
|
fnId == TUYA_MCU_FUNC_DIMMER ||
|
|
(fnId >= TUYA_MCU_FUNC_POWER && fnId <= TUYA_MCU_FUNC_VOLTAGE) ||
|
|
(fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) ||
|
|
(fnId == TUYA_MCU_FUNC_LOWPOWER_MODE);
|
|
}
|
|
|
|
uint8_t TuyaGetFuncId(uint8_t dpid) {
|
|
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
if (Settings.tuya_fnid_map[i].dpid == dpid) {
|
|
return Settings.tuya_fnid_map[i].fnid;
|
|
}
|
|
}
|
|
return TUYA_MCU_FUNC_NONE;
|
|
}
|
|
|
|
uint8_t TuyaGetDpId(uint8_t fnId) {
|
|
for (uint8_t i = 0; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
if (Settings.tuya_fnid_map[i].fnid == fnId) {
|
|
return Settings.tuya_fnid_map[i].dpid;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void TuyaSendCmd(uint8_t cmd, uint8_t payload[] = nullptr, uint16_t payload_len = 0)
|
|
{
|
|
uint8_t checksum = (0xFF + cmd + (payload_len >> 8) + (payload_len & 0xFF));
|
|
TuyaSerial->write(0x55);
|
|
TuyaSerial->write(0xAA);
|
|
TuyaSerial->write((uint8_t)0x00);
|
|
TuyaSerial->write(cmd);
|
|
TuyaSerial->write(payload_len >> 8);
|
|
TuyaSerial->write(payload_len & 0xFF);
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("TYA: Send \"55aa00%02x%02x%02x"), cmd, payload_len >> 8, payload_len & 0xFF);
|
|
for (uint32_t i = 0; i < payload_len; ++i) {
|
|
TuyaSerial->write(payload[i]);
|
|
checksum += payload[i];
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, payload[i]);
|
|
}
|
|
TuyaSerial->write(checksum);
|
|
TuyaSerial->flush();
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x\""), log_data, checksum);
|
|
AddLog(LOG_LEVEL_DEBUG);
|
|
}
|
|
|
|
void TuyaSendState(uint8_t id, uint8_t type, uint8_t* value)
|
|
{
|
|
uint16_t payload_len = 4;
|
|
uint8_t payload_buffer[8];
|
|
payload_buffer[0] = id;
|
|
payload_buffer[1] = type;
|
|
switch (type) {
|
|
case TUYA_TYPE_BOOL:
|
|
case TUYA_TYPE_ENUM:
|
|
payload_len += 1;
|
|
payload_buffer[2] = 0x00;
|
|
payload_buffer[3] = 0x01;
|
|
payload_buffer[4] = value[0];
|
|
break;
|
|
case TUYA_TYPE_VALUE:
|
|
payload_len += 4;
|
|
payload_buffer[2] = 0x00;
|
|
payload_buffer[3] = 0x04;
|
|
payload_buffer[4] = value[3];
|
|
payload_buffer[5] = value[2];
|
|
payload_buffer[6] = value[1];
|
|
payload_buffer[7] = value[0];
|
|
break;
|
|
|
|
}
|
|
|
|
TuyaSendCmd(TUYA_CMD_SET_DP, payload_buffer, payload_len);
|
|
}
|
|
|
|
void TuyaSendBool(uint8_t id, bool value)
|
|
{
|
|
TuyaSendState(id, TUYA_TYPE_BOOL, (uint8_t*)&value);
|
|
}
|
|
|
|
void TuyaSendValue(uint8_t id, uint32_t value)
|
|
{
|
|
TuyaSendState(id, TUYA_TYPE_VALUE, (uint8_t*)(&value));
|
|
}
|
|
|
|
void TuyaSendEnum(uint8_t id, uint32_t value)
|
|
{
|
|
TuyaSendState(id, TUYA_TYPE_ENUM, (uint8_t*)(&value));
|
|
}
|
|
|
|
void TuyaSendString(uint8_t id, char data[]) {
|
|
|
|
uint16_t len = strlen(data);
|
|
uint16_t payload_len = 4 + len;
|
|
uint8_t payload_buffer[payload_len];
|
|
payload_buffer[0] = id;
|
|
payload_buffer[1] = TUYA_TYPE_STRING;
|
|
payload_buffer[2] = len >> 8;
|
|
payload_buffer[3] = len & 0xFF;
|
|
|
|
for (uint16_t i = 0; i < len; i++) {
|
|
payload_buffer[4+i] = data[i];
|
|
}
|
|
|
|
TuyaSendCmd(TUYA_CMD_SET_DP, payload_buffer, payload_len);
|
|
}
|
|
|
|
bool TuyaSetPower(void)
|
|
{
|
|
bool status = false;
|
|
|
|
uint8_t rpower = XdrvMailbox.index;
|
|
int16_t source = XdrvMailbox.payload;
|
|
|
|
uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_REL1 + active_device - 1);
|
|
if (dpid == 0) dpid = TuyaGetDpId(TUYA_MCU_FUNC_REL1_INV + active_device - 1);
|
|
|
|
if (source != SRC_SWITCH && TuyaSerial) {
|
|
TuyaSendBool(dpid, bitRead(rpower, active_device-1) ^ bitRead(rel_inverted, active_device-1));
|
|
status = true;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
bool TuyaSetChannels(void)
|
|
{
|
|
LightSerialDuty(((uint8_t*)XdrvMailbox.data)[0]);
|
|
delay(20);
|
|
return true;
|
|
}
|
|
|
|
void LightSerialDuty(uint16_t duty)
|
|
{
|
|
uint8_t dpid = TuyaGetDpId(TUYA_MCU_FUNC_DIMMER);
|
|
if (duty > 0 && !Tuya.ignore_dim && TuyaSerial && dpid > 0) {
|
|
duty = changeUIntScale(duty, 0, 255, 0, Settings.dimmer_hw_max);
|
|
if (duty < Settings.dimmer_hw_min) { duty = Settings.dimmer_hw_min; }
|
|
if (Tuya.new_dim != duty) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim value=%d (id=%d)"), duty, dpid);
|
|
TuyaSendValue(dpid, duty);
|
|
}
|
|
} else if (dpid > 0) {
|
|
Tuya.ignore_dim = false;
|
|
duty = changeUIntScale(duty, 0, 255, 0, Settings.dimmer_hw_max);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Send dim skipped value=%d"), duty);
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Cannot set dimmer. Dimmer Id unknown"));
|
|
}
|
|
}
|
|
|
|
void TuyaRequestState(void)
|
|
{
|
|
if (TuyaSerial) {
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Read MCU state"));
|
|
|
|
TuyaSendCmd(TUYA_CMD_QUERY_STATE);
|
|
}
|
|
}
|
|
|
|
void TuyaResetWifi(void)
|
|
{
|
|
if (!Settings.flag.button_restrict) {
|
|
char scmnd[20];
|
|
snprintf_P(scmnd, sizeof(scmnd), D_CMND_WIFICONFIG " %d", 2);
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
}
|
|
}
|
|
|
|
void TuyaProcessStatePacket(void) {
|
|
char scmnd[20];
|
|
uint8_t dpidStart = 6;
|
|
uint8_t fnId;
|
|
uint16_t dpDataLen;
|
|
|
|
while (dpidStart + 4 < Tuya.byte_counter) {
|
|
dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3];
|
|
fnId = TuyaGetFuncId(Tuya.buffer[dpidStart]);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: fnId=%d is set for dpId=%d"), fnId, Tuya.buffer[dpidStart]);
|
|
|
|
if (Tuya.buffer[dpidStart + 1] == 1) {
|
|
|
|
if (fnId >= TUYA_MCU_FUNC_REL1 && fnId <= TUYA_MCU_FUNC_REL8) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Relay-%d --> MCU State: %s Current State:%s"), fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[dpidStart + 4]?"On":"Off",bitRead(power, fnId - TUYA_MCU_FUNC_REL1)?"On":"Off");
|
|
if ((power || Settings.light_dimmer > 0) && (Tuya.buffer[dpidStart + 4] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1))) {
|
|
ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1 + 1, Tuya.buffer[dpidStart + 4], SRC_SWITCH);
|
|
}
|
|
} else if (fnId >= TUYA_MCU_FUNC_REL1_INV && fnId <= TUYA_MCU_FUNC_REL8_INV) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Relay-%d-Inverted --> MCU State: %s Current State:%s"), fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[dpidStart + 4]?"Off":"On",bitRead(power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1?"Off":"On");
|
|
if (Tuya.buffer[dpidStart + 4] != bitRead(power, fnId - TUYA_MCU_FUNC_REL1_INV) ^ 1) {
|
|
ExecuteCommandPower(fnId - TUYA_MCU_FUNC_REL1_INV + 1, Tuya.buffer[dpidStart + 4] ^ 1, SRC_SWITCH);
|
|
}
|
|
} else if (fnId >= TUYA_MCU_FUNC_SWT1 && fnId <= TUYA_MCU_FUNC_SWT4) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Switch-%d --> MCU State: %d Current State:%d"),fnId - TUYA_MCU_FUNC_SWT1 + 1,Tuya.buffer[dpidStart + 4], SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1));
|
|
|
|
if (SwitchGetVirtual(fnId - TUYA_MCU_FUNC_SWT1) != Tuya.buffer[dpidStart + 4]) {
|
|
SwitchSetVirtual(fnId - TUYA_MCU_FUNC_SWT1, Tuya.buffer[dpidStart + 4]);
|
|
SwitchHandler(1);
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (Tuya.buffer[dpidStart + 1] == 2) {
|
|
bool tuya_energy_enabled = (XNRG_16 == energy_flg);
|
|
uint16_t packetValue = Tuya.buffer[dpidStart + 6] << 8 | Tuya.buffer[dpidStart + 7];
|
|
if (fnId == TUYA_MCU_FUNC_DIMMER) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX Dim State=%d"), packetValue);
|
|
Tuya.new_dim = changeUIntScale(packetValue, 0, Settings.dimmer_hw_max, 0, 100);
|
|
if ((power || Settings.flag3.tuya_apply_o20) &&
|
|
(Tuya.new_dim > 0) && (abs(Tuya.new_dim - Settings.light_dimmer) > 1)) {
|
|
Tuya.ignore_dim = true;
|
|
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), Tuya.new_dim );
|
|
ExecuteCommand(scmnd, SRC_SWITCH);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ENERGY_SENSOR
|
|
else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_VOLTAGE) {
|
|
Energy.voltage[0] = (float)packetValue / 10;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Voltage=%d"), Tuya.buffer[dpidStart], packetValue);
|
|
} else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_CURRENT) {
|
|
Energy.current[0] = (float)packetValue / 1000;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Current=%d"), Tuya.buffer[dpidStart], packetValue);
|
|
} else if (tuya_energy_enabled && fnId == TUYA_MCU_FUNC_POWER) {
|
|
Energy.active_power[0] = (float)packetValue / 10;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Rx ID=%d Active_Power=%d"), Tuya.buffer[dpidStart], packetValue);
|
|
|
|
if (Tuya.lastPowerCheckTime != 0 && Energy.active_power[0] > 0) {
|
|
Energy.kWhtoday += (float)Energy.active_power[0] * (Rtc.utc_time - Tuya.lastPowerCheckTime) / 36;
|
|
EnergyUpdateToday();
|
|
}
|
|
Tuya.lastPowerCheckTime = Rtc.utc_time;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
dpidStart += dpDataLen + 4;
|
|
}
|
|
}
|
|
|
|
void TuyaLowPowerModePacketProcess(void) {
|
|
switch (Tuya.buffer[3]) {
|
|
case TUYA_CMD_QUERY_PRODUCT:
|
|
TuyaHandleProductInfoPacket();
|
|
TuyaSetWifiLed();
|
|
break;
|
|
|
|
case TUYA_LOW_POWER_CMD_STATE:
|
|
TuyaProcessStatePacket();
|
|
Tuya.send_success_next_second = true;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
void TuyaHandleProductInfoPacket(void) {
|
|
uint16_t dataLength = Tuya.buffer[4] << 8 | Tuya.buffer[5];
|
|
char *data = &Tuya.buffer[6];
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TYA: MCU Product ID: %.*s"), dataLength, data);
|
|
}
|
|
|
|
void TuyaSendLowPowerSuccessIfNeeded(void) {
|
|
uint8_t success = 1;
|
|
|
|
if (Tuya.send_success_next_second) {
|
|
TuyaSendCmd(TUYA_LOW_POWER_CMD_STATE, &success, 1);
|
|
Tuya.send_success_next_second = false;
|
|
}
|
|
}
|
|
|
|
void TuyaNormalPowerModePacketProcess(void)
|
|
{
|
|
switch (Tuya.buffer[3]) {
|
|
case TUYA_CMD_QUERY_PRODUCT:
|
|
TuyaHandleProductInfoPacket();
|
|
TuyaSendCmd(TUYA_CMD_MCU_CONF);
|
|
break;
|
|
|
|
case TUYA_CMD_HEARTBEAT:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Heartbeat"));
|
|
if (Tuya.buffer[6] == 0) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Detected MCU restart"));
|
|
Tuya.wifi_state = -2;
|
|
}
|
|
break;
|
|
|
|
case TUYA_CMD_STATE:
|
|
TuyaProcessStatePacket();
|
|
break;
|
|
|
|
case TUYA_CMD_WIFI_RESET:
|
|
case TUYA_CMD_WIFI_SELECT:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX WiFi Reset"));
|
|
TuyaResetWifi();
|
|
break;
|
|
|
|
case TUYA_CMD_WIFI_STATE:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX WiFi LED set ACK"));
|
|
Tuya.wifi_state = TuyaGetTuyaWifiState();
|
|
break;
|
|
|
|
case TUYA_CMD_MCU_CONF:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: RX MCU configuration Mode=%d"), Tuya.buffer[5]);
|
|
|
|
if (Tuya.buffer[5] == 2) {
|
|
uint8_t led1_gpio = Tuya.buffer[6];
|
|
uint8_t key1_gpio = Tuya.buffer[7];
|
|
bool key1_set = false;
|
|
bool led1_set = false;
|
|
for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) {
|
|
if (Settings.my_gp.io[i] == GPIO_LED1) led1_set = true;
|
|
else if (Settings.my_gp.io[i] == GPIO_KEY1) key1_set = true;
|
|
}
|
|
if (!Settings.my_gp.io[led1_gpio] && !led1_set) {
|
|
Settings.my_gp.io[led1_gpio] = GPIO_LED1;
|
|
restart_flag = 2;
|
|
}
|
|
if (!Settings.my_gp.io[key1_gpio] && !key1_set) {
|
|
Settings.my_gp.io[key1_gpio] = GPIO_KEY1;
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
TuyaRequestState();
|
|
break;
|
|
|
|
default:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: RX unknown command"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool TuyaModuleSelected(void)
|
|
{
|
|
if (!(pin[GPIO_TUYA_RX] < 99) || !(pin[GPIO_TUYA_TX] < 99)) {
|
|
pin[GPIO_TUYA_TX] = 1;
|
|
pin[GPIO_TUYA_RX] = 3;
|
|
Settings.my_gp.io[1] = GPIO_TUYA_TX;
|
|
Settings.my_gp.io[3] = GPIO_TUYA_RX;
|
|
restart_flag = 2;
|
|
}
|
|
|
|
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) == 0 && TUYA_DIMMER_ID > 0) {
|
|
TuyaAddMcuFunc(TUYA_MCU_FUNC_DIMMER, TUYA_DIMMER_ID);
|
|
}
|
|
|
|
bool relaySet = false;
|
|
|
|
for (uint8_t i = 0 ; i < MAX_TUYA_FUNCTIONS; i++) {
|
|
if ((Settings.tuya_fnid_map[i].fnid >= TUYA_MCU_FUNC_REL1 && Settings.tuya_fnid_map[i].fnid <= TUYA_MCU_FUNC_REL8 ) ||
|
|
(Settings.tuya_fnid_map[i].fnid >= TUYA_MCU_FUNC_REL1_INV && Settings.tuya_fnid_map[i].fnid <= TUYA_MCU_FUNC_REL8_INV )) {
|
|
relaySet = true;
|
|
devices_present++;
|
|
}
|
|
}
|
|
|
|
if (!relaySet) {
|
|
TuyaAddMcuFunc(TUYA_MCU_FUNC_REL1, 1);
|
|
devices_present++;
|
|
SettingsSaveAll();
|
|
}
|
|
|
|
if (TuyaGetDpId(TUYA_MCU_FUNC_DIMMER) != 0) {
|
|
light_type = LT_SERIAL1;
|
|
} else {
|
|
light_type = LT_BASIC;
|
|
}
|
|
|
|
if (TuyaGetDpId(TUYA_MCU_FUNC_LOWPOWER_MODE) != 0) {
|
|
Tuya.low_power_mode = true;
|
|
Settings.flag3.fast_power_cycle_disable = true;
|
|
}
|
|
|
|
UpdateDevices();
|
|
return true;
|
|
}
|
|
|
|
void TuyaInit(void)
|
|
{
|
|
Tuya.buffer = (char*)(malloc(TUYA_BUFFER_SIZE));
|
|
if (Tuya.buffer != nullptr) {
|
|
TuyaSerial = new TasmotaSerial(pin[GPIO_TUYA_RX], pin[GPIO_TUYA_TX], 2);
|
|
if (TuyaSerial->begin(9600)) {
|
|
if (TuyaSerial->hardwareSerial()) { ClaimSerial(); }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Request MCU configuration"));
|
|
|
|
TuyaSendCmd(TUYA_CMD_QUERY_PRODUCT);
|
|
}
|
|
}
|
|
Tuya.heartbeat_timer = 0;
|
|
}
|
|
|
|
void TuyaSerialInput(void)
|
|
{
|
|
while (TuyaSerial->available()) {
|
|
yield();
|
|
uint8_t serial_in_byte = TuyaSerial->read();
|
|
|
|
if (serial_in_byte == 0x55) {
|
|
Tuya.cmd_status = 1;
|
|
Tuya.buffer[Tuya.byte_counter++] = serial_in_byte;
|
|
Tuya.cmd_checksum += serial_in_byte;
|
|
}
|
|
else if (Tuya.cmd_status == 1 && serial_in_byte == 0xAA) {
|
|
Tuya.cmd_status = 2;
|
|
|
|
Tuya.byte_counter = 0;
|
|
Tuya.buffer[Tuya.byte_counter++] = 0x55;
|
|
Tuya.buffer[Tuya.byte_counter++] = 0xAA;
|
|
Tuya.cmd_checksum = 0xFF;
|
|
}
|
|
else if (Tuya.cmd_status == 2) {
|
|
if (Tuya.byte_counter == 5) {
|
|
Tuya.cmd_status = 3;
|
|
Tuya.data_len = serial_in_byte;
|
|
}
|
|
Tuya.cmd_checksum += serial_in_byte;
|
|
Tuya.buffer[Tuya.byte_counter++] = serial_in_byte;
|
|
}
|
|
else if ((Tuya.cmd_status == 3) && (Tuya.byte_counter == (6 + Tuya.data_len)) && (Tuya.cmd_checksum == serial_in_byte)) {
|
|
Tuya.buffer[Tuya.byte_counter++] = serial_in_byte;
|
|
|
|
char hex_char[(Tuya.byte_counter * 2) + 2];
|
|
uint16_t len = Tuya.buffer[4] << 8 | Tuya.buffer[5];
|
|
Response_P(PSTR("{\"" D_JSON_TUYA_MCU_RECEIVED "\":{\"Data\":\"%s\",\"Cmnd\":%d"), ToHex_P((unsigned char*)Tuya.buffer, Tuya.byte_counter, hex_char, sizeof(hex_char)), Tuya.buffer[3]);
|
|
|
|
if (len > 0) {
|
|
ResponseAppend_P(PSTR(",\"CmndData\":\"%s\""), ToHex_P((unsigned char*)&Tuya.buffer[6], len, hex_char, sizeof(hex_char)));
|
|
if (TUYA_CMD_STATE == Tuya.buffer[3]) {
|
|
|
|
|
|
uint8_t dpidStart = 6;
|
|
while (dpidStart + 4 < Tuya.byte_counter) {
|
|
uint16_t dpDataLen = Tuya.buffer[dpidStart + 2] << 8 | Tuya.buffer[dpidStart + 3];
|
|
ResponseAppend_P(PSTR(",\"%d\":{\"DpId\":%d,\"DpIdType\":%d,\"DpIdData\":\"%s\""), Tuya.buffer[dpidStart], Tuya.buffer[dpidStart], Tuya.buffer[dpidStart + 1], ToHex_P((unsigned char*)&Tuya.buffer[dpidStart + 4], dpDataLen, hex_char, sizeof(hex_char)));
|
|
if (TUYA_TYPE_STRING == Tuya.buffer[dpidStart + 1]) {
|
|
ResponseAppend_P(PSTR(",\"Type3Data\":\"%.*s\""), dpDataLen, (char *)&Tuya.buffer[dpidStart + 4]);
|
|
}
|
|
ResponseAppend_P(PSTR("}"));
|
|
dpidStart += dpDataLen + 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
ResponseAppend_P(PSTR("}}"));
|
|
|
|
if (Settings.flag3.tuya_serial_mqtt_publish) {
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_TUYA_MCU_RECEIVED));
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_DEBUG, mqtt_data);
|
|
}
|
|
XdrvRulesProcess();
|
|
|
|
if (!Tuya.low_power_mode) {
|
|
TuyaNormalPowerModePacketProcess();
|
|
} else {
|
|
TuyaLowPowerModePacketProcess();
|
|
}
|
|
|
|
Tuya.byte_counter = 0;
|
|
Tuya.cmd_status = 0;
|
|
Tuya.cmd_checksum = 0;
|
|
Tuya.data_len = 0;
|
|
}
|
|
else if (Tuya.byte_counter < TUYA_BUFFER_SIZE -1) {
|
|
Tuya.buffer[Tuya.byte_counter++] = serial_in_byte;
|
|
Tuya.cmd_checksum += serial_in_byte;
|
|
} else {
|
|
Tuya.byte_counter = 0;
|
|
Tuya.cmd_status = 0;
|
|
Tuya.cmd_checksum = 0;
|
|
Tuya.data_len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TuyaButtonPressed(void)
|
|
{
|
|
if (!XdrvMailbox.index && ((PRESSED == XdrvMailbox.payload) && (NOT_PRESSED == Button.last_state[XdrvMailbox.index]))) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("TYA: Reset GPIO triggered"));
|
|
TuyaResetWifi();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint8_t TuyaGetTuyaWifiState(void) {
|
|
|
|
uint8_t wifi_state = 0x02;
|
|
switch(WifiState()){
|
|
case WIFI_MANAGER:
|
|
wifi_state = 0x01;
|
|
break;
|
|
case WIFI_RESTART:
|
|
wifi_state = 0x03;
|
|
break;
|
|
}
|
|
|
|
if (MqttIsConnected()) {
|
|
wifi_state = 0x04;
|
|
}
|
|
|
|
return wifi_state;
|
|
}
|
|
|
|
void TuyaSetWifiLed(void)
|
|
{
|
|
Tuya.wifi_state = TuyaGetTuyaWifiState();
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TYA: Set WiFi LED %d (%d)"), Tuya.wifi_state, WifiState());
|
|
|
|
if (Tuya.low_power_mode) {
|
|
TuyaSendCmd(TUYA_LOW_POWER_CMD_WIFI_STATE, &Tuya.wifi_state, 1);
|
|
} else {
|
|
TuyaSendCmd(TUYA_CMD_WIFI_STATE, &Tuya.wifi_state, 1);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ENERGY_SENSOR
|
|
|
|
|
|
|
|
|
|
bool Xnrg16(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (TUYA_DIMMER == my_module_type) {
|
|
if (FUNC_PRE_INIT == function) {
|
|
if (TuyaGetDpId(TUYA_MCU_FUNC_POWER) != 0) {
|
|
if (TuyaGetDpId(TUYA_MCU_FUNC_CURRENT) == 0) {
|
|
Energy.current_available = false;
|
|
}
|
|
if (TuyaGetDpId(TUYA_MCU_FUNC_VOLTAGE) == 0) {
|
|
Energy.voltage_available = false;
|
|
}
|
|
energy_flg = XNRG_16;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv16(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (TUYA_DIMMER == my_module_type) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (TuyaSerial) { TuyaSerialInput(); }
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
result = TuyaModuleSelected();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
TuyaInit();
|
|
break;
|
|
case FUNC_SET_DEVICE_POWER:
|
|
result = TuyaSetPower();
|
|
break;
|
|
case FUNC_BUTTON_PRESSED:
|
|
result = TuyaButtonPressed();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
if (TuyaSerial && Tuya.wifi_state != TuyaGetTuyaWifiState()) { TuyaSetWifiLed(); }
|
|
if (!Tuya.low_power_mode) {
|
|
Tuya.heartbeat_timer++;
|
|
if (Tuya.heartbeat_timer > 10) {
|
|
Tuya.heartbeat_timer = 0;
|
|
TuyaSendCmd(TUYA_CMD_HEARTBEAT);
|
|
}
|
|
} else {
|
|
TuyaSendLowPowerSuccessIfNeeded();
|
|
}
|
|
break;
|
|
case FUNC_SET_CHANNELS:
|
|
result = TuyaSetChannels();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kTuyaCommand, TuyaCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_17_rcswitch.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_17_rcswitch.ino"
|
|
#ifdef USE_RC_SWITCH
|
|
|
|
|
|
|
|
|
|
#define XDRV_17 17
|
|
|
|
#define D_JSON_RF_PROTOCOL "Protocol"
|
|
#define D_JSON_RF_BITS "Bits"
|
|
#define D_JSON_RF_DATA "Data"
|
|
|
|
#define D_CMND_RFSEND "RFSend"
|
|
#define D_JSON_RF_PULSE "Pulse"
|
|
#define D_JSON_RF_REPEAT "Repeat"
|
|
|
|
const char kRfSendCommands[] PROGMEM = "|"
|
|
D_CMND_RFSEND;
|
|
|
|
void (* const RfSendCommand[])(void) PROGMEM = {
|
|
&CmndRfSend };
|
|
|
|
#include <RCSwitch.h>
|
|
|
|
RCSwitch mySwitch = RCSwitch();
|
|
|
|
#define RF_TIME_AVOID_DUPLICATE 1000
|
|
|
|
uint32_t rf_lasttime = 0;
|
|
|
|
void RfReceiveCheck(void)
|
|
{
|
|
if (mySwitch.available()) {
|
|
|
|
unsigned long data = mySwitch.getReceivedValue();
|
|
unsigned int bits = mySwitch.getReceivedBitlength();
|
|
int protocol = mySwitch.getReceivedProtocol();
|
|
int delay = mySwitch.getReceivedDelay();
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFR: Data 0x%lX (%u), Bits %d, Protocol %d, Delay %d"), data, data, bits, protocol, delay);
|
|
|
|
uint32_t now = millis();
|
|
if ((now - rf_lasttime > RF_TIME_AVOID_DUPLICATE) && (data > 0)) {
|
|
rf_lasttime = now;
|
|
|
|
char stemp[16];
|
|
if (Settings.flag.rf_receive_decimal) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%u"), (uint32_t)data);
|
|
} else {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("\"0x%lX\""), (uint32_t)data);
|
|
}
|
|
ResponseTime_P(PSTR(",\"" D_JSON_RFRECEIVED "\":{\"" D_JSON_RF_DATA "\":%s,\"" D_JSON_RF_BITS "\":%d,\"" D_JSON_RF_PROTOCOL "\":%d,\"" D_JSON_RF_PULSE "\":%d}}"),
|
|
stemp, bits, protocol, delay);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_RFRECEIVED));
|
|
XdrvRulesProcess();
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzSensor(DZ_COUNT, data);
|
|
#endif
|
|
}
|
|
mySwitch.resetAvailable();
|
|
}
|
|
}
|
|
|
|
void RfInit(void)
|
|
{
|
|
if (pin[GPIO_RFSEND] < 99) {
|
|
mySwitch.enableTransmit(pin[GPIO_RFSEND]);
|
|
}
|
|
if (pin[GPIO_RFRECV] < 99) {
|
|
pinMode( pin[GPIO_RFRECV], INPUT);
|
|
mySwitch.enableReceive(pin[GPIO_RFRECV]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndRfSend(void)
|
|
{
|
|
bool error = false;
|
|
|
|
if (XdrvMailbox.data_len) {
|
|
unsigned long data = 0;
|
|
unsigned int bits = 24;
|
|
int protocol = 1;
|
|
int repeat = 10;
|
|
int pulse = 350;
|
|
|
|
char dataBufUc[XdrvMailbox.data_len + 1];
|
|
UpperCase(dataBufUc, XdrvMailbox.data);
|
|
StaticJsonBuffer<150> jsonBuf;
|
|
JsonObject &root = jsonBuf.parseObject(dataBufUc);
|
|
if (root.success()) {
|
|
|
|
char parm_uc[10];
|
|
data = strtoul(root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_DATA))], nullptr, 0);
|
|
bits = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_BITS))];
|
|
protocol = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_PROTOCOL))];
|
|
repeat = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_REPEAT))];
|
|
pulse = root[UpperCase_P(parm_uc, PSTR(D_JSON_RF_PULSE))];
|
|
} else {
|
|
|
|
char *p;
|
|
uint8_t i = 0;
|
|
for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 5; str = strtok_r(nullptr, ", ", &p)) {
|
|
switch (i++) {
|
|
case 0:
|
|
data = strtoul(str, nullptr, 0);
|
|
break;
|
|
case 1:
|
|
bits = atoi(str);
|
|
break;
|
|
case 2:
|
|
protocol = atoi(str);
|
|
break;
|
|
case 3:
|
|
repeat = atoi(str);
|
|
break;
|
|
case 4:
|
|
pulse = atoi(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!protocol) { protocol = 1; }
|
|
mySwitch.setProtocol(protocol);
|
|
if (!pulse) { pulse = 350; }
|
|
mySwitch.setPulseLength(pulse);
|
|
if (!repeat) { repeat = 10; }
|
|
mySwitch.setRepeatTransmit(repeat);
|
|
if (!bits) { bits = 24; }
|
|
if (data) {
|
|
mySwitch.send(data, bits);
|
|
ResponseCmndDone();
|
|
} else {
|
|
error = true;
|
|
}
|
|
} else {
|
|
error = true;
|
|
}
|
|
if (error) {
|
|
Response_P(PSTR("{\"" D_CMND_RFSEND "\":\"" D_JSON_NO " " D_JSON_RF_DATA ", " D_JSON_RF_BITS ", " D_JSON_RF_PROTOCOL ", " D_JSON_RF_REPEAT " " D_JSON_OR " " D_JSON_RF_PULSE "\"}"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv17(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if ((pin[GPIO_RFSEND] < 99) || (pin[GPIO_RFRECV] < 99)) {
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (pin[GPIO_RFRECV] < 99) {
|
|
RfReceiveCheck();
|
|
}
|
|
break;
|
|
case FUNC_COMMAND:
|
|
if (pin[GPIO_RFSEND] < 99) {
|
|
result = DecodeCommand(kRfSendCommands, RfSendCommand);
|
|
}
|
|
break;
|
|
case FUNC_INIT:
|
|
RfInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_18_armtronix_dimmers.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_18_armtronix_dimmers.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_ARMTRONIX_DIMMERS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_18 18
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *ArmtronixSerial = nullptr;
|
|
|
|
struct ARMTRONIX {
|
|
bool ignore_dim = false;
|
|
int8_t wifi_state = -2;
|
|
int8_t dim_state[2];
|
|
int8_t knob_state[2];
|
|
} Armtronix;
|
|
|
|
|
|
|
|
|
|
|
|
bool ArmtronixSetChannels(void)
|
|
{
|
|
LightSerial2Duty(((uint8_t*)XdrvMailbox.data)[0], ((uint8_t*)XdrvMailbox.data)[1]);
|
|
return true;
|
|
}
|
|
|
|
void LightSerial2Duty(uint8_t duty1, uint8_t duty2)
|
|
{
|
|
if (ArmtronixSerial && !Armtronix.ignore_dim) {
|
|
duty1 = ((float)duty1)/2.575757;
|
|
duty2 = ((float)duty2)/2.575757;
|
|
Armtronix.dim_state[0] = duty1;
|
|
Armtronix.dim_state[1] = duty2;
|
|
ArmtronixSerial->print("Dimmer1:");
|
|
ArmtronixSerial->print(duty1);
|
|
ArmtronixSerial->print("\nDimmer2:");
|
|
ArmtronixSerial->println(duty2);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Send Serial Packet Dim Values=%d,%d"), Armtronix.dim_state[0],Armtronix.dim_state[1]);
|
|
|
|
} else {
|
|
Armtronix.ignore_dim = false;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Send Dim Level skipped due to already set. Value=%d,%d"), Armtronix.dim_state[0],Armtronix.dim_state[1]);
|
|
|
|
}
|
|
}
|
|
|
|
void ArmtronixRequestState(void)
|
|
{
|
|
if (ArmtronixSerial) {
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("ARM: Request MCU state"));
|
|
ArmtronixSerial->println("Status");
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ArmtronixModuleSelected(void)
|
|
{
|
|
devices_present++;
|
|
light_type = LT_SERIAL2;
|
|
return true;
|
|
}
|
|
|
|
void ArmtronixInit(void)
|
|
{
|
|
Armtronix.dim_state[0] = -1;
|
|
Armtronix.dim_state[1] = -1;
|
|
Armtronix.knob_state[0] = -1;
|
|
Armtronix.knob_state[1] = -1;
|
|
ArmtronixSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2);
|
|
if (ArmtronixSerial->begin(115200)) {
|
|
if (ArmtronixSerial->hardwareSerial()) { ClaimSerial(); }
|
|
ArmtronixSerial->println("Status");
|
|
}
|
|
}
|
|
|
|
void ArmtronixSerialInput(void)
|
|
{
|
|
String answer;
|
|
int8_t newDimState[2];
|
|
uint8_t temp;
|
|
int commaIndex;
|
|
char scmnd[20];
|
|
if (ArmtronixSerial->available()) {
|
|
yield();
|
|
answer = ArmtronixSerial->readStringUntil('\n');
|
|
if (answer.substring(0,7) == "Status:") {
|
|
commaIndex = 6;
|
|
for (uint32_t i =0; i<2; i++) {
|
|
newDimState[i] = answer.substring(commaIndex+1,answer.indexOf(',',commaIndex+1)).toInt();
|
|
if (newDimState[i] != Armtronix.dim_state[i]) {
|
|
temp = ((float)newDimState[i])*1.01010101010101;
|
|
Armtronix.dim_state[i] = newDimState[i];
|
|
Armtronix.ignore_dim = true;
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_CHANNEL "%d %d"),i+1, temp);
|
|
ExecuteCommand(scmnd,SRC_SWITCH);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Send CMND_CHANNEL=%s"), scmnd );
|
|
}
|
|
commaIndex = answer.indexOf(',',commaIndex+1);
|
|
}
|
|
Armtronix.knob_state[0] = answer.substring(commaIndex+1,answer.indexOf(',',commaIndex+1)).toInt();
|
|
commaIndex = answer.indexOf(',',commaIndex+1);
|
|
Armtronix.knob_state[1] = answer.substring(commaIndex+1,answer.indexOf(',',commaIndex+1)).toInt();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArmtronixSetWifiLed(void)
|
|
{
|
|
uint8_t wifi_state = 0x02;
|
|
|
|
switch (WifiState()) {
|
|
case WIFI_MANAGER:
|
|
wifi_state = 0x01;
|
|
break;
|
|
case WIFI_RESTART:
|
|
wifi_state = 0x03;
|
|
break;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ARM: Set WiFi LED to state %d (%d)"), wifi_state, WifiState());
|
|
|
|
char state = '0' + ((wifi_state & 1) > 0);
|
|
ArmtronixSerial->print("Setled:");
|
|
ArmtronixSerial->write(state);
|
|
ArmtronixSerial->write(',');
|
|
state = '0' + ((wifi_state & 2) > 0);
|
|
ArmtronixSerial->write(state);
|
|
ArmtronixSerial->write(10);
|
|
Armtronix.wifi_state = WifiState();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv18(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (ARMTRONIX_DIMMERS == my_module_type) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (ArmtronixSerial) { ArmtronixSerialInput(); }
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
result = ArmtronixModuleSelected();
|
|
break;
|
|
case FUNC_INIT:
|
|
ArmtronixInit();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
if (ArmtronixSerial) {
|
|
if (Armtronix.wifi_state!=WifiState()) { ArmtronixSetWifiLed(); }
|
|
if (uptime &1) {
|
|
ArmtronixSerial->println("Status");
|
|
}
|
|
}
|
|
break;
|
|
case FUNC_SET_CHANNELS:
|
|
result = ArmtronixSetChannels();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_19_ps16dz_dimmer.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_19_ps16dz_dimmer.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_PS_16_DZ
|
|
|
|
|
|
|
|
|
|
#define XDRV_19 19
|
|
|
|
#define PS16DZ_BUFFER_SIZE 80
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *PS16DZSerial = nullptr;
|
|
|
|
struct PS16DZ {
|
|
char *rx_buffer = nullptr;
|
|
int byte_counter = 0;
|
|
uint8_t dimmer = 0;
|
|
} Ps16dz;
|
|
|
|
|
|
|
|
|
|
|
|
void PS16DZSerialSend(const char *tx_buffer)
|
|
{
|
|
|
|
|
|
PS16DZSerial->print(tx_buffer);
|
|
PS16DZSerial->write(0x1B);
|
|
PS16DZSerial->flush();
|
|
}
|
|
|
|
void PS16DZSerialSendOk(void)
|
|
{
|
|
char tx_buffer[16];
|
|
snprintf_P(tx_buffer, sizeof(tx_buffer), PSTR("AT+SEND=ok"));
|
|
PS16DZSerialSend(tx_buffer);
|
|
}
|
|
|
|
|
|
|
|
|
|
void PS16DZSerialSendUpdateCommand(void)
|
|
{
|
|
uint8_t light_state_dimmer = light_state.getDimmer();
|
|
|
|
light_state_dimmer = (light_state_dimmer < Settings.dimmer_hw_min) ? Settings.dimmer_hw_min : light_state_dimmer;
|
|
light_state_dimmer = (light_state_dimmer > Settings.dimmer_hw_max) ? Settings.dimmer_hw_max : light_state_dimmer;
|
|
|
|
char tx_buffer[80];
|
|
snprintf_P(tx_buffer, sizeof(tx_buffer), PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"bright\":%d"),
|
|
LocalTime(), millis()%1000, power?"on":"off", light_state_dimmer);
|
|
|
|
PS16DZSerialSend(tx_buffer);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void PS16DZSerialInput(void)
|
|
{
|
|
char scmnd[20];
|
|
while (PS16DZSerial->available()) {
|
|
yield();
|
|
uint8_t serial_in_byte = PS16DZSerial->read();
|
|
if (serial_in_byte != 0x1B) {
|
|
if (Ps16dz.byte_counter >= PS16DZ_BUFFER_SIZE - 1) {
|
|
memset(Ps16dz.rx_buffer, 0, PS16DZ_BUFFER_SIZE);
|
|
Ps16dz.byte_counter = 0;
|
|
}
|
|
if (Ps16dz.byte_counter || (!Ps16dz.byte_counter && ('A' == serial_in_byte))) {
|
|
Ps16dz.rx_buffer[Ps16dz.byte_counter++] = serial_in_byte;
|
|
}
|
|
} else {
|
|
Ps16dz.rx_buffer[Ps16dz.byte_counter++] = 0x00;
|
|
|
|
|
|
|
|
|
|
if (!strncmp(Ps16dz.rx_buffer+3, "RESULT", 6)) {
|
|
|
|
}
|
|
else if (!strncmp(Ps16dz.rx_buffer+3, "UPDATE", 6)) {
|
|
|
|
char *end_str;
|
|
char *string = Ps16dz.rx_buffer+10;
|
|
char *token = strtok_r(string, ",", &end_str);
|
|
|
|
bool is_switch_change = false;
|
|
bool is_brightness_change = false;
|
|
|
|
while (token != nullptr) {
|
|
char* end_token;
|
|
char* token2 = strtok_r(token, ":", &end_token);
|
|
char* token3 = strtok_r(nullptr, ":", &end_token);
|
|
|
|
if (!strncmp(token2, "\"switch\"", 8)) {
|
|
bool switch_state = !strncmp(token3, "\"on\"", 4) ? true : false;
|
|
|
|
|
|
|
|
is_switch_change = (switch_state != power);
|
|
if (is_switch_change) {
|
|
ExecuteCommandPower(1, switch_state, SRC_SWITCH);
|
|
}
|
|
}
|
|
else if (!strncmp(token2, "\"bright\"", 8)) {
|
|
Ps16dz.dimmer = atoi(token3);
|
|
|
|
|
|
|
|
is_brightness_change = Ps16dz.dimmer != Settings.light_dimmer;
|
|
if (power && (Ps16dz.dimmer > 0) && is_brightness_change) {
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_DIMMER " %d"), Ps16dz.dimmer);
|
|
ExecuteCommand(scmnd, SRC_SWITCH);
|
|
}
|
|
}
|
|
else if (!strncmp(token2, "\"sequence\"", 10)) {
|
|
|
|
|
|
|
|
}
|
|
token = strtok_r(nullptr, ",", &end_str);
|
|
}
|
|
|
|
if (!is_brightness_change) {
|
|
|
|
|
|
|
|
PS16DZSerialSendOk();
|
|
}
|
|
}
|
|
else if (!strncmp(Ps16dz.rx_buffer+3, "SETTING", 7)) {
|
|
|
|
|
|
if (!Settings.flag.button_restrict) {
|
|
int state = WIFI_MANAGER;
|
|
if (!strncmp(Ps16dz.rx_buffer+10, "=exit", 5)) { state = WIFI_RETRY; }
|
|
if (state != Settings.sta_config) {
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_WIFICONFIG " %d"), state);
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
}
|
|
}
|
|
}
|
|
memset(Ps16dz.rx_buffer, 0, PS16DZ_BUFFER_SIZE);
|
|
Ps16dz.byte_counter = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PS16DZSerialSendUpdateCommandIfRequired(void)
|
|
{
|
|
if (!PS16DZSerial) { return true; }
|
|
|
|
bool is_switch_change = (XdrvMailbox.payload != SRC_SWITCH);
|
|
bool is_brightness_change = (light_state.getDimmer() != Ps16dz.dimmer);
|
|
|
|
if (is_switch_change || is_brightness_change) {
|
|
PS16DZSerialSendUpdateCommand();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PS16DZInit(void)
|
|
{
|
|
Ps16dz.rx_buffer = (char*)(malloc(PS16DZ_BUFFER_SIZE));
|
|
if (Ps16dz.rx_buffer != nullptr) {
|
|
PS16DZSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2);
|
|
if (PS16DZSerial->begin(19200)) {
|
|
if (PS16DZSerial->hardwareSerial()) { ClaimSerial(); }
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PS16DZModuleSelected(void)
|
|
{
|
|
devices_present++;
|
|
light_type = LT_SERIAL1;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv19(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (PS_16_DZ == my_module_type) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (PS16DZSerial) { PS16DZSerialInput(); }
|
|
break;
|
|
case FUNC_SET_DEVICE_POWER:
|
|
case FUNC_SET_CHANNELS:
|
|
result = PS16DZSerialSendUpdateCommandIfRequired();
|
|
break;
|
|
case FUNC_INIT:
|
|
PS16DZInit();
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
result = PS16DZModuleSelected();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_20_hue.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_20_hue.ino"
|
|
#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && defined(USE_LIGHT)
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_20_hue.ino"
|
|
#define XDRV_20 20
|
|
|
|
const char HUE_RESPONSE[] PROGMEM =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"HOST: 239.255.255.250:1900\r\n"
|
|
"CACHE-CONTROL: max-age=100\r\n"
|
|
"EXT:\r\n"
|
|
"LOCATION: http://%s:80/description.xml\r\n"
|
|
"SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n"
|
|
"hue-bridgeid: %s\r\n";
|
|
const char HUE_ST1[] PROGMEM =
|
|
"ST: upnp:rootdevice\r\n"
|
|
"USN: uuid:%s::upnp:rootdevice\r\n"
|
|
"\r\n";
|
|
const char HUE_ST2[] PROGMEM =
|
|
"ST: uuid:%s\r\n"
|
|
"USN: uuid:%s\r\n"
|
|
"\r\n";
|
|
const char HUE_ST3[] PROGMEM =
|
|
"ST: urn:schemas-upnp-org:device:basic:1\r\n"
|
|
"USN: uuid:%s\r\n"
|
|
"\r\n";
|
|
|
|
String HueBridgeId(void)
|
|
{
|
|
String temp = WiFi.macAddress();
|
|
temp.replace(":", "");
|
|
String bridgeid = temp.substring(0, 6) + "FFFE" + temp.substring(6);
|
|
return bridgeid;
|
|
}
|
|
|
|
String HueSerialnumber(void)
|
|
{
|
|
String serial = WiFi.macAddress();
|
|
serial.replace(":", "");
|
|
serial.toLowerCase();
|
|
return serial;
|
|
}
|
|
|
|
String HueUuid(void)
|
|
{
|
|
String uuid = F("f6543a06-da50-11ba-8d8f-");
|
|
uuid += HueSerialnumber();
|
|
return uuid;
|
|
}
|
|
|
|
void HueRespondToMSearch(void)
|
|
{
|
|
char message[TOPSZ];
|
|
|
|
TickerMSearch.detach();
|
|
if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
|
|
char response[320];
|
|
snprintf_P(response, sizeof(response), HUE_RESPONSE, WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
|
|
int len = strlen(response);
|
|
|
|
snprintf_P(response + len, sizeof(response) - len, HUE_ST1, HueUuid().c_str());
|
|
PortUdp.write(response);
|
|
PortUdp.endPacket();
|
|
|
|
snprintf_P(response + len, sizeof(response) - len, HUE_ST2, HueUuid().c_str(), HueUuid().c_str());
|
|
PortUdp.write(response);
|
|
PortUdp.endPacket();
|
|
|
|
snprintf_P(response + len, sizeof(response) - len, HUE_ST3, HueUuid().c_str());
|
|
PortUdp.write(response);
|
|
PortUdp.endPacket();
|
|
|
|
snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
|
|
} else {
|
|
snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
|
|
}
|
|
|
|
PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
|
|
message, udp_remote_ip.toString().c_str(), udp_remote_port);
|
|
|
|
udp_response_mutex = false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char HUE_DESCRIPTION_XML[] PROGMEM =
|
|
"<?xml version=\"1.0\"?>"
|
|
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
|
|
"<specVersion>"
|
|
"<major>1</major>"
|
|
"<minor>0</minor>"
|
|
"</specVersion>"
|
|
|
|
"<URLBase>http://{x1:80/</URLBase>"
|
|
"<device>"
|
|
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
|
|
"<friendlyName>Amazon-Echo-HA-Bridge ({x1)</friendlyName>"
|
|
|
|
"<manufacturer>Royal Philips Electronics</manufacturer>"
|
|
"<manufacturerURL>http://www.philips.com</manufacturerURL>"
|
|
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
|
|
"<modelName>Philips hue bridge 2012</modelName>"
|
|
"<modelNumber>929000226503</modelNumber>"
|
|
"<serialNumber>{x3</serialNumber>"
|
|
"<UDN>uuid:{x2</UDN>"
|
|
"</device>"
|
|
"</root>\r\n"
|
|
"\r\n";
|
|
const char HUE_LIGHTS_STATUS_JSON1[] PROGMEM =
|
|
"{\"on\":{state},"
|
|
"{light_status}"
|
|
"\"alert\":\"none\","
|
|
"\"effect\":\"none\","
|
|
"\"reachable\":true}";
|
|
const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
|
|
",\"type\":\"Extended color light\","
|
|
"\"name\":\"{j1\","
|
|
"\"modelid\":\"LCT007\","
|
|
"\"uniqueid\":\"{j2\","
|
|
"\"swversion\":\"5.50.1.19085\"}";
|
|
const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
|
|
"{\"name\":\"Group 0\","
|
|
"\"lights\":[{l1],"
|
|
"\"type\":\"LightGroup\","
|
|
"\"action\":";
|
|
|
|
const char HueConfigResponse_JSON[] PROGMEM =
|
|
"{\"name\":\"Philips hue\","
|
|
"\"mac\":\"{ma\","
|
|
"\"dhcp\":true,"
|
|
"\"ipaddress\":\"{ip\","
|
|
"\"netmask\":\"{ms\","
|
|
"\"gateway\":\"{gw\","
|
|
"\"proxyaddress\":\"none\","
|
|
"\"proxyport\":0,"
|
|
"\"bridgeid\":\"{br\","
|
|
"\"UTC\":\"{dt\","
|
|
"\"whitelist\":{\"{id\":{"
|
|
"\"last use date\":\"{dt\","
|
|
"\"create date\":\"{dt\","
|
|
"\"name\":\"Remote\"}},"
|
|
"\"swversion\":\"01041302\","
|
|
"\"apiversion\":\"1.17.0\","
|
|
"\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
|
|
"\"linkbutton\":false,"
|
|
"\"portalservices\":false"
|
|
"}";
|
|
const char HUE_LIGHT_RESPONSE_JSON[] PROGMEM =
|
|
"{\"success\":{\"/lights/{id/state/{cm\":{re}}";
|
|
const char HUE_ERROR_JSON[] PROGMEM =
|
|
"[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
|
|
|
|
|
|
|
|
String GetHueDeviceId(uint8_t id)
|
|
{
|
|
String deviceid = WiFi.macAddress() + F(":00:11-") + String(id);
|
|
deviceid.toLowerCase();
|
|
return deviceid;
|
|
}
|
|
|
|
String GetHueUserId(void)
|
|
{
|
|
char userid[7];
|
|
|
|
snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP.getChipId());
|
|
return String(userid);
|
|
}
|
|
|
|
void HandleUpnpSetupHue(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_HUE_BRIDGE_SETUP));
|
|
String description_xml = FPSTR(HUE_DESCRIPTION_XML);
|
|
description_xml.replace("{x1", WiFi.localIP().toString());
|
|
description_xml.replace("{x2", HueUuid());
|
|
description_xml.replace("{x3", HueSerialnumber());
|
|
WSSend(200, CT_XML, description_xml);
|
|
}
|
|
|
|
void HueNotImplemented(String *path)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
|
|
|
|
WSSend(200, CT_JSON, "{}");
|
|
}
|
|
|
|
void HueConfigResponse(String *response)
|
|
{
|
|
*response += FPSTR(HueConfigResponse_JSON);
|
|
response->replace("{ma", WiFi.macAddress());
|
|
response->replace("{ip", WiFi.localIP().toString());
|
|
response->replace("{ms", WiFi.subnetMask().toString());
|
|
response->replace("{gw", WiFi.gatewayIP().toString());
|
|
response->replace("{br", HueBridgeId());
|
|
response->replace("{dt", GetDateAndTime(DT_UTC));
|
|
response->replace("{id", GetHueUserId());
|
|
}
|
|
|
|
void HueConfig(String *path)
|
|
{
|
|
String response = "";
|
|
HueConfigResponse(&response);
|
|
WSSend(200, CT_JSON, response);
|
|
}
|
|
|
|
|
|
|
|
bool g_gotct = false;
|
|
|
|
|
|
|
|
|
|
uint16_t prev_hue = 0;
|
|
uint8_t prev_sat = 0;
|
|
uint8_t prev_bri = 254;
|
|
uint16_t prev_ct = 254;
|
|
char prev_x_str[24] = "\0";
|
|
char prev_y_str[24] = "\0";
|
|
|
|
uint8_t getLocalLightSubtype(uint8_t device) {
|
|
if (light_type) {
|
|
if (device >= Light.device) {
|
|
if (Settings.flag3.pwm_multi_channels) {
|
|
return LST_SINGLE;
|
|
} else {
|
|
return Light.subtype;
|
|
}
|
|
} else {
|
|
return LST_NONE;
|
|
}
|
|
} else {
|
|
return LST_NONE;
|
|
}
|
|
}
|
|
|
|
void HueLightStatus1(uint8_t device, String *response)
|
|
{
|
|
uint16_t ct = 0;
|
|
uint8_t color_mode;
|
|
String light_status = "";
|
|
uint16_t hue = 0;
|
|
uint8_t sat = 0;
|
|
uint8_t bri = 254;
|
|
uint32_t echo_gen = findEchoGeneration();
|
|
|
|
|
|
uint8_t local_light_subtype = getLocalLightSubtype(device);
|
|
|
|
bri = LightGetBri(device);
|
|
if (bri > 254) bri = 254;
|
|
if (bri < 1) bri = 1;
|
|
|
|
#ifdef USE_SHUTTER
|
|
if (ShutterState(device)) {
|
|
bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
|
|
}
|
|
#endif
|
|
|
|
if (light_type) {
|
|
light_state.getHSB(&hue, &sat, nullptr);
|
|
|
|
if ((bri > prev_bri ? bri - prev_bri : prev_bri - bri) < 1)
|
|
bri = prev_bri;
|
|
|
|
if (sat > 254) sat = 254;
|
|
if ((sat > prev_sat ? sat - prev_sat : prev_sat - sat) < 1) {
|
|
sat = prev_sat;
|
|
} else {
|
|
prev_x_str[0] = prev_y_str[0] = 0;
|
|
}
|
|
|
|
hue = changeUIntScale(hue, 0, 359, 0, 65535);
|
|
if ((hue > prev_hue ? hue - prev_hue : prev_hue - hue) < 400) {
|
|
hue = prev_hue;
|
|
} else {
|
|
prev_x_str[0] = prev_y_str[0] = 0;
|
|
}
|
|
|
|
color_mode = light_state.getColorMode();
|
|
ct = light_state.getCT();
|
|
if (LCM_RGB == color_mode) { g_gotct = false; }
|
|
if (LCM_CT == color_mode) { g_gotct = true; }
|
|
|
|
|
|
|
|
if ((ct > prev_ct ? ct - prev_ct : prev_ct - ct) < 1)
|
|
ct = prev_ct;
|
|
|
|
|
|
|
|
}
|
|
|
|
*response += FPSTR(HUE_LIGHTS_STATUS_JSON1);
|
|
response->replace("{state}", (power & (1 << (device-1))) ? "true" : "false");
|
|
|
|
if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) {
|
|
light_status += "\"bri\":";
|
|
light_status += String(bri);
|
|
light_status += ",";
|
|
}
|
|
if (LST_COLDWARM <= local_light_subtype) {
|
|
light_status += F("\"colormode\":\"");
|
|
light_status += (g_gotct ? "ct" : "hs");
|
|
light_status += "\",";
|
|
}
|
|
if (LST_RGB <= local_light_subtype) {
|
|
if (prev_x_str[0] && prev_y_str[0]) {
|
|
light_status += "\"xy\":[";
|
|
light_status += prev_x_str;
|
|
light_status += ",";
|
|
light_status += prev_y_str;
|
|
light_status += "],";
|
|
} else {
|
|
float x, y;
|
|
light_state.getXY(&x, &y);
|
|
light_status += "\"xy\":[";
|
|
light_status += String(x, 5);
|
|
light_status += ",";
|
|
light_status += String(y, 5);
|
|
light_status += "],";
|
|
}
|
|
light_status += "\"hue\":";
|
|
light_status += String(hue);
|
|
light_status += ",";
|
|
|
|
light_status += "\"sat\":";
|
|
light_status += String(sat);
|
|
light_status += ",";
|
|
}
|
|
if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) {
|
|
light_status += "\"ct\":";
|
|
light_status += String(ct > 0 ? ct : 284);
|
|
light_status += ",";
|
|
}
|
|
response->replace("{light_status}", light_status);
|
|
}
|
|
|
|
|
|
|
|
bool HueActive(uint8_t device) {
|
|
if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
|
|
return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
|
|
}
|
|
|
|
void HueLightStatus2(uint8_t device, String *response)
|
|
{
|
|
*response += FPSTR(HUE_LIGHTS_STATUS_JSON2);
|
|
if (device <= MAX_FRIENDLYNAMES) {
|
|
response->replace("{j1", SettingsText(SET_FRIENDLYNAME1 +device -1));
|
|
} else {
|
|
char fname[33];
|
|
strcpy(fname, SettingsText(SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1));
|
|
uint32_t fname_len = strlen(fname);
|
|
if (fname_len > 30) { fname_len = 30; }
|
|
fname[fname_len++] = '-';
|
|
if (device - MAX_FRIENDLYNAMES < 10) {
|
|
fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
|
|
} else {
|
|
fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
|
|
}
|
|
fname[fname_len] = 0x00;
|
|
|
|
response->replace("{j1", fname);
|
|
}
|
|
response->replace("{j2", GetHueDeviceId(device));
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t EncodeLightId(uint8_t relay_id)
|
|
{
|
|
uint8_t mac[6];
|
|
WiFi.macAddress(mac);
|
|
uint32_t id = 0;
|
|
|
|
if (relay_id >= 32) {
|
|
relay_id = 0;
|
|
}
|
|
if (relay_id > 15) {
|
|
id = (1 << 28);
|
|
}
|
|
|
|
id |= (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (relay_id & 0xF);
|
|
return id;
|
|
}
|
|
|
|
|
|
|
|
uint32_t DecodeLightId(uint32_t hue_id) {
|
|
uint8_t relay_id = hue_id & 0xF;
|
|
if (hue_id & (1 << 28)) {
|
|
relay_id += 16;
|
|
}
|
|
if (0 == relay_id) {
|
|
relay_id = 32;
|
|
}
|
|
return relay_id;
|
|
}
|
|
|
|
static const char * FIRST_GEN_UA[] = {
|
|
"AEOBC",
|
|
};
|
|
|
|
|
|
uint32_t findEchoGeneration(void) {
|
|
|
|
String user_agent = WebServer->header("User-Agent");
|
|
uint32_t gen = 2;
|
|
|
|
for (uint32_t i = 0; i < sizeof(FIRST_GEN_UA)/sizeof(char*); i++) {
|
|
if (user_agent.indexOf(FIRST_GEN_UA[i]) >= 0) {
|
|
gen = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (0 == user_agent.length()) {
|
|
gen = 1;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, D_LOG_HTTP D_HUE " User-Agent: %s, gen=%d", user_agent.c_str(), gen);
|
|
|
|
return gen;
|
|
}
|
|
|
|
void HueGlobalConfig(String *path) {
|
|
String response;
|
|
uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present;
|
|
|
|
path->remove(0,1);
|
|
response = F("{\"lights\":{");
|
|
bool appending = false;
|
|
for (uint32_t i = 1; i <= maxhue; i++) {
|
|
if (HueActive(i)) {
|
|
if (appending) { response += ","; }
|
|
response += "\"";
|
|
response += EncodeLightId(i);
|
|
response += F("\":{\"state\":");
|
|
HueLightStatus1(i, &response);
|
|
HueLightStatus2(i, &response);
|
|
appending = true;
|
|
}
|
|
}
|
|
response += F("},\"groups\":{},\"schedules\":{},\"config\":");
|
|
HueConfigResponse(&response);
|
|
response += "}";
|
|
WSSend(200, CT_JSON, response);
|
|
}
|
|
|
|
void HueAuthentication(String *path)
|
|
{
|
|
char response[38];
|
|
|
|
snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
|
|
WSSend(200, CT_JSON, response);
|
|
}
|
|
|
|
void HueLights(String *path)
|
|
{
|
|
|
|
|
|
|
|
String response;
|
|
int code = 200;
|
|
uint16_t tmp = 0;
|
|
uint16_t hue = 0;
|
|
uint8_t sat = 0;
|
|
uint8_t bri = 254;
|
|
uint16_t ct = 0;
|
|
bool resp = false;
|
|
bool on = false;
|
|
bool change = false;
|
|
uint8_t device = 1;
|
|
uint8_t local_light_subtype = Light.subtype;
|
|
uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present;
|
|
|
|
path->remove(0,path->indexOf("/lights"));
|
|
if (path->endsWith("/lights")) {
|
|
response = "{";
|
|
bool appending = false;
|
|
for (uint32_t i = 1; i <= maxhue; i++) {
|
|
if (HueActive(i)) {
|
|
if (appending) { response += ","; }
|
|
response += "\"";
|
|
response += EncodeLightId(i);
|
|
response += F("\":{\"state\":");
|
|
HueLightStatus1(i, &response);
|
|
HueLightStatus2(i, &response);
|
|
appending = true;
|
|
}
|
|
}
|
|
#ifdef USE_SCRIPT_HUE
|
|
Script_Check_Hue(&response);
|
|
#endif
|
|
response += "}";
|
|
}
|
|
else if (path->endsWith("/state")) {
|
|
path->remove(0,8);
|
|
path->remove(path->indexOf("/state"));
|
|
device = DecodeLightId(atoi(path->c_str()));
|
|
|
|
#ifdef USE_SCRIPT_HUE
|
|
if (device>devices_present) {
|
|
return Script_Handle_Hue(path);
|
|
}
|
|
#endif
|
|
|
|
if ((device < 1) || (device > maxhue)) {
|
|
device = 1;
|
|
}
|
|
local_light_subtype = getLocalLightSubtype(device);
|
|
|
|
if (WebServer->args()) {
|
|
response = "[";
|
|
|
|
StaticJsonBuffer<400> jsonBuffer;
|
|
JsonObject &hue_json = jsonBuffer.parseObject(WebServer->arg((WebServer->args())-1));
|
|
if (hue_json.containsKey("on")) {
|
|
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(EncodeLightId(device)));
|
|
response.replace("{cm", "on");
|
|
|
|
#ifdef USE_SHUTTER
|
|
if (ShutterState(device)) {
|
|
if (!change) {
|
|
on = hue_json["on"];
|
|
bri = on ? 1.0f : 0.0f;
|
|
change = true;
|
|
}
|
|
response.replace("{re", on ? "true" : "false");
|
|
} else {
|
|
#endif
|
|
on = hue_json["on"];
|
|
switch(on)
|
|
{
|
|
case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE);
|
|
response.replace("{re", "false");
|
|
break;
|
|
case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE);
|
|
response.replace("{re", "true");
|
|
break;
|
|
default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false");
|
|
break;
|
|
}
|
|
resp = true;
|
|
#ifdef USE_SHUTTER
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (light_type && (local_light_subtype >= LST_SINGLE)) {
|
|
if (!Settings.flag3.pwm_multi_channels) {
|
|
light_state.getHSB(&hue, &sat, nullptr);
|
|
bri = light_state.getBri();
|
|
ct = light_state.getCT();
|
|
uint8_t color_mode = light_state.getColorMode();
|
|
if (LCM_RGB == color_mode) { g_gotct = false; }
|
|
if (LCM_CT == color_mode) { g_gotct = true; }
|
|
|
|
} else {
|
|
bri = LightGetBri(device);
|
|
}
|
|
}
|
|
prev_x_str[0] = prev_y_str[0] = 0;
|
|
|
|
if (hue_json.containsKey("bri")) {
|
|
tmp = hue_json["bri"];
|
|
prev_bri = bri = tmp;
|
|
|
|
if (254 <= bri) { bri = 255; }
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(device));
|
|
response.replace("{cm", "bri");
|
|
response.replace("{re", String(tmp));
|
|
if (LST_SINGLE <= Light.subtype) {
|
|
change = true;
|
|
}
|
|
resp = true;
|
|
}
|
|
|
|
|
|
if (hue_json.containsKey("xy")) {
|
|
float x, y;
|
|
x = hue_json["xy"][0];
|
|
y = hue_json["xy"][1];
|
|
const String &x_str = hue_json["xy"][0];
|
|
const String &y_str = hue_json["xy"][1];
|
|
x_str.toCharArray(prev_x_str, sizeof(prev_x_str));
|
|
y_str.toCharArray(prev_y_str, sizeof(prev_y_str));
|
|
|
|
uint8_t rr,gg,bb;
|
|
LightStateClass::XyToRgb(x, y, &rr, &gg, &bb);
|
|
LightStateClass::RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
|
|
prev_hue = changeUIntScale(hue, 0, 359, 0, 65535);
|
|
prev_sat = (sat > 254 ? 254 : sat);
|
|
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(device));
|
|
response.replace("{cm", "xy");
|
|
response.replace("{re", "[" + x_str + "," + y_str + "]");
|
|
g_gotct = false;
|
|
resp = true;
|
|
change = true;
|
|
}
|
|
if (hue_json.containsKey("hue")) {
|
|
tmp = hue_json["hue"];
|
|
prev_hue = tmp;
|
|
|
|
hue = changeUIntScale(tmp, 0, 65535, 0, 359);
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(device));
|
|
response.replace("{cm", "hue");
|
|
response.replace("{re", String(tmp));
|
|
if (LST_RGB <= Light.subtype) {
|
|
g_gotct = false;
|
|
change = true;
|
|
}
|
|
resp = true;
|
|
}
|
|
if (hue_json.containsKey("sat")) {
|
|
tmp = hue_json["sat"];
|
|
prev_sat = sat = tmp;
|
|
|
|
if (254 <= sat) { sat = 255; }
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(device));
|
|
response.replace("{cm", "sat");
|
|
response.replace("{re", String(tmp));
|
|
if (LST_RGB <= Light.subtype) {
|
|
g_gotct = false;
|
|
change = true;
|
|
}
|
|
resp = true;
|
|
}
|
|
if (hue_json.containsKey("ct")) {
|
|
ct = hue_json["ct"];
|
|
prev_ct = ct;
|
|
if (resp) { response += ","; }
|
|
response += FPSTR(HUE_LIGHT_RESPONSE_JSON);
|
|
response.replace("{id", String(device));
|
|
response.replace("{cm", "ct");
|
|
response.replace("{re", String(ct));
|
|
if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
|
|
g_gotct = true;
|
|
change = true;
|
|
}
|
|
resp = true;
|
|
}
|
|
if (change) {
|
|
#ifdef USE_SHUTTER
|
|
if (ShutterState(device)) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
|
|
ShutterSetPosition(device, bri * 100.0f );
|
|
} else
|
|
#endif
|
|
if (light_type && (local_light_subtype > LST_NONE)) {
|
|
if (!Settings.flag3.pwm_multi_channels) {
|
|
if (g_gotct) {
|
|
light_controller.changeCTB(ct, bri);
|
|
} else {
|
|
light_controller.changeHSB(hue, sat, bri);
|
|
}
|
|
LightPreparePower();
|
|
} else {
|
|
LightSetBri(device, bri);
|
|
}
|
|
if (LST_COLDWARM <= local_light_subtype) {
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
|
|
} else {
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
|
|
}
|
|
XdrvRulesProcess();
|
|
}
|
|
change = false;
|
|
}
|
|
response += "]";
|
|
if (2 == response.length()) {
|
|
response = FPSTR(HUE_ERROR_JSON);
|
|
}
|
|
}
|
|
else {
|
|
response = FPSTR(HUE_ERROR_JSON);
|
|
}
|
|
}
|
|
else if(path->indexOf("/lights/") >= 0) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, "/lights path=%s", path->c_str());
|
|
path->remove(0,8);
|
|
device = DecodeLightId(atoi(path->c_str()));
|
|
|
|
#ifdef USE_SCRIPT_HUE
|
|
if (device>devices_present) {
|
|
Script_HueStatus(&response,device-devices_present-1);
|
|
goto exit;
|
|
}
|
|
#endif
|
|
|
|
if ((device < 1) || (device > maxhue)) {
|
|
device = 1;
|
|
}
|
|
response += F("{\"state\":");
|
|
HueLightStatus1(device, &response);
|
|
HueLightStatus2(device, &response);
|
|
}
|
|
else {
|
|
response = "{}";
|
|
code = 406;
|
|
}
|
|
exit:
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
|
|
WSSend(code, CT_JSON, response);
|
|
}
|
|
|
|
void HueGroups(String *path)
|
|
{
|
|
|
|
|
|
|
|
String response = "{}";
|
|
uint8_t maxhue = (devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : devices_present;
|
|
|
|
if (path->endsWith("/0")) {
|
|
response = FPSTR(HUE_GROUP0_STATUS_JSON);
|
|
String lights = F("\"1\"");
|
|
for (uint32_t i = 2; i <= maxhue; i++) {
|
|
lights += ",\"";
|
|
lights += EncodeLightId(i);
|
|
lights += "\"";
|
|
}
|
|
response.replace("{l1", lights);
|
|
HueLightStatus1(1, &response);
|
|
response += F("}");
|
|
}
|
|
|
|
WSSend(200, CT_JSON, response);
|
|
}
|
|
|
|
void HandleHueApi(String *path)
|
|
{
|
|
# 784 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_20_hue.ino"
|
|
uint8_t args = 0;
|
|
|
|
path->remove(0, 4);
|
|
uint16_t apilen = path->length();
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s)"), path->c_str());
|
|
for (args = 0; args < WebServer->args(); args++) {
|
|
String json = WebServer->arg(args);
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str());
|
|
}
|
|
|
|
if (path->endsWith("/invalid/")) {}
|
|
else if (!apilen) HueAuthentication(path);
|
|
else if (path->endsWith("/")) HueAuthentication(path);
|
|
else if (path->endsWith("/config")) HueConfig(path);
|
|
else if (path->indexOf("/lights") >= 0) HueLights(path);
|
|
else if (path->indexOf("/groups") >= 0) HueGroups(path);
|
|
else if (path->endsWith("/schedules")) HueNotImplemented(path);
|
|
else if (path->endsWith("/sensors")) HueNotImplemented(path);
|
|
else if (path->endsWith("/scenes")) HueNotImplemented(path);
|
|
else if (path->endsWith("/rules")) HueNotImplemented(path);
|
|
else if (path->endsWith("/resourcelinks")) HueNotImplemented(path);
|
|
else HueGlobalConfig(path);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv20(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
#ifdef USE_SCRIPT_HUE
|
|
if ((EMUL_HUE == Settings.flag2.emulation)) {
|
|
#else
|
|
if (devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
|
|
#endif
|
|
switch (function) {
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/description.xml", HandleUpnpSetupHue);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_21_wemo.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_21_wemo.ino"
|
|
#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined (USE_EMULATION_WEMO)
|
|
|
|
|
|
|
|
|
|
#define XDRV_21 21
|
|
|
|
const char WEMO_MSEARCH[] PROGMEM =
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"CACHE-CONTROL: max-age=86400\r\n"
|
|
"DATE: Fri, 15 Apr 2016 04:56:29 GMT\r\n"
|
|
"EXT:\r\n"
|
|
"LOCATION: http://%s:80/setup.xml\r\n"
|
|
"OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
|
|
"01-NLS: b9200ebb-736d-4b93-bf03-835149d13983\r\n"
|
|
"SERVER: Unspecified, UPnP/1.0, Unspecified\r\n"
|
|
"ST: %s\r\n"
|
|
"USN: uuid:%s::%s\r\n"
|
|
"X-User-Agent: redsonic\r\n"
|
|
"\r\n";
|
|
|
|
String WemoSerialnumber(void)
|
|
{
|
|
char serial[16];
|
|
|
|
snprintf_P(serial, sizeof(serial), PSTR("201612K%08X"), ESP.getChipId());
|
|
return String(serial);
|
|
}
|
|
|
|
String WemoUuid(void)
|
|
{
|
|
char uuid[27];
|
|
|
|
snprintf_P(uuid, sizeof(uuid), PSTR("Socket-1_0-%s"), WemoSerialnumber().c_str());
|
|
return String(uuid);
|
|
}
|
|
|
|
void WemoRespondToMSearch(int echo_type)
|
|
{
|
|
char message[TOPSZ];
|
|
|
|
TickerMSearch.detach();
|
|
if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
|
|
char type[24];
|
|
if (1 == echo_type) {
|
|
strcpy_P(type, URN_BELKIN_DEVICE_CAP);
|
|
} else {
|
|
strcpy_P(type, UPNP_ROOTDEVICE);
|
|
}
|
|
char response[400];
|
|
snprintf_P(response, sizeof(response), WEMO_MSEARCH, WiFi.localIP().toString().c_str(), type, WemoUuid().c_str(), type);
|
|
PortUdp.write(response);
|
|
PortUdp.endPacket();
|
|
snprintf_P(message, sizeof(message), PSTR(D_RESPONSE_SENT));
|
|
} else {
|
|
snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
|
|
}
|
|
|
|
PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"),
|
|
echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port);
|
|
|
|
udp_response_mutex = false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char WEMO_EVENTSERVICE_XML[] PROGMEM =
|
|
"<scpd xmlns=\"urn:Belkin:service-1-0\">"
|
|
"<actionList>"
|
|
"<action>"
|
|
"<name>SetBinaryState</name>"
|
|
"<argumentList>"
|
|
"<argument>"
|
|
"<retval/>"
|
|
"<name>BinaryState</name>"
|
|
"<relatedStateVariable>BinaryState</relatedStateVariable>"
|
|
"<direction>in</direction>"
|
|
"</argument>"
|
|
"</argumentList>"
|
|
"</action>"
|
|
"<action>"
|
|
"<name>GetBinaryState</name>"
|
|
"<argumentList>"
|
|
"<argument>"
|
|
"<retval/>"
|
|
"<name>BinaryState</name>"
|
|
"<relatedStateVariable>BinaryState</relatedStateVariable>"
|
|
"<direction>out</direction>"
|
|
"</argument>"
|
|
"</argumentList>"
|
|
"</action>"
|
|
"</actionList>"
|
|
"<serviceStateTable>"
|
|
"<stateVariable sendEvents=\"yes\">"
|
|
"<name>BinaryState</name>"
|
|
"<dataType>bool</dataType>"
|
|
"<defaultValue>0</defaultValue>"
|
|
"</stateVariable>"
|
|
"<stateVariable sendEvents=\"yes\">"
|
|
"<name>level</name>"
|
|
"<dataType>string</dataType>"
|
|
"<defaultValue>0</defaultValue>"
|
|
"</stateVariable>"
|
|
"</serviceStateTable>"
|
|
"</scpd>\r\n\r\n";
|
|
|
|
const char WEMO_METASERVICE_XML[] PROGMEM =
|
|
"<scpd xmlns=\"urn:Belkin:service-1-0\">"
|
|
"<specVersion>"
|
|
"<major>1</major>"
|
|
"<minor>0</minor>"
|
|
"</specVersion>"
|
|
"<actionList>"
|
|
"<action>"
|
|
"<name>GetMetaInfo</name>"
|
|
"<argumentList>"
|
|
"<retval />"
|
|
"<name>GetMetaInfo</name>"
|
|
"<relatedStateVariable>MetaInfo</relatedStateVariable>"
|
|
"<direction>in</direction>"
|
|
"</argumentList>"
|
|
"</action>"
|
|
"</actionList>"
|
|
"<serviceStateTable>"
|
|
"<stateVariable sendEvents=\"yes\">"
|
|
"<name>MetaInfo</name>"
|
|
"<dataType>string</dataType>"
|
|
"<defaultValue>0</defaultValue>"
|
|
"</stateVariable>"
|
|
"</serviceStateTable>"
|
|
"</scpd>\r\n\r\n";
|
|
|
|
const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM =
|
|
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
|
|
"<s:Body>"
|
|
"<u:%cetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">"
|
|
"<BinaryState>%d</BinaryState>"
|
|
"</u:%cetBinaryStateResponse>"
|
|
"</s:Body>"
|
|
"</s:Envelope>\r\n";
|
|
|
|
const char WEMO_SETUP_XML[] PROGMEM =
|
|
"<?xml version=\"1.0\"?>"
|
|
"<root xmlns=\"urn:Belkin:device-1-0\">"
|
|
"<device>"
|
|
"<deviceType>urn:Belkin:device:controllee:1</deviceType>"
|
|
"<friendlyName>{x1</friendlyName>"
|
|
"<manufacturer>Belkin International Inc.</manufacturer>"
|
|
"<modelName>Socket</modelName>"
|
|
"<modelNumber>3.1415</modelNumber>"
|
|
"<UDN>uuid:{x2</UDN>"
|
|
"<serialNumber>{x3</serialNumber>"
|
|
"<binaryState>0</binaryState>"
|
|
"<serviceList>"
|
|
"<service>"
|
|
"<serviceType>urn:Belkin:service:basicevent:1</serviceType>"
|
|
"<serviceId>urn:Belkin:serviceId:basicevent1</serviceId>"
|
|
"<controlURL>/upnp/control/basicevent1</controlURL>"
|
|
"<eventSubURL>/upnp/event/basicevent1</eventSubURL>"
|
|
"<SCPDURL>/eventservice.xml</SCPDURL>"
|
|
"</service>"
|
|
"<service>"
|
|
"<serviceType>urn:Belkin:service:metainfo:1</serviceType>"
|
|
"<serviceId>urn:Belkin:serviceId:metainfo1</serviceId>"
|
|
"<controlURL>/upnp/control/metainfo1</controlURL>"
|
|
"<eventSubURL>/upnp/event/metainfo1</eventSubURL>"
|
|
"<SCPDURL>/metainfoservice.xml</SCPDURL>"
|
|
"</service>"
|
|
"</serviceList>"
|
|
"</device>"
|
|
"</root>\r\n";
|
|
|
|
|
|
|
|
void HandleUpnpEvent(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT));
|
|
|
|
char event[500];
|
|
strlcpy(event, WebServer->arg(0).c_str(), sizeof(event));
|
|
|
|
|
|
|
|
|
|
char state = 'G';
|
|
if (strstr_P(event, PSTR("SetBinaryState")) != nullptr) {
|
|
state = 'S';
|
|
uint8_t power = POWER_TOGGLE;
|
|
if (strstr_P(event, PSTR("State>1</Binary")) != nullptr) {
|
|
power = POWER_ON;
|
|
}
|
|
else if (strstr_P(event, PSTR("State>0</Binary")) != nullptr) {
|
|
power = POWER_OFF;
|
|
}
|
|
if (power != POWER_TOGGLE) {
|
|
uint8_t device = (light_type) ? devices_present : 1;
|
|
ExecuteCommandPower(device, power, SRC_WEMO);
|
|
}
|
|
}
|
|
|
|
snprintf_P(event, sizeof(event), WEMO_RESPONSE_STATE_SOAP, state, bitRead(power, devices_present -1), state);
|
|
WSSend(200, CT_XML, event);
|
|
}
|
|
|
|
void HandleUpnpService(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_EVENT_SERVICE));
|
|
|
|
WSSend(200, CT_PLAIN, FPSTR(WEMO_EVENTSERVICE_XML));
|
|
}
|
|
|
|
void HandleUpnpMetaService(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_META_SERVICE));
|
|
|
|
WSSend(200, CT_PLAIN, FPSTR(WEMO_METASERVICE_XML));
|
|
}
|
|
|
|
void HandleUpnpSetupWemo(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_SETUP));
|
|
|
|
String setup_xml = FPSTR(WEMO_SETUP_XML);
|
|
setup_xml.replace("{x1", SettingsText(SET_FRIENDLYNAME1));
|
|
setup_xml.replace("{x2", WemoUuid());
|
|
setup_xml.replace("{x3", WemoSerialnumber());
|
|
WSSend(200, CT_XML, setup_xml);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv21(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (devices_present && (EMUL_WEMO == Settings.flag2.emulation)) {
|
|
switch (function) {
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/upnp/control/basicevent1", HTTP_POST, HandleUpnpEvent);
|
|
WebServer->on("/eventservice.xml", HandleUpnpService);
|
|
WebServer->on("/metainfoservice.xml", HandleUpnpMetaService);
|
|
WebServer->on("/setup.xml", HandleUpnpSetupWemo);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino"
|
|
#ifdef USE_SONOFF_IFAN
|
|
|
|
|
|
|
|
|
|
#define XDRV_22 22
|
|
|
|
const uint8_t MAX_FAN_SPEED = 4;
|
|
|
|
const uint8_t kIFan02Speed[MAX_FAN_SPEED] = { 0x00, 0x01, 0x03, 0x05 };
|
|
const uint8_t kIFan03Speed[MAX_FAN_SPEED +2] = { 0x00, 0x01, 0x03, 0x04, 0x05, 0x06 };
|
|
const uint8_t kIFan03Sequence[MAX_FAN_SPEED][MAX_FAN_SPEED] = {{0, 2, 2, 2}, {0, 1, 2, 4}, {1, 1, 2, 5}, {4, 4, 5, 3}};
|
|
|
|
const char kSonoffIfanCommands[] PROGMEM = "|"
|
|
D_CMND_FANSPEED;
|
|
|
|
void (* const SonoffIfanCommand[])(void) PROGMEM = {
|
|
&CmndFanspeed };
|
|
|
|
uint8_t ifan_fanspeed_timer = 0;
|
|
uint8_t ifan_fanspeed_goal = 0;
|
|
bool ifan_receive_flag = false;
|
|
bool ifan_restart_flag = true;
|
|
|
|
|
|
|
|
bool IsModuleIfan(void)
|
|
{
|
|
return ((SONOFF_IFAN02 == my_module_type) || (SONOFF_IFAN03 == my_module_type));
|
|
}
|
|
|
|
uint8_t MaxFanspeed(void)
|
|
{
|
|
return MAX_FAN_SPEED;
|
|
}
|
|
|
|
uint8_t GetFanspeed(void)
|
|
{
|
|
if (ifan_fanspeed_timer) {
|
|
return ifan_fanspeed_goal;
|
|
} else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t fanspeed = (uint8_t)(power &0xF) >> 1;
|
|
if (fanspeed) { fanspeed = (fanspeed >> 1) +1; }
|
|
return fanspeed;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence)
|
|
{
|
|
ifan_fanspeed_timer = 0;
|
|
ifan_fanspeed_goal = fanspeed;
|
|
|
|
uint8_t fanspeed_now = GetFanspeed();
|
|
|
|
if (fanspeed == fanspeed_now) { return; }
|
|
|
|
uint8_t fans = kIFan02Speed[fanspeed];
|
|
if (SONOFF_IFAN03 == my_module_type) {
|
|
if (sequence) {
|
|
fanspeed = kIFan03Sequence[fanspeed_now][ifan_fanspeed_goal];
|
|
if (fanspeed != ifan_fanspeed_goal) {
|
|
if (0 == fanspeed_now) {
|
|
ifan_fanspeed_timer = 20;
|
|
} else {
|
|
ifan_fanspeed_timer = 2;
|
|
}
|
|
}
|
|
}
|
|
fans = kIFan03Speed[fanspeed];
|
|
}
|
|
for (uint32_t i = 2; i < 5; i++) {
|
|
uint8_t state = (fans &1) + POWER_OFF_NO_STATE;
|
|
ExecuteCommandPower(i, state, SRC_IGNORE);
|
|
fans >>= 1;
|
|
}
|
|
|
|
#ifdef USE_DOMOTICZ
|
|
if (sequence) { DomoticzUpdateFanState(); }
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
void SonoffIfanReceived(void)
|
|
{
|
|
char svalue[32];
|
|
|
|
uint8_t mode = serial_in_buffer[3];
|
|
uint8_t action = serial_in_buffer[6];
|
|
|
|
if (4 == mode) {
|
|
if (action < 4) {
|
|
|
|
|
|
|
|
|
|
if (action != GetFanspeed()) {
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), action);
|
|
ExecuteCommand(svalue, SRC_REMOTE);
|
|
#ifdef USE_BUZZER
|
|
BuzzerEnabledBeep((action) ? action : 1, (action) ? 1 : 4);
|
|
#endif
|
|
}
|
|
} else {
|
|
|
|
ExecuteCommandPower(1, POWER_TOGGLE, SRC_REMOTE);
|
|
}
|
|
}
|
|
if (6 == mode) {
|
|
|
|
Settings.flag3.buzzer_enable = !Settings.flag3.buzzer_enable;
|
|
}
|
|
if (7 == mode) {
|
|
|
|
#ifdef USE_BUZZER
|
|
BuzzerEnabledBeep(4, 1);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
serial_in_buffer[5] = 0;
|
|
serial_in_buffer[6] = 0;
|
|
for (uint32_t i = 0; i < 7; i++) {
|
|
if ((i > 1) && (i < 6)) { serial_in_buffer[6] += serial_in_buffer[i]; }
|
|
Serial.write(serial_in_buffer[i]);
|
|
}
|
|
}
|
|
|
|
bool SonoffIfanSerialInput(void)
|
|
{
|
|
if (SONOFF_IFAN03 == my_module_type) {
|
|
if (0xAA == serial_in_byte) {
|
|
serial_in_byte_counter = 0;
|
|
ifan_receive_flag = true;
|
|
}
|
|
if (ifan_receive_flag) {
|
|
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
|
|
if (serial_in_byte_counter == 8) {
|
|
# 176 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_22_sonoff_ifan.ino"
|
|
AddLogSerial(LOG_LEVEL_DEBUG);
|
|
uint8_t crc = 0;
|
|
for (uint32_t i = 2; i < 7; i++) {
|
|
crc += serial_in_buffer[i];
|
|
}
|
|
if (crc == serial_in_buffer[7]) {
|
|
SonoffIfanReceived();
|
|
ifan_receive_flag = false;
|
|
return true;
|
|
}
|
|
}
|
|
serial_in_byte = 0;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndFanspeed(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if ('-' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = (int16_t)GetFanspeed() -1;
|
|
if (XdrvMailbox.payload < 0) { XdrvMailbox.payload = MAX_FAN_SPEED -1; }
|
|
}
|
|
else if ('+' == XdrvMailbox.data[0]) {
|
|
XdrvMailbox.payload = GetFanspeed() +1;
|
|
if (XdrvMailbox.payload > MAX_FAN_SPEED -1) { XdrvMailbox.payload = 0; }
|
|
}
|
|
}
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_FAN_SPEED)) {
|
|
SonoffIFanSetFanspeed(XdrvMailbox.payload, true);
|
|
}
|
|
ResponseCmndNumber(GetFanspeed());
|
|
}
|
|
|
|
|
|
|
|
bool SonoffIfanInit(void)
|
|
{
|
|
if (SONOFF_IFAN03 == my_module_type) {
|
|
SetSerial(9600, TS_SERIAL_8N1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SonoffIfanUpdate(void)
|
|
{
|
|
if (SONOFF_IFAN03 == my_module_type) {
|
|
if (ifan_fanspeed_timer) {
|
|
ifan_fanspeed_timer--;
|
|
if (!ifan_fanspeed_timer) {
|
|
SonoffIFanSetFanspeed(ifan_fanspeed_goal, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ifan_restart_flag && (4 == uptime) && (SONOFF_IFAN02 == my_module_type)) {
|
|
ifan_restart_flag = false;
|
|
SetDevicePower(1, SRC_RETRY);
|
|
SetDevicePower(power, SRC_RETRY);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv22(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (IsModuleIfan()) {
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
SonoffIfanUpdate();
|
|
break;
|
|
case FUNC_SERIAL:
|
|
result = SonoffIfanSerialInput();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kSonoffIfanCommands, SonoffIfanCommand);
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
result = SonoffIfanInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
#define OCCUPANCY "Occupancy"
|
|
|
|
typedef uint64_t Z_IEEEAddress;
|
|
typedef uint16_t Z_ShortAddress;
|
|
|
|
enum ZnpCommandType {
|
|
Z_POLL = 0x00,
|
|
Z_SREQ = 0x20,
|
|
Z_AREQ = 0x40,
|
|
Z_SRSP = 0x60
|
|
};
|
|
|
|
enum ZnpSubsystem {
|
|
Z_RPC_Error = 0x00,
|
|
Z_SYS = 0x01,
|
|
Z_MAC = 0x02,
|
|
Z_NWK = 0x03,
|
|
Z_AF = 0x04,
|
|
Z_ZDO = 0x05,
|
|
Z_SAPI = 0x06,
|
|
Z_UTIL = 0x07,
|
|
Z_DEBUG = 0x08,
|
|
Z_APP = 0x09
|
|
};
|
|
|
|
|
|
enum SysCommand {
|
|
SYS_RESET = 0x00,
|
|
SYS_PING = 0x01,
|
|
SYS_VERSION = 0x02,
|
|
SYS_SET_EXTADDR = 0x03,
|
|
SYS_GET_EXTADDR = 0x04,
|
|
SYS_RAM_READ = 0x05,
|
|
SYS_RAM_WRITE = 0x06,
|
|
SYS_OSAL_NV_ITEM_INIT = 0x07,
|
|
SYS_OSAL_NV_READ = 0x08,
|
|
SYS_OSAL_NV_WRITE = 0x09,
|
|
SYS_OSAL_START_TIMER = 0x0A,
|
|
SYS_OSAL_STOP_TIMER = 0x0B,
|
|
SYS_RANDOM = 0x0C,
|
|
SYS_ADC_READ = 0x0D,
|
|
SYS_GPIO = 0x0E,
|
|
SYS_STACK_TUNE = 0x0F,
|
|
SYS_SET_TIME = 0x10,
|
|
SYS_GET_TIME = 0x11,
|
|
SYS_OSAL_NV_DELETE = 0x12,
|
|
SYS_OSAL_NV_LENGTH = 0x13,
|
|
SYS_TEST_RF = 0x40,
|
|
SYS_TEST_LOOPBACK = 0x41,
|
|
SYS_RESET_IND = 0x80,
|
|
SYS_OSAL_TIMER_EXPIRED = 0x81,
|
|
};
|
|
|
|
enum SapiCommand {
|
|
SAPI_START_REQUEST = 0x00,
|
|
SAPI_BIND_DEVICE = 0x01,
|
|
SAPI_ALLOW_BIND = 0x02,
|
|
SAPI_SEND_DATA_REQUEST = 0x03,
|
|
SAPI_READ_CONFIGURATION = 0x04,
|
|
SAPI_WRITE_CONFIGURATION = 0x05,
|
|
SAPI_GET_DEVICE_INFO = 0x06,
|
|
SAPI_FIND_DEVICE_REQUEST = 0x07,
|
|
SAPI_PERMIT_JOINING_REQUEST = 0x08,
|
|
SAPI_SYSTEM_RESET = 0x09,
|
|
SAPI_START_CONFIRM = 0x80,
|
|
SAPI_BIND_CONFIRM = 0x81,
|
|
SAPI_ALLOW_BIND_CONFIRM = 0x82,
|
|
SAPI_SEND_DATA_CONFIRM = 0x83,
|
|
SAPI_FIND_DEVICE_CONFIRM = 0x85,
|
|
SAPI_RECEIVE_DATA_INDICATION = 0x87,
|
|
};
|
|
enum Z_configuration {
|
|
CONF_EXTADDR = 0x01,
|
|
CONF_BOOTCOUNTER = 0x02,
|
|
CONF_STARTUP_OPTION = 0x03,
|
|
CONF_START_DELAY = 0x04,
|
|
CONF_NIB = 0x21,
|
|
CONF_DEVICE_LIST = 0x22,
|
|
CONF_ADDRMGR = 0x23,
|
|
CONF_POLL_RATE = 0x24,
|
|
CONF_QUEUED_POLL_RATE = 0x25,
|
|
CONF_RESPONSE_POLL_RATE = 0x26,
|
|
CONF_REJOIN_POLL_RATE = 0x27,
|
|
CONF_DATA_RETRIES = 0x28,
|
|
CONF_POLL_FAILURE_RETRIES = 0x29,
|
|
CONF_STACK_PROFILE = 0x2A,
|
|
CONF_INDIRECT_MSG_TIMEOUT = 0x2B,
|
|
CONF_ROUTE_EXPIRY_TIME = 0x2C,
|
|
CONF_EXTENDED_PAN_ID = 0x2D,
|
|
CONF_BCAST_RETRIES = 0x2E,
|
|
CONF_PASSIVE_ACK_TIMEOUT = 0x2F,
|
|
CONF_BCAST_DELIVERY_TIME = 0x30,
|
|
CONF_NWK_MODE = 0x31,
|
|
CONF_CONCENTRATOR_ENABLE = 0x32,
|
|
CONF_CONCENTRATOR_DISCOVERY = 0x33,
|
|
CONF_CONCENTRATOR_RADIUS = 0x34,
|
|
CONF_CONCENTRATOR_RC = 0x36,
|
|
CONF_NWK_MGR_MODE = 0x37,
|
|
CONF_SRC_RTG_EXPIRY_TIME = 0x38,
|
|
CONF_ROUTE_DISCOVERY_TIME = 0x39,
|
|
CONF_NWK_ACTIVE_KEY_INFO = 0x3A,
|
|
CONF_NWK_ALTERN_KEY_INFO = 0x3B,
|
|
CONF_ROUTER_OFF_ASSOC_CLEANUP = 0x3C,
|
|
CONF_NWK_LEAVE_REQ_ALLOWED = 0x3D,
|
|
CONF_NWK_CHILD_AGE_ENABLE = 0x3E,
|
|
CONF_DEVICE_LIST_KA_TIMEOUT = 0x3F,
|
|
CONF_BINDING_TABLE = 0x41,
|
|
CONF_GROUP_TABLE = 0x42,
|
|
CONF_APS_FRAME_RETRIES = 0x43,
|
|
CONF_APS_ACK_WAIT_DURATION = 0x44,
|
|
CONF_APS_ACK_WAIT_MULTIPLIER = 0x45,
|
|
CONF_BINDING_TIME = 0x46,
|
|
CONF_APS_USE_EXT_PANID = 0x47,
|
|
CONF_APS_USE_INSECURE_JOIN = 0x48,
|
|
CONF_COMMISSIONED_NWK_ADDR = 0x49,
|
|
CONF_APS_NONMEMBER_RADIUS = 0x4B,
|
|
CONF_APS_LINK_KEY_TABLE = 0x4C,
|
|
CONF_APS_DUPREJ_TIMEOUT_INC = 0x4D,
|
|
CONF_APS_DUPREJ_TIMEOUT_COUNT = 0x4E,
|
|
CONF_APS_DUPREJ_TABLE_SIZE = 0x4F,
|
|
CONF_DIAGNOSTIC_STATS = 0x50,
|
|
CONF_SECURITY_LEVEL = 0x61,
|
|
CONF_PRECFGKEY = 0x62,
|
|
CONF_PRECFGKEYS_ENABLE = 0x63,
|
|
CONF_SECURITY_MODE = 0x64,
|
|
CONF_SECURE_PERMIT_JOIN = 0x65,
|
|
CONF_APS_LINK_KEY_TYPE = 0x66,
|
|
CONF_APS_ALLOW_R19_SECURITY = 0x67,
|
|
CONF_IMPLICIT_CERTIFICATE = 0x69,
|
|
CONF_DEVICE_PRIVATE_KEY = 0x6A,
|
|
CONF_CA_PUBLIC_KEY = 0x6B,
|
|
CONF_KE_MAX_DEVICES = 0x6C,
|
|
CONF_USE_DEFAULT_TCLK = 0x6D,
|
|
CONF_RNG_COUNTER = 0x6F,
|
|
CONF_RANDOM_SEED = 0x70,
|
|
CONF_TRUSTCENTER_ADDR = 0x71,
|
|
CONF_USERDESC = 0x81,
|
|
CONF_NWKKEY = 0x82,
|
|
CONF_PANID = 0x83,
|
|
CONF_CHANLIST = 0x84,
|
|
CONF_LEAVE_CTRL = 0x85,
|
|
CONF_SCAN_DURATION = 0x86,
|
|
CONF_LOGICAL_TYPE = 0x87,
|
|
CONF_NWKMGR_MIN_TX = 0x88,
|
|
CONF_NWKMGR_ADDR = 0x89,
|
|
CONF_ZDO_DIRECT_CB = 0x8F,
|
|
CONF_TCLK_TABLE_START = 0x0101,
|
|
ZNP_HAS_CONFIGURED = 0xF00
|
|
};
|
|
# 210 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
|
|
enum Z_Status {
|
|
Z_Success = 0x00,
|
|
Z_Failure = 0x01,
|
|
Z_InvalidParameter = 0x02,
|
|
Z_MemError = 0x03,
|
|
Z_Created = 0x09,
|
|
Z_BufferFull = 0x11
|
|
};
|
|
|
|
enum Z_App_Profiles {
|
|
Z_PROF_IPM = 0x0101,
|
|
Z_PROF_HA = 0x0104,
|
|
Z_PROF_CBA = 0x0105,
|
|
Z_PROF_TA = 0x0107,
|
|
Z_PROF_PHHC = 0x0108,
|
|
Z_PROF_AMI = 0x0109,
|
|
};
|
|
|
|
enum Z_Device_Ids {
|
|
Z_DEVID_CONF_TOOL = 0x0005,
|
|
# 262 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
|
|
};
|
|
# 275 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_0_constants.ino"
|
|
enum AfCommand : uint8_t {
|
|
AF_REGISTER = 0x00,
|
|
AF_DATA_REQUEST = 0x01,
|
|
AF_DATA_REQUEST_EXT = 0x02,
|
|
AF_DATA_REQUEST_SRC_RTG = 0x03,
|
|
AF_INTER_PAN_CTL = 0x10,
|
|
AF_DATA_STORE = 0x11,
|
|
AF_DATA_RETRIEVE = 0x12,
|
|
AF_APSF_CONFIG_SET = 0x13,
|
|
AF_DATA_CONFIRM = 0x80,
|
|
AF_REFLECT_ERROR = 0x83,
|
|
AF_INCOMING_MSG = 0x81,
|
|
AF_INCOMING_MSG_EXT = 0x82
|
|
};
|
|
|
|
|
|
enum : uint8_t {
|
|
ZDO_NWK_ADDR_REQ = 0x00,
|
|
ZDO_IEEE_ADDR_REQ = 0x01,
|
|
ZDO_NODE_DESC_REQ = 0x02,
|
|
ZDO_POWER_DESC_REQ = 0x03,
|
|
ZDO_SIMPLE_DESC_REQ = 0x04,
|
|
ZDO_ACTIVE_EP_REQ = 0x05,
|
|
ZDO_MATCH_DESC_REQ = 0x06,
|
|
ZDO_COMPLEX_DESC_REQ = 0x07,
|
|
ZDO_USER_DESC_REQ = 0x08,
|
|
ZDO_DEVICE_ANNCE = 0x0A,
|
|
ZDO_USER_DESC_SET = 0x0B,
|
|
ZDO_SERVER_DISC_REQ = 0x0C,
|
|
ZDO_END_DEVICE_BIND_REQ = 0x20,
|
|
ZDO_BIND_REQ = 0x21,
|
|
ZDO_UNBIND_REQ = 0x22,
|
|
ZDO_SET_LINK_KEY = 0x23,
|
|
ZDO_REMOVE_LINK_KEY = 0x24,
|
|
ZDO_GET_LINK_KEY = 0x25,
|
|
ZDO_MGMT_NWK_DISC_REQ = 0x30,
|
|
ZDO_MGMT_LQI_REQ = 0x31,
|
|
ZDO_MGMT_RTQ_REQ = 0x32,
|
|
ZDO_MGMT_BIND_REQ = 0x33,
|
|
ZDO_MGMT_LEAVE_REQ = 0x34,
|
|
ZDO_MGMT_DIRECT_JOIN_REQ = 0x35,
|
|
ZDO_MGMT_PERMIT_JOIN_REQ = 0x36,
|
|
ZDO_MGMT_NWK_UPDATE_REQ = 0x37,
|
|
ZDO_MSG_CB_REGISTER = 0x3E,
|
|
ZDO_MGS_CB_REMOVE = 0x3F,
|
|
ZDO_STARTUP_FROM_APP = 0x40,
|
|
ZDO_AUTO_FIND_DESTINATION = 0x41,
|
|
ZDO_EXT_REMOVE_GROUP = 0x47,
|
|
ZDO_EXT_REMOVE_ALL_GROUP = 0x48,
|
|
ZDO_EXT_FIND_ALL_GROUPS_ENDPOINT = 0x49,
|
|
ZDO_EXT_FIND_GROUP = 0x4A,
|
|
ZDO_EXT_ADD_GROUP = 0x4B,
|
|
ZDO_EXT_COUNT_ALL_GROUPS = 0x4C,
|
|
ZDO_NWK_ADDR_RSP = 0x80,
|
|
ZDO_IEEE_ADDR_RSP = 0x81,
|
|
ZDO_NODE_DESC_RSP = 0x82,
|
|
ZDO_POWER_DESC_RSP = 0x83,
|
|
ZDO_SIMPLE_DESC_RSP = 0x84,
|
|
ZDO_ACTIVE_EP_RSP = 0x85,
|
|
ZDO_MATCH_DESC_RSP = 0x86,
|
|
ZDO_COMPLEX_DESC_RSP = 0x87,
|
|
ZDO_USER_DESC_RSP = 0x88,
|
|
ZDO_USER_DESC_CONF = 0x89,
|
|
ZDO_SERVER_DISC_RSP = 0x8A,
|
|
ZDO_END_DEVICE_BIND_RSP = 0xA0,
|
|
ZDO_BIND_RSP = 0xA1,
|
|
ZDO_UNBIND_RSP = 0xA2,
|
|
ZDO_MGMT_NWK_DISC_RSP = 0xB0,
|
|
ZDO_MGMT_LQI_RSP = 0xB1,
|
|
ZDO_MGMT_RTG_RSP = 0xB2,
|
|
ZDO_MGMT_BIND_RSP = 0xB3,
|
|
ZDO_MGMT_LEAVE_RSP = 0xB4,
|
|
ZDO_MGMT_DIRECT_JOIN_RSP = 0xB5,
|
|
ZDO_MGMT_PERMIT_JOIN_RSP = 0xB6,
|
|
ZDO_STATE_CHANGE_IND = 0xC0,
|
|
ZDO_END_DEVICE_ANNCE_IND = 0xC1,
|
|
ZDO_MATCH_DESC_RSP_SENT = 0xC2,
|
|
ZDO_STATUS_ERROR_RSP = 0xC3,
|
|
ZDO_SRC_RTG_IND = 0xC4,
|
|
ZDO_LEAVE_IND = 0xC9,
|
|
ZDO_TC_DEV_IND = 0xCA,
|
|
ZDO_PERMIT_JOIN_IND = 0xCB,
|
|
ZDO_MSG_CB_INCOMING = 0xFF
|
|
};
|
|
|
|
|
|
enum ZdoStates {
|
|
ZDO_DEV_HOLD = 0x00,
|
|
ZDO_DEV_INIT = 0x01,
|
|
ZDO_DEV_NWK_DISC = 0x02,
|
|
ZDO_DEV_NWK_JOINING = 0x03,
|
|
ZDO_DEV_NWK_REJOIN = 0x04,
|
|
ZDO_DEV_END_DEVICE_UNAUTH = 0x05,
|
|
ZDO_DEV_END_DEVICE = 0x06,
|
|
ZDO_DEV_ROUTER = 0x07,
|
|
ZDO_DEV_COORD_STARTING = 0x08,
|
|
ZDO_DEV_ZB_COORD = 0x09,
|
|
ZDO_DEV_NWK_ORPHAN = 0x0A,
|
|
};
|
|
|
|
|
|
enum Z_Util {
|
|
Z_UTIL_GET_DEVICE_INFO = 0x00,
|
|
Z_UTIL_GET_NV_INFO = 0x01,
|
|
Z_UTIL_SET_PANID = 0x02,
|
|
Z_UTIL_SET_CHANNELS = 0x03,
|
|
Z_UTIL_SET_SECLEVEL = 0x04,
|
|
Z_UTIL_SET_PRECFGKEY = 0x05,
|
|
Z_UTIL_CALLBACK_SUB_CMD = 0x06,
|
|
Z_UTIL_KEY_EVENT = 0x07,
|
|
Z_UTIL_TIME_ALIVE = 0x09,
|
|
Z_UTIL_LED_CONTROL = 0x0A,
|
|
Z_UTIL_TEST_LOOPBACK = 0x10,
|
|
Z_UTIL_DATA_REQ = 0x11,
|
|
Z_UTIL_SRC_MATCH_ENABLE = 0x20,
|
|
Z_UTIL_SRC_MATCH_ADD_ENTRY = 0x21,
|
|
Z_UTIL_SRC_MATCH_DEL_ENTRY = 0x22,
|
|
Z_UTIL_SRC_MATCH_CHECK_SRC_ADDR = 0x23,
|
|
Z_UTIL_SRC_MATCH_ACK_ALL_PENDING = 0x24,
|
|
Z_UTIL_SRC_MATCH_CHECK_ALL_PENDING = 0x25,
|
|
Z_UTIL_ADDRMGR_EXT_ADDR_LOOKUP = 0x40,
|
|
Z_UTIL_ADDRMGR_NWK_ADDR_LOOKUP = 0x41,
|
|
Z_UTIL_APSME_LINK_KEY_DATA_GET = 0x44,
|
|
Z_UTIL_APSME_LINK_KEY_NV_ID_GET = 0x45,
|
|
Z_UTIL_ASSOC_COUNT = 0x48,
|
|
Z_UTIL_ASSOC_FIND_DEVICE = 0x49,
|
|
Z_UTIL_ASSOC_GET_WITH_ADDRESS = 0x4A,
|
|
Z_UTIL_APSME_REQUEST_KEY_CMD = 0x4B,
|
|
Z_UTIL_ZCL_KEY_EST_INIT_EST = 0x80,
|
|
Z_UTIL_ZCL_KEY_EST_SIGN = 0x81,
|
|
Z_UTIL_UTIL_SYNC_REQ = 0xE0,
|
|
Z_UTIL_ZCL_KEY_ESTABLISH_IND = 0xE1
|
|
};
|
|
|
|
enum ZCL_Global_Commands {
|
|
ZCL_READ_ATTRIBUTES = 0x00,
|
|
ZCL_READ_ATTRIBUTES_RESPONSE = 0x01,
|
|
ZCL_WRITE_ATTRIBUTES = 0x02,
|
|
ZCL_WRITE_ATTRIBUTES_UNDIVIDED = 0x03,
|
|
ZCL_WRITE_ATTRIBUTES_RESPONSE = 0x04,
|
|
ZCL_WRITE_ATTRIBUTES_NORESPONSE = 0x05,
|
|
ZCL_CONFIGURE_REPORTING = 0x06,
|
|
ZCL_CONFIGURE_REPORTING_RESPONSE = 0x07,
|
|
ZCL_READ_REPORTING_CONFIGURATION = 0x08,
|
|
ZCL_READ_REPORTING_CONFIGURATION_RESPONSE = 0x09,
|
|
ZCL_REPORT_ATTRIBUTES = 0x0a,
|
|
ZCL_DEFAULT_RESPONSE = 0x0b,
|
|
ZCL_DISCOVER_ATTRIBUTES = 0x0c,
|
|
ZCL_DISCOVER_ATTRIBUTES_RESPONSE = 0x0d
|
|
|
|
};
|
|
|
|
const uint16_t Z_ProfileIds[] PROGMEM = { 0x0104, 0x0109, 0xA10E, 0xC05E };
|
|
const char Z_ProfileNames[] PROGMEM = "ZigBee Home Automation|ZigBee Smart Energy|ZigBee Green Power|ZigBee Light Link";
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_1_headers.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_1_headers.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
|
|
|
|
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1);
|
|
|
|
|
|
|
|
JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) {
|
|
|
|
if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
|
|
return *(JsonVariant*)nullptr;
|
|
}
|
|
|
|
for (auto kv : json) {
|
|
const char *key = kv.key;
|
|
JsonVariant &value = kv.value;
|
|
|
|
if (0 == strcasecmp_P(key, needle)) {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
return *(JsonVariant*)nullptr;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
#include <vector>
|
|
#include <map>
|
|
|
|
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
|
|
#define ZIGBEE_SAVE_DELAY_SECONDS 10;
|
|
#endif
|
|
const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS;
|
|
|
|
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value);
|
|
|
|
typedef struct Z_Device {
|
|
uint16_t shortaddr;
|
|
uint64_t longaddr;
|
|
uint32_t firstSeen;
|
|
uint32_t lastSeen;
|
|
String manufacturerId;
|
|
String modelId;
|
|
String friendlyName;
|
|
std::vector<uint32_t> endpoints;
|
|
std::vector<uint32_t> clusters_in;
|
|
std::vector<uint32_t> clusters_out;
|
|
|
|
uint32_t timer;
|
|
uint16_t cluster;
|
|
uint16_t endpoint;
|
|
uint32_t value;
|
|
Z_DeviceTimer func;
|
|
|
|
DynamicJsonBuffer *json_buffer;
|
|
JsonObject *json;
|
|
} Z_Device;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Z_Devices {
|
|
public:
|
|
Z_Devices() {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t isKnownShortAddr(uint16_t shortaddr) const;
|
|
uint16_t isKnownLongAddr(uint64_t longaddr) const;
|
|
uint16_t isKnownIndex(uint32_t index) const;
|
|
uint16_t isKnownFriendlyName(const char * name) const;
|
|
|
|
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
|
|
|
|
|
|
|
|
void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0);
|
|
|
|
|
|
void addEndoint(uint16_t shortaddr, uint8_t endpoint);
|
|
|
|
|
|
void addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t profileId);
|
|
|
|
|
|
void addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out);
|
|
|
|
uint8_t findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster);
|
|
|
|
void setManufId(uint16_t shortaddr, const char * str);
|
|
void setModelId(uint16_t shortaddr, const char * str);
|
|
void setFriendlyName(uint16_t shortaddr, const char * str);
|
|
const String * getFriendlyName(uint16_t) const;
|
|
|
|
|
|
void updateLastSeen(uint16_t shortaddr);
|
|
|
|
|
|
String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const;
|
|
|
|
|
|
void resetTimer(uint32_t shortaddr);
|
|
void setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func);
|
|
void runTimer(void);
|
|
|
|
|
|
void jsonClear(uint16_t shortaddr);
|
|
void jsonAppend(uint16_t shortaddr, const JsonObject &values);
|
|
const JsonObject *jsonGet(uint16_t shortaddr);
|
|
void jsonPublishFlush(uint16_t shortaddr);
|
|
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
|
|
void jsonPublishNow(uint16_t shortaddr, JsonObject &values);
|
|
|
|
|
|
size_t devicesSize(void) const {
|
|
return _devices.size();
|
|
}
|
|
const Z_Device &devicesAt(size_t i) const {
|
|
return _devices.at(i);
|
|
}
|
|
|
|
|
|
bool removeDevice(uint16_t shortaddr);
|
|
|
|
|
|
void dirty(void);
|
|
void clean(void);
|
|
|
|
|
|
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
|
|
|
|
private:
|
|
std::vector<Z_Device> _devices = {};
|
|
uint32_t _saveTimer = 0;
|
|
|
|
template < typename T>
|
|
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
|
|
|
|
template < typename T>
|
|
static int32_t findEndpointInVector(const std::vector<T> & vecOfElements, uint8_t element);
|
|
|
|
|
|
static int32_t findClusterEndpoint(const std::vector<uint32_t> & vecOfElements, uint16_t element);
|
|
|
|
Z_Device & getShortAddr(uint16_t shortaddr);
|
|
const Z_Device & getShortAddrConst(uint16_t shortaddr) const ;
|
|
Z_Device & getLongAddr(uint64_t longaddr);
|
|
|
|
int32_t findShortAddr(uint16_t shortaddr) const;
|
|
int32_t findLongAddr(uint64_t longaddr) const;
|
|
int32_t findFriendlyName(const char * name) const;
|
|
|
|
void _updateLastSeen(Z_Device &device) {
|
|
if (&device != nullptr) {
|
|
device.lastSeen = Rtc.utc_time;
|
|
}
|
|
};
|
|
|
|
|
|
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
|
};
|
|
|
|
Z_Devices zigbee_devices = Z_Devices();
|
|
|
|
|
|
uint64_t localIEEEAddr = 0;
|
|
|
|
|
|
template < typename T>
|
|
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
|
|
|
|
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
|
|
|
|
if (it != vecOfElements.end()) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
template < typename T>
|
|
int32_t Z_Devices::findEndpointInVector(const std::vector<T> & vecOfElements, uint8_t element) {
|
|
|
|
|
|
int32_t found = 0;
|
|
for (auto &elem : vecOfElements) {
|
|
if ( ((elem >> 16) & 0xFF) == element) { return found; }
|
|
found++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
# 204 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
int32_t Z_Devices::findClusterEndpoint(const std::vector<uint32_t> & vecOfElements, uint16_t cluster) {
|
|
int32_t found = 0;
|
|
for (auto &elem : vecOfElements) {
|
|
if ((elem & 0xFFFF) == cluster) { return found; }
|
|
found++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
|
if (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; }
|
|
Z_Device device = { shortaddr, longaddr,
|
|
Rtc.utc_time, Rtc.utc_time,
|
|
String(),
|
|
String(),
|
|
String(),
|
|
std::vector<uint32_t>(),
|
|
std::vector<uint32_t>(),
|
|
std::vector<uint32_t>(),
|
|
0,0,0,0,
|
|
nullptr,
|
|
nullptr, nullptr };
|
|
device.json_buffer = new DynamicJsonBuffer();
|
|
_devices.push_back(device);
|
|
dirty();
|
|
return _devices.back();
|
|
}
|
|
# 244 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const {
|
|
if (!shortaddr) { return -1; }
|
|
int32_t found = 0;
|
|
if (shortaddr) {
|
|
for (auto &elem : _devices) {
|
|
if (elem.shortaddr == shortaddr) { return found; }
|
|
found++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
# 263 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
|
|
if (!longaddr) { return -1; }
|
|
int32_t found = 0;
|
|
if (longaddr) {
|
|
for (auto &elem : _devices) {
|
|
if (elem.longaddr == longaddr) { return found; }
|
|
found++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
# 282 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
int32_t Z_Devices::findFriendlyName(const char * name) const {
|
|
if (!name) { return -1; }
|
|
size_t name_len = strlen(name);
|
|
int32_t found = 0;
|
|
if (name_len) {
|
|
for (auto &elem : _devices) {
|
|
if (elem.friendlyName == name) { return found; }
|
|
found++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const {
|
|
int32_t found = findShortAddr(shortaddr);
|
|
if (found >= 0) {
|
|
return shortaddr;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
|
|
int32_t found = findLongAddr(longaddr);
|
|
if (found >= 0) {
|
|
const Z_Device & device = devicesAt(found);
|
|
return device.shortaddr;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint16_t Z_Devices::isKnownIndex(uint32_t index) const {
|
|
if (index < devicesSize()) {
|
|
const Z_Device & device = devicesAt(index);
|
|
return device.shortaddr;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
|
|
if ((!name) || (0 == strlen(name))) { return 0xFFFF; }
|
|
int32_t found = findFriendlyName(name);
|
|
if (found >= 0) {
|
|
const Z_Device & device = devicesAt(found);
|
|
return device.shortaddr;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
|
|
const Z_Device & device = getShortAddrConst(shortaddr);
|
|
return device.longaddr;
|
|
}
|
|
|
|
|
|
|
|
|
|
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
|
|
if (!shortaddr) { return *(Z_Device*) nullptr; }
|
|
int32_t found = findShortAddr(shortaddr);
|
|
if (found >= 0) {
|
|
return _devices[found];
|
|
}
|
|
|
|
return createDeviceEntry(shortaddr, 0);
|
|
}
|
|
|
|
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
|
|
if (!shortaddr) { return *(Z_Device*) nullptr; }
|
|
int32_t found = findShortAddr(shortaddr);
|
|
if (found >= 0) {
|
|
return _devices[found];
|
|
}
|
|
return *((Z_Device*)nullptr);
|
|
}
|
|
|
|
|
|
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
|
|
if (!longaddr) { return *(Z_Device*) nullptr; }
|
|
int32_t found = findLongAddr(longaddr);
|
|
if (found > 0) {
|
|
return _devices[found];
|
|
}
|
|
return createDeviceEntry(0, longaddr);
|
|
}
|
|
|
|
|
|
bool Z_Devices::removeDevice(uint16_t shortaddr) {
|
|
int32_t found = findShortAddr(shortaddr);
|
|
if (found >= 0) {
|
|
_devices.erase(_devices.begin() + found);
|
|
dirty();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
|
|
int32_t s_found = findShortAddr(shortaddr);
|
|
int32_t l_found = findLongAddr(longaddr);
|
|
|
|
if ((s_found >= 0) && (l_found >= 0)) {
|
|
if (s_found == l_found) {
|
|
updateLastSeen(shortaddr);
|
|
} else {
|
|
|
|
_devices[l_found].shortaddr = shortaddr;
|
|
|
|
_devices.erase(_devices.begin() + s_found);
|
|
updateLastSeen(shortaddr);
|
|
dirty();
|
|
}
|
|
} else if (s_found >= 0) {
|
|
|
|
|
|
_devices[s_found].longaddr = longaddr;
|
|
updateLastSeen(shortaddr);
|
|
dirty();
|
|
} else if (l_found >= 0) {
|
|
|
|
_devices[l_found].shortaddr = shortaddr;
|
|
dirty();
|
|
} else {
|
|
|
|
if (shortaddr || longaddr) {
|
|
createDeviceEntry(shortaddr, longaddr);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void Z_Devices::addEndoint(uint16_t shortaddr, uint8_t endpoint) {
|
|
if (!shortaddr) { return; }
|
|
uint32_t ep_profile = (endpoint << 16);
|
|
Z_Device &device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
if (findEndpointInVector(device.endpoints, endpoint) < 0) {
|
|
device.endpoints.push_back(ep_profile);
|
|
dirty();
|
|
}
|
|
}
|
|
|
|
void Z_Devices::addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t profileId) {
|
|
if (!shortaddr) { return; }
|
|
uint32_t ep_profile = (endpoint << 16) | profileId;
|
|
Z_Device &device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
int32_t found = findEndpointInVector(device.endpoints, endpoint);
|
|
if (found < 0) {
|
|
device.endpoints.push_back(ep_profile);
|
|
dirty();
|
|
} else {
|
|
if (device.endpoints[found] != ep_profile) {
|
|
device.endpoints[found] = ep_profile;
|
|
dirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluster, bool out) {
|
|
if (!shortaddr) { return; }
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
uint32_t ep_cluster = (endpoint << 16) | cluster;
|
|
if (!out) {
|
|
if (!findInVector(device.clusters_in, ep_cluster)) {
|
|
device.clusters_in.push_back(ep_cluster);
|
|
dirty();
|
|
}
|
|
} else {
|
|
if (!findInVector(device.clusters_out, ep_cluster)) {
|
|
device.clusters_out.push_back(ep_cluster);
|
|
dirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
uint8_t Z_Devices::findClusterEndpointIn(uint16_t shortaddr, uint16_t cluster){
|
|
int32_t short_found = findShortAddr(shortaddr);
|
|
if (short_found < 0) return 0;
|
|
Z_Device &device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return 0; }
|
|
int32_t found = findClusterEndpoint(device.clusters_in, cluster);
|
|
if (found >= 0) {
|
|
return (device.clusters_in[found] >> 16) & 0xFF;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
void Z_Devices::setManufId(uint16_t shortaddr, const char * str) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
if (!device.manufacturerId.equals(str)) {
|
|
dirty();
|
|
}
|
|
device.manufacturerId = str;
|
|
}
|
|
void Z_Devices::setModelId(uint16_t shortaddr, const char * str) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
if (!device.modelId.equals(str)) {
|
|
dirty();
|
|
}
|
|
device.modelId = str;
|
|
}
|
|
void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
if (!device.friendlyName.equals(str)) {
|
|
dirty();
|
|
}
|
|
device.friendlyName = str;
|
|
}
|
|
|
|
const String * Z_Devices::getFriendlyName(uint16_t shortaddr) const {
|
|
int32_t found = findShortAddr(shortaddr);
|
|
if (found >= 0) {
|
|
const Z_Device & device = devicesAt(found);
|
|
if (device.friendlyName.length() > 0) {
|
|
return &device.friendlyName;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void Z_Devices::updateLastSeen(uint16_t shortaddr) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
_updateLastSeen(device);
|
|
}
|
|
|
|
|
|
|
|
|
|
void Z_Devices::resetTimer(uint32_t shortaddr) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
device.timer = 0;
|
|
device.func = nullptr;
|
|
}
|
|
|
|
|
|
void Z_Devices::setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
|
|
device.cluster = cluster;
|
|
device.endpoint = endpoint;
|
|
device.value = value;
|
|
device.func = func;
|
|
device.timer = wait_ms + millis();
|
|
}
|
|
|
|
|
|
void Z_Devices::runTimer(void) {
|
|
for (std::vector<Z_Device>::iterator it = _devices.begin(); it != _devices.end(); ++it) {
|
|
Z_Device &device = *it;
|
|
uint16_t shortaddr = device.shortaddr;
|
|
|
|
uint32_t timer = device.timer;
|
|
if ((timer) && TimeReached(timer)) {
|
|
device.timer = 0;
|
|
|
|
(*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value);
|
|
}
|
|
}
|
|
|
|
if ((_saveTimer) && TimeReached(_saveTimer)) {
|
|
saveZigbeeDevices();
|
|
_saveTimer = 0;
|
|
}
|
|
}
|
|
|
|
void Z_Devices::jsonClear(uint16_t shortaddr) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
|
|
device.json = nullptr;
|
|
device.json_buffer->clear();
|
|
}
|
|
|
|
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
|
|
to.remove(key);
|
|
|
|
if (val.is<char*>()) {
|
|
String sval = val.as<String>();
|
|
to.set(key, sval);
|
|
} else if (val.is<JsonArray>()) {
|
|
JsonArray &nested_arr = to.createNestedArray(key);
|
|
CopyJsonArray(nested_arr, val.as<JsonArray>());
|
|
} else if (val.is<JsonObject>()) {
|
|
JsonObject &nested_obj = to.createNestedObject(key);
|
|
CopyJsonObject(nested_obj, val.as<JsonObject>());
|
|
} else {
|
|
to.set(key, val);
|
|
}
|
|
}
|
|
|
|
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
|
|
for (auto v : arr) {
|
|
if (v.is<char*>()) {
|
|
String sval = v.as<String>();
|
|
to.add(sval);
|
|
} else if (v.is<JsonArray>()) {
|
|
} else if (v.is<JsonObject>()) {
|
|
} else {
|
|
to.add(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
|
|
for (auto kv : from) {
|
|
String key_string = kv.key;
|
|
JsonVariant &val = kv.value;
|
|
|
|
CopyJsonVariant(to, key_string, val);
|
|
}
|
|
}
|
|
|
|
|
|
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return false; }
|
|
if (&values == nullptr) { return false; }
|
|
|
|
if (nullptr == device.json) {
|
|
return false;
|
|
}
|
|
|
|
for (auto kv : values) {
|
|
String key_string = kv.key;
|
|
|
|
if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) {
|
|
if (device.json->containsKey(kv.key)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
if (&values == nullptr) { return; }
|
|
|
|
if (nullptr == device.json) {
|
|
device.json = &(device.json_buffer->createObject());
|
|
}
|
|
|
|
char sa[8];
|
|
snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr);
|
|
device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa);
|
|
|
|
const String * fname = zigbee_devices.getFriendlyName(shortaddr);
|
|
if (fname) {
|
|
device.json->set(F(D_JSON_ZIGBEE_NAME), (char*)fname->c_str());
|
|
}
|
|
|
|
|
|
CopyJsonObject(*device.json, values);
|
|
}
|
|
|
|
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return nullptr; }
|
|
return device.json;
|
|
}
|
|
|
|
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
|
Z_Device & device = getShortAddr(shortaddr);
|
|
if (&device == nullptr) { return; }
|
|
JsonObject * json = device.json;
|
|
if (json == nullptr) { return; }
|
|
|
|
const String * fname = zigbee_devices.getFriendlyName(shortaddr);
|
|
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname);
|
|
# 693 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_3_devices.ino"
|
|
if (use_fname) {
|
|
json->remove(F(D_JSON_ZIGBEE_NAME));
|
|
} else {
|
|
json->remove(F(D_JSON_ZIGBEE_DEVICE));
|
|
}
|
|
|
|
String msg = "";
|
|
json->printTo(msg);
|
|
zigbee_devices.jsonClear(shortaddr);
|
|
|
|
if (use_fname) {
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
XdrvRulesProcess();
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str());
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
XdrvRulesProcess();
|
|
} else {
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
XdrvRulesProcess();
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
XdrvRulesProcess();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
|
|
jsonPublishFlush(shortaddr);
|
|
jsonAppend(shortaddr, values);
|
|
jsonPublishFlush(shortaddr);
|
|
}
|
|
|
|
void Z_Devices::dirty(void) {
|
|
_saveTimer = kZigbeeSaveDelaySeconds * 1000 + millis();
|
|
}
|
|
void Z_Devices::clean(void) {
|
|
_saveTimer = 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_known) const {
|
|
if (nullptr == param) { return 0; }
|
|
size_t param_len = strlen(param);
|
|
char dataBuf[param_len + 1];
|
|
strcpy(dataBuf, param);
|
|
RemoveSpace(dataBuf);
|
|
uint16_t shortaddr = 0;
|
|
|
|
if (strlen(dataBuf) < 4) {
|
|
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) {
|
|
shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1);
|
|
}
|
|
} else if ((dataBuf[0] == '0') && (dataBuf[1] == 'x')) {
|
|
|
|
if (strlen(dataBuf) < 18) {
|
|
|
|
shortaddr = strtoull(dataBuf, nullptr, 0);
|
|
if (short_must_be_known) {
|
|
shortaddr = zigbee_devices.isKnownShortAddr(shortaddr);
|
|
}
|
|
|
|
} else {
|
|
|
|
uint64_t longaddr = strtoull(dataBuf, nullptr, 0);
|
|
shortaddr = zigbee_devices.isKnownLongAddr(longaddr);
|
|
}
|
|
} else {
|
|
|
|
shortaddr = zigbee_devices.isKnownFriendlyName(dataBuf);
|
|
}
|
|
|
|
return shortaddr;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
|
|
DynamicJsonBuffer jsonBuffer;
|
|
JsonArray& json = jsonBuffer.createArray();
|
|
JsonArray& devices = json;
|
|
|
|
for (std::vector<Z_Device>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
|
|
const Z_Device& device = *it;
|
|
uint16_t shortaddr = device.shortaddr;
|
|
char hex[22];
|
|
|
|
|
|
if ((status_shortaddr) && (status_shortaddr != shortaddr)) { continue; }
|
|
|
|
JsonObject& dev = devices.createNestedObject();
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
|
dev[F(D_JSON_ZIGBEE_DEVICE)] = hex;
|
|
|
|
if (device.friendlyName.length() > 0) {
|
|
dev[F(D_JSON_ZIGBEE_NAME)] = device.friendlyName;
|
|
}
|
|
|
|
if (2 <= dump_mode) {
|
|
hex[0] = '0';
|
|
hex[1] = 'x';
|
|
Uint64toHex(device.longaddr, &hex[2], 64);
|
|
dev[F("IEEEAddr")] = hex;
|
|
if (device.modelId.length() > 0) {
|
|
dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId;
|
|
}
|
|
if (device.manufacturerId.length() > 0) {
|
|
dev[F("Manufacturer")] = device.manufacturerId;
|
|
}
|
|
}
|
|
|
|
|
|
if (3 <= dump_mode) {
|
|
JsonObject& dev_endpoints = dev.createNestedObject(F("Endpoints"));
|
|
for (std::vector<uint32_t>::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) {
|
|
uint32_t ep_profile = *ite;
|
|
uint8_t endpoint = (ep_profile >> 16) & 0xFF;
|
|
uint16_t profileId = ep_profile & 0xFFFF;
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
|
JsonObject& ep = dev_endpoints.createNestedObject(hex);
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), profileId);
|
|
ep[F("ProfileId")] = hex;
|
|
|
|
int32_t found = -1;
|
|
for (uint32_t i = 0; i < sizeof(Z_ProfileIds) / sizeof(Z_ProfileIds[0]); i++) {
|
|
if (pgm_read_word(&Z_ProfileIds[i]) == profileId) {
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
if (found > 0) {
|
|
GetTextIndexed(hex, sizeof(hex), found, Z_ProfileNames);
|
|
ep[F("ProfileIdName")] = hex;
|
|
}
|
|
|
|
ep.createNestedArray(F("ClustersIn"));
|
|
ep.createNestedArray(F("ClustersOut"));
|
|
}
|
|
|
|
for (std::vector<uint32_t>::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) {
|
|
uint16_t cluster = *itc & 0xFFFF;
|
|
uint8_t endpoint = (*itc >> 16) & 0xFF;
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
|
JsonArray &cluster_arr = dev_endpoints[hex][F("ClustersIn")];
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
|
|
cluster_arr.add(hex);
|
|
}
|
|
|
|
for (std::vector<uint32_t>::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) {
|
|
uint16_t cluster = *itc & 0xFFFF;
|
|
uint8_t endpoint = (*itc >> 16) & 0xFF;
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%02X"), endpoint);
|
|
JsonArray &cluster_arr = dev_endpoints[hex][F("ClustersOut")];
|
|
|
|
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), cluster);
|
|
cluster_arr.add(hex);
|
|
}
|
|
}
|
|
}
|
|
String payload = "";
|
|
payload.reserve(200);
|
|
json.printTo(payload);
|
|
return payload;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino"
|
|
#ifdef USE_ZIGBEE
|
|
# 50 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_4_persistence.ino"
|
|
const static uint16_t z_spi_start_sector = 0xFF;
|
|
const static uint8_t* z_spi_start = (uint8_t*) 0x402FF000;
|
|
const static uint8_t* z_dev_start = z_spi_start + 0x0800;
|
|
const static size_t z_spi_len = 0x1000;
|
|
const static size_t z_block_offset = 0x0800;
|
|
const static size_t z_block_len = 0x0800;
|
|
|
|
class z_flashdata_t {
|
|
public:
|
|
uint32_t name;
|
|
uint16_t len;
|
|
uint16_t reserved;
|
|
};
|
|
|
|
const static uint32_t ZIGB_NAME = 0x3167697A;
|
|
const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t);
|
|
|
|
|
|
const uint16_t Z_ClusterNumber[] PROGMEM = {
|
|
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
|
|
0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
|
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
|
|
0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
|
|
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
|
|
0x0100, 0x0101, 0x0102,
|
|
0x0201, 0x0202, 0x0203, 0x0204,
|
|
0x0300, 0x0301,
|
|
0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406,
|
|
0x0500, 0x0501, 0x0502,
|
|
0x0700, 0x0701, 0x0702,
|
|
0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05,
|
|
0x1000,
|
|
0xFC0F,
|
|
};
|
|
|
|
|
|
uint16_t fromClusterCode(uint8_t c) {
|
|
if (c >= sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0])) {
|
|
return 0xFFFF;
|
|
}
|
|
return pgm_read_word(&Z_ClusterNumber[c]);
|
|
}
|
|
|
|
|
|
uint8_t toClusterCode(uint16_t c) {
|
|
for (uint32_t i = 0; i < sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0]); i++) {
|
|
if (c == pgm_read_word(&Z_ClusterNumber[i])) {
|
|
return i;
|
|
}
|
|
}
|
|
return 0xFF;
|
|
}
|
|
|
|
class SBuffer hibernateDevice(const struct Z_Device &device) {
|
|
SBuffer buf(128);
|
|
|
|
buf.add8(0x00);
|
|
buf.add16(device.shortaddr);
|
|
buf.add64(device.longaddr);
|
|
uint32_t endpoints = device.endpoints.size();
|
|
if (endpoints > 254) { endpoints = 254; }
|
|
buf.add8(endpoints);
|
|
|
|
for (std::vector<uint32_t>::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) {
|
|
uint32_t ep_profile = *ite;
|
|
uint8_t endpoint = (ep_profile >> 16) & 0xFF;
|
|
uint16_t profileId = ep_profile & 0xFFFF;
|
|
|
|
buf.add8(endpoint);
|
|
buf.add16(profileId);
|
|
for (std::vector<uint32_t>::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) {
|
|
uint16_t cluster = *itc & 0xFFFF;
|
|
uint8_t c_endpoint = (*itc >> 16) & 0xFF;
|
|
|
|
if (endpoint == c_endpoint) {
|
|
uint8_t clusterCode = toClusterCode(cluster);
|
|
if (0xFF != clusterCode) { buf.add8(clusterCode); }
|
|
}
|
|
}
|
|
buf.add8(0xFF);
|
|
|
|
for (std::vector<uint32_t>::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) {
|
|
uint16_t cluster = *itc & 0xFFFF;
|
|
uint8_t c_endpoint = (*itc >> 16) & 0xFF;
|
|
|
|
if (endpoint == c_endpoint) {
|
|
uint8_t clusterCode = toClusterCode(cluster);
|
|
if (0xFF != clusterCode) { buf.add8(clusterCode); }
|
|
}
|
|
}
|
|
buf.add8(0xFF);
|
|
}
|
|
|
|
|
|
size_t model_len = device.modelId.length();
|
|
if (model_len > 32) { model_len = 32; }
|
|
buf.addBuffer(device.modelId.c_str(), model_len);
|
|
buf.add8(0x00);
|
|
|
|
|
|
size_t manuf_len = device.manufacturerId.length();
|
|
if (manuf_len > 32) {manuf_len = 32; }
|
|
buf.addBuffer(device.manufacturerId.c_str(), manuf_len);
|
|
buf.add8(0x00);
|
|
|
|
|
|
size_t frname_len = device.friendlyName.length();
|
|
if (frname_len > 32) {frname_len = 32; }
|
|
buf.addBuffer(device.friendlyName.c_str(), frname_len);
|
|
buf.add8(0x00);
|
|
|
|
|
|
buf.set8(0, buf.len());
|
|
|
|
return buf;
|
|
}
|
|
|
|
class SBuffer hibernateDevices(void) {
|
|
SBuffer buf(2048);
|
|
|
|
size_t devices_size = zigbee_devices.devicesSize();
|
|
if (devices_size > 32) { devices_size = 32; }
|
|
buf.add8(devices_size);
|
|
|
|
for (uint32_t i = 0; i < devices_size; i++) {
|
|
const Z_Device & device = zigbee_devices.devicesAt(i);
|
|
const SBuffer buf_device = hibernateDevice(device);
|
|
buf.addBuffer(buf_device);
|
|
}
|
|
|
|
size_t buf_len = buf.len();
|
|
if (buf_len > 2040) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Devices list too big to fit in Flash (%d)"), buf_len);
|
|
}
|
|
|
|
|
|
char *hex_char = (char*) malloc((buf_len * 2) + 2);
|
|
if (hex_char) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbFlashStore %s"),
|
|
ToHex_P(buf.getBuffer(), buf_len, hex_char, (buf_len * 2) + 2));
|
|
free(hex_char);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
void hidrateDevices(const SBuffer &buf) {
|
|
uint32_t buf_len = buf.len();
|
|
if (buf_len <= 10) { return; }
|
|
|
|
uint32_t k = 0;
|
|
uint32_t num_devices = buf.get8(k++);
|
|
|
|
for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) {
|
|
uint32_t dev_record_len = buf.get8(k);
|
|
|
|
SBuffer buf_d = buf.subBuffer(k, dev_record_len);
|
|
|
|
uint32_t d = 1;
|
|
uint16_t shortaddr = buf_d.get16(d); d += 2;
|
|
uint64_t longaddr = buf_d.get64(d); d += 8;
|
|
zigbee_devices.updateDevice(shortaddr, longaddr);
|
|
|
|
uint32_t endpoints = buf_d.get8(d++);
|
|
for (uint32_t j = 0; j < endpoints; j++) {
|
|
uint8_t ep = buf_d.get8(d++);
|
|
uint16_t ep_profile = buf_d.get16(d); d += 2;
|
|
zigbee_devices.addEndointProfile(shortaddr, ep, ep_profile);
|
|
|
|
|
|
while (d < dev_record_len) {
|
|
uint8_t ep_cluster = buf_d.get8(d++);
|
|
if (0xFF == ep_cluster) { break; }
|
|
zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), false);
|
|
}
|
|
|
|
while (d < dev_record_len) {
|
|
uint8_t ep_cluster = buf_d.get8(d++);
|
|
if (0xFF == ep_cluster) { break; }
|
|
zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), true);
|
|
}
|
|
}
|
|
|
|
|
|
char empty[] = "";
|
|
|
|
|
|
uint32_t s_len = buf_d.strlen_s(d);
|
|
char *ptr = s_len ? buf_d.charptr(d) : empty;
|
|
zigbee_devices.setModelId(shortaddr, ptr);
|
|
d += s_len + 1;
|
|
|
|
|
|
s_len = buf_d.strlen_s(d);
|
|
ptr = s_len ? buf_d.charptr(d) : empty;
|
|
zigbee_devices.setManufId(shortaddr, ptr);
|
|
d += s_len + 1;
|
|
|
|
|
|
s_len = buf_d.strlen_s(d);
|
|
ptr = s_len ? buf_d.charptr(d) : empty;
|
|
zigbee_devices.setFriendlyName(shortaddr, ptr);
|
|
d += s_len + 1;
|
|
|
|
|
|
k += dev_record_len;
|
|
}
|
|
}
|
|
|
|
void loadZigbeeDevices(void) {
|
|
z_flashdata_t flashdata;
|
|
memcpy_P(&flashdata, z_dev_start, sizeof(z_flashdata_t));
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len);
|
|
|
|
|
|
if ((flashdata.name == ZIGB_NAME) && (flashdata.len > 0)) {
|
|
uint16_t buf_len = flashdata.len;
|
|
|
|
SBuffer buf(buf_len);
|
|
buf.addBuffer(z_dev_start + sizeof(z_flashdata_t), buf_len);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash (%d bytes)"), buf_len);
|
|
hidrateDevices(buf);
|
|
zigbee_devices.clean();
|
|
} else {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash"));
|
|
}
|
|
}
|
|
|
|
void saveZigbeeDevices(void) {
|
|
SBuffer buf = hibernateDevices();
|
|
size_t buf_len = buf.len();
|
|
if (buf_len > Z_MAX_FLASH) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Buffer too big to fit in Flash (%d bytes)"), buf_len);
|
|
return;
|
|
}
|
|
|
|
|
|
uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len);
|
|
if (!spi_buffer) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer"));
|
|
return;
|
|
}
|
|
|
|
ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
|
|
|
|
z_flashdata_t *flashdata = (z_flashdata_t*)(spi_buffer + z_block_offset);
|
|
flashdata->name = ZIGB_NAME;
|
|
flashdata->len = buf_len;
|
|
flashdata->reserved = 0;
|
|
|
|
memcpy(spi_buffer + z_block_offset + sizeof(z_flashdata_t), buf.getBuffer(), buf_len);
|
|
|
|
|
|
if (ESP.flashEraseSector(z_spi_start_sector)) {
|
|
ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
|
|
}
|
|
|
|
free(spi_buffer);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data store in Flash (0x%08X - %d bytes)"), z_dev_start, buf_len);
|
|
}
|
|
|
|
|
|
void eraseZigbeeDevices(void) {
|
|
zigbee_devices.clean();
|
|
|
|
uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len);
|
|
if (!spi_buffer) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer"));
|
|
return;
|
|
}
|
|
|
|
ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
|
|
|
|
|
|
memset(spi_buffer + z_block_offset, 0xFF, z_block_len);
|
|
|
|
|
|
if (ESP.flashEraseSector(z_spi_start_sector)) {
|
|
ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE);
|
|
}
|
|
|
|
free(spi_buffer);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data erased (0x%08X - %d bytes)"), z_dev_start, z_block_len);
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
|
|
|
|
|
|
|
|
typedef union ZCLHeaderFrameControl_t {
|
|
struct {
|
|
uint8_t frame_type : 2;
|
|
uint8_t manuf_specific : 1;
|
|
uint8_t direction : 1;
|
|
uint8_t disable_def_resp : 1;
|
|
uint8_t reserved : 3;
|
|
} b;
|
|
uint32_t d8;
|
|
} ZCLHeaderFrameControl_t;
|
|
|
|
|
|
class ZCLFrame {
|
|
public:
|
|
|
|
ZCLFrame(uint8_t frame_control, uint16_t manuf_code, uint8_t transact_seq, uint8_t cmd_id,
|
|
const char *buf, size_t buf_len, uint16_t clusterid, uint16_t groupid,
|
|
uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
|
|
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
|
|
uint32_t timestamp):
|
|
_cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq),
|
|
_payload(buf_len ? buf_len : 250),
|
|
_cluster_id(clusterid), _group_id(groupid),
|
|
_srcaddr(srcaddr), _srcendpoint(srcendpoint), _dstendpoint(dstendpoint), _wasbroadcast(wasbroadcast),
|
|
_linkquality(linkquality), _securityuse(securityuse), _seqnumber(seqnumber),
|
|
_timestamp(timestamp)
|
|
{
|
|
_frame_control.d8 = frame_control;
|
|
_payload.addBuffer(buf, buf_len);
|
|
};
|
|
|
|
|
|
void log(void) {
|
|
char hex_char[_payload.len()*2+2];
|
|
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
|
|
"\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\","
|
|
"\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d,"
|
|
"\"" D_CMND_ZIGBEE_LINKQUALITY "\":%d," "\"securityuse\":%d," "\"seqnumber\":%d,"
|
|
"\"timestamp\":%d,"
|
|
"\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d,"
|
|
"\"cmdid\":\"0x%02X\",\"payload\":\"%s\"}}"),
|
|
_group_id, _cluster_id, _srcaddr,
|
|
_srcendpoint, _dstendpoint, _wasbroadcast,
|
|
_linkquality, _securityuse, _seqnumber,
|
|
_timestamp,
|
|
_frame_control, _manuf_code, _transact_seq, _cmd_id,
|
|
hex_char);
|
|
if (Settings.flag3.tuya_serial_mqtt_publish) {
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
XdrvRulesProcess();
|
|
} else {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data);
|
|
}
|
|
}
|
|
|
|
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid,
|
|
uint16_t srcaddr, uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
|
|
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
|
|
uint32_t timestamp) {
|
|
uint32_t i = offset;
|
|
ZCLHeaderFrameControl_t frame_control;
|
|
uint16_t manuf_code = 0;
|
|
uint8_t transact_seq;
|
|
uint8_t cmd_id;
|
|
|
|
frame_control.d8 = buf.get8(i++);
|
|
if (frame_control.b.manuf_specific) {
|
|
manuf_code = buf.get16(i);
|
|
i += 2;
|
|
}
|
|
transact_seq = buf.get8(i++);
|
|
cmd_id = buf.get8(i++);
|
|
ZCLFrame zcl_frame(frame_control.d8, manuf_code, transact_seq, cmd_id,
|
|
(const char *)(buf.buf() + i), len + offset - i,
|
|
clusterid, groupid,
|
|
srcaddr, srcendpoint, dstendpoint, wasbroadcast,
|
|
linkquality, securityuse, seqnumber,
|
|
timestamp);
|
|
return zcl_frame;
|
|
}
|
|
|
|
bool isClusterSpecificCommand(void) {
|
|
return _frame_control.b.frame_type & 1;
|
|
}
|
|
|
|
static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len);
|
|
void parseRawAttributes(JsonObject& json, uint8_t offset = 0);
|
|
void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
|
|
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
|
|
void postProcessAttributes(uint16_t shortaddr, JsonObject& json);
|
|
|
|
inline void setGroupId(uint16_t groupid) {
|
|
_group_id = groupid;
|
|
}
|
|
|
|
inline void setClusterId(uint16_t clusterid) {
|
|
_cluster_id = clusterid;
|
|
}
|
|
|
|
inline uint8_t getCmdId(void) const {
|
|
return _cmd_id;
|
|
}
|
|
|
|
inline uint16_t getClusterId(void) const {
|
|
return _cluster_id;
|
|
}
|
|
|
|
inline uint16_t getSrcEndpoint(void) const {
|
|
return _srcendpoint;
|
|
}
|
|
|
|
const SBuffer &getPayload(void) const {
|
|
return _payload;
|
|
}
|
|
|
|
uint16_t getManufCode(void) const {
|
|
return _manuf_code;
|
|
}
|
|
|
|
private:
|
|
ZCLHeaderFrameControl_t _frame_control = { .d8 = 0 };
|
|
uint16_t _manuf_code = 0;
|
|
uint8_t _transact_seq = 0;
|
|
uint8_t _cmd_id = 0;
|
|
uint16_t _cluster_id = 0;
|
|
uint16_t _group_id = 0;
|
|
SBuffer _payload;
|
|
|
|
uint16_t _srcaddr;
|
|
uint8_t _srcendpoint;
|
|
uint8_t _dstendpoint;
|
|
uint8_t _wasbroadcast;
|
|
uint8_t _linkquality;
|
|
uint8_t _securityuse;
|
|
uint8_t _seqnumber;
|
|
uint32_t _timestamp;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t toPercentageCR2032(uint32_t voltage) {
|
|
uint32_t percentage;
|
|
if (voltage < 2100) {
|
|
percentage = 0;
|
|
} else if (voltage < 2440) {
|
|
percentage = 6 - ((2440 - voltage) * 6) / 340;
|
|
} else if (voltage < 2740) {
|
|
percentage = 18 - ((2740 - voltage) * 12) / 300;
|
|
} else if (voltage < 2900) {
|
|
percentage = 42 - ((2900 - voltage) * 24) / 160;
|
|
} else if (voltage < 3000) {
|
|
percentage = 100 - ((3000 - voltage) * 58) / 100;
|
|
} else if (voltage >= 3000) {
|
|
percentage = 100;
|
|
}
|
|
return percentage;
|
|
}
|
|
|
|
|
|
uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf,
|
|
uint32_t offset, uint32_t len) {
|
|
|
|
uint32_t i = offset;
|
|
uint32_t attrtype = buf.get8(i++);
|
|
|
|
|
|
json[attrid_str] = (char*) nullptr;
|
|
|
|
|
|
switch (attrtype) {
|
|
case 0x00:
|
|
case 0xFF:
|
|
break;
|
|
case 0x10:
|
|
{
|
|
uint8_t val_bool = buf.get8(i++);
|
|
if (0xFF != val_bool) {
|
|
json[attrid_str] = (bool) (val_bool ? true : false);
|
|
}
|
|
}
|
|
break;
|
|
case 0x20:
|
|
{
|
|
uint8_t uint8_val = buf.get8(i);
|
|
i += 1;
|
|
if (0xFF != uint8_val) {
|
|
json[attrid_str] = uint8_val;
|
|
}
|
|
}
|
|
break;
|
|
case 0x21:
|
|
{
|
|
uint16_t uint16_val = buf.get16(i);
|
|
i += 2;
|
|
if (0xFFFF != uint16_val) {
|
|
json[attrid_str] = uint16_val;
|
|
}
|
|
}
|
|
break;
|
|
case 0x23:
|
|
{
|
|
uint32_t uint32_val = buf.get32(i);
|
|
i += 4;
|
|
if (0xFFFFFFFF != uint32_val) {
|
|
json[attrid_str] = uint32_val;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x24:
|
|
case 0x25:
|
|
case 0x26:
|
|
case 0x27:
|
|
{
|
|
uint8_t len = attrtype - 0x1F;
|
|
|
|
char hex[2*len+1];
|
|
ToHex_P(buf.buf(i), len, hex, sizeof(hex));
|
|
json[attrid_str] = hex;
|
|
i += len;
|
|
}
|
|
break;
|
|
case 0x28:
|
|
{
|
|
int8_t int8_val = buf.get8(i);
|
|
i += 1;
|
|
if (0x80 != int8_val) {
|
|
json[attrid_str] = int8_val;
|
|
}
|
|
}
|
|
break;
|
|
case 0x29:
|
|
{
|
|
int16_t int16_val = buf.get16(i);
|
|
i += 2;
|
|
if (0x8000 != int16_val) {
|
|
json[attrid_str] = int16_val;
|
|
}
|
|
}
|
|
break;
|
|
case 0x2B:
|
|
{
|
|
int32_t int32_val = buf.get32(i);
|
|
i += 4;
|
|
if (0x80000000 != int32_val) {
|
|
json[attrid_str] = int32_val;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x2C:
|
|
case 0x2D:
|
|
case 0x2E:
|
|
case 0x2F:
|
|
{
|
|
uint8_t len = attrtype - 0x27;
|
|
|
|
char hex[2*len+1];
|
|
ToHex_P(buf.buf(i), len, hex, sizeof(hex));
|
|
json[attrid_str] = hex;
|
|
i += len;
|
|
}
|
|
break;
|
|
|
|
case 0x41:
|
|
case 0x42:
|
|
case 0x43:
|
|
case 0x44:
|
|
|
|
{
|
|
bool parse_as_string = true;
|
|
uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i);
|
|
i += (attrtype <= 0x42) ? 1 : 2;
|
|
if (i + len > buf.len()) {
|
|
len = buf.len() - i;
|
|
}
|
|
|
|
|
|
if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
|
|
# 318 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino"
|
|
if (parse_as_string) {
|
|
char str[len+1];
|
|
strncpy(str, buf.charptr(i), len);
|
|
str[len] = 0x00;
|
|
json[attrid_str] = str;
|
|
} else {
|
|
|
|
char hex[2*len+1];
|
|
ToHex_P(buf.buf(i), len, hex, sizeof(hex));
|
|
json[attrid_str] = hex;
|
|
}
|
|
|
|
i += len;
|
|
break;
|
|
}
|
|
i += buf.get8(i) + 1;
|
|
break;
|
|
|
|
case 0x08:
|
|
case 0x18:
|
|
{
|
|
uint8_t uint8_val = buf.get8(i);
|
|
i += 1;
|
|
json[attrid_str] = uint8_val;
|
|
}
|
|
break;
|
|
case 0x09:
|
|
case 0x19:
|
|
{
|
|
uint16_t uint16_val = buf.get16(i);
|
|
i += 2;
|
|
json[attrid_str] = uint16_val;
|
|
}
|
|
break;
|
|
case 0x0B:
|
|
case 0x1B:
|
|
{
|
|
uint32_t uint32_val = buf.get32(i);
|
|
i += 4;
|
|
json[attrid_str] = uint32_val;
|
|
}
|
|
break;
|
|
|
|
case 0x30:
|
|
case 0x31:
|
|
i += attrtype - 0x2F;
|
|
break;
|
|
|
|
|
|
case 0x39:
|
|
{
|
|
uint32_t uint32_val = buf.get32(i);
|
|
float * float_val = (float*) &uint32_val;
|
|
i += 4;
|
|
json[attrid_str] = *float_val;
|
|
}
|
|
break;
|
|
|
|
case 0xE0:
|
|
case 0xE1:
|
|
case 0xE2:
|
|
i += 4;
|
|
break;
|
|
|
|
case 0xE8:
|
|
case 0xE9:
|
|
i += 2;
|
|
break;
|
|
case 0xEA:
|
|
i += 4;
|
|
break;
|
|
|
|
case 0xF0:
|
|
i += 8;
|
|
break;
|
|
case 0xF1:
|
|
i += 16;
|
|
break;
|
|
|
|
|
|
case 0x0A:
|
|
case 0x0C:
|
|
case 0x0D:
|
|
case 0x0E:
|
|
case 0x0F:
|
|
i += attrtype - 0x07;
|
|
break;
|
|
|
|
case 0x1A:
|
|
case 0x1C:
|
|
case 0x1D:
|
|
case 0x1E:
|
|
case 0x1F:
|
|
i += attrtype - 0x17;
|
|
break;
|
|
|
|
case 0x38:
|
|
i += 2;
|
|
break;
|
|
case 0x3A:
|
|
{
|
|
uint64_t uint64_val = buf.get64(i);
|
|
double * double_val = (double*) &uint64_val;
|
|
i += 8;
|
|
json[attrid_str] = *double_val;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return i - offset;
|
|
}
|
|
|
|
|
|
void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len) {
|
|
uint32_t suffix = 1;
|
|
|
|
snprintf_P(key, key_len, PSTR("%04X/%04X"), cluster, attr);
|
|
while (json.containsKey(key)) {
|
|
suffix++;
|
|
snprintf_P(key, key_len, PSTR("%04X/%04X+%d"), cluster, attr, suffix);
|
|
}
|
|
}
|
|
|
|
|
|
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
|
|
uint32_t i = offset;
|
|
uint32_t len = _payload.len();
|
|
|
|
while (len >= i + 3) {
|
|
uint16_t attrid = _payload.get16(i);
|
|
i += 2;
|
|
|
|
char key[16];
|
|
generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
|
|
|
|
|
|
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
|
|
if (0x42 == _payload.get8(i)) {
|
|
_payload.set8(i, 0x41);
|
|
}
|
|
}
|
|
i += parseSingleAttribute(json, key, _payload, i, len);
|
|
}
|
|
}
|
|
|
|
|
|
void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
|
|
uint32_t i = offset;
|
|
uint32_t len = _payload.len();
|
|
|
|
while (len - i >= 4) {
|
|
uint16_t attrid = _payload.get16(i);
|
|
i += 2;
|
|
uint8_t status = _payload.get8(i++);
|
|
|
|
if (0 == status) {
|
|
char key[16];
|
|
generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
|
|
|
|
i += parseSingleAttribute(json, key, _payload, i, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
|
|
uint32_t i = offset;
|
|
uint32_t len = _payload.len();
|
|
|
|
char attrid_str[12];
|
|
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cluster_id, _cmd_id);
|
|
|
|
char hex_char[_payload.len()*2+2];
|
|
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
|
|
|
|
json[attrid_str] = hex_char;
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
|
|
typedef struct Z_AttributeConverter {
|
|
uint16_t cluster;
|
|
uint16_t attribute;
|
|
const char * name;
|
|
Z_AttrConverter func;
|
|
} Z_AttributeConverter;
|
|
|
|
|
|
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
|
{ 0x0000, 0x0000, "ZCLVersion", &Z_Copy },
|
|
{ 0x0000, 0x0001, "AppVersion", &Z_Copy },
|
|
{ 0x0000, 0x0002, "StackVersion", &Z_Copy },
|
|
{ 0x0000, 0x0003, "HWVersion", &Z_Copy },
|
|
{ 0x0000, 0x0004, "Manufacturer", &Z_ManufKeep },
|
|
{ 0x0000, 0x0005, D_JSON_MODEL D_JSON_ID, &Z_ModelKeep },
|
|
{ 0x0000, 0x0006, "DateCode", &Z_Copy },
|
|
{ 0x0000, 0x0007, "PowerSource", &Z_Copy },
|
|
{ 0x0000, 0x4000, "SWBuildID", &Z_Copy },
|
|
{ 0x0000, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
{ 0x0000, 0xFF01, nullptr, &Z_AqaraSensor },
|
|
|
|
|
|
{ 0x0001, 0x0000, "MainsVoltage", &Z_Copy },
|
|
{ 0x0001, 0x0001, "MainsFrequency", &Z_Copy },
|
|
{ 0x0001, 0x0020, "BatteryVoltage", &Z_Copy },
|
|
{ 0x0001, 0x0021, "BatteryPercentageRemaining",&Z_Copy },
|
|
|
|
|
|
{ 0x0002, 0x0000, "CurrentTemperature", &Z_Copy },
|
|
{ 0x0002, 0x0001, "MinTempExperienced", &Z_Copy },
|
|
{ 0x0002, 0x0002, "MaxTempExperienced", &Z_Copy },
|
|
{ 0x0002, 0x0003, "OverTempTotalDwell", &Z_Copy },
|
|
|
|
|
|
{ 0x0006, 0x0000, "Power", &Z_Copy },
|
|
{ 0x0006, 0x8000, "Power", &Z_Copy },
|
|
|
|
|
|
{ 0x0007, 0x0000, "SwitchType", &Z_Copy },
|
|
|
|
|
|
{ 0x0008, 0x0000, "Dimmer", &Z_Copy },
|
|
# 558 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino"
|
|
{ 0x0009, 0x0000, "AlarmCount", &Z_Copy },
|
|
|
|
{ 0x000A, 0x0000, "Time", &Z_Copy },
|
|
{ 0x000A, 0x0001, "TimeStatus", &Z_Copy },
|
|
{ 0x000A, 0x0002, "TimeZone", &Z_Copy },
|
|
{ 0x000A, 0x0003, "DstStart", &Z_Copy },
|
|
{ 0x000A, 0x0004, "DstStart", &Z_Copy },
|
|
{ 0x000A, 0x0005, "DstShift", &Z_Copy },
|
|
{ 0x000A, 0x0006, "StandardTime", &Z_Copy },
|
|
{ 0x000A, 0x0007, "LocalTime", &Z_Copy },
|
|
{ 0x000A, 0x0008, "LastSetTime", &Z_Copy },
|
|
{ 0x000A, 0x0009, "ValidUntilTime", &Z_Copy },
|
|
|
|
{ 0x000B, 0x0000, "LocationType", &Z_Copy },
|
|
{ 0x000B, 0x0000, "LocationMethod", &Z_Copy },
|
|
{ 0x000B, 0x0000, "LocationAge", &Z_Copy },
|
|
{ 0x000B, 0x0000, "QualityMeasure", &Z_Copy },
|
|
{ 0x000B, 0x0000, "NumberOfDevices", &Z_Copy },
|
|
|
|
{ 0x000C, 0x0004, "ActiveText", &Z_Copy },
|
|
{ 0x000C, 0x001C, "Description", &Z_Copy },
|
|
{ 0x000C, 0x002E, "InactiveText", &Z_Copy },
|
|
{ 0x000C, 0x0041, "MaxPresentValue", &Z_Copy },
|
|
{ 0x000C, 0x0045, "MinPresentValue", &Z_Copy },
|
|
{ 0x000C, 0x0051, "OutOfService", &Z_Copy },
|
|
{ 0x000C, 0x0055, "AqaraRotate", &Z_Copy },
|
|
{ 0x000C, 0x0057, "PriorityArray", &Z_Copy },
|
|
{ 0x000C, 0x0067, "Reliability", &Z_Copy },
|
|
{ 0x000C, 0x0068, "RelinquishDefault", &Z_Copy },
|
|
{ 0x000C, 0x006A, "Resolution", &Z_Copy },
|
|
{ 0x000C, 0x006F, "StatusFlags", &Z_Copy },
|
|
{ 0x000C, 0x0075, "EngineeringUnits", &Z_Copy },
|
|
{ 0x000C, 0x0100, "ApplicationType", &Z_Copy },
|
|
{ 0x000C, 0xFF05, "Aqara_FF05", &Z_Copy },
|
|
|
|
{ 0x0010, 0x0004, "ActiveText", &Z_Copy },
|
|
{ 0x0010, 0x001C, "Description", &Z_Copy },
|
|
{ 0x0010, 0x002E, "InactiveText", &Z_Copy },
|
|
{ 0x0010, 0x0042, "MinimumOffTime", &Z_Copy },
|
|
{ 0x0010, 0x0043, "MinimumOnTime", &Z_Copy },
|
|
{ 0x0010, 0x0051, "OutOfService", &Z_Copy },
|
|
{ 0x0010, 0x0054, "Polarity", &Z_Copy },
|
|
{ 0x0010, 0x0055, "PresentValue", &Z_Copy },
|
|
{ 0x0010, 0x0057, "PriorityArray", &Z_Copy },
|
|
{ 0x0010, 0x0067, "Reliability", &Z_Copy },
|
|
{ 0x0010, 0x0068, "RelinquishDefault", &Z_Copy },
|
|
{ 0x0010, 0x006F, "StatusFlags", &Z_Copy },
|
|
{ 0x0010, 0x0100, "ApplicationType", &Z_Copy },
|
|
|
|
{ 0x0011, 0x0004, "ActiveText", &Z_Copy },
|
|
{ 0x0011, 0x001C, "Description", &Z_Copy },
|
|
{ 0x0011, 0x002E, "InactiveText", &Z_Copy },
|
|
{ 0x0011, 0x0042, "MinimumOffTime", &Z_Copy },
|
|
{ 0x0011, 0x0043, "MinimumOnTime", &Z_Copy },
|
|
{ 0x0011, 0x0051, "OutOfService", &Z_Copy },
|
|
{ 0x0011, 0x0055, "PresentValue", &Z_Copy },
|
|
{ 0x0011, 0x0057, "PriorityArray", &Z_Copy },
|
|
{ 0x0011, 0x0067, "Reliability", &Z_Copy },
|
|
{ 0x0011, 0x0068, "RelinquishDefault", &Z_Copy },
|
|
{ 0x0011, 0x006F, "StatusFlags", &Z_Copy },
|
|
{ 0x0011, 0x0100, "ApplicationType", &Z_Copy },
|
|
|
|
{ 0x0012, 0x000E, "StateText", &Z_Copy },
|
|
{ 0x0012, 0x001C, "Description", &Z_Copy },
|
|
{ 0x0012, 0x004A, "NumberOfStates", &Z_Copy },
|
|
{ 0x0012, 0x0051, "OutOfService", &Z_Copy },
|
|
{ 0x0012, 0x0055, "PresentValue", &Z_AqaraCube },
|
|
{ 0x0012, 0x0067, "Reliability", &Z_Copy },
|
|
{ 0x0012, 0x006F, "StatusFlags", &Z_Copy },
|
|
{ 0x0012, 0x0100, "ApplicationType", &Z_Copy },
|
|
|
|
{ 0x0013, 0x000E, "StateText", &Z_Copy },
|
|
{ 0x0013, 0x001C, "Description", &Z_Copy },
|
|
{ 0x0013, 0x004A, "NumberOfStates", &Z_Copy },
|
|
{ 0x0013, 0x0051, "OutOfService", &Z_Copy },
|
|
{ 0x0013, 0x0055, "PresentValue", &Z_Copy },
|
|
{ 0x0013, 0x0057, "PriorityArray", &Z_Copy },
|
|
{ 0x0013, 0x0067, "Reliability", &Z_Copy },
|
|
{ 0x0013, 0x0068, "RelinquishDefault", &Z_Copy },
|
|
{ 0x0013, 0x006F, "StatusFlags", &Z_Copy },
|
|
{ 0x0013, 0x0100, "ApplicationType", &Z_Copy },
|
|
|
|
{ 0x0014, 0x000E, "StateText", &Z_Copy },
|
|
{ 0x0014, 0x001C, "Description", &Z_Copy },
|
|
{ 0x0014, 0x004A, "NumberOfStates", &Z_Copy },
|
|
{ 0x0014, 0x0051, "OutOfService", &Z_Copy },
|
|
{ 0x0014, 0x0055, "PresentValue", &Z_Copy },
|
|
{ 0x0014, 0x0067, "Reliability", &Z_Copy },
|
|
{ 0x0014, 0x0068, "RelinquishDefault", &Z_Copy },
|
|
{ 0x0014, 0x006F, "StatusFlags", &Z_Copy },
|
|
{ 0x0014, 0x0100, "ApplicationType", &Z_Copy },
|
|
|
|
{ 0x001A, 0x0000, "TotalProfileNum", &Z_Copy },
|
|
{ 0x001A, 0x0001, "MultipleScheduling", &Z_Copy },
|
|
{ 0x001A, 0x0002, "EnergyFormatting", &Z_Copy },
|
|
{ 0x001A, 0x0003, "EnergyRemote", &Z_Copy },
|
|
{ 0x001A, 0x0004, "ScheduleMode", &Z_Copy },
|
|
|
|
{ 0x0020, 0x0000, "CheckinInterval", &Z_Copy },
|
|
{ 0x0020, 0x0001, "LongPollInterval", &Z_Copy },
|
|
{ 0x0020, 0x0002, "ShortPollInterval", &Z_Copy },
|
|
{ 0x0020, 0x0003, "FastPollTimeout", &Z_Copy },
|
|
{ 0x0020, 0x0004, "CheckinIntervalMin", &Z_Copy },
|
|
{ 0x0020, 0x0005, "LongPollIntervalMin", &Z_Copy },
|
|
{ 0x0020, 0x0006, "FastPollTimeoutMax", &Z_Copy },
|
|
|
|
{ 0x0100, 0x0000, "PhysicalClosedLimit", &Z_Copy },
|
|
{ 0x0100, 0x0001, "MotorStepSize", &Z_Copy },
|
|
{ 0x0100, 0x0002, "Status", &Z_Copy },
|
|
{ 0x0100, 0x0010, "ClosedLimit", &Z_Copy },
|
|
{ 0x0100, 0x0011, "Mode", &Z_Copy },
|
|
|
|
{ 0x0101, 0x0000, "LockState", &Z_Copy },
|
|
{ 0x0101, 0x0001, "LockType", &Z_Copy },
|
|
{ 0x0101, 0x0002, "ActuatorEnabled", &Z_Copy },
|
|
{ 0x0101, 0x0003, "DoorState", &Z_Copy },
|
|
{ 0x0101, 0x0004, "DoorOpenEvents", &Z_Copy },
|
|
{ 0x0101, 0x0005, "DoorClosedEvents", &Z_Copy },
|
|
{ 0x0101, 0x0006, "OpenPeriod", &Z_Copy },
|
|
|
|
{ 0x0101, 0x0055, "AqaraVibrationMode", &Z_AqaraVibration },
|
|
{ 0x0101, 0x0503, "AqaraVibrationsOrAngle", &Z_Copy },
|
|
{ 0x0101, 0x0505, "AqaraVibration505", &Z_Copy },
|
|
{ 0x0101, 0x0508, "AqaraAccelerometer", &Z_AqaraVibration },
|
|
|
|
{ 0x0102, 0x0000, "WindowCoveringType", &Z_Copy },
|
|
{ 0x0102, 0x0001, "PhysicalClosedLimitLift",&Z_Copy },
|
|
{ 0x0102, 0x0002, "PhysicalClosedLimitTilt",&Z_Copy },
|
|
{ 0x0102, 0x0003, "CurrentPositionLift", &Z_Copy },
|
|
{ 0x0102, 0x0004, "CurrentPositionTilt", &Z_Copy },
|
|
{ 0x0102, 0x0005, "NumberofActuationsLift",&Z_Copy },
|
|
{ 0x0102, 0x0006, "NumberofActuationsTilt",&Z_Copy },
|
|
{ 0x0102, 0x0007, "ConfigStatus", &Z_Copy },
|
|
{ 0x0102, 0x0008, "CurrentPositionLiftPercentage",&Z_Copy },
|
|
{ 0x0102, 0x0009, "CurrentPositionTiltPercentage",&Z_Copy },
|
|
{ 0x0102, 0x0010, "InstalledOpenLimitLift",&Z_Copy },
|
|
{ 0x0102, 0x0011, "InstalledClosedLimitLift",&Z_Copy },
|
|
{ 0x0102, 0x0012, "InstalledOpenLimitTilt", &Z_Copy },
|
|
{ 0x0102, 0x0013, "InstalledClosedLimitTilt", &Z_Copy },
|
|
{ 0x0102, 0x0014, "VelocityLift",&Z_Copy },
|
|
{ 0x0102, 0x0015, "AccelerationTimeLift",&Z_Copy },
|
|
{ 0x0102, 0x0016, "DecelerationTimeLift", &Z_Copy },
|
|
{ 0x0102, 0x0017, "Mode",&Z_Copy },
|
|
{ 0x0102, 0x0018, "IntermediateSetpointsLift",&Z_Copy },
|
|
{ 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy },
|
|
|
|
|
|
{ 0x0300, 0x0000, "Hue", &Z_Copy },
|
|
{ 0x0300, 0x0001, "Sat", &Z_Copy },
|
|
{ 0x0300, 0x0002, "RemainingTime", &Z_Copy },
|
|
{ 0x0300, 0x0003, "X", &Z_Copy },
|
|
{ 0x0300, 0x0004, "Y", &Z_Copy },
|
|
{ 0x0300, 0x0005, "DriftCompensation", &Z_Copy },
|
|
{ 0x0300, 0x0006, "CompensationText", &Z_Copy },
|
|
{ 0x0300, 0x0007, "CT", &Z_Copy },
|
|
{ 0x0300, 0x0008, "ColorMode", &Z_Copy },
|
|
{ 0x0300, 0x0010, "NumberOfPrimaries", &Z_Copy },
|
|
{ 0x0300, 0x0011, "Primary1X", &Z_Copy },
|
|
{ 0x0300, 0x0012, "Primary1Y", &Z_Copy },
|
|
{ 0x0300, 0x0013, "Primary1Intensity", &Z_Copy },
|
|
{ 0x0300, 0x0015, "Primary2X", &Z_Copy },
|
|
{ 0x0300, 0x0016, "Primary2Y", &Z_Copy },
|
|
{ 0x0300, 0x0017, "Primary2Intensity", &Z_Copy },
|
|
{ 0x0300, 0x0019, "Primary3X", &Z_Copy },
|
|
{ 0x0300, 0x001A, "Primary3Y", &Z_Copy },
|
|
{ 0x0300, 0x001B, "Primary3Intensity", &Z_Copy },
|
|
{ 0x0300, 0x0030, "WhitePointX", &Z_Copy },
|
|
{ 0x0300, 0x0031, "WhitePointY", &Z_Copy },
|
|
{ 0x0300, 0x0032, "ColorPointRX", &Z_Copy },
|
|
{ 0x0300, 0x0033, "ColorPointRY", &Z_Copy },
|
|
{ 0x0300, 0x0034, "ColorPointRIntensity", &Z_Copy },
|
|
{ 0x0300, 0x0036, "ColorPointGX", &Z_Copy },
|
|
{ 0x0300, 0x0037, "ColorPointGY", &Z_Copy },
|
|
{ 0x0300, 0x0038, "ColorPointGIntensity", &Z_Copy },
|
|
{ 0x0300, 0x003A, "ColorPointBX", &Z_Copy },
|
|
{ 0x0300, 0x003B, "ColorPointBY", &Z_Copy },
|
|
{ 0x0300, 0x003C, "ColorPointBIntensity", &Z_Copy },
|
|
|
|
|
|
{ 0x0400, 0x0000, D_JSON_ILLUMINANCE, &Z_Copy },
|
|
{ 0x0400, 0x0001, "MinMeasuredValue", &Z_Copy },
|
|
{ 0x0400, 0x0002, "MaxMeasuredValue", &Z_Copy },
|
|
{ 0x0400, 0x0003, "Tolerance", &Z_Copy },
|
|
{ 0x0400, 0x0004, "LightSensorType", &Z_Copy },
|
|
{ 0x0400, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0401, 0x0000, "LevelStatus", &Z_Copy },
|
|
{ 0x0401, 0x0001, "LightSensorType", &Z_Copy },
|
|
{ 0x0401, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0402, 0x0000, D_JSON_TEMPERATURE, &Z_FloatDiv100 },
|
|
{ 0x0402, 0x0001, "MinMeasuredValue", &Z_FloatDiv100 },
|
|
{ 0x0402, 0x0002, "MaxMeasuredValue", &Z_FloatDiv100 },
|
|
{ 0x0402, 0x0003, "Tolerance", &Z_FloatDiv100 },
|
|
{ 0x0402, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0403, 0x0000, D_JSON_PRESSURE_UNIT, &Z_AddPressureUnit },
|
|
{ 0x0403, 0x0000, D_JSON_PRESSURE, &Z_Copy },
|
|
{ 0x0403, 0x0001, "MinMeasuredValue", &Z_Copy },
|
|
{ 0x0403, 0x0002, "MaxMeasuredValue", &Z_Copy },
|
|
{ 0x0403, 0x0003, "Tolerance", &Z_Copy },
|
|
{ 0x0403, 0x0010, "ScaledValue", &Z_Copy },
|
|
{ 0x0403, 0x0011, "MinScaledValue", &Z_Copy },
|
|
{ 0x0403, 0x0012, "MaxScaledValue", &Z_Copy },
|
|
{ 0x0403, 0x0013, "ScaledTolerance", &Z_Copy },
|
|
{ 0x0403, 0x0014, "Scale", &Z_Copy },
|
|
{ 0x0403, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0404, 0x0000, D_JSON_FLOWRATE, &Z_FloatDiv10 },
|
|
{ 0x0404, 0x0001, "MinMeasuredValue", &Z_Copy },
|
|
{ 0x0404, 0x0002, "MaxMeasuredValue", &Z_Copy },
|
|
{ 0x0404, 0x0003, "Tolerance", &Z_Copy },
|
|
{ 0x0404, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0405, 0x0000, D_JSON_HUMIDITY, &Z_FloatDiv100 },
|
|
{ 0x0405, 0x0001, "MinMeasuredValue", &Z_Copy },
|
|
{ 0x0405, 0x0002, "MaxMeasuredValue", &Z_Copy },
|
|
{ 0x0405, 0x0003, "Tolerance", &Z_Copy },
|
|
{ 0x0405, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0406, 0x0000, OCCUPANCY, &Z_Copy },
|
|
{ 0x0406, 0x0001, "OccupancySensorType", &Z_Copy },
|
|
{ 0x0406, 0xFFFF, nullptr, &Z_Remove },
|
|
|
|
|
|
{ 0x0B01, 0x0000, "CompanyName", &Z_Copy },
|
|
{ 0x0B01, 0x0001, "MeterTypeID", &Z_Copy },
|
|
{ 0x0B01, 0x0004, "DataQualityID", &Z_Copy },
|
|
{ 0x0B01, 0x0005, "CustomerName", &Z_Copy },
|
|
{ 0x0B01, 0x0006, "Model", &Z_Copy },
|
|
{ 0x0B01, 0x0007, "PartNumber", &Z_Copy },
|
|
{ 0x0B01, 0x000A, "SoftwareRevision", &Z_Copy },
|
|
{ 0x0B01, 0x000C, "POD", &Z_Copy },
|
|
{ 0x0B01, 0x000D, "AvailablePower", &Z_Copy },
|
|
{ 0x0B01, 0x000E, "PowerThreshold", &Z_Copy },
|
|
|
|
|
|
{ 0x0B05, 0x0000, "NumberOfResets", &Z_Copy },
|
|
{ 0x0B05, 0x0001, "PersistentMemoryWrites",&Z_Copy },
|
|
{ 0x0B05, 0x011C, "LastMessageLQI", &Z_Copy },
|
|
{ 0x0B05, 0x011D, "LastMessageRSSI", &Z_Copy },
|
|
|
|
};
|
|
|
|
|
|
|
|
int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = value;
|
|
zigbee_devices.setManufId(shortaddr, value.as<const char*>());
|
|
return 1;
|
|
}
|
|
|
|
int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = value;
|
|
zigbee_devices.setModelId(shortaddr, value.as<const char*>());
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
return 1;
|
|
}
|
|
|
|
|
|
int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = value;
|
|
return 1;
|
|
}
|
|
|
|
|
|
int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = F(D_UNIT_PRESSURE);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = ((float)value) / 100.0f;
|
|
return 1;
|
|
}
|
|
|
|
int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = ((float)value) / 10.0f;
|
|
return 1;
|
|
}
|
|
|
|
|
|
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
|
DynamicJsonBuffer jsonBuffer;
|
|
JsonObject& json = jsonBuffer.createObject();
|
|
json[F(OCCUPANCY)] = 0;
|
|
zigbee_devices.jsonPublishNow(shortaddr, json);
|
|
}
|
|
|
|
|
|
int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
json[new_name] = value;
|
|
int32_t val = value;
|
|
const __FlashStringHelper *aqara_cube = F("AqaraCube");
|
|
const __FlashStringHelper *aqara_cube_side = F("AqaraCubeSide");
|
|
const __FlashStringHelper *aqara_cube_from_side = F("AqaraCubeFromSide");
|
|
|
|
switch (val) {
|
|
case 0:
|
|
json[aqara_cube] = F("shake");
|
|
break;
|
|
case 2:
|
|
json[aqara_cube] = F("wakeup");
|
|
break;
|
|
case 3:
|
|
json[aqara_cube] = F("fall");
|
|
break;
|
|
case 64 ... 127:
|
|
json[aqara_cube] = F("flip90");
|
|
json[aqara_cube_side] = val % 8;
|
|
json[aqara_cube_from_side] = (val - 64) / 8;
|
|
break;
|
|
case 128 ... 132:
|
|
json[aqara_cube] = F("flip180");
|
|
json[aqara_cube_side] = val - 128;
|
|
break;
|
|
case 256 ... 261:
|
|
json[aqara_cube] = F("slide");
|
|
json[aqara_cube_side] = val - 256;
|
|
break;
|
|
case 512 ... 517:
|
|
json[aqara_cube] = F("tap");
|
|
json[aqara_cube_side] = val - 512;
|
|
break;
|
|
}
|
|
# 915 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_5_converters.ino"
|
|
return 1;
|
|
}
|
|
|
|
|
|
int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
|
|
switch (attr) {
|
|
case 0x0055:
|
|
{
|
|
int32_t ivalue = value;
|
|
const __FlashStringHelper * svalue;
|
|
switch (ivalue) {
|
|
case 1: svalue = F("vibrate"); break;
|
|
case 2: svalue = F("tilt"); break;
|
|
case 3: svalue = F("drop"); break;
|
|
default: svalue = F("unknown"); break;
|
|
}
|
|
json[new_name] = svalue;
|
|
}
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x0508:
|
|
{
|
|
|
|
|
|
String hex = value;
|
|
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
|
int16_t x, y, z;
|
|
z = buf2.get16(0);
|
|
y = buf2.get16(2);
|
|
x = buf2.get16(4);
|
|
JsonArray& xyz = json.createNestedArray(new_name);
|
|
xyz.add(x);
|
|
xyz.add(y);
|
|
xyz.add(z);
|
|
|
|
float X = x;
|
|
float Y = y;
|
|
float Z = z;
|
|
int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi;
|
|
int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi;
|
|
int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi;
|
|
|
|
|
|
|
|
JsonArray& angles = json.createNestedArray(F("AqaraAngles"));
|
|
angles.add(Angle_X);
|
|
angles.add(Angle_Y);
|
|
angles.add(Angle_Z);
|
|
}
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
|
|
String hex = value;
|
|
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
|
|
uint32_t i = 0;
|
|
uint32_t len = buf2.len();
|
|
char tmp[] = "tmp";
|
|
|
|
JsonVariant sub_value;
|
|
|
|
while (len - i >= 2) {
|
|
uint8_t attrid = buf2.get8(i++);
|
|
|
|
i += parseSingleAttribute(json, tmp, buf2, i, len);
|
|
float val = json[tmp];
|
|
json.remove(tmp);
|
|
if (0x01 == attrid) {
|
|
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
|
|
json[F("Battery")] = toPercentageCR2032(val);
|
|
} else if (0 == zcl->getManufCode()) {
|
|
|
|
if (0x64 == attrid) {
|
|
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
|
|
} else if (0x65 == attrid) {
|
|
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
|
|
} else if (0x66 == attrid) {
|
|
json[F(D_JSON_PRESSURE)] = val / 100.0f;
|
|
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE);
|
|
} else if (0x01 == attrid) {
|
|
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
|
|
json[F("Battery")] = toPercentageCR2032(val);
|
|
}
|
|
} else if (0x115F == zcl->getManufCode()) {
|
|
|
|
json[F("AqaraUnknown")] = val;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
|
|
|
|
for (auto kv : json) {
|
|
String key_string = kv.key;
|
|
const char * key = key_string.c_str();
|
|
JsonVariant& value = kv.value;
|
|
|
|
char * delimiter = strchr(key, '/');
|
|
char * delimiter2 = strchr(key, '+');
|
|
if (delimiter) {
|
|
uint16_t attribute;
|
|
uint16_t suffix = 1;
|
|
uint16_t cluster = strtoul(key, &delimiter, 16);
|
|
if (!delimiter2) {
|
|
attribute = strtoul(delimiter+1, nullptr, 16);
|
|
} else {
|
|
attribute = strtoul(delimiter+1, &delimiter2, 16);
|
|
suffix = strtoul(delimiter2+1, nullptr, 10);
|
|
}
|
|
|
|
|
|
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
|
|
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
|
uint16_t conv_cluster = pgm_read_word(&converter->cluster);
|
|
uint16_t conv_attribute = pgm_read_word(&converter->attribute);
|
|
|
|
if ((conv_cluster == cluster) &&
|
|
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
|
|
String new_name_str = converter->name;
|
|
if (suffix > 1) { new_name_str += suffix; }
|
|
int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute);
|
|
if (drop) {
|
|
json.remove(key);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_6_commands.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
|
|
typedef struct Z_CommandConverter {
|
|
const char * tasmota_cmd;
|
|
const char * zcl_cmd;
|
|
} Z_CommandConverter;
|
|
|
|
|
|
const Z_CommandConverter Z_Commands[] = {
|
|
{ "Power", "0006!xx" },
|
|
{ "Dimmer", "0008!04/xx0A00" },
|
|
{ "Dimmer+", "0008!06/001902" },
|
|
{ "Dimmer-", "0008!06/011902" },
|
|
{ "DimmerStop", "0008!03" },
|
|
{ "ResetAlarm", "0009!00/xxyyyy" },
|
|
{ "ResetAllAlarms","0009!01" },
|
|
{ "Hue", "0300!00/xx000A00" },
|
|
{ "Sat", "0300!03/xx0A00" },
|
|
{ "HueSat", "0300!06/xxyy0A00" },
|
|
{ "Color", "0300!07/xxxxyyyy0A00" },
|
|
{ "CT", "0300!0A/xxxx0A00" },
|
|
{ "Shutter", "0102!xx" },
|
|
{ "ShutterOpen", "0102!00" },
|
|
{ "ShutterClose", "0102!01" },
|
|
{ "ShutterStop", "0102!02" },
|
|
{ "ShutterLift", "0102!05xx" },
|
|
{ "ShutterTilt", "0102!08xx" },
|
|
};
|
|
|
|
#define ZLE(x) ((x) & 0xFF), ((x) >> 8)
|
|
|
|
|
|
const uint8_t CLUSTER_0006[] = { ZLE(0x0000) };
|
|
const uint8_t CLUSTER_0008[] = { ZLE(0x0000) };
|
|
const uint8_t CLUSTER_0009[] = { ZLE(0x0000) };
|
|
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) };
|
|
|
|
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
|
size_t attrs_len = 0;
|
|
const uint8_t* attrs = nullptr;
|
|
|
|
switch (cluster) {
|
|
case 0x0006:
|
|
attrs = CLUSTER_0006;
|
|
attrs_len = sizeof(CLUSTER_0006);
|
|
break;
|
|
case 0x0008:
|
|
attrs = CLUSTER_0008;
|
|
attrs_len = sizeof(CLUSTER_0008);
|
|
break;
|
|
case 0x0009:
|
|
attrs = CLUSTER_0009;
|
|
attrs_len = sizeof(CLUSTER_0009);
|
|
break;
|
|
case 0x0300:
|
|
attrs = CLUSTER_0300;
|
|
attrs_len = sizeof(CLUSTER_0300);
|
|
break;
|
|
}
|
|
if (attrs) {
|
|
ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) {
|
|
uint32_t wait_ms = 0;
|
|
|
|
switch (cluster) {
|
|
case 0x0006:
|
|
case 0x0009:
|
|
wait_ms = 200;
|
|
break;
|
|
case 0x0008:
|
|
case 0x0300:
|
|
wait_ms = 1050;
|
|
break;
|
|
case 0x0102:
|
|
wait_ms = 10000;
|
|
break;
|
|
}
|
|
if (wait_ms) {
|
|
zigbee_devices.setTimer(shortaddr, wait_ms, cluster, endpoint, 0 , &Z_ReadAttrCallback);
|
|
}
|
|
}
|
|
|
|
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
|
|
char parm_uc[16];
|
|
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
|
|
const Z_CommandConverter *conv = &Z_Commands[i];
|
|
if (0 == strcasecmp_P(command, conv->tasmota_cmd)) {
|
|
return (const __FlashStringHelper*) conv->zcl_cmd;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
inline bool isXYZ(char c) {
|
|
return (c >= 'x') && (c <= 'z');
|
|
}
|
|
|
|
|
|
inline char hexDigit(uint32_t h) {
|
|
uint32_t nybble = h & 0x0F;
|
|
return (nybble > 9) ? 'A' - 10 + nybble : '0' + nybble;
|
|
}
|
|
|
|
|
|
String zigbeeCmdAddParams(const char *zcl_cmd_P, uint32_t x, uint32_t y, uint32_t z) {
|
|
size_t len = strlen_P(zcl_cmd_P);
|
|
char zcl_cmd[len+1];
|
|
strcpy_P(zcl_cmd, zcl_cmd_P);
|
|
|
|
char *p = zcl_cmd;
|
|
while (*p) {
|
|
if (isXYZ(*p) && (*p == *(p+1))) {
|
|
uint8_t val;
|
|
switch (*p) {
|
|
case 'x':
|
|
val = x & 0xFF;
|
|
x = x >> 8;
|
|
break;
|
|
case 'y':
|
|
val = y & 0xFF;
|
|
y = y >> 8;
|
|
break;
|
|
case 'z':
|
|
val = z & 0xFF;
|
|
z = z >> 8;
|
|
break;
|
|
}
|
|
*p = hexDigit(val >> 4);
|
|
*(p+1) = hexDigit(val);
|
|
p++;
|
|
}
|
|
p++;
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SendZCLCommand_P: zcl_cmd = %s"), zcl_cmd);
|
|
|
|
return String(zcl_cmd);
|
|
}
|
|
|
|
const char kZ_Alias[] PROGMEM = "OFF|" D_OFF "|" D_FALSE "|" D_STOP "|" "OPEN" "|"
|
|
"ON|" D_ON "|" D_TRUE "|" D_START "|" "CLOSE" "|"
|
|
"TOGGLE|" D_TOGGLE "|"
|
|
"ALL" ;
|
|
|
|
const uint8_t kZ_Numbers[] PROGMEM = { 0,0,0,0,0,
|
|
1,1,1,1,1,
|
|
2,2,
|
|
255 };
|
|
|
|
|
|
uint32_t ZigbeeAliasOrNumber(const char *state_text) {
|
|
char command[16];
|
|
int state_number = GetCommandCode(command, sizeof(command), state_text, kZ_Alias);
|
|
if (state_number >= 0) {
|
|
|
|
return pgm_read_byte(kZ_Numbers + state_number);
|
|
} else {
|
|
|
|
return strtoul(state_text, nullptr, 0);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
|
|
|
|
const uint8_t ZIGBEE_STATUS_OK = 0;
|
|
const uint8_t ZIGBEE_STATUS_BOOT = 1;
|
|
const uint8_t ZIGBEE_STATUS_RESET_CONF = 2;
|
|
const uint8_t ZIGBEE_STATUS_STARTING = 3;
|
|
const uint8_t ZIGBEE_STATUS_PERMITJOIN_CLOSE = 20;
|
|
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_60 = 21;
|
|
const uint8_t ZIGBEE_STATUS_PERMITJOIN_OPEN_XX = 22;
|
|
const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30;
|
|
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31;
|
|
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32;
|
|
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33;
|
|
const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34;
|
|
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50;
|
|
const uint8_t ZIGBEE_STATUS_CC_INFO = 51;
|
|
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98;
|
|
const uint8_t ZIGBEE_STATUS_ABORT = 99;
|
|
|
|
typedef int32_t (*ZB_Func)(uint8_t value);
|
|
typedef int32_t (*ZB_RecvMsgFunc)(int32_t res, const class SBuffer &buf);
|
|
|
|
typedef union Zigbee_Instruction {
|
|
struct {
|
|
uint8_t i;
|
|
uint8_t d8;
|
|
uint16_t d16;
|
|
} i;
|
|
const void *p;
|
|
|
|
|
|
|
|
} Zigbee_Instruction;
|
|
|
|
|
|
|
|
|
|
typedef struct Zigbee_Instruction_Type {
|
|
uint8_t instr;
|
|
uint8_t data;
|
|
} Zigbee_Instruction_Type;
|
|
|
|
enum Zigbee_StateMachine_Instruction_Set {
|
|
|
|
ZGB_INSTR_4_BYTES = 0,
|
|
ZGB_INSTR_NOOP = 0,
|
|
ZGB_INSTR_LABEL,
|
|
ZGB_INSTR_GOTO,
|
|
ZGB_INSTR_ON_ERROR_GOTO,
|
|
ZGB_INSTR_ON_TIMEOUT_GOTO,
|
|
ZGB_INSTR_WAIT,
|
|
ZGB_INSTR_WAIT_FOREVER,
|
|
ZGB_INSTR_STOP,
|
|
|
|
|
|
ZGB_INSTR_8_BYTES = 0x80,
|
|
ZGB_INSTR_CALL = 0x80,
|
|
ZGB_INSTR_LOG,
|
|
ZGB_INSTR_MQTT_STATE,
|
|
ZGB_INSTR_SEND,
|
|
ZGB_INSTR_WAIT_UNTIL,
|
|
ZGB_INSTR_WAIT_RECV,
|
|
ZGB_ON_RECV_UNEXPECTED,
|
|
|
|
|
|
ZGB_INSTR_12_BYTES = 0xF0,
|
|
ZGB_INSTR_WAIT_RECV_CALL,
|
|
};
|
|
|
|
#define ZI_NOOP() { .i = { ZGB_INSTR_NOOP, 0x00, 0x0000} },
|
|
#define ZI_LABEL(x) { .i = { ZGB_INSTR_LABEL, (x), 0x0000} },
|
|
#define ZI_GOTO(x) { .i = { ZGB_INSTR_GOTO, (x), 0x0000} },
|
|
#define ZI_ON_ERROR_GOTO(x) { .i = { ZGB_INSTR_ON_ERROR_GOTO, (x), 0x0000} },
|
|
#define ZI_ON_TIMEOUT_GOTO(x) { .i = { ZGB_INSTR_ON_TIMEOUT_GOTO, (x), 0x0000} },
|
|
#define ZI_WAIT(x) { .i = { ZGB_INSTR_WAIT, 0x00, (x)} },
|
|
#define ZI_WAIT_FOREVER() { .i = { ZGB_INSTR_WAIT_FOREVER, 0x00, 0x0000} },
|
|
#define ZI_STOP(x) { .i = { ZGB_INSTR_STOP, (x), 0x0000} },
|
|
|
|
#define ZI_CALL(f,x) { .i = { ZGB_INSTR_CALL, (x), 0x0000} }, { .p = (const void*)(f) },
|
|
#define ZI_LOG(x,m) { .i = { ZGB_INSTR_LOG, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
|
#define ZI_MQTT_STATE(x,m) { .i = { ZGB_INSTR_MQTT_STATE, (x), 0x0000 } }, { .p = ((const void*)(m)) },
|
|
#define ZI_ON_RECV_UNEXPECTED(f) { .i = { ZGB_ON_RECV_UNEXPECTED, 0x00, 0x0000} }, { .p = (const void*)(f) },
|
|
#define ZI_SEND(m) { .i = { ZGB_INSTR_SEND, sizeof(m), 0x0000} }, { .p = (const void*)(m) },
|
|
#define ZI_WAIT_RECV(x,m) { .i = { ZGB_INSTR_WAIT_RECV, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
|
#define ZI_WAIT_UNTIL(x,m) { .i = { ZGB_INSTR_WAIT_UNTIL, sizeof(m), (x)} }, { .p = (const void*)(m) },
|
|
#define ZI_WAIT_RECV_FUNC(x,m,f) { .i = { ZGB_INSTR_WAIT_RECV_CALL, sizeof(m), (x)} }, { .p = (const void*)(m) }, { .p = (const void*)(f) },
|
|
|
|
|
|
const uint8_t ZIGBEE_LABEL_START = 10;
|
|
const uint8_t ZIGBEE_LABEL_READY = 20;
|
|
const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21;
|
|
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_CLOSE = 30;
|
|
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60 = 31;
|
|
const uint8_t ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX = 32;
|
|
|
|
const uint8_t ZIGBEE_LABEL_ABORT = 99;
|
|
const uint8_t ZIGBEE_LABEL_UNSUPPORTED_VERSION = 98;
|
|
|
|
struct ZigbeeStatus {
|
|
bool active = true;
|
|
bool state_machine = false;
|
|
bool state_waiting = false;
|
|
bool state_no_timeout = false;
|
|
bool ready = false;
|
|
uint8_t on_error_goto = ZIGBEE_LABEL_ABORT;
|
|
uint8_t on_timeout_goto = ZIGBEE_LABEL_ABORT;
|
|
int16_t pc = 0;
|
|
uint32_t next_timeout = 0;
|
|
|
|
uint8_t *recv_filter = nullptr;
|
|
bool recv_until = false;
|
|
size_t recv_filter_len = 0;
|
|
ZB_RecvMsgFunc recv_func = nullptr;
|
|
ZB_RecvMsgFunc recv_unexpected = nullptr;
|
|
|
|
bool init_phase = true;
|
|
};
|
|
struct ZigbeeStatus zigbee;
|
|
|
|
SBuffer *zigbee_buffer = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
#define Z_B0(a) (uint8_t)( ((a) ) & 0xFF )
|
|
#define Z_B1(a) (uint8_t)( ((a) >> 8) & 0xFF )
|
|
#define Z_B2(a) (uint8_t)( ((a) >> 16) & 0xFF )
|
|
#define Z_B3(a) (uint8_t)( ((a) >> 24) & 0xFF )
|
|
#define Z_B4(a) (uint8_t)( ((a) >> 32) & 0xFF )
|
|
#define Z_B5(a) (uint8_t)( ((a) >> 40) & 0xFF )
|
|
#define Z_B6(a) (uint8_t)( ((a) >> 48) & 0xFF )
|
|
#define Z_B7(a) (uint8_t)( ((a) >> 56) & 0xFF )
|
|
|
|
#define ZBM(n,x...) const uint8_t n[] PROGMEM = { x };
|
|
|
|
#define USE_ZIGBEE_CHANNEL_MASK (1 << (USE_ZIGBEE_CHANNEL))
|
|
|
|
|
|
|
|
ZBM(ZBS_RESET, Z_AREQ | Z_SYS, SYS_RESET, 0x00 )
|
|
ZBM(ZBR_RESET, Z_AREQ | Z_SYS, SYS_RESET_IND )
|
|
|
|
ZBM(ZBS_VERSION, Z_SREQ | Z_SYS, SYS_VERSION )
|
|
ZBM(ZBR_VERSION, Z_SRSP | Z_SYS, SYS_VERSION )
|
|
|
|
|
|
ZBM(ZBS_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_READ, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8, 0x00 )
|
|
ZBM(ZBR_ZNPHC, Z_SRSP | Z_SYS, SYS_OSAL_NV_READ, Z_Success, 0x01 , 0x55)
|
|
|
|
|
|
ZBM(ZBS_PAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PANID )
|
|
ZBM(ZBR_PAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PANID, 0x02 ,
|
|
Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) )
|
|
|
|
ZBM(ZBS_EXTPAN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_EXTENDED_PAN_ID )
|
|
ZBM(ZBR_EXTPAN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_EXTENDED_PAN_ID,
|
|
0x08 ,
|
|
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
|
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID),
|
|
)
|
|
|
|
ZBM(ZBS_CHANN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_CHANLIST )
|
|
ZBM(ZBR_CHANN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_CHANLIST,
|
|
0x04 ,
|
|
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
|
|
)
|
|
|
|
ZBM(ZBS_PFGK, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEY )
|
|
ZBM(ZBR_PFGK, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEY,
|
|
0x10 ,
|
|
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
|
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
|
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
|
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
|
|
|
)
|
|
|
|
ZBM(ZBS_PFGKEN, Z_SREQ | Z_SAPI, SAPI_READ_CONFIGURATION, CONF_PRECFGKEYS_ENABLE )
|
|
ZBM(ZBR_PFGKEN, Z_SRSP | Z_SAPI, SAPI_READ_CONFIGURATION, Z_Success, CONF_PRECFGKEYS_ENABLE,
|
|
0x01 , 0x00 )
|
|
|
|
|
|
|
|
ZBM(ZBR_W_OK, Z_SRSP | Z_SAPI, SAPI_WRITE_CONFIGURATION, Z_Success )
|
|
ZBM(ZBR_WNV_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_WRITE, Z_Success )
|
|
|
|
|
|
ZBM(ZBS_FACTRES, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 , 0x02 )
|
|
|
|
ZBM(ZBS_W_PAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PANID, 0x02 , Z_B0(USE_ZIGBEE_PANID), Z_B1(USE_ZIGBEE_PANID) )
|
|
|
|
ZBM(ZBS_W_EXTPAN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_EXTENDED_PAN_ID, 0x08 ,
|
|
Z_B0(USE_ZIGBEE_EXTPANID), Z_B1(USE_ZIGBEE_EXTPANID), Z_B2(USE_ZIGBEE_EXTPANID), Z_B3(USE_ZIGBEE_EXTPANID),
|
|
Z_B4(USE_ZIGBEE_EXTPANID), Z_B5(USE_ZIGBEE_EXTPANID), Z_B6(USE_ZIGBEE_EXTPANID), Z_B7(USE_ZIGBEE_EXTPANID)
|
|
)
|
|
|
|
ZBM(ZBS_W_CHANN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_CHANLIST, 0x04 ,
|
|
Z_B0(USE_ZIGBEE_CHANNEL_MASK), Z_B1(USE_ZIGBEE_CHANNEL_MASK), Z_B2(USE_ZIGBEE_CHANNEL_MASK), Z_B3(USE_ZIGBEE_CHANNEL_MASK),
|
|
)
|
|
|
|
ZBM(ZBS_W_LOGTYP, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_LOGICAL_TYPE, 0x01 , 0x00 )
|
|
|
|
ZBM(ZBS_W_PFGK, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEY,
|
|
0x10 ,
|
|
Z_B0(USE_ZIGBEE_PRECFGKEY_L), Z_B1(USE_ZIGBEE_PRECFGKEY_L), Z_B2(USE_ZIGBEE_PRECFGKEY_L), Z_B3(USE_ZIGBEE_PRECFGKEY_L),
|
|
Z_B4(USE_ZIGBEE_PRECFGKEY_L), Z_B5(USE_ZIGBEE_PRECFGKEY_L), Z_B6(USE_ZIGBEE_PRECFGKEY_L), Z_B7(USE_ZIGBEE_PRECFGKEY_L),
|
|
Z_B0(USE_ZIGBEE_PRECFGKEY_H), Z_B1(USE_ZIGBEE_PRECFGKEY_H), Z_B2(USE_ZIGBEE_PRECFGKEY_H), Z_B3(USE_ZIGBEE_PRECFGKEY_H),
|
|
Z_B4(USE_ZIGBEE_PRECFGKEY_H), Z_B5(USE_ZIGBEE_PRECFGKEY_H), Z_B6(USE_ZIGBEE_PRECFGKEY_H), Z_B7(USE_ZIGBEE_PRECFGKEY_H),
|
|
|
|
)
|
|
|
|
ZBM(ZBS_W_PFGKEN, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_PRECFGKEYS_ENABLE, 0x01 , 0x00 )
|
|
|
|
ZBM(ZBS_WNV_SECMODE, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(CONF_TCLK_TABLE_START), Z_B1(CONF_TCLK_TABLE_START),
|
|
0x00 , 0x20 ,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c,
|
|
0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
|
|
ZBM(ZBS_W_ZDODCB, Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_ZDO_DIRECT_CB, 0x01 , 0x01 )
|
|
|
|
ZBM(ZBS_WNV_INITZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_ITEM_INIT, ZNP_HAS_CONFIGURED & 0xFF, ZNP_HAS_CONFIGURED >> 8,
|
|
0x01, 0x00 , 0x01 , 0x00 )
|
|
|
|
|
|
ZBM(ZBR_WNV_INIT_OK, Z_SRSP | Z_SYS, SYS_OSAL_NV_ITEM_INIT )
|
|
|
|
|
|
ZBM(ZBS_WNV_ZNPHC, Z_SREQ | Z_SYS, SYS_OSAL_NV_WRITE, Z_B0(ZNP_HAS_CONFIGURED), Z_B1(ZNP_HAS_CONFIGURED),
|
|
0x00 , 0x01 , 0x55 )
|
|
|
|
ZBM(ZBS_STARTUPFROMAPP, Z_SREQ | Z_ZDO, ZDO_STARTUP_FROM_APP, 100, 0 )
|
|
ZBM(ZBR_STARTUPFROMAPP, Z_SRSP | Z_ZDO, ZDO_STARTUP_FROM_APP )
|
|
ZBM(AREQ_STARTUPFROMAPP, Z_AREQ | Z_ZDO, ZDO_STATE_CHANGE_IND, ZDO_DEV_ZB_COORD )
|
|
|
|
ZBM(ZBS_GETDEVICEINFO, Z_SREQ | Z_UTIL, Z_UTIL_GET_DEVICE_INFO )
|
|
ZBM(ZBR_GETDEVICEINFO, Z_SRSP | Z_UTIL, Z_UTIL_GET_DEVICE_INFO, Z_Success )
|
|
# 271 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino"
|
|
ZBM(ZBS_ZDO_NODEDESCREQ, Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, 0x00, 0x00 , 0x00, 0x00 )
|
|
ZBM(ZBR_ZDO_NODEDESCREQ, Z_SRSP | Z_ZDO, ZDO_NODE_DESC_REQ, Z_Success )
|
|
|
|
ZBM(AREQ_ZDO_NODEDESCRSP, Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP)
|
|
# 289 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_7_statemachine.ino"
|
|
ZBM(ZBS_ZDO_ACTIVEEPREQ, Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, 0x00, 0x00, 0x00, 0x00)
|
|
ZBM(ZBR_ZDO_ACTIVEEPREQ, Z_SRSP | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_Success)
|
|
ZBM(ZBR_ZDO_ACTIVEEPRSP_NONE, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 , Z_Success,
|
|
0x00, 0x00 , 0x00 )
|
|
ZBM(ZBR_ZDO_ACTIVEEPRSP_OK, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP, 0x00, 0x00 , Z_Success,
|
|
0x00, 0x00 , 0x02 , 0x0B, 0x01 )
|
|
|
|
|
|
ZBM(ZBS_AF_REGISTER01, Z_SREQ | Z_AF, AF_REGISTER, 0x01 , Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
|
|
0x05, 0x00 , 0x00 , 0x00 ,
|
|
0x00 , 0x00 )
|
|
ZBM(ZBR_AF_REGISTER, Z_SRSP | Z_AF, AF_REGISTER, Z_Success)
|
|
ZBM(ZBS_AF_REGISTER0B, Z_SREQ | Z_AF, AF_REGISTER, 0x0B , Z_B0(Z_PROF_HA), Z_B1(Z_PROF_HA),
|
|
0x05, 0x00 , 0x00 , 0x00 ,
|
|
0x00 , 0x00 )
|
|
|
|
ZBM(ZBS_PERMITJOINREQ_CLOSE, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x02 ,
|
|
0x00, 0x00 , 0x00 , 0x00 )
|
|
ZBM(ZBS_PERMITJOINREQ_OPEN_60, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F ,
|
|
0xFC, 0xFF , 60 , 0x00 )
|
|
ZBM(ZBS_PERMITJOINREQ_OPEN_XX, Z_SREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, 0x0F ,
|
|
0xFC, 0xFF , 0xFF , 0x00 )
|
|
ZBM(ZBR_PERMITJOINREQ, Z_SRSP | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_REQ, Z_Success)
|
|
ZBM(ZBR_PERMITJOIN_AREQ_CLOSE, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0x00 )
|
|
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_60, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 60 )
|
|
ZBM(ZBR_PERMITJOIN_AREQ_OPEN_FF, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND, 0xFF )
|
|
ZBM(ZBR_PERMITJOIN_AREQ_RSP, Z_AREQ | Z_ZDO, ZDO_MGMT_PERMIT_JOIN_RSP, 0x00, 0x00 , Z_Success )
|
|
|
|
static const Zigbee_Instruction zb_prog[] PROGMEM = {
|
|
ZI_LABEL(0)
|
|
ZI_NOOP()
|
|
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
|
ZI_ON_TIMEOUT_GOTO(ZIGBEE_LABEL_ABORT)
|
|
ZI_ON_RECV_UNEXPECTED(&Z_Recv_Default)
|
|
ZI_WAIT(10500)
|
|
ZI_ON_ERROR_GOTO(50)
|
|
|
|
|
|
|
|
ZI_SEND(ZBS_RESET)
|
|
ZI_WAIT_RECV_FUNC(5000, ZBR_RESET, &Z_Reboot)
|
|
ZI_WAIT(100)
|
|
ZI_LOG(LOG_LEVEL_DEBUG, D_LOG_ZIGBEE "checking device configuration")
|
|
ZI_SEND(ZBS_ZNPHC)
|
|
ZI_WAIT_RECV(2000, ZBR_ZNPHC)
|
|
ZI_SEND(ZBS_VERSION)
|
|
ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion)
|
|
ZI_SEND(ZBS_PAN)
|
|
ZI_WAIT_RECV(1000, ZBR_PAN)
|
|
ZI_SEND(ZBS_EXTPAN)
|
|
ZI_WAIT_RECV(1000, ZBR_EXTPAN)
|
|
ZI_SEND(ZBS_CHANN)
|
|
ZI_WAIT_RECV(1000, ZBR_CHANN)
|
|
ZI_SEND(ZBS_PFGK)
|
|
ZI_WAIT_RECV(1000, ZBR_PFGK)
|
|
ZI_SEND(ZBS_PFGKEN)
|
|
ZI_WAIT_RECV(1000, ZBR_PFGKEN)
|
|
|
|
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_START)
|
|
ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, "Configured, starting coordinator")
|
|
|
|
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
|
|
|
|
|
ZI_SEND(ZBS_STARTUPFROMAPP)
|
|
ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP)
|
|
ZI_WAIT_UNTIL(10000, AREQ_STARTUPFROMAPP)
|
|
ZI_SEND(ZBS_GETDEVICEINFO)
|
|
ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo)
|
|
|
|
ZI_SEND(ZBS_ZDO_NODEDESCREQ)
|
|
ZI_WAIT_RECV(1000, ZBR_ZDO_NODEDESCREQ)
|
|
ZI_WAIT_UNTIL(5000, AREQ_ZDO_NODEDESCRSP)
|
|
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ)
|
|
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
|
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_NONE)
|
|
ZI_SEND(ZBS_AF_REGISTER01)
|
|
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
|
ZI_SEND(ZBS_AF_REGISTER0B)
|
|
ZI_WAIT_RECV(1000, ZBR_AF_REGISTER)
|
|
|
|
|
|
ZI_SEND(ZBS_ZDO_ACTIVEEPREQ)
|
|
ZI_WAIT_RECV(1000, ZBR_ZDO_ACTIVEEPREQ)
|
|
ZI_WAIT_UNTIL(1000, ZBR_ZDO_ACTIVEEPRSP_OK)
|
|
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE)
|
|
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
|
ZI_WAIT_UNTIL(1000, ZBR_PERMITJOIN_AREQ_RSP)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_READY)
|
|
ZI_MQTT_STATE(ZIGBEE_STATUS_OK, "Started")
|
|
ZI_LOG(LOG_LEVEL_INFO, D_LOG_ZIGBEE "Zigbee started")
|
|
ZI_CALL(&Z_State_Ready, 1)
|
|
ZI_CALL(&Z_Load_Devices, 0)
|
|
ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP)
|
|
ZI_WAIT_FOREVER()
|
|
ZI_GOTO(ZIGBEE_LABEL_READY)
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE)
|
|
|
|
ZI_SEND(ZBS_PERMITJOINREQ_CLOSE)
|
|
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
|
|
|
|
|
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60)
|
|
|
|
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_60)
|
|
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
|
|
|
|
|
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX)
|
|
|
|
ZI_SEND(ZBS_PERMITJOINREQ_OPEN_XX)
|
|
ZI_WAIT_RECV(1000, ZBR_PERMITJOINREQ)
|
|
|
|
|
|
ZI_GOTO(ZIGBEE_LABEL_MAIN_LOOP)
|
|
|
|
ZI_LABEL(50)
|
|
ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, "Reseting configuration")
|
|
|
|
ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT)
|
|
ZI_SEND(ZBS_FACTRES)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_RESET)
|
|
ZI_WAIT_RECV(5000, ZBR_RESET)
|
|
ZI_SEND(ZBS_W_PAN)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_W_EXTPAN)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_W_CHANN)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_W_LOGTYP)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_W_PFGK)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_W_PFGKEN)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
ZI_SEND(ZBS_WNV_SECMODE)
|
|
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
|
ZI_SEND(ZBS_W_ZDODCB)
|
|
ZI_WAIT_RECV(1000, ZBR_W_OK)
|
|
|
|
ZI_SEND(ZBS_WNV_INITZNPHC)
|
|
ZI_WAIT_RECV_FUNC(1000, ZBR_WNV_INIT_OK, &Z_CheckNVWrite)
|
|
ZI_SEND(ZBS_WNV_ZNPHC)
|
|
ZI_WAIT_RECV(1000, ZBR_WNV_OK)
|
|
|
|
|
|
ZI_GOTO(ZIGBEE_LABEL_START)
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_UNSUPPORTED_VERSION)
|
|
ZI_MQTT_STATE(ZIGBEE_STATUS_UNSUPPORTED_VERSION, "Only ZNP 1.2 is currently supported")
|
|
ZI_GOTO(ZIGBEE_LABEL_ABORT)
|
|
|
|
ZI_LABEL(ZIGBEE_LABEL_ABORT)
|
|
ZI_MQTT_STATE(ZIGBEE_STATUS_ABORT, "Abort")
|
|
ZI_LOG(LOG_LEVEL_ERROR, D_LOG_ZIGBEE "Abort")
|
|
ZI_STOP(ZIGBEE_LABEL_ABORT)
|
|
};
|
|
|
|
uint8_t ZigbeeGetInstructionSize(uint8_t instr) {
|
|
if (instr >= ZGB_INSTR_12_BYTES) {
|
|
return 3;
|
|
} else if (instr >= ZGB_INSTR_8_BYTES) {
|
|
return 2;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void ZigbeeGotoLabel(uint8_t label) {
|
|
|
|
uint16_t goto_pc = 0xFFFF;
|
|
uint8_t cur_instr = 0;
|
|
uint8_t cur_d8 = 0;
|
|
uint8_t cur_instr_len = 1;
|
|
|
|
for (uint32_t i = 0; i < sizeof(zb_prog)/sizeof(zb_prog[0]); i += cur_instr_len) {
|
|
const Zigbee_Instruction *cur_instr_line = &zb_prog[i];
|
|
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
|
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
|
|
|
|
|
if (ZGB_INSTR_LABEL == cur_instr) {
|
|
|
|
if (label == cur_d8) {
|
|
|
|
zigbee.pc = i;
|
|
zigbee.state_machine = true;
|
|
zigbee.state_waiting = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
cur_instr_len = ZigbeeGetInstructionSize(cur_instr);
|
|
}
|
|
|
|
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Goto label not found, label=%d pc=%d"), label, zigbee.pc);
|
|
if (ZIGBEE_LABEL_ABORT != label) {
|
|
|
|
ZigbeeGotoLabel(ZIGBEE_LABEL_ABORT);
|
|
} else {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Label Abort (%d) not present, aborting Zigbee"), ZIGBEE_LABEL_ABORT);
|
|
zigbee.state_machine = false;
|
|
zigbee.active = false;
|
|
}
|
|
}
|
|
|
|
void ZigbeeStateMachine_Run(void) {
|
|
uint8_t cur_instr = 0;
|
|
uint8_t cur_d8 = 0;
|
|
uint16_t cur_d16 = 0;
|
|
const void* cur_ptr1 = nullptr;
|
|
const void* cur_ptr2 = nullptr;
|
|
uint32_t now = millis();
|
|
|
|
if (zigbee.state_waiting) {
|
|
|
|
if ((zigbee.next_timeout) && (now > zigbee.next_timeout)) {
|
|
|
|
if (!zigbee.state_no_timeout) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "timeout, goto label %d"), zigbee.on_timeout_goto);
|
|
ZigbeeGotoLabel(zigbee.on_timeout_goto);
|
|
} else {
|
|
zigbee.state_waiting = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
while ((zigbee.state_machine) && (!zigbee.state_waiting)) {
|
|
|
|
zigbee.recv_filter = nullptr;
|
|
zigbee.recv_func = nullptr;
|
|
zigbee.recv_until = false;
|
|
zigbee.state_no_timeout = false;
|
|
|
|
if (zigbee.pc > (sizeof(zb_prog)/sizeof(zb_prog[0]))) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Invalid pc: %d, aborting"), zigbee.pc);
|
|
zigbee.pc = -1;
|
|
}
|
|
if (zigbee.pc < 0) {
|
|
zigbee.state_machine = false;
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc];
|
|
cur_instr = pgm_read_byte(&cur_instr_line->i.i);
|
|
cur_d8 = pgm_read_byte(&cur_instr_line->i.d8);
|
|
cur_d16 = pgm_read_word(&cur_instr_line->i.d16);
|
|
if (cur_instr >= ZGB_INSTR_8_BYTES) {
|
|
cur_instr_line++;
|
|
cur_ptr1 = cur_instr_line->p;
|
|
}
|
|
if (cur_instr >= ZGB_INSTR_12_BYTES) {
|
|
cur_instr_line++;
|
|
cur_ptr2 = cur_instr_line->p;
|
|
}
|
|
|
|
zigbee.pc += ZigbeeGetInstructionSize(cur_instr);
|
|
|
|
switch (cur_instr) {
|
|
case ZGB_INSTR_NOOP:
|
|
case ZGB_INSTR_LABEL:
|
|
break;
|
|
case ZGB_INSTR_GOTO:
|
|
ZigbeeGotoLabel(cur_d8);
|
|
break;
|
|
case ZGB_INSTR_ON_ERROR_GOTO:
|
|
zigbee.on_error_goto = cur_d8;
|
|
break;
|
|
case ZGB_INSTR_ON_TIMEOUT_GOTO:
|
|
zigbee.on_timeout_goto = cur_d8;
|
|
break;
|
|
case ZGB_INSTR_WAIT:
|
|
zigbee.next_timeout = now + cur_d16;
|
|
zigbee.state_waiting = true;
|
|
zigbee.state_no_timeout = true;
|
|
break;
|
|
case ZGB_INSTR_WAIT_FOREVER:
|
|
zigbee.next_timeout = 0;
|
|
zigbee.state_waiting = true;
|
|
|
|
break;
|
|
case ZGB_INSTR_STOP:
|
|
zigbee.state_machine = false;
|
|
if (cur_d8) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Stopping (%d)"), cur_d8);
|
|
}
|
|
break;
|
|
case ZGB_INSTR_CALL:
|
|
if (cur_ptr1) {
|
|
uint32_t res;
|
|
res = (*((ZB_Func)cur_ptr1))(cur_d8);
|
|
if (res > 0) {
|
|
ZigbeeGotoLabel(res);
|
|
continue;
|
|
} else if (res == 0) {
|
|
|
|
} else if (res == -1) {
|
|
|
|
} else {
|
|
ZigbeeGotoLabel(zigbee.on_error_goto);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
case ZGB_INSTR_LOG:
|
|
AddLog_P(cur_d8, (char*) cur_ptr1);
|
|
break;
|
|
case ZGB_INSTR_MQTT_STATE:
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{\"Status\":%d,\"Message\":\"%s\"}}"),
|
|
cur_d8, (char*) cur_ptr1);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
|
XdrvRulesProcess();
|
|
break;
|
|
case ZGB_INSTR_SEND:
|
|
ZigbeeZNPSend((uint8_t*) cur_ptr1, cur_d8 );
|
|
break;
|
|
case ZGB_INSTR_WAIT_UNTIL:
|
|
zigbee.recv_until = true;
|
|
case ZGB_INSTR_WAIT_RECV:
|
|
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
|
zigbee.recv_filter_len = cur_d8;
|
|
zigbee.next_timeout = now + cur_d16;
|
|
zigbee.state_waiting = true;
|
|
break;
|
|
case ZGB_ON_RECV_UNEXPECTED:
|
|
zigbee.recv_unexpected = (ZB_RecvMsgFunc) cur_ptr1;
|
|
break;
|
|
case ZGB_INSTR_WAIT_RECV_CALL:
|
|
zigbee.recv_filter = (uint8_t *) cur_ptr1;
|
|
zigbee.recv_filter_len = cur_d8;
|
|
zigbee.recv_func = (ZB_RecvMsgFunc) cur_ptr2;
|
|
zigbee.next_timeout = now + cur_d16;
|
|
zigbee.state_waiting = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
int32_t Z_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Z_IEEEAddress long_adr = buf.get64(3);
|
|
Z_ShortAddress short_adr = buf.get16(11);
|
|
uint8_t device_type = buf.get8(13);
|
|
uint8_t device_state = buf.get8(14);
|
|
uint8_t device_associated = buf.get8(15);
|
|
|
|
|
|
localIEEEAddr = long_adr;
|
|
|
|
char hex[20];
|
|
Uint64toHex(long_adr, hex, 64);
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
|
",\"DeviceType\":%d,\"DeviceState\":%d"
|
|
",\"NumAssocDevices\":%d"),
|
|
ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state,
|
|
device_associated);
|
|
|
|
if (device_associated > 0) {
|
|
uint idx = 16;
|
|
ResponseAppend_P(PSTR(",\"AssocDevicesList\":["));
|
|
for (uint32_t i = 0; i < device_associated; i++) {
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(idx));
|
|
idx += 2;
|
|
}
|
|
ResponseAppend_P(PSTR("]"));
|
|
}
|
|
|
|
ResponseJsonEnd();
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
|
XdrvRulesProcess();
|
|
|
|
return res;
|
|
}
|
|
|
|
int32_t Z_CheckNVWrite(int32_t res, class SBuffer &buf) {
|
|
|
|
|
|
|
|
uint8_t status = buf.get8(2);
|
|
if ((0x00 == status) || (0x09 == status)) {
|
|
return 0;
|
|
} else {
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
const char Z_RebootReason[] PROGMEM = "Power-up|External|Watchdog";
|
|
|
|
int32_t Z_Reboot(int32_t res, class SBuffer &buf) {
|
|
|
|
|
|
|
|
uint8_t reason = buf.get8(2);
|
|
uint8_t transport_rev = buf.get8(3);
|
|
uint8_t product_id = buf.get8(4);
|
|
uint8_t major_rel = buf.get8(5);
|
|
uint8_t minor_rel = buf.get8(6);
|
|
uint8_t hw_rev = buf.get8(7);
|
|
char reason_str[12];
|
|
|
|
if (reason > 3) { reason = 3; }
|
|
GetTextIndexed(reason_str, sizeof(reason_str), reason, Z_RebootReason);
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"Message\":\"%s\",\"RestartReason\":\"%s\""
|
|
",\"MajorRel\":%d,\"MinorRel\":%d}}"),
|
|
ZIGBEE_STATUS_BOOT, "CC2530 booted", reason_str,
|
|
major_rel, minor_rel);
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
|
XdrvRulesProcess();
|
|
|
|
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
|
return 0;
|
|
} else {
|
|
return ZIGBEE_LABEL_UNSUPPORTED_VERSION;
|
|
}
|
|
}
|
|
|
|
int32_t Z_ReceiveCheckVersion(int32_t res, class SBuffer &buf) {
|
|
# 122 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_8_parsers.ino"
|
|
uint8_t major_rel = buf.get8(4);
|
|
uint8_t minor_rel = buf.get8(5);
|
|
uint8_t maint_rel = buf.get8(6);
|
|
uint32_t revision = buf.get32(7);
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"MajorRel\":%d,\"MinorRel\":%d"
|
|
",\"MaintRel\":%d,\"Revision\":%d}}"),
|
|
ZIGBEE_STATUS_CC_VERSION, major_rel, minor_rel,
|
|
maint_rel, revision);
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
|
XdrvRulesProcess();
|
|
|
|
if ((0x02 == major_rel) && (0x06 == minor_rel)) {
|
|
return 0;
|
|
} else {
|
|
return ZIGBEE_LABEL_UNSUPPORTED_VERSION;
|
|
}
|
|
}
|
|
|
|
bool Z_ReceiveMatchPrefix(const class SBuffer &buf, const uint8_t *match) {
|
|
if ( (pgm_read_byte(&match[0]) == buf.get8(0)) &&
|
|
(pgm_read_byte(&match[1]) == buf.get8(1)) ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) {
|
|
|
|
uint8_t duration = buf.get8(2);
|
|
uint8_t status_code;
|
|
const char* message;
|
|
|
|
if (0xFF == duration) {
|
|
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_XX;
|
|
message = PSTR("Enable Pairing mode until next boot");
|
|
} else if (duration > 0) {
|
|
status_code = ZIGBEE_STATUS_PERMITJOIN_OPEN_60;
|
|
message = PSTR("Enable Pairing mode for %d seconds");
|
|
} else {
|
|
status_code = ZIGBEE_STATUS_PERMITJOIN_CLOSE;
|
|
message = PSTR("Disable Pairing mode");
|
|
}
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"Message\":\""),
|
|
status_code);
|
|
ResponseAppend_P(message, duration);
|
|
ResponseAppend_P(PSTR("\"}}"));
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE));
|
|
XdrvRulesProcess();
|
|
return -1;
|
|
}
|
|
|
|
|
|
void Z_SendActiveEpReq(uint16_t shortaddr) {
|
|
uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ,
|
|
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
|
|
|
uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ,
|
|
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) };
|
|
|
|
ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq));
|
|
|
|
}
|
|
|
|
|
|
void Z_SendSimpleDescReq(uint16_t shortaddr, uint8_t endpoint) {
|
|
uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ,
|
|
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr),
|
|
endpoint };
|
|
|
|
ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq));
|
|
}
|
|
|
|
const char* Z_DeviceType[] = { "Coordinator", "Router", "End Device", "Unknown" };
|
|
int32_t Z_ReceiveNodeDesc(int32_t res, const class SBuffer &buf) {
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
uint8_t status = buf.get8(4);
|
|
Z_ShortAddress nwkAddr = buf.get16(5);
|
|
uint8_t logicalType = buf.get8(7);
|
|
uint8_t apsFlags = buf.get8(8);
|
|
uint8_t MACCapabilityFlags = buf.get8(9);
|
|
uint16_t manufacturerCapabilities = buf.get16(10);
|
|
uint8_t maxBufferSize = buf.get8(12);
|
|
uint16_t maxInTransferSize = buf.get16(13);
|
|
uint16_t serverMask = buf.get16(15);
|
|
uint16_t maxOutTransferSize = buf.get16(17);
|
|
uint8_t descriptorCapabilities = buf.get8(19);
|
|
|
|
if (0 == status) {
|
|
zigbee_devices.updateLastSeen(nwkAddr);
|
|
|
|
uint8_t deviceType = logicalType & 0x7;
|
|
if (deviceType > 3) { deviceType = 3; }
|
|
bool complexDescriptorAvailable = (logicalType & 0x08) ? 1 : 0;
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"NodeType\":\"%s\",\"ComplexDesc\":%s}}"),
|
|
ZIGBEE_STATUS_NODE_DESC, Z_DeviceType[deviceType],
|
|
complexDescriptorAvailable ? "true" : "false"
|
|
);
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
XdrvRulesProcess();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
uint8_t status = buf.get8(4);
|
|
Z_ShortAddress nwkAddr = buf.get16(5);
|
|
uint8_t activeEpCount = buf.get8(7);
|
|
uint8_t* activeEpList = (uint8_t*) buf.charptr(8);
|
|
|
|
|
|
for (uint32_t i = 0; i < activeEpCount; i++) {
|
|
zigbee_devices.addEndoint(nwkAddr, activeEpList[i]);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < activeEpCount; i++) {
|
|
Z_SendSimpleDescReq(nwkAddr, activeEpList[i]);
|
|
}
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"ActiveEndpoints\":["),
|
|
ZIGBEE_STATUS_ACTIVE_EP);
|
|
for (uint32_t i = 0; i < activeEpCount; i++) {
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(PSTR("\"0x%02X\""), activeEpList[i]);
|
|
}
|
|
ResponseAppend_P(PSTR("]}}"));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
XdrvRulesProcess();
|
|
return -1;
|
|
}
|
|
|
|
void Z_SendAFInfoRequest(uint16_t shortaddr, uint8_t endpoint, uint16_t clusterid, uint8_t transacid) {
|
|
SBuffer buf(100);
|
|
buf.add8(Z_SREQ | Z_AF);
|
|
buf.add8(AF_DATA_REQUEST);
|
|
buf.add16(shortaddr);
|
|
buf.add8(endpoint);
|
|
buf.add8(0x01);
|
|
buf.add16(clusterid);
|
|
buf.add8(transacid);
|
|
buf.add8(0x30);
|
|
buf.add8(0x1E);
|
|
|
|
buf.add8(3 + 2*sizeof(uint16_t));
|
|
buf.add8(0x00);
|
|
buf.add8(transacid);
|
|
buf.add8(ZCL_READ_ATTRIBUTES);
|
|
buf.add16(0x0004);
|
|
buf.add16(0x0005);
|
|
|
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
|
}
|
|
|
|
|
|
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
|
|
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
uint8_t status = buf.get8(4);
|
|
Z_ShortAddress nwkAddr = buf.get16(5);
|
|
uint8_t lenDescriptor = buf.get8(7);
|
|
uint8_t endpoint = buf.get8(8);
|
|
uint16_t profileId = buf.get16(9);
|
|
uint16_t deviceId = buf.get16(11);
|
|
uint8_t deviceVersion = buf.get8(13);
|
|
uint8_t numInCluster = buf.get8(14);
|
|
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
|
|
|
|
if (0 == status) {
|
|
zigbee_devices.addEndointProfile(nwkAddr, endpoint, profileId);
|
|
for (uint32_t i = 0; i < numInCluster; i++) {
|
|
zigbee_devices.addCluster(nwkAddr, endpoint, buf.get16(15 + i*2), false);
|
|
}
|
|
for (uint32_t i = 0; i < numOutCluster; i++) {
|
|
zigbee_devices.addCluster(nwkAddr, endpoint, buf.get16(16 + numInCluster*2 + i*2), true);
|
|
}
|
|
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
|
|
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d"
|
|
"\"InClusters\":["),
|
|
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
|
|
profileId, deviceId, deviceVersion);
|
|
for (uint32_t i = 0; i < numInCluster; i++) {
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(15 + i*2));
|
|
}
|
|
ResponseAppend_P(PSTR("],\"OutClusters\":["));
|
|
for (uint32_t i = 0; i < numOutCluster; i++) {
|
|
if (i > 0) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(16 + numInCluster*2 + i*2));
|
|
}
|
|
ResponseAppend_P(PSTR("]}}"));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
XdrvRulesProcess();
|
|
|
|
uint8_t cluster = zigbee_devices.findClusterEndpointIn(nwkAddr, 0x0000);
|
|
if (cluster) {
|
|
Z_SendAFInfoRequest(nwkAddr, cluster, 0x0000, 0x01);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
Z_ShortAddress nwkAddr = buf.get16(4);
|
|
Z_IEEEAddress ieeeAddr = buf.get64(6);
|
|
uint8_t capabilities = buf.get8(14);
|
|
|
|
zigbee_devices.updateDevice(nwkAddr, ieeeAddr);
|
|
|
|
char hex[20];
|
|
Uint64toHex(ieeeAddr, hex, 64);
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
|
",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"),
|
|
ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr,
|
|
(capabilities & 0x04) ? "true" : "false",
|
|
(capabilities & 0x08) ? "true" : "false",
|
|
(capabilities & 0x40) ? "true" : "false"
|
|
);
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
XdrvRulesProcess();
|
|
Z_SendActiveEpReq(nwkAddr);
|
|
return -1;
|
|
}
|
|
|
|
|
|
int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) {
|
|
Z_ShortAddress srcAddr = buf.get16(2);
|
|
Z_IEEEAddress ieeeAddr = buf.get64(4);
|
|
Z_ShortAddress parentNw = buf.get16(12);
|
|
|
|
zigbee_devices.updateDevice(srcAddr, ieeeAddr);
|
|
|
|
char hex[20];
|
|
Uint64toHex(ieeeAddr, hex, 64);
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
|
|
"\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
|
|
",\"ParentNetwork\":\"0x%04X\"}}"),
|
|
ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw
|
|
);
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
|
|
XdrvRulesProcess();
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000;
|
|
|
|
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json) {
|
|
|
|
const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY));
|
|
if (nullptr != &val_endpoint) {
|
|
uint32_t occupancy = strToUInt(val_endpoint);
|
|
|
|
if (occupancy) {
|
|
zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, endpoint, 0, &Z_OccupancyCallback);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
|
|
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
|
if (json == nullptr) { return 0; }
|
|
|
|
Z_AqaraOccupancy(shortaddr, cluster, endpoint, json);
|
|
|
|
zigbee_devices.jsonPublishFlush(shortaddr);
|
|
return 1;
|
|
}
|
|
|
|
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
|
|
uint16_t groupid = buf.get16(2);
|
|
uint16_t clusterid = buf.get16(4);
|
|
Z_ShortAddress srcaddr = buf.get16(6);
|
|
uint8_t srcendpoint = buf.get8(8);
|
|
uint8_t dstendpoint = buf.get8(9);
|
|
uint8_t wasbroadcast = buf.get8(10);
|
|
uint8_t linkquality = buf.get8(11);
|
|
uint8_t securityuse = buf.get8(12);
|
|
uint32_t timestamp = buf.get32(13);
|
|
uint8_t seqnumber = buf.get8(17);
|
|
|
|
bool defer_attributes = false;
|
|
|
|
zigbee_devices.updateLastSeen(srcaddr);
|
|
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid,
|
|
srcaddr,
|
|
srcendpoint, dstendpoint, wasbroadcast,
|
|
linkquality, securityuse, seqnumber,
|
|
timestamp);
|
|
zcl_received.log();
|
|
char shortaddr[8];
|
|
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
|
|
|
DynamicJsonBuffer jsonBuffer;
|
|
JsonObject& json = jsonBuffer.createObject();
|
|
|
|
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
|
zcl_received.parseRawAttributes(json);
|
|
if (clusterid) { defer_attributes = true; }
|
|
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
|
|
zcl_received.parseReadAttributes(json);
|
|
} else if (zcl_received.isClusterSpecificCommand()) {
|
|
zcl_received.parseClusterSpecificCommand(json);
|
|
}
|
|
String msg("");
|
|
msg.reserve(100);
|
|
json.printTo(msg);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
|
|
|
|
zcl_received.postProcessAttributes(srcaddr, json);
|
|
|
|
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
|
|
|
|
if (defer_attributes) {
|
|
|
|
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
|
|
|
|
zigbee_devices.jsonPublishFlush(srcaddr);
|
|
} else {
|
|
zigbee_devices.jsonAppend(srcaddr, json);
|
|
zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes);
|
|
}
|
|
} else {
|
|
|
|
zigbee_devices.jsonPublishNow(srcaddr, json);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
typedef struct Z_Dispatcher {
|
|
const uint8_t* match;
|
|
ZB_RecvMsgFunc func;
|
|
} Z_Dispatcher;
|
|
|
|
|
|
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG)
|
|
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND)
|
|
ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND)
|
|
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND )
|
|
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP)
|
|
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP)
|
|
|
|
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
|
|
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
|
|
{ AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce },
|
|
{ AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd },
|
|
{ AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus },
|
|
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
|
|
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
|
|
{ AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc },
|
|
};
|
|
|
|
int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) {
|
|
|
|
if (zigbee.init_phase) {
|
|
|
|
return -1;
|
|
} else {
|
|
for (uint32_t i = 0; i < sizeof(Z_DispatchTable)/sizeof(Z_Dispatcher); i++) {
|
|
if (Z_ReceiveMatchPrefix(buf, Z_DispatchTable[i].match)) {
|
|
(*Z_DispatchTable[i].func)(res, buf);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int32_t Z_Load_Devices(uint8_t value) {
|
|
|
|
loadZigbeeDevices();
|
|
return 0;
|
|
}
|
|
|
|
int32_t Z_State_Ready(uint8_t value) {
|
|
zigbee.init_phase = false;
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino"
|
|
#ifdef USE_ZIGBEE
|
|
|
|
#define XDRV_23 23
|
|
|
|
const uint32_t ZIGBEE_BUFFER_SIZE = 256;
|
|
const uint8_t ZIGBEE_SOF = 0xFE;
|
|
const uint8_t ZIGBEE_SOF_ALT = 0xFF;
|
|
|
|
#include <TasmotaSerial.h>
|
|
TasmotaSerial *ZigbeeSerial = nullptr;
|
|
|
|
|
|
const char kZbCommands[] PROGMEM = D_PRFX_ZB "|"
|
|
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
|
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
|
|
D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|"
|
|
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ;
|
|
|
|
const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|"
|
|
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
|
|
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
|
|
D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|"
|
|
D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ;
|
|
|
|
void (* const ZigbeeCommand[])(void) PROGMEM = {
|
|
&CmndZbZNPSend, &CmndZbPermitJoin,
|
|
&CmndZbStatus, &CmndZbReset, &CmndZbSend,
|
|
&CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive,
|
|
&CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind
|
|
};
|
|
|
|
int32_t ZigbeeProcessInput(class SBuffer &buf) {
|
|
if (!zigbee.state_machine) { return -1; }
|
|
|
|
|
|
bool recv_filter_match = true;
|
|
bool recv_prefix_match = false;
|
|
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
|
|
if (zigbee.recv_filter_len >= 2) {
|
|
recv_prefix_match = false;
|
|
if ( (pgm_read_byte(&zigbee.recv_filter[0]) == buf.get8(0)) &&
|
|
(pgm_read_byte(&zigbee.recv_filter[1]) == buf.get8(1)) ) {
|
|
recv_prefix_match = true;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < zigbee.recv_filter_len; i++) {
|
|
if (pgm_read_byte(&zigbee.recv_filter[i]) != buf.get8(i)) {
|
|
recv_filter_match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int32_t res = -1;
|
|
|
|
|
|
|
|
|
|
|
|
if ((zigbee.recv_filter) && (zigbee.recv_filter_len > 0)) {
|
|
if (!recv_prefix_match) {
|
|
res = -1;
|
|
} else {
|
|
if (recv_filter_match) {
|
|
res = 0;
|
|
} else {
|
|
if (zigbee.recv_until) {
|
|
res = -1;
|
|
} else {
|
|
res = -2;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
res = -1;
|
|
}
|
|
|
|
if (recv_prefix_match) {
|
|
if (zigbee.recv_func) {
|
|
res = (*zigbee.recv_func)(res, buf);
|
|
}
|
|
}
|
|
if (-1 == res) {
|
|
|
|
if (zigbee.recv_unexpected) {
|
|
res = (*zigbee.recv_unexpected)(res, buf);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: res = %d"), res);
|
|
|
|
|
|
if (0 == res) {
|
|
|
|
zigbee.state_waiting = false;
|
|
} else if (res > 0) {
|
|
ZigbeeGotoLabel(res);
|
|
} else if (-1 == res) {
|
|
|
|
|
|
} else {
|
|
|
|
ZigbeeGotoLabel(zigbee.on_error_goto);
|
|
}
|
|
}
|
|
|
|
void ZigbeeInput(void)
|
|
{
|
|
static uint32_t zigbee_polling_window = 0;
|
|
static uint8_t fcs = ZIGBEE_SOF;
|
|
static uint32_t zigbee_frame_len = 5;
|
|
# 142 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino"
|
|
while (ZigbeeSerial->available()) {
|
|
yield();
|
|
uint8_t zigbee_in_byte = ZigbeeSerial->read();
|
|
|
|
|
|
if (0 == zigbee_buffer->len()) {
|
|
zigbee_frame_len = 5;
|
|
fcs = ZIGBEE_SOF;
|
|
|
|
|
|
|
|
if (ZIGBEE_SOF_ALT == zigbee_in_byte) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte);
|
|
zigbee_in_byte = ZIGBEE_SOF;
|
|
}
|
|
}
|
|
|
|
if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput discarding byte %02X"), zigbee_in_byte);
|
|
continue;
|
|
}
|
|
|
|
if (zigbee_buffer->len() < zigbee_frame_len) {
|
|
zigbee_buffer->add8(zigbee_in_byte);
|
|
zigbee_polling_window = millis();
|
|
fcs ^= zigbee_in_byte;
|
|
}
|
|
|
|
if (zigbee_buffer->len() >= zigbee_frame_len) {
|
|
zigbee_polling_window = 0;
|
|
break;
|
|
}
|
|
|
|
|
|
if (02 == zigbee_buffer->len()) {
|
|
|
|
uint8_t len_byte = zigbee_buffer->get8(1);
|
|
if (len_byte > 250) len_byte = 250;
|
|
|
|
zigbee_frame_len = len_byte + 5;
|
|
}
|
|
}
|
|
|
|
if (zigbee_buffer->len() && (millis() > (zigbee_polling_window + ZIGBEE_POLLING))) {
|
|
char hex_char[(zigbee_buffer->len() * 2) + 2];
|
|
ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char));
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric());
|
|
|
|
if (zigbee_buffer->len() != zigbee_frame_len) {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received frame of wrong size %s, len %d, expected %d"), hex_char, zigbee_buffer->len(), zigbee_frame_len);
|
|
} else if (0x00 != fcs) {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEEZNPRECEIVED ": received bad FCS frame %s, %d"), hex_char, fcs);
|
|
} else {
|
|
|
|
|
|
|
|
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3);
|
|
|
|
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
|
|
Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
|
|
if (Settings.flag3.tuya_serial_mqtt_publish) {
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
|
|
XdrvRulesProcess();
|
|
} else {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "%s"), mqtt_data);
|
|
}
|
|
|
|
ZigbeeProcessInput(znp_buffer);
|
|
}
|
|
zigbee_buffer->setLen(0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ZigbeeInit(void)
|
|
{
|
|
zigbee.active = false;
|
|
if ((pin[GPIO_ZIGBEE_RX] < 99) && (pin[GPIO_ZIGBEE_TX] < 99)) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]);
|
|
|
|
ZigbeeSerial = new TasmotaSerial(pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], seriallog_level ? 1 : 2, 0, 256);
|
|
ZigbeeSerial->begin(115200);
|
|
if (ZigbeeSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
uint32_t aligned_buffer = ((uint32_t)serial_in_buffer + 3) & ~3;
|
|
zigbee_buffer = new PreAllocatedSBuffer(sizeof(serial_in_buffer) - 3, (char*) aligned_buffer);
|
|
} else {
|
|
zigbee_buffer = new SBuffer(ZIGBEE_BUFFER_SIZE);
|
|
}
|
|
zigbee.active = true;
|
|
zigbee.init_phase = true;
|
|
zigbee.state_machine = true;
|
|
ZigbeeSerial->flush();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint32_t strToUInt(const JsonVariant &val) {
|
|
|
|
if (val.is<unsigned int>()) {
|
|
return val.as<unsigned int>();
|
|
} else {
|
|
if (val.is<const char*>()) {
|
|
String sval = val.as<String>();
|
|
return strtoull(sval.c_str(), nullptr, 0);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const unsigned char ZIGBEE_FACTORY_RESET[] PROGMEM =
|
|
{ Z_SREQ | Z_SAPI, SAPI_WRITE_CONFIGURATION, CONF_STARTUP_OPTION, 0x01 , 0x01 };
|
|
|
|
|
|
void CmndZbReset(void) {
|
|
if (ZigbeeSerial) {
|
|
switch (XdrvMailbox.payload) {
|
|
case 1:
|
|
ZigbeeZNPSend(ZIGBEE_FACTORY_RESET, sizeof(ZIGBEE_FACTORY_RESET));
|
|
eraseZigbeeDevices();
|
|
restart_flag = 2;
|
|
ResponseCmndChar(D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING);
|
|
break;
|
|
default:
|
|
ResponseCmndChar(D_JSON_ONE_TO_RESET);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndZbZNPSendOrReceive(bool send)
|
|
{
|
|
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
|
|
uint8_t code;
|
|
|
|
char *codes = RemoveSpace(XdrvMailbox.data);
|
|
int32_t size = strlen(XdrvMailbox.data);
|
|
|
|
SBuffer buf((size+1)/2);
|
|
|
|
while (size > 1) {
|
|
char stemp[3];
|
|
strlcpy(stemp, codes, sizeof(stemp));
|
|
code = strtol(stemp, nullptr, 16);
|
|
buf.add8(code);
|
|
size -= 2;
|
|
codes += 2;
|
|
}
|
|
if (send) {
|
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
|
} else {
|
|
ZigbeeProcessInput(buf);
|
|
}
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
|
|
void CmndZbZNPReceive(void)
|
|
{
|
|
CmndZbZNPSendOrReceive(false);
|
|
}
|
|
|
|
void CmndZbZNPSend(void)
|
|
{
|
|
CmndZbZNPSendOrReceive(true);
|
|
}
|
|
|
|
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
|
|
if ((len < 2) || (len > 252)) {
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_JSON_ZIGBEEZNPSENT ": bad message len %d"), len);
|
|
return;
|
|
}
|
|
uint8_t data_len = len - 2;
|
|
|
|
if (ZigbeeSerial) {
|
|
uint8_t fcs = data_len;
|
|
|
|
ZigbeeSerial->write(ZIGBEE_SOF);
|
|
|
|
ZigbeeSerial->write(data_len);
|
|
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
uint8_t b = pgm_read_byte(msg + i);
|
|
ZigbeeSerial->write(b);
|
|
fcs ^= b;
|
|
|
|
}
|
|
ZigbeeSerial->write(fcs);
|
|
|
|
}
|
|
|
|
char hex_char[(len * 2) + 2];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"),
|
|
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
|
|
}
|
|
|
|
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId) {
|
|
SBuffer buf(25+len);
|
|
buf.add8(Z_SREQ | Z_AF);
|
|
buf.add8(AF_DATA_REQUEST);
|
|
buf.add16(dtsAddr);
|
|
buf.add8(endpoint);
|
|
buf.add8(0x01);
|
|
buf.add16(clusterId);
|
|
buf.add8(transacId);
|
|
buf.add8(0x30);
|
|
buf.add8(0x1E);
|
|
|
|
buf.add8(3 + len);
|
|
buf.add8((disableDefResp ? 0x10 : 0x00) | (clusterSpecific ? 0x01 : 0x00));
|
|
buf.add8(transacId);
|
|
buf.add8(cmdId);
|
|
if (len > 0) {
|
|
buf.addBuffer(msg, len);
|
|
}
|
|
|
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
|
}
|
|
|
|
inline int8_t hexValue(char c) {
|
|
if ((c >= '0') && (c <= '9')) {
|
|
return c - '0';
|
|
}
|
|
if ((c >= 'A') && (c <= 'F')) {
|
|
return 10 + c - 'A';
|
|
}
|
|
if ((c >= 'a') && (c <= 'f')) {
|
|
return 10 + c - 'a';
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint32_t parseHex(const char **data, size_t max_len = 8) {
|
|
uint32_t ret = 0;
|
|
for (uint32_t i = 0; i < max_len; i++) {
|
|
int8_t v = hexValue(**data);
|
|
if (v < 0) { break; }
|
|
ret = (ret << 4) | v;
|
|
*data += 1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
|
|
|
|
uint16_t cluster = 0x0000;
|
|
uint8_t cmd = ZCL_READ_ATTRIBUTES;
|
|
bool clusterSpecific = false;
|
|
|
|
|
|
|
|
cluster = parseHex(&data, 4);
|
|
|
|
|
|
if (('_' == *data) || ('!' == *data)) {
|
|
if ('!' == *data) { clusterSpecific = true; }
|
|
data++;
|
|
} else {
|
|
ResponseCmndChar("Wrong delimiter for payload");
|
|
return;
|
|
}
|
|
|
|
cmd = parseHex(&data, 2);
|
|
|
|
|
|
|
|
if ('/' == *data) { data++; }
|
|
|
|
size_t size = strlen(data);
|
|
SBuffer buf((size+2)/2);
|
|
|
|
while (*data) {
|
|
uint8_t code = parseHex(&data, 2);
|
|
buf.add8(code);
|
|
}
|
|
|
|
if (0 == endpoint) {
|
|
|
|
endpoint = zigbee_devices.findClusterEndpointIn(dstAddr, cluster);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint);
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"),
|
|
dstAddr, cluster, endpoint, cmd, data);
|
|
|
|
if (0 == endpoint) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint"));
|
|
return;
|
|
}
|
|
|
|
|
|
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
|
|
|
|
if (clusterSpecific) {
|
|
zigbeeSetCommandTimer(dstAddr, cluster, endpoint);
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndZbSend(void) {
|
|
# 461 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_23_zigbee_9_impl.ino"
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
DynamicJsonBuffer jsonBuf;
|
|
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
|
|
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
|
|
|
|
|
|
static char delim[] = ", ";
|
|
uint16_t device = 0xFFFF;
|
|
uint8_t endpoint = 0x00;
|
|
String cmd_str = "";
|
|
|
|
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
|
|
if (nullptr != &val_device) {
|
|
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
|
if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; }
|
|
}
|
|
if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; }
|
|
|
|
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
|
|
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
|
|
const JsonVariant &val_cmd = getCaseInsensitive(json, PSTR("Send"));
|
|
if (nullptr != &val_cmd) {
|
|
|
|
|
|
|
|
if (val_cmd.is<JsonObject>()) {
|
|
|
|
JsonObject &cmd_obj = val_cmd.as<JsonObject&>();
|
|
int32_t cmd_size = cmd_obj.size();
|
|
if (cmd_size > 1) {
|
|
Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size);
|
|
return;
|
|
} else if (1 == cmd_size) {
|
|
|
|
JsonObject::iterator it = cmd_obj.begin();
|
|
String key = it->key;
|
|
JsonVariant& value = it->value;
|
|
uint32_t x = 0, y = 0, z = 0;
|
|
|
|
const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str());
|
|
if (tasmota_cmd) {
|
|
cmd_str = tasmota_cmd;
|
|
} else {
|
|
Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str());
|
|
return;
|
|
}
|
|
|
|
|
|
if (value.is<bool>()) {
|
|
x = value.as<bool>() ? 1 : 0;
|
|
} else if (value.is<unsigned int>()) {
|
|
x = value.as<unsigned int>();
|
|
} else {
|
|
|
|
const char *s_const = value.as<const char*>();
|
|
if (s_const != nullptr) {
|
|
char s[strlen(s_const)+1];
|
|
strcpy(s, s_const);
|
|
if ((nullptr != s) && (0x00 != *s)) {
|
|
char *sval = strtok(s, delim);
|
|
if (sval) {
|
|
x = ZigbeeAliasOrNumber(sval);
|
|
sval = strtok(nullptr, delim);
|
|
if (sval) {
|
|
y = ZigbeeAliasOrNumber(sval);
|
|
sval = strtok(nullptr, delim);
|
|
if (sval) {
|
|
z = ZigbeeAliasOrNumber(sval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str());
|
|
cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str());
|
|
} else {
|
|
|
|
}
|
|
} else if (val_cmd.is<char*>()) {
|
|
|
|
cmd_str = val_cmd.as<String>();
|
|
} else {
|
|
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
|
|
device, endpoint, cmd_str.c_str());
|
|
zigbeeZCLSendStr(device, endpoint, cmd_str.c_str());
|
|
} else {
|
|
Response_P(PSTR("Missing zigbee 'Send'"));
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
ZBM(ZBS_BIND_REQ, Z_SREQ | Z_ZDO, ZDO_BIND_REQ,
|
|
0,0,
|
|
0,0,0,0,0,0,0,0,
|
|
0x00,
|
|
0x00, 0x00,
|
|
0x03,
|
|
0,0,0,0,0,0,0,0,
|
|
0x01
|
|
)
|
|
|
|
void CmndZbBind(void) {
|
|
|
|
|
|
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
DynamicJsonBuffer jsonBuf;
|
|
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
|
|
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
|
|
|
|
|
|
|
|
uint16_t device = 0xFFFF;
|
|
uint8_t endpoint = 0x00;
|
|
uint16_t cluster = 0;
|
|
uint32_t group = 0xFFFFFFFF;
|
|
|
|
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
|
|
if (nullptr != &val_device) {
|
|
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
|
if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; }
|
|
}
|
|
if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; }
|
|
|
|
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
|
|
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
|
|
const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster"));
|
|
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
|
|
|
|
|
|
|
|
SBuffer buf(sizeof(ZBS_BIND_REQ));
|
|
buf.add8(Z_SREQ | Z_ZDO);
|
|
buf.add8(ZDO_BIND_REQ);
|
|
buf.add16(device);
|
|
buf.add64(zigbee_devices.getDeviceLongAddr(device));
|
|
buf.add8(endpoint);
|
|
buf.add16(cluster);
|
|
buf.add8(0x03);
|
|
buf.add64(localIEEEAddr);
|
|
buf.add8(0x01);
|
|
|
|
ZigbeeZNPSend(buf.getBuffer(), buf.len());
|
|
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
|
|
void CmndZbProbe(void) {
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
|
|
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
|
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
|
|
|
|
|
Z_SendActiveEpReq(shortaddr);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
|
|
void CmndZbName(void) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
|
|
|
|
char *p;
|
|
char *str = strtok_r(XdrvMailbox.data, ", ", &p);
|
|
|
|
|
|
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true);
|
|
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
|
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
|
|
|
if (p == nullptr) {
|
|
const String * friendlyName = zigbee_devices.getFriendlyName(shortaddr);
|
|
Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), shortaddr, friendlyName ? friendlyName->c_str() : "");
|
|
} else {
|
|
zigbee_devices.setFriendlyName(shortaddr, p);
|
|
Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), shortaddr, p);
|
|
}
|
|
}
|
|
|
|
|
|
void CmndZbForget(void) {
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
|
|
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
|
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
|
|
|
|
|
if (zigbee_devices.removeDevice(shortaddr)) {
|
|
ResponseCmndDone();
|
|
} else {
|
|
ResponseCmndChar("Unknown device");
|
|
}
|
|
}
|
|
|
|
|
|
void CmndZbSave(void) {
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
|
|
saveZigbeeDevices();
|
|
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
|
|
void CmndZbRead(void) {
|
|
|
|
|
|
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
DynamicJsonBuffer jsonBuf;
|
|
JsonObject &json = jsonBuf.parseObject(XdrvMailbox.data);
|
|
if (!json.success()) { ResponseCmndChar(D_JSON_INVALID_JSON); return; }
|
|
|
|
|
|
uint16_t device = 0xFFFF;
|
|
uint16_t cluster = 0x0000;
|
|
uint8_t endpoint = 0x00;
|
|
size_t attrs_len = 0;
|
|
uint8_t* attrs = nullptr;
|
|
|
|
const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device"));
|
|
if (nullptr != &val_device) {
|
|
device = zigbee_devices.parseDeviceParam(val_device.as<char*>());
|
|
if (0xFFFF == device) { ResponseCmndChar("Invalid parameter"); return; }
|
|
}
|
|
if ((nullptr == &val_device) || (0x000 == device)) { ResponseCmndChar("Unknown device"); return; }
|
|
|
|
const JsonVariant &val_cluster = getCaseInsensitive(json, PSTR("Cluster"));
|
|
if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); }
|
|
const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("Endpoint"));
|
|
if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); }
|
|
|
|
const JsonVariant &val_attr = getCaseInsensitive(json, PSTR("Read"));
|
|
if (nullptr != &val_attr) {
|
|
uint16_t val = strToUInt(val_attr);
|
|
if (val_attr.is<JsonArray>()) {
|
|
JsonArray& attr_arr = val_attr;
|
|
attrs_len = attr_arr.size() * 2;
|
|
attrs = new uint8_t[attrs_len];
|
|
|
|
uint32_t i = 0;
|
|
for (auto value : attr_arr) {
|
|
uint16_t val = strToUInt(value);
|
|
attrs[i++] = val & 0xFF;
|
|
attrs[i++] = val >> 8;
|
|
}
|
|
} else {
|
|
attrs_len = 2;
|
|
attrs = new uint8_t[attrs_len];
|
|
attrs[0] = val & 0xFF;
|
|
attrs[1] = val >> 8;
|
|
}
|
|
}
|
|
|
|
if ((0 != endpoint) && (attrs_len > 0)) {
|
|
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false );
|
|
ResponseCmndDone();
|
|
} else {
|
|
ResponseCmndChar("Missing parameters");
|
|
}
|
|
|
|
if (attrs) { delete[] attrs; }
|
|
}
|
|
|
|
|
|
void CmndZbPermitJoin(void)
|
|
{
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
uint32_t payload = XdrvMailbox.payload;
|
|
if (payload < 0) { payload = 0; }
|
|
if ((99 != payload) && (payload > 1)) { payload = 1; }
|
|
|
|
if (1 == payload) {
|
|
ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_60);
|
|
} else if (99 == payload){
|
|
ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_OPEN_XX);
|
|
} else {
|
|
ZigbeeGotoLabel(ZIGBEE_LABEL_PERMIT_JOIN_CLOSE);
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndZbStatus(void) {
|
|
if (ZigbeeSerial) {
|
|
if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; }
|
|
uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data);
|
|
if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; }
|
|
if (XdrvMailbox.payload > 0) {
|
|
if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; }
|
|
}
|
|
|
|
String dump = zigbee_devices.dump(XdrvMailbox.index, shortaddr);
|
|
Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv23(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (zigbee.active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (!zigbee.init_phase) {
|
|
zigbee_devices.runTimer();
|
|
}
|
|
break;
|
|
case FUNC_LOOP:
|
|
if (ZigbeeSerial) { ZigbeeInput(); }
|
|
if (zigbee.state_machine) {
|
|
|
|
ZigbeeStateMachine_Run();
|
|
}
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
ZigbeeInit();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kZbCommands, ZigbeeCommand);
|
|
result = result || DecodeCommand(kZigbeeCommands, ZigbeeCommand);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_24_buzzer.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_24_buzzer.ino"
|
|
#ifdef USE_BUZZER
|
|
|
|
|
|
|
|
|
|
#define XDRV_24 24
|
|
|
|
struct BUZZER {
|
|
uint32_t tune = 0;
|
|
uint32_t tune_reload = 0;
|
|
bool active = true;
|
|
bool enable = false;
|
|
uint8_t inverted = 0;
|
|
uint8_t count = 0;
|
|
uint8_t mode = 0;
|
|
uint8_t set[2];
|
|
uint8_t duration;
|
|
uint8_t state = 0;
|
|
} Buzzer;
|
|
|
|
|
|
|
|
void BuzzerOff(void)
|
|
{
|
|
DigitalWrite(GPIO_BUZZER, Buzzer.inverted);
|
|
}
|
|
|
|
|
|
void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32_t mode)
|
|
{
|
|
Buzzer.set[0] = off;
|
|
Buzzer.set[1] = on;
|
|
Buzzer.duration = 1;
|
|
Buzzer.tune_reload = 0;
|
|
Buzzer.mode = mode;
|
|
|
|
if (tune) {
|
|
uint32_t tune1 = tune;
|
|
uint32_t tune2 = tune;
|
|
for (uint32_t i = 0; i < 32; i++) {
|
|
if (!(tune2 & 0x80000000)) {
|
|
tune2 <<= 1;
|
|
} else {
|
|
Buzzer.tune_reload <<= 1;
|
|
Buzzer.tune_reload |= tune1 & 1;
|
|
tune1 >>= 1;
|
|
}
|
|
}
|
|
Buzzer.tune = Buzzer.tune_reload;
|
|
}
|
|
Buzzer.count = count * 2;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BUZ: %d(%d),%d,%d,0x%08X(0x%08X)"), count, Buzzer.count, on, off, tune, Buzzer.tune);
|
|
|
|
Buzzer.enable = (Buzzer.count > 0);
|
|
if (!Buzzer.enable) {
|
|
BuzzerOff();
|
|
}
|
|
}
|
|
|
|
void BuzzerSetStateToLed(uint32_t state)
|
|
{
|
|
if (Buzzer.enable && (2 == Buzzer.mode)) {
|
|
Buzzer.state = (state != 0);
|
|
DigitalWrite(GPIO_BUZZER, (Buzzer.inverted) ? !Buzzer.state : Buzzer.state);
|
|
}
|
|
}
|
|
|
|
void BuzzerBeep(uint32_t count)
|
|
{
|
|
BuzzerBeep(count, 1, 1, 0, 0);
|
|
}
|
|
|
|
void BuzzerEnabledBeep(uint32_t count, uint32_t duration)
|
|
{
|
|
if (Settings.flag3.buzzer_enable) {
|
|
BuzzerBeep(count, duration, 1, 0, 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool BuzzerPinState(void)
|
|
{
|
|
if (XdrvMailbox.index == GPIO_BUZZER_INV) {
|
|
Buzzer.inverted = 1;
|
|
XdrvMailbox.index -= (GPIO_BUZZER_INV - GPIO_BUZZER);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BuzzerInit(void)
|
|
{
|
|
if (pin[GPIO_BUZZER] < 99) {
|
|
pinMode(pin[GPIO_BUZZER], OUTPUT);
|
|
BuzzerOff();
|
|
} else {
|
|
Buzzer.active = false;
|
|
}
|
|
}
|
|
|
|
void BuzzerEvery100mSec(void)
|
|
{
|
|
if (Buzzer.enable && (Buzzer.mode != 2)) {
|
|
if (Buzzer.count) {
|
|
if (Buzzer.duration) {
|
|
Buzzer.duration--;
|
|
if (!Buzzer.duration) {
|
|
if (Buzzer.tune) {
|
|
Buzzer.state = Buzzer.tune & 1;
|
|
Buzzer.tune >>= 1;
|
|
} else {
|
|
Buzzer.tune = Buzzer.tune_reload;
|
|
Buzzer.count -= (Buzzer.tune_reload) ? 2 : 1;
|
|
Buzzer.state = Buzzer.count & 1;
|
|
if (Buzzer.mode) {
|
|
Buzzer.count |= 2;
|
|
}
|
|
}
|
|
Buzzer.duration = Buzzer.set[Buzzer.state];
|
|
}
|
|
}
|
|
DigitalWrite(GPIO_BUZZER, (Buzzer.inverted) ? !Buzzer.state : Buzzer.state);
|
|
} else {
|
|
Buzzer.enable = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char kBuzzerCommands[] PROGMEM = "|"
|
|
"Buzzer" ;
|
|
|
|
void (* const BuzzerCommand[])(void) PROGMEM = {
|
|
&CmndBuzzer };
|
|
|
|
void CmndBuzzer(void)
|
|
{
|
|
# 174 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_24_buzzer.ino"
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (XdrvMailbox.payload != 0) {
|
|
uint32_t parm[4] = { 0 };
|
|
uint32_t mode = 0;
|
|
ParseParameters(4, parm);
|
|
if (XdrvMailbox.payload <= 0) {
|
|
parm[0] = 1;
|
|
mode = -XdrvMailbox.payload;
|
|
}
|
|
for (uint32_t i = 1; i < 3; i++) {
|
|
if (parm[i] < 1) { parm[i] = 1; }
|
|
}
|
|
BuzzerBeep(parm[0], parm[1], parm[2], parm[3], mode);
|
|
} else {
|
|
BuzzerBeep(0);
|
|
}
|
|
} else {
|
|
BuzzerBeep(1);
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv24(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Buzzer.active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_100_MSECOND:
|
|
BuzzerEvery100mSec();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kBuzzerCommands, BuzzerCommand);
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
BuzzerInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = BuzzerPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_25_A4988_Stepper.ino"
|
|
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_25_A4988_Stepper.ino"
|
|
#ifdef USE_A4988_STEPPER
|
|
|
|
|
|
|
|
|
|
#define XDRV_25 25
|
|
|
|
#include <A4988_Stepper.h>
|
|
|
|
short A4988_dir_pin = pin[GPIO_MAX];
|
|
short A4988_stp_pin = pin[GPIO_MAX];
|
|
short A4988_ms1_pin = pin[GPIO_MAX];
|
|
short A4988_ms2_pin = pin[GPIO_MAX];
|
|
short A4988_ms3_pin = pin[GPIO_MAX];
|
|
short A4988_ena_pin = pin[GPIO_MAX];
|
|
int A4988_spr = 0;
|
|
float A4988_rpm = 0;
|
|
short A4988_mis = 0;
|
|
|
|
A4988_Stepper* myA4988 = nullptr;
|
|
|
|
void A4988Init(void)
|
|
{
|
|
A4988_dir_pin = pin[GPIO_A4988_DIR];
|
|
A4988_stp_pin = pin[GPIO_A4988_STP];
|
|
A4988_ena_pin = pin[GPIO_A4988_ENA];
|
|
A4988_ms1_pin = pin[GPIO_A4988_MS1];
|
|
A4988_ms2_pin = pin[GPIO_A4988_MS2];
|
|
A4988_ms3_pin = pin[GPIO_A4988_MS3];
|
|
A4988_spr = 200;
|
|
A4988_rpm = 30;
|
|
A4988_mis = 1;
|
|
|
|
myA4988 = new A4988_Stepper( A4988_spr
|
|
, A4988_rpm
|
|
, A4988_mis
|
|
, A4988_dir_pin
|
|
, A4988_stp_pin
|
|
, A4988_ena_pin
|
|
, A4988_ms1_pin
|
|
, A4988_ms2_pin
|
|
, A4988_ms3_pin );
|
|
}
|
|
|
|
const char kA4988Commands[] PROGMEM = "Motor|"
|
|
"Move|Rotate|Turn|MIS|SPR|RPM";
|
|
|
|
void (* const A4988Command[])(void) PROGMEM = {
|
|
&CmndDoMove,&CmndDoRotate,&CmndDoTurn,&CmndSetMIS,&CmndSetSPR,&CmndSetRPM};
|
|
|
|
void CmndDoMove(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
long stepsPlease = strtoul(XdrvMailbox.data,nullptr,10);
|
|
myA4988->doMove(stepsPlease);
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
void CmndDoRotate(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
long degrsPlease = strtoul(XdrvMailbox.data,nullptr,10);
|
|
myA4988->doRotate(degrsPlease);
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
void CmndDoTurn(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
float turnsPlease = strtod(XdrvMailbox.data,nullptr);
|
|
myA4988->doTurn(turnsPlease);
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
void CmndSetMIS(void) {
|
|
if ((pin[GPIO_A4988_MS1] < 99) && (pin[GPIO_A4988_MS2] < 99) && (pin[GPIO_A4988_MS3] < 99) && (XdrvMailbox.data_len > 0)) {
|
|
short newMIS = strtoul(XdrvMailbox.data,nullptr,10);
|
|
myA4988->setMIS(newMIS);
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
void CmndSetSPR(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
int newSPR = strtoul(XdrvMailbox.data,nullptr,10);
|
|
myA4988->setSPR(newSPR);
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
void CmndSetRPM(void) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
short newRPM = strtoul(XdrvMailbox.data,nullptr,10);
|
|
myA4988->setRPM(newRPM);
|
|
ResponseCmndDone();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Xdrv25(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
if ((pin[GPIO_A4988_DIR] < 99) && (pin[GPIO_A4988_STP] < 99)) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
A4988Init();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kA4988Commands, A4988Command);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_26_ariluxrf.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_26_ariluxrf.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_ARILUX_RF
|
|
|
|
|
|
|
|
|
|
#define XDRV_26 26
|
|
|
|
const uint32_t ARILUX_RF_TIME_AVOID_DUPLICATE = 1000;
|
|
|
|
const uint8_t ARILUX_RF_MAX_CHANGES = 51;
|
|
const uint32_t ARILUX_RF_SEPARATION_LIMIT = 4300;
|
|
const uint32_t ARILUX_RF_RECEIVE_TOLERANCE = 60;
|
|
|
|
struct ARILUX {
|
|
unsigned int rf_timings[ARILUX_RF_MAX_CHANGES];
|
|
|
|
unsigned long rf_received_value = 0;
|
|
unsigned long rf_last_received_value = 0;
|
|
unsigned long rf_last_time = 0;
|
|
unsigned long rf_lasttime = 0;
|
|
|
|
unsigned int rf_change_count = 0;
|
|
unsigned int rf_repeat_count = 0;
|
|
|
|
uint8_t rf_toggle = 0;
|
|
} Arilux;
|
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
#ifndef USE_WS2812_DMA
|
|
void AriluxRfInterrupt(void) ICACHE_RAM_ATTR;
|
|
#endif
|
|
#endif
|
|
|
|
void AriluxRfInterrupt(void)
|
|
{
|
|
unsigned long time = micros();
|
|
unsigned int duration = time - Arilux.rf_lasttime;
|
|
|
|
if (duration > ARILUX_RF_SEPARATION_LIMIT) {
|
|
if (abs(duration - Arilux.rf_timings[0]) < 200) {
|
|
Arilux.rf_repeat_count++;
|
|
if (Arilux.rf_repeat_count == 2) {
|
|
unsigned long code = 0;
|
|
const unsigned int delay = Arilux.rf_timings[0] / 31;
|
|
const unsigned int delayTolerance = delay * ARILUX_RF_RECEIVE_TOLERANCE / 100;
|
|
for (unsigned int i = 1; i < Arilux.rf_change_count -1; i += 2) {
|
|
code <<= 1;
|
|
if (abs(Arilux.rf_timings[i] - (delay *3)) < delayTolerance && abs(Arilux.rf_timings[i +1] - delay) < delayTolerance) {
|
|
code |= 1;
|
|
}
|
|
}
|
|
if (Arilux.rf_change_count > 49) {
|
|
Arilux.rf_received_value = code;
|
|
}
|
|
Arilux.rf_repeat_count = 0;
|
|
}
|
|
}
|
|
Arilux.rf_change_count = 0;
|
|
}
|
|
if (Arilux.rf_change_count >= ARILUX_RF_MAX_CHANGES) {
|
|
Arilux.rf_change_count = 0;
|
|
Arilux.rf_repeat_count = 0;
|
|
}
|
|
Arilux.rf_timings[Arilux.rf_change_count++] = duration;
|
|
Arilux.rf_lasttime = time;
|
|
}
|
|
|
|
void AriluxRfHandler(void)
|
|
{
|
|
unsigned long now = millis();
|
|
if (Arilux.rf_received_value && !((Arilux.rf_received_value == Arilux.rf_last_received_value) && (now - Arilux.rf_last_time < ARILUX_RF_TIME_AVOID_DUPLICATE))) {
|
|
Arilux.rf_last_received_value = Arilux.rf_received_value;
|
|
Arilux.rf_last_time = now;
|
|
|
|
uint16_t hostcode = Arilux.rf_received_value >> 8 & 0xFFFF;
|
|
if (Settings.rf_code[1][6] == Settings.rf_code[1][7]) {
|
|
Settings.rf_code[1][6] = hostcode >> 8 & 0xFF;
|
|
Settings.rf_code[1][7] = hostcode & 0xFF;
|
|
}
|
|
uint16_t stored_hostcode = Settings.rf_code[1][6] << 8 | Settings.rf_code[1][7];
|
|
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_RFR D_HOST D_CODE " 0x%04X, " D_RECEIVED " 0x%06X"), stored_hostcode, Arilux.rf_received_value);
|
|
|
|
if (hostcode == stored_hostcode) {
|
|
char command[33];
|
|
char value = '-';
|
|
command[0] = '\0';
|
|
uint8_t keycode = Arilux.rf_received_value & 0xFF;
|
|
switch (keycode) {
|
|
case 1:
|
|
case 3:
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_POWER " %d"), (1 == keycode) ? 1 : 0);
|
|
break;
|
|
case 2:
|
|
Arilux.rf_toggle++;
|
|
Arilux.rf_toggle &= 0x3;
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), 200 + Arilux.rf_toggle);
|
|
break;
|
|
case 4:
|
|
value = '+';
|
|
case 7:
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_SPEED " %c"), value);
|
|
break;
|
|
case 5:
|
|
value = '+';
|
|
case 8:
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_SCHEME " %c"), value);
|
|
break;
|
|
case 6:
|
|
value = '+';
|
|
case 9:
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_DIMMER " %c"), value);
|
|
break;
|
|
default: {
|
|
if ((keycode >= 10) && (keycode <= 21)) {
|
|
snprintf_P(command, sizeof(command), PSTR(D_CMND_COLOR " %d"), keycode -9);
|
|
}
|
|
}
|
|
}
|
|
if (strlen(command)) {
|
|
ExecuteCommand(command, SRC_LIGHT);
|
|
}
|
|
}
|
|
}
|
|
Arilux.rf_received_value = 0;
|
|
}
|
|
|
|
void AriluxRfInit(void)
|
|
{
|
|
if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) {
|
|
if (Settings.last_module != Settings.module) {
|
|
Settings.rf_code[1][6] = 0;
|
|
Settings.rf_code[1][7] = 0;
|
|
Settings.last_module = Settings.module;
|
|
}
|
|
Arilux.rf_received_value = 0;
|
|
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 0);
|
|
attachInterrupt(pin[GPIO_ARIRFRCV], AriluxRfInterrupt, CHANGE);
|
|
}
|
|
}
|
|
|
|
void AriluxRfDisable(void)
|
|
{
|
|
if ((pin[GPIO_ARIRFRCV] < 99) && (pin[GPIO_ARIRFSEL] < 99)) {
|
|
detachInterrupt(pin[GPIO_ARIRFRCV]);
|
|
digitalWrite(pin[GPIO_ARIRFSEL], 1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv26(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (pin[GPIO_ARIRFRCV] < 99) { AriluxRfHandler(); }
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
if (10 == uptime) { AriluxRfInit(); }
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_27_shutter.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_27_shutter.ino"
|
|
#ifdef USE_SHUTTER
|
|
|
|
|
|
|
|
|
|
#define XDRV_27 27
|
|
|
|
#define D_SHUTTER "SHUTTER"
|
|
|
|
const uint16_t MOTOR_STOP_TIME = 500;
|
|
const uint8_t steps_per_second = 20;
|
|
|
|
uint8_t calibrate_pos[6] = {0,30,50,70,90,100};
|
|
uint16_t messwerte[5] = {30,50,70,90,100};
|
|
uint16_t last_execute_step;
|
|
|
|
enum ShutterModes { SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE, SHT_OFF_ON__OPEN_CLOSE_STEPPER,};
|
|
enum ShutterButtonStates { SHT_NOT_PRESSED, SHT_PRESSED_MULTI, SHT_PRESSED_HOLD, SHT_PRESSED_IMMEDIATE, SHT_PRESSED_MULTI_SIMULTANEOUS, SHT_PRESSED_HOLD_SIMULTANEOUS, SHT_PRESSED_EXT_HOLD_SIMULTANEOUS,};
|
|
|
|
const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|"
|
|
D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|"
|
|
D_CMND_SHUTTER_OPENTIME "|" D_CMND_SHUTTER_CLOSETIME "|" D_CMND_SHUTTER_RELAY "|"
|
|
D_CMND_SHUTTER_SETHALFWAY "|" D_CMND_SHUTTER_SETCLOSE "|" D_CMND_SHUTTER_INVERT "|" D_CMND_SHUTTER_CLIBRATION "|"
|
|
D_CMND_SHUTTER_MOTORDELAY "|" D_CMND_SHUTTER_FREQUENCY "|" D_CMND_SHUTTER_BUTTON "|" D_CMND_SHUTTER_LOCK "|" D_CMND_SHUTTER_ENABLEENDSTOPTIME;
|
|
|
|
void (* const ShutterCommand[])(void) PROGMEM = {
|
|
&CmndShutterOpen, &CmndShutterClose, &CmndShutterStop, &CmndShutterPosition,
|
|
&CmndShutterOpenTime, &CmndShutterCloseTime, &CmndShutterRelay,
|
|
&CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay,
|
|
&CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime};
|
|
|
|
const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d}";
|
|
const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}";
|
|
|
|
#include <Ticker.h>
|
|
|
|
Ticker TickerShutter;
|
|
|
|
struct SHUTTER {
|
|
power_t mask = 0;
|
|
power_t old_power = 0;
|
|
power_t switched_relay = 0;
|
|
uint32_t time[MAX_SHUTTERS];
|
|
int32_t open_max[MAX_SHUTTERS];
|
|
int32_t target_position[MAX_SHUTTERS];
|
|
int32_t start_position[MAX_SHUTTERS];
|
|
int32_t real_position[MAX_SHUTTERS];
|
|
uint16_t open_time[MAX_SHUTTERS];
|
|
uint16_t close_time[MAX_SHUTTERS];
|
|
uint16_t close_velocity[MAX_SHUTTERS];
|
|
int8_t direction[MAX_SHUTTERS];
|
|
uint8_t mode = 0;
|
|
int16_t motordelay[MAX_SHUTTERS];
|
|
int16_t pwm_frequency;
|
|
uint16_t max_pwm_frequency = 1000;
|
|
uint16_t max_close_pwm_frequency[MAX_SHUTTERS];
|
|
uint8_t skip_relay_change;
|
|
int32_t accelerator[MAX_SHUTTERS];
|
|
} Shutter;
|
|
|
|
void ShutterLogPos(uint32_t i)
|
|
{
|
|
char stemp2[10];
|
|
dtostrfd((float)Shutter.time[i] / steps_per_second, 2, stemp2);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter%d Real %d, Start %d, Stop %d, Dir %d, Delay %d, Rtc %s [s], Freq %d"),
|
|
i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i], stemp2, Shutter.pwm_frequency);
|
|
}
|
|
|
|
void ShutterRtc50mS(void)
|
|
{
|
|
for (uint32_t i = 0; i < shutters_present; i++) {
|
|
Shutter.time[i]++;
|
|
if (Shutter.accelerator[i]) {
|
|
Shutter.pwm_frequency += Shutter.accelerator[i];
|
|
Shutter.pwm_frequency = tmax(0,tmin(Shutter.direction[i]==1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i],Shutter.pwm_frequency));
|
|
analogWriteFreq(Shutter.pwm_frequency);
|
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SHT_DIV_ROUND(__A,__B) (((__A) + (__B)/2) / (__B))
|
|
|
|
int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index)
|
|
{
|
|
if (0 == percent) return 0;
|
|
if (100 == percent) return Shutter.open_max[index];
|
|
if (Settings.shutter_set50percent[index] != 50) {
|
|
return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index];
|
|
} else {
|
|
uint32_t realpos;
|
|
|
|
for (uint32_t j = 0; j < 5; j++) {
|
|
if (0 == Settings.shuttercoeff[j][index]) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SHT: RESET/INIT CALIBRATION MATRIX DIV 0"));
|
|
for (uint32_t k = 0; k < 5; k++) {
|
|
Settings.shuttercoeff[k][index] = SHT_DIV_ROUND(calibrate_pos[k+1] * 1000, calibrate_pos[5]);
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
if ((percent * 10) >= Settings.shuttercoeff[i][index]) {
|
|
realpos = SHT_DIV_ROUND(Shutter.open_max[index] * calibrate_pos[i+1], 100);
|
|
|
|
} else {
|
|
if (0 == i) {
|
|
realpos = SHT_DIV_ROUND(SHT_DIV_ROUND(percent * Shutter.open_max[index] * calibrate_pos[i+1], Settings.shuttercoeff[i][index]), 10);
|
|
} else {
|
|
|
|
|
|
realpos += SHT_DIV_ROUND(SHT_DIV_ROUND((percent*10 - Settings.shuttercoeff[i-1][index] ) * Shutter.open_max[index] * (calibrate_pos[i+1] - calibrate_pos[i]), Settings.shuttercoeff[i][index] - Settings.shuttercoeff[i-1][index]), 100);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return realpos;
|
|
}
|
|
}
|
|
|
|
uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index)
|
|
{
|
|
if (0 >= realpos) return 0;
|
|
if (Shutter.open_max[index] <= realpos) return 100;
|
|
if (Settings.shutter_set50percent[index] != 50) {
|
|
return (Settings.shuttercoeff[2][index] * 5 > realpos) ? SHT_DIV_ROUND(realpos, Settings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos-Settings.shuttercoeff[0][index], Settings.shuttercoeff[1][index]);
|
|
} else {
|
|
uint16_t realpercent;
|
|
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
if (realpos >= Shutter.open_max[index] * calibrate_pos[i+1] / 100) {
|
|
realpercent = SHT_DIV_ROUND(Settings.shuttercoeff[i][index], 10);
|
|
|
|
} else {
|
|
if (0 == i) {
|
|
realpercent = SHT_DIV_ROUND(SHT_DIV_ROUND((realpos - SHT_DIV_ROUND(Shutter.open_max[index] * calibrate_pos[i], 100)) * 10 * Settings.shuttercoeff[i][index], calibrate_pos[i+1]), Shutter.open_max[index]);
|
|
} else {
|
|
|
|
|
|
|
|
realpercent += SHT_DIV_ROUND(SHT_DIV_ROUND((realpos - SHT_DIV_ROUND(Shutter.open_max[index] * calibrate_pos[i], 100)) * 10 * (Settings.shuttercoeff[i][index] - Settings.shuttercoeff[i-1][index]), (calibrate_pos[i+1] - calibrate_pos[i])), Shutter.open_max[index]) ;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return realpercent;
|
|
}
|
|
}
|
|
|
|
void ShutterInit(void)
|
|
{
|
|
shutters_present = 0;
|
|
Shutter.mask = 0;
|
|
|
|
Shutter.old_power = power;
|
|
bool relay_in_interlock = false;
|
|
|
|
|
|
if (Settings.shutter_startrelay[MAX_SHUTTERS] == 0) {
|
|
Shutter.max_pwm_frequency = Settings.shuttercoeff[4][3] > 0 ? Settings.shuttercoeff[4][3] : Shutter.max_pwm_frequency;
|
|
}
|
|
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
|
|
|
|
Settings.shutter_startrelay[i] = (Settings.shutter_startrelay[i] == 0 && i == 0? 1 : Settings.shutter_startrelay[i]);
|
|
if (Settings.shutter_startrelay[i] && (Settings.shutter_startrelay[i] < 9)) {
|
|
shutters_present++;
|
|
|
|
|
|
Shutter.mask |= 3 << (Settings.shutter_startrelay[i] -1) ;
|
|
|
|
for (uint32_t j = 0; j < MAX_INTERLOCKS * Settings.flag.interlock; j++) {
|
|
|
|
if (Settings.interlock[j] && (Settings.interlock[j] & Shutter.mask)) {
|
|
|
|
relay_in_interlock = true;
|
|
}
|
|
}
|
|
if (relay_in_interlock) {
|
|
if (Settings.pulse_timer[i] > 0) {
|
|
Shutter.mode = SHT_PULSE_OPEN__PULSE_CLOSE;
|
|
} else {
|
|
Shutter.mode = SHT_OFF_OPEN__OFF_CLOSE;
|
|
}
|
|
} else {
|
|
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE;
|
|
if ((pin[GPIO_PWM1+i] < 99) && (pin[GPIO_CNTR1+i] < 99)) {
|
|
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE_STEPPER;
|
|
Shutter.pwm_frequency = 0;
|
|
analogWriteFreq(Shutter.pwm_frequency);
|
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
|
}
|
|
}
|
|
|
|
TickerShutter.attach_ms(50, ShutterRtc50mS );
|
|
|
|
Settings.shutter_set50percent[i] = (Settings.shutter_set50percent[i] > 0) ? Settings.shutter_set50percent[i] : 50;
|
|
|
|
|
|
Shutter.open_time[i] = (Settings.shutter_opentime[i] > 0) ? Settings.shutter_opentime[i] : 100;
|
|
Shutter.close_time[i] = (Settings.shutter_closetime[i] > 0) ? Settings.shutter_closetime[i] : 100;
|
|
|
|
|
|
Shutter.open_max[i] = 200 * Shutter.open_time[i];
|
|
Shutter.close_velocity[i] = Shutter.open_max[i] / Shutter.close_time[i] / 2 ;
|
|
Shutter.max_close_pwm_frequency[i] = Shutter.max_pwm_frequency*Shutter.open_time[i] / Shutter.close_time[i];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d Closefreq: %d"),i, Shutter.max_close_pwm_frequency[i]);
|
|
|
|
|
|
if (Settings.shutter_set50percent[i] != 50) {
|
|
Settings.shuttercoeff[1][i] = Shutter.open_max[i] * (100 - Settings.shutter_set50percent[i] ) / 5000;
|
|
Settings.shuttercoeff[0][i] = Shutter.open_max[i] - (Settings.shuttercoeff[1][i] * 100);
|
|
Settings.shuttercoeff[2][i] = (Settings.shuttercoeff[0][i] + 5 * Settings.shuttercoeff[1][i]) / 5;
|
|
}
|
|
Shutter.mask |= 3 << (Settings.shutter_startrelay[i] -1);
|
|
|
|
Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i);
|
|
|
|
Shutter.start_position[i] = Shutter.target_position[i] = Shutter.real_position[i];
|
|
Shutter.motordelay[i] = Settings.shutter_motordelay[i];
|
|
|
|
char shutter_open_chr[10];
|
|
dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr);
|
|
char shutter_close_chr[10];
|
|
dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100, Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoeffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, is locked %d, end stop time enabled %d, shuttermode %d, motordelay %d"),
|
|
i+1, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr,
|
|
Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i],
|
|
Shutter.mask, (Settings.shutter_options[i]&1) ? 1 : 0, (Settings.shutter_options[i]&2) ? 1 : 0, (Settings.shutter_options[i]&4) ? 1 : 0, Shutter.mode, Shutter.motordelay[i]);
|
|
|
|
} else {
|
|
|
|
break;
|
|
}
|
|
ShutterLimitRealAndTargetPositions(i);
|
|
Settings.shutter_accuracy = 1;
|
|
}
|
|
}
|
|
|
|
void ShutterReportPosition(bool always)
|
|
{
|
|
uint32_t shutter_moving = 0;
|
|
Response_P(PSTR("{"));
|
|
for (uint32_t i = 0; i < shutters_present; i++) {
|
|
|
|
uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i);
|
|
if (Shutter.direction[i] != 0) {
|
|
shutter_moving = 1;
|
|
ShutterLogPos(i);
|
|
}
|
|
if (i) { ResponseAppend_P(PSTR(",")); }
|
|
ResponseAppend_P(JSON_SHUTTER_POS, i+1, (Settings.shutter_options[i] & 1) ? 100-position : position, Shutter.direction[i]);
|
|
}
|
|
ResponseJsonEnd();
|
|
if (always || (1 == shutter_moving)) {
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER));
|
|
}
|
|
if (rules_flag.shutter_moving > shutter_moving) {
|
|
rules_flag.shutter_moved = 1;
|
|
} else {
|
|
rules_flag.shutter_moved = 0;
|
|
}
|
|
rules_flag.shutter_moving = shutter_moving;
|
|
|
|
}
|
|
|
|
void ShutterLimitRealAndTargetPositions(uint32_t i) {
|
|
if (Shutter.real_position[i]<0) Shutter.real_position[i] = 0;
|
|
if (Shutter.real_position[i]>Shutter.open_max[i]) Shutter.real_position[i] = Shutter.open_max[i];
|
|
if (Shutter.target_position[i]<0) Shutter.target_position[i] = 0;
|
|
if (Shutter.target_position[i]>Shutter.open_max[i]) Shutter.target_position[i] = Shutter.open_max[i];
|
|
}
|
|
|
|
void ShutterUpdatePosition(void)
|
|
{
|
|
|
|
char scommand[CMDSZ];
|
|
char stopic[TOPSZ];
|
|
|
|
for (uint32_t i = 0; i < shutters_present; i++) {
|
|
if (Shutter.direction[i] != 0) {
|
|
int32_t stop_position_delta = 20;
|
|
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) {
|
|
|
|
|
|
Shutter.real_position[i] = ShutterCounterBasedPosition(i);
|
|
|
|
int32_t max_frequency = Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i];
|
|
int32_t max_freq_change_per_sec = Shutter.max_pwm_frequency*steps_per_second / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1);
|
|
int32_t min_runtime_ms = Shutter.pwm_frequency*1000 / max_freq_change_per_sec;
|
|
int32_t velocity = Shutter.direction[i] == 1 ? 100 : Shutter.close_velocity[i];
|
|
int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency / max_frequency * Shutter.direction[i] ;
|
|
|
|
int32_t next_possible_stop = Shutter.real_position[i] + minstopway ;
|
|
stop_position_delta =200 * Shutter.pwm_frequency/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]);
|
|
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: time: %d, velocity %d, minstopway %d,cur_freq %d, max_frequency %d, act_freq_change %d, min_runtime_ms %d, act.pos %d, next_stop %d, target: %d"),Shutter.time[i],velocity,minstopway,
|
|
Shutter.pwm_frequency,max_frequency, Shutter.accelerator[i],min_runtime_ms,Shutter.real_position[i], next_possible_stop,Shutter.target_position[i]);
|
|
|
|
if (Shutter.accelerator[i] < 0 || next_possible_stop * Shutter.direction[i] > Shutter.target_position[i] * Shutter.direction[i] ) {
|
|
|
|
Shutter.accelerator[i] = - tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*12/200);
|
|
|
|
} else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency == max_frequency) {
|
|
Shutter.accelerator[i] = 0;
|
|
}
|
|
} else {
|
|
Shutter.real_position[i] = Shutter.start_position[i] + ( (Shutter.time[i] - Shutter.motordelay[i]) * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i]));
|
|
}
|
|
if ( Shutter.real_position[i] * Shutter.direction[i] + stop_position_delta >= Shutter.target_position[i] * Shutter.direction[i] ) {
|
|
|
|
|
|
uint8_t cur_relay = Settings.shutter_startrelay[i] + (Shutter.direction[i] == 1 ? 0 : 1) ;
|
|
int16_t missing_steps;
|
|
|
|
switch (Shutter.mode) {
|
|
case SHT_PULSE_OPEN__PULSE_CLOSE:
|
|
|
|
if (SRC_PULSETIMER == last_source || SRC_SHUTTER == last_source || SRC_WEBGUI == last_source) {
|
|
ExecuteCommandPower(cur_relay, 1, SRC_SHUTTER);
|
|
} else {
|
|
last_source = SRC_SHUTTER;
|
|
}
|
|
break;
|
|
case SHT_OFF_ON__OPEN_CLOSE_STEPPER:
|
|
missing_steps = ((Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) - RtcSettings.pulse_counter[i];
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Remain steps %d, counter %d, freq %d"), missing_steps, RtcSettings.pulse_counter[i] ,Shutter.pwm_frequency);
|
|
Shutter.accelerator[i] = 0;
|
|
Shutter.pwm_frequency = Shutter.pwm_frequency > 250 ? 250 : Shutter.pwm_frequency;
|
|
analogWriteFreq(Shutter.pwm_frequency);
|
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
|
Shutter.pwm_frequency = 0;
|
|
analogWriteFreq(Shutter.pwm_frequency);
|
|
while (RtcSettings.pulse_counter[i] < (uint32_t)(Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) {
|
|
delay(1);
|
|
}
|
|
analogWrite(pin[GPIO_PWM1+i], 0);
|
|
Shutter.real_position[i] = ShutterCounterBasedPosition(i);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:Real %d, pulsecount %d, start %d"), Shutter.real_position[i],RtcSettings.pulse_counter[i], Shutter.start_position[i]);
|
|
|
|
if ((1 << (Settings.shutter_startrelay[i]-1)) & power) {
|
|
ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER);
|
|
ExecuteCommandPower(Settings.shutter_startrelay[i]+1, 0, SRC_SHUTTER);
|
|
}
|
|
break;
|
|
case SHT_OFF_ON__OPEN_CLOSE:
|
|
if ((1 << (Settings.shutter_startrelay[i]-1)) & power) {
|
|
ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER);
|
|
ExecuteCommandPower(Settings.shutter_startrelay[i]+1, 0, SRC_SHUTTER);
|
|
}
|
|
break;
|
|
case SHT_OFF_OPEN__OFF_CLOSE:
|
|
|
|
if ((1 << (cur_relay-1)) & power) {
|
|
|
|
ExecuteCommandPower(cur_relay, 0, SRC_SHUTTER);
|
|
}
|
|
break;
|
|
}
|
|
ShutterLimitRealAndTargetPositions(i);
|
|
Settings.shutter_position[i] = ShutterRealToPercentPosition(Shutter.real_position[i], i);
|
|
|
|
ShutterLogPos(i);
|
|
|
|
Shutter.start_position[i] = Shutter.real_position[i];
|
|
|
|
|
|
snprintf_P(scommand, sizeof(scommand),PSTR(D_SHUTTER "%d"), i+1);
|
|
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
|
|
Response_P("%d", (Settings.shutter_options[i] & 1) ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]);
|
|
MqttPublish(stopic, Settings.flag.mqtt_power_retain);
|
|
|
|
Shutter.direction[i] = 0;
|
|
ShutterReportPosition(true);
|
|
XdrvRulesProcess();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ShutterState(uint32_t device)
|
|
{
|
|
device--;
|
|
device &= 3;
|
|
return (Settings.flag3.shutter_mode &&
|
|
(Shutter.mask & (1 << (Settings.shutter_startrelay[device]-1))) );
|
|
}
|
|
|
|
void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos)
|
|
{
|
|
|
|
if ( ( (1 == direction) && ((Shutter.open_max[i] - Shutter.real_position[i]) / 100 <= 2) )
|
|
|| ( (-1 == direction) && (Shutter.real_position[i] / Shutter.close_velocity[i] <= 2)) ) {
|
|
Shutter.skip_relay_change = 1;
|
|
} else {
|
|
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) {
|
|
Shutter.pwm_frequency = 0;
|
|
analogWriteFreq(Shutter.pwm_frequency);
|
|
analogWrite(pin[GPIO_PWM1+i], 0);
|
|
|
|
if (pin[GPIO_CNTR1+i] < 99) {
|
|
RtcSettings.pulse_counter[i] = 0;
|
|
}
|
|
Shutter.accelerator[i] = Shutter.max_pwm_frequency / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Ramp up: %d"), Shutter.accelerator[i]);
|
|
}
|
|
Shutter.target_position[i] = target_pos;
|
|
Shutter.start_position[i] = Shutter.real_position[i];
|
|
Shutter.time[i] = 0;
|
|
Shutter.skip_relay_change = 0;
|
|
Shutter.direction[i] = direction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void ShutterWaitForMotorStop(uint32_t i)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Wait for Motorstop.."));
|
|
if ((SHT_OFF_ON__OPEN_CLOSE == Shutter.mode) || (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode)) {
|
|
if (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode) {
|
|
|
|
while (Shutter.pwm_frequency > 0) {
|
|
Shutter.accelerator[i] = 0;
|
|
Shutter.pwm_frequency = tmax(Shutter.pwm_frequency-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0);
|
|
analogWriteFreq(Shutter.pwm_frequency);
|
|
analogWrite(pin[GPIO_PWM1+i], 50);
|
|
delay(50);
|
|
}
|
|
analogWrite(pin[GPIO_PWM1+i], 0);
|
|
Shutter.real_position[i] = ShutterCounterBasedPosition(i);
|
|
} else {
|
|
ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER);
|
|
delay(MOTOR_STOP_TIME);
|
|
}
|
|
} else {
|
|
delay(MOTOR_STOP_TIME);
|
|
}
|
|
}
|
|
|
|
int32_t ShutterCounterBasedPosition(uint32_t i)
|
|
{
|
|
return ((int32_t)RtcSettings.pulse_counter[i]*Shutter.direction[i]*2000 / Shutter.max_pwm_frequency)+Shutter.start_position[i];
|
|
}
|
|
|
|
void ShutterRelayChanged(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
char stemp1[10];
|
|
|
|
for (uint32_t i = 0; i < shutters_present; i++) {
|
|
power_t powerstate_local = (power >> (Settings.shutter_startrelay[i] -1)) & 3;
|
|
|
|
uint8 manual_relays_changed = ((Shutter.switched_relay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ;
|
|
|
|
if (manual_relays_changed) {
|
|
|
|
ShutterLimitRealAndTargetPositions(i);
|
|
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE || Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) {
|
|
ShutterWaitForMotorStop(i);
|
|
switch (powerstate_local) {
|
|
case 1:
|
|
ShutterStartInit(i, 1, Shutter.open_max[i]);
|
|
break;
|
|
case 3:
|
|
ShutterStartInit(i, -1, 0);
|
|
break;
|
|
default:
|
|
|
|
Shutter.target_position[i] = Shutter.real_position[i];
|
|
}
|
|
} else {
|
|
if (Shutter.direction[i] != 0 && (!powerstate_local || (powerstate_local && Shutter.mode == SHT_PULSE_OPEN__PULSE_CLOSE))) {
|
|
Shutter.target_position[i] = Shutter.real_position[i];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor. Target: %ld, source: %s, powerstate_local %ld, Shutter.switched_relay %d, manual change %d"), i+1, Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,Shutter.switched_relay,manual_relays_changed);
|
|
} else {
|
|
last_source = SRC_SHUTTER;
|
|
if (powerstate_local == 2) {
|
|
|
|
ShutterWaitForMotorStop(i);
|
|
ShutterStartInit(i, -1, 0);
|
|
} else {
|
|
|
|
ShutterWaitForMotorStop(i);
|
|
ShutterStartInit(i, 1, Shutter.open_max[i]);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Target: %ld, powerstatelocal %d"), i+1, Shutter.target_position[i], powerstate_local);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ShutterButtonIsSimultaneousHold(uint32_t button_index, uint32_t shutter_index) {
|
|
|
|
uint32 min_shutterbutton_hold_timer = -1;
|
|
for (uint32_t i = 0; i < MAX_KEYS; i++) {
|
|
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (Button.hold_timer[i] < min_shutterbutton_hold_timer))
|
|
min_shutterbutton_hold_timer = Button.hold_timer[i];
|
|
}
|
|
return (min_shutterbutton_hold_timer > (Button.hold_timer[button_index]>>1));
|
|
}
|
|
|
|
void ShutterButtonHandler(void)
|
|
{
|
|
uint8_t buttonState = SHT_NOT_PRESSED;
|
|
uint8_t button = XdrvMailbox.payload;
|
|
uint8_t press_index;
|
|
uint32_t button_index = XdrvMailbox.index;
|
|
uint8_t shutter_index = Settings.shutter_button[button_index] & 0x03;
|
|
|
|
uint16_t loops_per_second = 1000 / Settings.button_debounce;
|
|
|
|
if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) {
|
|
if (Settings.flag.button_single) {
|
|
buttonState = SHT_PRESSED_MULTI;
|
|
press_index = 1;
|
|
} else {
|
|
if ((Shutter.direction[shutter_index]) && (Button.press_counter[button_index]==0)) {
|
|
buttonState = SHT_PRESSED_IMMEDIATE;
|
|
press_index = 1;
|
|
Button.press_counter[button_index] = 99;
|
|
} else
|
|
Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1;
|
|
Button.window_timer[button_index] = loops_per_second / 2;
|
|
}
|
|
blinks = 201;
|
|
}
|
|
|
|
if (NOT_PRESSED == button) {
|
|
Button.hold_timer[button_index] = 0;
|
|
} else {
|
|
Button.hold_timer[button_index]++;
|
|
if (!Settings.flag.button_single) {
|
|
if (Settings.param[P_HOLD_IGNORE] > 0) {
|
|
if (Button.hold_timer[button_index] > loops_per_second * Settings.param[P_HOLD_IGNORE] / 10) {
|
|
Button.hold_timer[button_index] = 0;
|
|
Button.press_counter[button_index] = 0;
|
|
}
|
|
}
|
|
if ((Button.press_counter[button_index]<99) && (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10)) {
|
|
|
|
if (ShutterButtonIsSimultaneousHold(button_index, shutter_index)) {
|
|
|
|
for (uint32_t i = 0; i < MAX_KEYS; i++)
|
|
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index))
|
|
Button.press_counter[i] = 99;
|
|
press_index = 0;
|
|
buttonState = SHT_PRESSED_HOLD_SIMULTANEOUS;
|
|
}
|
|
if (Button.press_counter[button_index]<99) {
|
|
press_index = 0;
|
|
buttonState = SHT_PRESSED_HOLD;
|
|
}
|
|
Button.press_counter[button_index] = 0;
|
|
}
|
|
if ((Button.press_counter[button_index]==0) && (Button.hold_timer[button_index] == loops_per_second * IMMINENT_RESET_FACTOR * Settings.param[P_HOLD_TIME] / 10)) {
|
|
|
|
if (ShutterButtonIsSimultaneousHold(button_index, shutter_index)) {
|
|
|
|
press_index = 0;
|
|
buttonState = SHT_PRESSED_EXT_HOLD_SIMULTANEOUS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Settings.flag.button_single) {
|
|
if (Button.window_timer[button_index]) {
|
|
Button.window_timer[button_index]--;
|
|
} else {
|
|
if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0)) {
|
|
if (Button.press_counter[button_index]<99) {
|
|
|
|
uint32 min_shutterbutton_press_counter = -1;
|
|
for (uint32_t i = 0; i < MAX_KEYS; i++) {
|
|
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (Button.press_counter[i] < min_shutterbutton_press_counter))
|
|
min_shutterbutton_press_counter = Button.press_counter[i];
|
|
}
|
|
if (min_shutterbutton_press_counter == Button.press_counter[button_index]) {
|
|
|
|
press_index = Button.press_counter[button_index];
|
|
for (uint32_t i = 0; i < MAX_KEYS; i++)
|
|
if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index))
|
|
Button.press_counter[i] = 99;
|
|
buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS;
|
|
}
|
|
if ((buttonState != SHT_PRESSED_MULTI_SIMULTANEOUS) && (Button.press_counter[button_index]<99)) {
|
|
|
|
press_index = Button.press_counter[button_index];
|
|
buttonState = SHT_PRESSED_MULTI;
|
|
}
|
|
}
|
|
Button.press_counter[button_index] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (buttonState != SHT_NOT_PRESSED) {
|
|
if (buttonState == SHT_PRESSED_MULTI_SIMULTANEOUS) {
|
|
if ((press_index>=5) && (press_index<=7) && (!Settings.flag.button_restrict)) {
|
|
|
|
char scmnd[20];
|
|
GetTextIndexed(scmnd, sizeof(scmnd), press_index -3, kCommands);
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
return;
|
|
}
|
|
} else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) {
|
|
|
|
if (!Settings.flag.button_restrict) {
|
|
char scmnd[20];
|
|
snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1"));
|
|
ExecuteCommand(scmnd, SRC_BUTTON);
|
|
return;
|
|
}
|
|
} else if (buttonState <= SHT_PRESSED_IMMEDIATE) {
|
|
if (Settings.shutter_startrelay[shutter_index] && Settings.shutter_startrelay[shutter_index] <9) {
|
|
uint8_t pos_press_index = (buttonState == SHT_PRESSED_HOLD) ? 3 : (press_index-1);
|
|
if (pos_press_index>3) pos_press_index=3;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: shutter %d, button %d = %d (single=1, double=2, tripple=3, hold=4)"), shutter_index+1, button_index+1, pos_press_index+1);
|
|
XdrvMailbox.index = shutter_index +1;
|
|
last_source = SRC_BUTTON;
|
|
XdrvMailbox.data_len = 0;
|
|
char databuf[1] = "";
|
|
XdrvMailbox.data = databuf;
|
|
XdrvMailbox.command = NULL;
|
|
if (buttonState == SHT_PRESSED_IMMEDIATE) {
|
|
XdrvMailbox.payload = XdrvMailbox.index;
|
|
CmndShutterStop();
|
|
}
|
|
else {
|
|
uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f;
|
|
if (position) {
|
|
if (Shutter.direction[shutter_index]) {
|
|
XdrvMailbox.payload = XdrvMailbox.index;
|
|
CmndShutterStop();
|
|
} else {
|
|
XdrvMailbox.payload = position = (position-1)<<1;
|
|
CmndShutterPosition();
|
|
if (Settings.shutter_button[button_index] & ((0x01<<26)<<pos_press_index)) {
|
|
|
|
char scommand[CMDSZ];
|
|
char stopic[TOPSZ];
|
|
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
|
|
if ((i==shutter_index) || (Settings.shutter_button[button_index] & (0x01<<30))) {
|
|
snprintf_P(scommand, sizeof(scommand),PSTR("ShutterPosition%d"), i+1);
|
|
GetGroupTopic_P(stopic, scommand);
|
|
Response_P("%d", position);
|
|
MqttPublish(stopic, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Response_P(PSTR("{"));
|
|
ResponseAppend_P(JSON_SHUTTER_BUTTON, shutter_index+1, (buttonState <= SHT_PRESSED_IMMEDIATE) ? (button_index+1) : 0, press_index);
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER));
|
|
XdrvRulesProcess();
|
|
}
|
|
}
|
|
|
|
void ShutterSetPosition(uint32_t device, uint32_t position)
|
|
{
|
|
char svalue[32];
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION "%d %d"), device, position);
|
|
ExecuteCommand(svalue, SRC_IGNORE);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndShutterOpen(void)
|
|
{
|
|
|
|
if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) {
|
|
XdrvMailbox.index = XdrvMailbox.payload;
|
|
}
|
|
XdrvMailbox.payload = 100;
|
|
last_source = SRC_WEBGUI;
|
|
CmndShutterPosition();
|
|
}
|
|
|
|
void CmndShutterClose(void)
|
|
{
|
|
|
|
if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) {
|
|
XdrvMailbox.index = XdrvMailbox.payload;
|
|
}
|
|
XdrvMailbox.payload = 0;
|
|
XdrvMailbox.data_len = 0;
|
|
last_source = SRC_WEBGUI;
|
|
CmndShutterPosition();
|
|
}
|
|
|
|
void CmndShutterStop(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (!(Settings.shutter_options[XdrvMailbox.index-1] & 2)) {
|
|
if ((1 == XdrvMailbox.index) && (XdrvMailbox.payload != -99)) {
|
|
XdrvMailbox.index = XdrvMailbox.payload;
|
|
}
|
|
uint32_t i = XdrvMailbox.index -1;
|
|
if (Shutter.direction[i] != 0) {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Stop moving %d: dir: %d"), XdrvMailbox.index, Shutter.direction[i]);
|
|
|
|
int32_t temp_realpos = Shutter.start_position[i] + ( (Shutter.time[i]+10) * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i]));
|
|
XdrvMailbox.payload = ShutterRealToPercentPosition(temp_realpos, i);
|
|
|
|
last_source = SRC_WEBGUI;
|
|
CmndShutterPosition();
|
|
} else {
|
|
if (XdrvMailbox.command)
|
|
ResponseCmndDone();
|
|
}
|
|
} else {
|
|
if (XdrvMailbox.command)
|
|
ResponseCmndIdxChar("Locked");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndShutterPosition(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (!(Settings.shutter_options[XdrvMailbox.index-1] & 2)) {
|
|
uint32_t index = XdrvMailbox.index-1;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Pos. in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, last_source );
|
|
|
|
|
|
if ((XdrvMailbox.data_len > 1) && (XdrvMailbox.payload <= 0)) {
|
|
|
|
if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_UP) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_OPEN) || ((Shutter.direction[index]==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEUP))) {
|
|
CmndShutterOpen();
|
|
return;
|
|
}
|
|
if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_DOWN) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_CLOSE) || ((Shutter.direction[index]==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEDOWN))) {
|
|
CmndShutterClose();
|
|
return;
|
|
}
|
|
if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_STOP) || ((Shutter.direction[index]) && (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEUP) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEDOWN)))) {
|
|
XdrvMailbox.payload = -99;
|
|
CmndShutterStop();
|
|
return;
|
|
}
|
|
}
|
|
|
|
int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? 0 : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload);
|
|
|
|
target_pos_percent = ((Settings.shutter_options[index] & 1) && (SRC_WEBGUI != last_source)) ? 100 - target_pos_percent : target_pos_percent;
|
|
if (XdrvMailbox.payload != -99) {
|
|
|
|
Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, index);
|
|
Shutter.accelerator[index] = Shutter.max_pwm_frequency / ((Shutter.motordelay[index] > 0) ? Shutter.motordelay[index] : 1);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, real %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent);
|
|
}
|
|
if ( (target_pos_percent >= 0) && (target_pos_percent <= 100) && abs(Shutter.target_position[index] - Shutter.real_position[index] ) / Shutter.close_velocity[index] > 2) {
|
|
if (Settings.shutter_options[index] & 4) {
|
|
if (0 == target_pos_percent) Shutter.target_position[index] -= 1 * 2000;
|
|
if (100 == target_pos_percent) Shutter.target_position[index] += 1 * 2000;
|
|
}
|
|
int8_t new_shutterdirection = Shutter.real_position[index] < Shutter.target_position[index] ? 1 : -1;
|
|
if (Shutter.direction[index] == -new_shutterdirection) {
|
|
|
|
if (SHT_PULSE_OPEN__PULSE_CLOSE == Shutter.mode) {
|
|
|
|
ExecuteCommandPower(Settings.shutter_startrelay[index] + ((new_shutterdirection == 1) ? 0 : 1), 1, SRC_SHUTTER);
|
|
delay(100);
|
|
} else {
|
|
if (SHT_OFF_OPEN__OFF_CLOSE == Shutter.mode) {
|
|
ExecuteCommandPower(Settings.shutter_startrelay[index] + ((new_shutterdirection == 1) ? 1 : 0), 0, SRC_SHUTTER);
|
|
ShutterWaitForMotorStop(index);
|
|
}
|
|
}
|
|
}
|
|
if (Shutter.direction[index] != new_shutterdirection) {
|
|
if ((SHT_OFF_ON__OPEN_CLOSE == Shutter.mode) || (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode)) {
|
|
|
|
ShutterWaitForMotorStop(index);
|
|
ExecuteCommandPower(Settings.shutter_startrelay[index], 0, SRC_SHUTTER);
|
|
ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]);
|
|
if (Shutter.skip_relay_change == 0) {
|
|
|
|
ExecuteCommandPower(Settings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER);
|
|
|
|
ExecuteCommandPower(Settings.shutter_startrelay[index], 1, SRC_SHUTTER);
|
|
}
|
|
} else {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start in dir %d"), Shutter.direction[index]);
|
|
ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]);
|
|
if (Shutter.skip_relay_change == 0) {
|
|
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER);
|
|
}
|
|
|
|
}
|
|
Shutter.switched_relay = 0;
|
|
}
|
|
} else {
|
|
target_pos_percent = ShutterRealToPercentPosition(Shutter.real_position[index], index);
|
|
ShutterReportPosition(true);
|
|
}
|
|
XdrvMailbox.index = index +1;
|
|
if (XdrvMailbox.command)
|
|
ResponseCmndIdxNumber((Settings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent);
|
|
} else {
|
|
ShutterReportPosition(true);
|
|
if (XdrvMailbox.command)
|
|
ResponseCmndIdxChar("Locked");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndShutterOpenTime(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
Settings.shutter_opentime[XdrvMailbox.index -1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data));
|
|
ShutterInit();
|
|
}
|
|
char time_chr[10];
|
|
dtostrfd((float)(Settings.shutter_opentime[XdrvMailbox.index -1]) / 10, 1, time_chr);
|
|
ResponseCmndIdxChar(time_chr);
|
|
}
|
|
}
|
|
|
|
void CmndShutterCloseTime(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
Settings.shutter_closetime[XdrvMailbox.index -1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data));
|
|
ShutterInit();
|
|
}
|
|
char time_chr[10];
|
|
dtostrfd((float)(Settings.shutter_closetime[XdrvMailbox.index -1]) / 10, 1, time_chr);
|
|
ResponseCmndIdxChar(time_chr);
|
|
}
|
|
}
|
|
|
|
void CmndShutterMotorDelay(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
Settings.shutter_motordelay[XdrvMailbox.index -1] = (uint16_t)(steps_per_second * CharToFloat(XdrvMailbox.data));
|
|
ShutterInit();
|
|
}
|
|
char time_chr[10];
|
|
dtostrfd((float)(Settings.shutter_motordelay[XdrvMailbox.index -1]) / steps_per_second, 2, time_chr);
|
|
ResponseCmndIdxChar(time_chr);
|
|
}
|
|
}
|
|
|
|
void CmndShutterRelay(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 64)) {
|
|
Settings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
if (XdrvMailbox.payload > 0) {
|
|
Shutter.mask |= 3 << (XdrvMailbox.payload - 1);
|
|
} else {
|
|
Shutter.mask ^= 3 << (Settings.shutter_startrelay[XdrvMailbox.index -1] - 1);
|
|
}
|
|
Settings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
ShutterInit();
|
|
|
|
}
|
|
ResponseCmndIdxNumber(Settings.shutter_startrelay[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndShutterButton(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) {
|
|
uint32_t setting = 0;
|
|
# 915 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_27_shutter.ino"
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint32_t i = 0;
|
|
uint32_t button_index = 0;
|
|
bool done = false;
|
|
bool isShortCommand = false;
|
|
char *str_ptr;
|
|
|
|
char data_copy[strlen(XdrvMailbox.data) +1];
|
|
strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy));
|
|
|
|
for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < (1+4+4+1); str = strtok_r(nullptr, " ", &str_ptr), i++) {
|
|
int field;
|
|
if (str[0] == '-') {
|
|
field = -1;
|
|
} else {
|
|
field = atoi(str);
|
|
}
|
|
switch (i) {
|
|
case 0:
|
|
if ((field >= -1) && (field<=4)) {
|
|
button_index = (field<=0)?(-1):field;
|
|
done = (button_index==-1);
|
|
} else
|
|
done = true;
|
|
break;
|
|
case 1:
|
|
if (!strcmp_P(str, PSTR("up"))) {
|
|
setting |= (((100>>1)+1)<<2) | (((50>>1)+1)<<8) | (((75>>1)+1)<<14) | (((100>>1)+1)<<20);
|
|
isShortCommand = true;
|
|
break;
|
|
} else if (!strcmp_P(str, PSTR("down"))) {
|
|
setting |= (((0>>1)+1)<<2) | (((50>>1)+1)<<8) | (((25>>1)+1)<<14) | (((0>>1)+1)<<20);
|
|
isShortCommand = true;
|
|
break;
|
|
} else if (!strcmp_P(str, PSTR("updown"))) {
|
|
setting |= (((100>>1)+1)<<2) | (((0>>1)+1)<<8) | (((50>>1)+1)<<14);
|
|
isShortCommand = true;
|
|
break;
|
|
}
|
|
case 2:
|
|
if (isShortCommand) {
|
|
if ((field==1) && (setting & (0x3F<<(2+6*3))))
|
|
|
|
setting |= (0x3<<29);
|
|
done = true;
|
|
break;
|
|
}
|
|
case 3:
|
|
case 4:
|
|
if ((field >= -1) && (field<=100))
|
|
setting |= (((field>>1)+1)<<(i*6 + (2-6)));
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
case 9:
|
|
if (field==1)
|
|
setting |= (1<<(i + (26-5)));
|
|
break;
|
|
}
|
|
if (done) break;
|
|
}
|
|
|
|
if (button_index) {
|
|
if (button_index==-1) {
|
|
|
|
for (uint32_t i=0 ; i < MAX_KEYS ; i++)
|
|
if ((Settings.shutter_button[i]&0x3) == (XdrvMailbox.index-1))
|
|
Settings.shutter_button[i] = 0;
|
|
} else {
|
|
if (setting) {
|
|
|
|
setting |= (1<<31);
|
|
setting |= (XdrvMailbox.index-1) & 0x3;
|
|
}
|
|
Settings.shutter_button[button_index-1] = setting;
|
|
}
|
|
}
|
|
}
|
|
char setting_chr[30*MAX_KEYS] = "-", *setting_chr_ptr = setting_chr;
|
|
for (uint32_t i=0 ; i < MAX_KEYS ; i++) {
|
|
setting = Settings.shutter_button[i];
|
|
if ((setting&(1<<31)) && ((setting&0x3) == (XdrvMailbox.index-1))) {
|
|
if (*setting_chr_ptr == 0)
|
|
setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR("|"));
|
|
setting_chr_ptr += snprintf_P(setting_chr_ptr, 2, PSTR("%d"), i+1);
|
|
|
|
for (uint32_t j=0 ; j < 4 ; j++) {
|
|
int8_t pos = (((setting>> (2+6*j))&(0x3f))-1)<<1;
|
|
if (pos>=0)
|
|
setting_chr_ptr += snprintf_P(setting_chr_ptr, 5, PSTR(" %d"), pos);
|
|
else
|
|
setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" -"));
|
|
}
|
|
for (uint32_t j=0 ; j < 5 ; j++) {
|
|
bool mqtt = ((setting>>(26+j))&(0x01)!=0);
|
|
if (mqtt)
|
|
setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" 1"));
|
|
else
|
|
setting_chr_ptr += sprintf_P(setting_chr_ptr, PSTR(" -"));
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndIdxChar(setting_chr);
|
|
}
|
|
}
|
|
|
|
void CmndShutterSetHalfway(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
|
|
Settings.shutter_set50percent[XdrvMailbox.index -1] = (Settings.shutter_options[XdrvMailbox.index -1] & 1) ? 100 - XdrvMailbox.payload : XdrvMailbox.payload;
|
|
ShutterInit();
|
|
}
|
|
ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 1) ? 100 - Settings.shutter_set50percent[XdrvMailbox.index -1] : Settings.shutter_set50percent[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndShutterFrequency(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 20000)) {
|
|
Shutter.max_pwm_frequency = XdrvMailbox.payload;
|
|
if (shutters_present < 4) {
|
|
Settings.shuttercoeff[4][3] = Shutter.max_pwm_frequency;
|
|
}
|
|
ShutterInit();
|
|
ResponseCmndNumber(XdrvMailbox.payload);
|
|
} else {
|
|
ResponseCmndNumber(Shutter.max_pwm_frequency);
|
|
}
|
|
}
|
|
|
|
void CmndShutterSetClose(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
Shutter.real_position[XdrvMailbox.index -1] = 0;
|
|
ShutterStartInit(XdrvMailbox.index -1, 0, 0);
|
|
Settings.shutter_position[XdrvMailbox.index -1] = 0;
|
|
ResponseCmndIdxChar(D_CONFIGURATION_RESET);
|
|
}
|
|
}
|
|
|
|
void CmndShutterInvert(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.payload == 0) {
|
|
Settings.shutter_options[XdrvMailbox.index -1] &= ~(1);
|
|
} else if (XdrvMailbox.payload == 1) {
|
|
Settings.shutter_options[XdrvMailbox.index -1] |= (1);
|
|
}
|
|
ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 1) ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
void CmndShutterCalibration(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
uint32_t i = 0;
|
|
char *str_ptr;
|
|
|
|
char data_copy[strlen(XdrvMailbox.data) +1];
|
|
strncpy(data_copy, XdrvMailbox.data, sizeof(data_copy));
|
|
|
|
for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < 5; str = strtok_r(nullptr, " ", &str_ptr), i++) {
|
|
int field = atoi(str);
|
|
|
|
|
|
if ((field <= 0) || (field > 30000) || ( (i>0) && (field <= messwerte[i-1]) ) ) {
|
|
break;
|
|
}
|
|
messwerte[i] = field;
|
|
}
|
|
for (i = 0; i < 5; i++) {
|
|
Settings.shuttercoeff[i][XdrvMailbox.index -1] = SHT_DIV_ROUND((uint32_t)messwerte[i] * 1000, messwerte[4]);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Settings.shuttercoeff: %d, i: %d, value: %d, messwert %d"), i,XdrvMailbox.index -1,Settings.shuttercoeff[i][XdrvMailbox.index -1], messwerte[i]);
|
|
}
|
|
ShutterInit();
|
|
ResponseCmndIdxChar(XdrvMailbox.data);
|
|
} else {
|
|
char setting_chr[30] = "0";
|
|
snprintf_P(setting_chr, sizeof(setting_chr), PSTR("%d %d %d %d %d"), Settings.shuttercoeff[0][XdrvMailbox.index -1], Settings.shuttercoeff[1][XdrvMailbox.index -1], Settings.shuttercoeff[2][XdrvMailbox.index -1], Settings.shuttercoeff[3][XdrvMailbox.index -1], Settings.shuttercoeff[4][XdrvMailbox.index -1]);
|
|
ResponseCmndIdxChar(setting_chr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmndShutterLock(void) {
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.payload == 0) {
|
|
Settings.shutter_options[XdrvMailbox.index -1] &= ~(2);
|
|
} else if (XdrvMailbox.payload == 1) {
|
|
Settings.shutter_options[XdrvMailbox.index -1] |= (2);
|
|
}
|
|
ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 2) ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
void CmndShutterEnableEndStopTime(void) {
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
|
|
if (XdrvMailbox.payload == 0) {
|
|
Settings.shutter_options[XdrvMailbox.index -1] &= ~(4);
|
|
} else if (XdrvMailbox.payload == 1) {
|
|
Settings.shutter_options[XdrvMailbox.index -1] |= (4);
|
|
}
|
|
ResponseCmndIdxNumber((Settings.shutter_options[XdrvMailbox.index -1] & 4) ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv27(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Settings.flag3.shutter_mode) {
|
|
switch (function) {
|
|
case FUNC_PRE_INIT:
|
|
ShutterInit();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
ShutterUpdatePosition();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
|
|
ShutterReportPosition(false);
|
|
break;
|
|
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kShutterCommands, ShutterCommand);
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
for (uint8_t i = 0; i < shutters_present; i++) {
|
|
uint8_t position = (Settings.shutter_options[i] & 1) ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i];
|
|
ResponseAppend_P(",");
|
|
ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, Shutter.direction[i]);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzSensor(DZ_SHUTTER, position);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
case FUNC_SET_POWER:
|
|
char stemp1[10];
|
|
|
|
Shutter.switched_relay = XdrvMailbox.index ^ Shutter.old_power;
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Switched relay: %d by %s"), Shutter.switched_relay,GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource));
|
|
ShutterRelayChanged();
|
|
Shutter.old_power = XdrvMailbox.index;
|
|
break;
|
|
case FUNC_SET_DEVICE_POWER:
|
|
if (Shutter.skip_relay_change ) {
|
|
uint8_t i;
|
|
for (i = 0; i < devices_present; i++) {
|
|
if (Shutter.switched_relay &1) {
|
|
break;
|
|
}
|
|
Shutter.switched_relay >>= 1;
|
|
}
|
|
|
|
result = true;
|
|
Shutter.skip_relay_change = 0;
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Skipping switch off relay %d"),i);
|
|
ExecuteCommandPower(i+1, 0, SRC_SHUTTER);
|
|
}
|
|
break;
|
|
case FUNC_BUTTON_PRESSED:
|
|
if (Settings.shutter_button[XdrvMailbox.index] & (1<<31)) {
|
|
ShutterButtonHandler();
|
|
result = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_28_pcf8574.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_28_pcf8574.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_PCF8574
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_28 28
|
|
#define XI2C_02 2
|
|
|
|
#define PCF8574_ADDR1 0x20
|
|
#define PCF8574_ADDR2 0x38
|
|
|
|
struct PCF8574 {
|
|
int error;
|
|
uint8_t pin[64];
|
|
uint8_t address[MAX_PCF8574];
|
|
uint8_t pin_mask[MAX_PCF8574] = { 0 };
|
|
uint8_t max_connected_ports = 0;
|
|
uint8_t max_devices = 0;
|
|
char stype[9];
|
|
bool type = false;
|
|
} Pcf8574;
|
|
|
|
void Pcf8574SwitchRelay(void)
|
|
{
|
|
for (uint32_t i = 0; i < devices_present; i++) {
|
|
uint8_t relay_state = bitRead(XdrvMailbox.index, i);
|
|
|
|
|
|
|
|
if (Pcf8574.max_devices > 0 && Pcf8574.pin[i] < 99) {
|
|
uint8_t board = Pcf8574.pin[i]>>3;
|
|
uint8_t oldpinmask = Pcf8574.pin_mask[board];
|
|
uint8_t _val = bitRead(rel_inverted, i) ? !relay_state : relay_state;
|
|
|
|
|
|
|
|
if (_val) {
|
|
Pcf8574.pin_mask[board] |= _val << (Pcf8574.pin[i]&0x7);
|
|
} else {
|
|
Pcf8574.pin_mask[board] &= ~(1 << (Pcf8574.pin[i]&0x7));
|
|
}
|
|
if (oldpinmask != Pcf8574.pin_mask[board]) {
|
|
Wire.beginTransmission(Pcf8574.address[board]);
|
|
Wire.write(Pcf8574.pin_mask[board]);
|
|
Pcf8574.error = Wire.endTransmission();
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void Pcf8574Init(void)
|
|
{
|
|
uint8_t pcf8574_address = PCF8574_ADDR1;
|
|
while ((Pcf8574.max_devices < MAX_PCF8574) && (pcf8574_address < PCF8574_ADDR2 +8)) {
|
|
|
|
|
|
|
|
if (I2cSetDevice(pcf8574_address)) {
|
|
Pcf8574.type = true;
|
|
|
|
Pcf8574.address[Pcf8574.max_devices] = pcf8574_address;
|
|
Pcf8574.max_devices++;
|
|
|
|
strcpy(Pcf8574.stype, "PCF8574");
|
|
if (pcf8574_address >= PCF8574_ADDR2) {
|
|
strcpy(Pcf8574.stype, "PCF8574A");
|
|
}
|
|
I2cSetActiveFound(pcf8574_address, Pcf8574.stype);
|
|
}
|
|
|
|
pcf8574_address++;
|
|
#ifdef USE_MCP230xx_ADDR
|
|
if (USE_MCP230xx_ADDR == pcf8574_address) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Addr: 0x%x reserved for MCP320xx, skipping PCF8574 probe"), pcf8574_address);
|
|
pcf8574_address++;
|
|
}
|
|
#endif
|
|
if ((PCF8574_ADDR1 +7) == pcf8574_address) {
|
|
pcf8574_address = PCF8574_ADDR2 +1;
|
|
}
|
|
}
|
|
if (Pcf8574.type) {
|
|
for (uint32_t i = 0; i < sizeof(Pcf8574.pin); i++) {
|
|
Pcf8574.pin[i] = 99;
|
|
}
|
|
devices_present = devices_present - Pcf8574.max_connected_ports;
|
|
Pcf8574.max_connected_ports = 0;
|
|
for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) {
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Device %d config 0x%02x"), idx +1, Settings.pcf8574_config[idx]);
|
|
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
uint8_t _result = Settings.pcf8574_config[idx] >> i &1;
|
|
|
|
if (_result > 0) {
|
|
Pcf8574.pin[devices_present] = i + 8 * idx;
|
|
bitWrite(rel_inverted, devices_present, Settings.flag3.pcf8574_ports_inverted);
|
|
devices_present++;
|
|
Pcf8574.max_connected_ports++;
|
|
}
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), Pcf8574.max_devices, Pcf8574.max_connected_ports);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
#define WEB_HANDLE_PCF8574 "pcf"
|
|
|
|
const char HTTP_BTN_MENU_PCF8574[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_PCF8574 "' method='get'><button>" D_CONFIGURE_PCF8574 "</button></form></p>";
|
|
|
|
const char HTTP_FORM_I2C_PCF8574_1[] PROGMEM =
|
|
"<fieldset><legend><b> " D_PCF8574_PARAMETERS " </b></legend>"
|
|
"<form method='get' action='" WEB_HANDLE_PCF8574 "'>"
|
|
"<p><input id='b1' name='b1' type='checkbox'%s><b>" D_INVERT_PORTS "</b></p><hr/>";
|
|
|
|
const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM =
|
|
"<tr><td><b>" D_DEVICE " %d " D_PORT " %d</b></td><td style='width:100px'><select id='i2cs%d' name='i2cs%d'>"
|
|
"<option%s value='0'>" D_DEVICE_INPUT "</option>"
|
|
"<option%s value='1'>" D_DEVICE_OUTPUT "</option>"
|
|
"</select></td></tr>";
|
|
|
|
void HandlePcf8574(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_PCF8574));
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
Pcf8574SaveSettings();
|
|
WebRestart(1);
|
|
return;
|
|
}
|
|
|
|
WSContentStart_P(D_CONFIGURE_PCF8574);
|
|
WSContentSendStyle();
|
|
WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? " checked" : "");
|
|
WSContentSend_P(HTTP_TABLE100);
|
|
for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) {
|
|
for (uint32_t idx2 = 0; idx2 < 8; idx2++) {
|
|
uint8_t helper = 1 << idx2;
|
|
WSContentSend_P(HTTP_FORM_I2C_PCF8574_2,
|
|
idx +1, idx2,
|
|
idx2 + 8*idx,
|
|
idx2 + 8*idx,
|
|
((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? " selected " : " ",
|
|
((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? " selected " : " "
|
|
);
|
|
}
|
|
}
|
|
WSContentSend_P(PSTR("</table>"));
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void Pcf8574SaveSettings(void)
|
|
{
|
|
char stemp[7];
|
|
char tmp[100];
|
|
|
|
|
|
|
|
Settings.flag3.pcf8574_ports_inverted = WebServer->hasArg("b1");
|
|
for (byte idx = 0; idx < Pcf8574.max_devices; idx++) {
|
|
byte count=0;
|
|
byte n = Settings.pcf8574_config[idx];
|
|
while(n!=0) {
|
|
n = n&(n-1);
|
|
count++;
|
|
}
|
|
if (count <= devices_present) {
|
|
devices_present = devices_present - count;
|
|
}
|
|
for (byte i = 0; i < 8; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("i2cs%d"), i+8*idx);
|
|
WebGetArg(stemp, tmp, sizeof(tmp));
|
|
byte _value = (!strlen(tmp)) ? 0 : atoi(tmp);
|
|
if (_value) {
|
|
Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] | 1 << i;
|
|
devices_present++;
|
|
Pcf8574.max_connected_ports++;
|
|
} else {
|
|
Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] & ~(1 << i );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv28(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_02)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_PRE_INIT == function) {
|
|
Pcf8574Init();
|
|
}
|
|
else if (Pcf8574.type) {
|
|
switch (function) {
|
|
case FUNC_SET_POWER:
|
|
Pcf8574SwitchRelay();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_PCF8574);
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/" WEB_HANDLE_PCF8574, HandlePcf8574);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino"
|
|
#ifdef USE_DEEPSLEEP
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_29_deepsleep.ino"
|
|
#define XDRV_29 29
|
|
|
|
#define D_PRFX_DEEPSLEEP "DeepSleep"
|
|
#define D_CMND_DEEPSLEEP_TIME "Time"
|
|
|
|
const uint32_t DEEPSLEEP_MAX = 10 * 366 * 24 * 60 * 60;
|
|
const uint32_t DEEPSLEEP_MAX_CYCLE = 60 * 60;
|
|
const uint32_t DEEPSLEEP_MIN_TIME = 5;
|
|
const uint32_t DEEPSLEEP_START_COUNTDOWN = 4;
|
|
|
|
const char kDeepsleepCommands[] PROGMEM = D_PRFX_DEEPSLEEP "|"
|
|
D_CMND_DEEPSLEEP_TIME ;
|
|
|
|
void (* const DeepsleepCommand[])(void) PROGMEM = {
|
|
&CmndDeepsleepTime };
|
|
|
|
uint32_t deepsleep_sleeptime = 0;
|
|
uint8_t deepsleep_flag = 0;
|
|
|
|
bool DeepSleepEnabled(void)
|
|
{
|
|
if ((Settings.deepsleep < 10) || (Settings.deepsleep > DEEPSLEEP_MAX)) {
|
|
Settings.deepsleep = 0;
|
|
return false;
|
|
}
|
|
|
|
if (pin[GPIO_DEEPSLEEP] < 99) {
|
|
pinMode(pin[GPIO_DEEPSLEEP], INPUT_PULLUP);
|
|
return (digitalRead(pin[GPIO_DEEPSLEEP]));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DeepSleepReInit(void)
|
|
{
|
|
if ((ResetReason() == REASON_DEEP_SLEEP_AWAKE) && DeepSleepEnabled()) {
|
|
if ((RtcSettings.ultradeepsleep > DEEPSLEEP_MAX_CYCLE) && (RtcSettings.ultradeepsleep < 1700000000)) {
|
|
|
|
RtcSettings.ultradeepsleep = RtcSettings.ultradeepsleep - DEEPSLEEP_MAX_CYCLE;
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Remain DeepSleep %d"), RtcSettings.ultradeepsleep);
|
|
RtcSettingsSave();
|
|
RtcRebootReset();
|
|
ESP.deepSleep(100 * RtcSettings.deepsleep_slip * (DEEPSLEEP_MAX_CYCLE < RtcSettings.ultradeepsleep ? DEEPSLEEP_MAX_CYCLE : RtcSettings.ultradeepsleep), WAKE_RF_DEFAULT);
|
|
yield();
|
|
|
|
}
|
|
}
|
|
|
|
RtcSettings.ultradeepsleep = 0;
|
|
}
|
|
|
|
void DeepSleepPrepare(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
if ((RtcSettings.nextwakeup == 0) ||
|
|
(RtcSettings.deepsleep_slip < 9000) ||
|
|
(RtcSettings.deepsleep_slip > 11000) ||
|
|
(RtcSettings.nextwakeup > (UtcTime() + Settings.deepsleep))) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Reset wrong settings wakeup: %ld, slip %ld"), RtcSettings.nextwakeup, RtcSettings.deepsleep_slip );
|
|
RtcSettings.nextwakeup = 0;
|
|
RtcSettings.deepsleep_slip = 10000;
|
|
}
|
|
|
|
|
|
|
|
int16_t timeslip = (int16_t)(RtcSettings.nextwakeup + millis() / 1000 - UtcTime()) * 10;
|
|
|
|
|
|
|
|
timeslip = (timeslip < -(int32_t)Settings.deepsleep) ? 0 : (timeslip > (int32_t)Settings.deepsleep) ? 0 : 1;
|
|
if (timeslip) {
|
|
RtcSettings.deepsleep_slip = (Settings.deepsleep + RtcSettings.nextwakeup - UtcTime()) * RtcSettings.deepsleep_slip / tmax((Settings.deepsleep - (millis() / 1000)),5);
|
|
|
|
RtcSettings.deepsleep_slip = tmin(tmax(RtcSettings.deepsleep_slip, 9000), 11000);
|
|
RtcSettings.nextwakeup += Settings.deepsleep;
|
|
}
|
|
|
|
|
|
|
|
if (RtcSettings.nextwakeup <= (UtcTime() - DEEPSLEEP_MIN_TIME)) {
|
|
|
|
RtcSettings.nextwakeup += (((UtcTime() + DEEPSLEEP_MIN_TIME - RtcSettings.nextwakeup) / Settings.deepsleep) + 1) * Settings.deepsleep;
|
|
}
|
|
|
|
String dt = GetDT(RtcSettings.nextwakeup + LocalTime() - UtcTime());
|
|
|
|
|
|
deepsleep_sleeptime = tmin((uint32_t)DEEPSLEEP_MAX_CYCLE ,RtcSettings.nextwakeup - UtcTime());
|
|
|
|
|
|
Response_P(PSTR("{\"" D_PRFX_DEEPSLEEP "\":{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%d}}"), (char*)dt.c_str(), RtcSettings.nextwakeup);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_STATUS));
|
|
|
|
|
|
|
|
}
|
|
|
|
void DeepSleepStart(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "Sleeping"));
|
|
|
|
WifiShutdown();
|
|
RtcSettings.ultradeepsleep = RtcSettings.nextwakeup - UtcTime();
|
|
RtcSettingsSave();
|
|
|
|
ESP.deepSleep(100 * RtcSettings.deepsleep_slip * deepsleep_sleeptime);
|
|
yield();
|
|
}
|
|
|
|
void DeepSleepEverySecond(void)
|
|
{
|
|
if (!deepsleep_flag) { return; }
|
|
|
|
if (DeepSleepEnabled()) {
|
|
if (DEEPSLEEP_START_COUNTDOWN == deepsleep_flag) {
|
|
SettingsSaveAll();
|
|
DeepSleepPrepare();
|
|
}
|
|
deepsleep_flag--;
|
|
if (deepsleep_flag <= 0) {
|
|
DeepSleepStart();
|
|
}
|
|
} else {
|
|
deepsleep_flag = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndDeepsleepTime(void)
|
|
{
|
|
if ((0 == XdrvMailbox.payload) ||
|
|
((XdrvMailbox.payload > 10) && (XdrvMailbox.payload < DEEPSLEEP_MAX))) {
|
|
Settings.deepsleep = XdrvMailbox.payload;
|
|
RtcSettings.nextwakeup = 0;
|
|
deepsleep_flag = (0 == XdrvMailbox.payload) ? 0 : DEEPSLEEP_START_COUNTDOWN;
|
|
if (deepsleep_flag) {
|
|
if (!Settings.tele_period) {
|
|
Settings.tele_period = TELE_PERIOD;
|
|
}
|
|
}
|
|
}
|
|
Response_P(S_JSON_COMMAND_NVALUE, XdrvMailbox.command, Settings.deepsleep);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv29(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DeepSleepEverySecond();
|
|
break;
|
|
case FUNC_AFTER_TELEPERIOD:
|
|
if (DeepSleepEnabled() && !deepsleep_flag && (Settings.tele_period == 10 || Settings.tele_period == 300 || UpTime() > Settings.tele_period)) {
|
|
deepsleep_flag = DEEPSLEEP_START_COUNTDOWN;
|
|
}
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kDeepsleepCommands, DeepsleepCommand);
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
DeepSleepReInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_EXS_DIMMER
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_30 30
|
|
|
|
#define EXS_GATE_1_ON 0x20
|
|
#define EXS_GATE_1_OFF 0x21
|
|
#define EXS_DIMM_1_ON 0x22
|
|
#define EXS_DIMM_1_OFF 0x23
|
|
#define EXS_DIMM_1_TBL 0x24
|
|
#define EXS_DIMM_1_VAL 0x25
|
|
#define EXS_GATE_2_ON 0x30
|
|
#define EXS_GATE_2_OFF 0x31
|
|
#define EXS_DIMM_2_ON 0x32
|
|
#define EXS_DIMM_2_OFF 0x33
|
|
#define EXS_DIMM_2_TBL 0x34
|
|
#define EXS_DIMM_2_VAL 0x35
|
|
#define EXS_GATES_ON 0x40
|
|
#define EXS_GATES_OFF 0x41
|
|
#define EXS_DIMMS_ON 0x50
|
|
#define EXS_DIMMS_OFF 0x51
|
|
#define EXS_CH_LOCK 0x60
|
|
#define EXS_GET_VALUES 0xFA
|
|
#define EXS_WRITE_EE 0xFC
|
|
#define EXS_READ_EE 0xFD
|
|
#define EXS_GET_VERSION 0xFE
|
|
#define EXS_RESET 0xFF
|
|
|
|
#define EXS_BUFFER_SIZE 256
|
|
#define EXS_ACK_TIMEOUT 200
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *ExsSerial = nullptr;
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t on = 0;
|
|
uint8_t bright_tbl = 0;
|
|
uint8_t dimm = 0;
|
|
uint8_t impuls_start = 0;
|
|
uint32_t impuls_len = 0;
|
|
} CHANNEL;
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t version_major = 0;
|
|
uint8_t version_minor = 0;
|
|
CHANNEL channel[2];
|
|
uint8_t gate_lock = 0;
|
|
} DIMMER;
|
|
|
|
struct EXS
|
|
{
|
|
uint8_t *buffer = nullptr;
|
|
int byte_counter = 0;
|
|
int cmd_status = 0;
|
|
uint8_t power = 0;
|
|
uint8_t dimm[2] = {0, 0};
|
|
DIMMER dimmer;
|
|
} Exs;
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t crc8(const uint8_t *p, uint8_t len)
|
|
{
|
|
const uint8_t table[] = {
|
|
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
|
|
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D};
|
|
|
|
const uint8_t table_rev[] = {
|
|
0x00, 0x70, 0xE0, 0x90, 0xC1, 0xB1, 0x21, 0x51,
|
|
0x83, 0xF3, 0x63, 0x13, 0x42, 0x32, 0xA2, 0xD2};
|
|
|
|
uint8_t offset;
|
|
uint8_t temp, crc8_temp;
|
|
uint8_t crc8 = 0;
|
|
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
temp = *(p + i);
|
|
offset = temp ^ crc8;
|
|
offset >>= 4;
|
|
crc8_temp = crc8 & 0x0f;
|
|
crc8 = crc8_temp ^ table_rev[offset];
|
|
offset = crc8 ^ temp;
|
|
offset &= 0x0f;
|
|
crc8_temp = crc8 & 0xf0;
|
|
crc8 = crc8_temp ^ table[offset];
|
|
}
|
|
return crc8 ^ 0x55;
|
|
}
|
|
|
|
void ExsSerialSend(const uint8_t data[] = nullptr, uint16_t len = 0)
|
|
{
|
|
int retries = 3;
|
|
char rc;
|
|
|
|
#ifdef EXS_DEBUG
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("EXS: Tx Packet: \""));
|
|
for (uint32_t i = 0; i < len; i++)
|
|
{
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, data[i]);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data);
|
|
AddLog(LOG_LEVEL_DEBUG_MORE);
|
|
#endif
|
|
|
|
while (retries)
|
|
{
|
|
retries--;
|
|
|
|
ExsSerial->write(data, len);
|
|
ExsSerial->flush();
|
|
|
|
|
|
uint32_t snd_time = millis();
|
|
while ((TimePassedSince(snd_time) < EXS_ACK_TIMEOUT) &&
|
|
(!ExsSerial->available()))
|
|
;
|
|
|
|
if (!ExsSerial->available())
|
|
{
|
|
|
|
#ifdef EXS_DEBUG
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("ESX: serial send timeout"));
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
rc = ExsSerial->read();
|
|
if (rc == 0xFF)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ExsSendCmd(uint8_t cmd, uint8_t value)
|
|
{
|
|
uint8_t buffer[8];
|
|
uint16_t len;
|
|
|
|
buffer[0] = 0x7b;
|
|
buffer[3] = cmd;
|
|
|
|
switch (cmd)
|
|
{
|
|
case EXS_GATE_1_ON:
|
|
case EXS_GATE_1_OFF:
|
|
case EXS_DIMM_1_ON:
|
|
case EXS_DIMM_1_OFF:
|
|
case EXS_GATE_2_ON:
|
|
case EXS_GATE_2_OFF:
|
|
case EXS_DIMM_2_ON:
|
|
case EXS_DIMM_2_OFF:
|
|
case EXS_GATES_ON:
|
|
case EXS_GATES_OFF:
|
|
case EXS_DIMMS_ON:
|
|
case EXS_DIMMS_OFF:
|
|
case EXS_GET_VALUES:
|
|
case EXS_GET_VERSION:
|
|
case EXS_RESET:
|
|
buffer[2] = 1;
|
|
len = 4;
|
|
break;
|
|
|
|
case EXS_CH_LOCK:
|
|
case EXS_DIMM_1_TBL:
|
|
case EXS_DIMM_1_VAL:
|
|
case EXS_DIMM_2_TBL:
|
|
case EXS_DIMM_2_VAL:
|
|
buffer[2] = 2;
|
|
buffer[4] = value;
|
|
len = 5;
|
|
break;
|
|
}
|
|
buffer[1] = crc8(&buffer[3], buffer[2]);
|
|
|
|
ExsSerialSend(buffer, len);
|
|
}
|
|
|
|
uint8_t ExsSetPower(uint8_t device, uint8_t power)
|
|
{
|
|
Exs.dimmer.channel[device].dimm = power;
|
|
ExsSendCmd(EXS_DIMM_1_ON + 0x10 * device + power ^ 1, 0);
|
|
}
|
|
|
|
uint8_t ExsSetBri(uint8_t device, uint8_t bri)
|
|
{
|
|
Exs.dimmer.channel[device].bright_tbl = bri;
|
|
ExsSendCmd(EXS_DIMM_1_TBL + 0x10 * device, bri);
|
|
}
|
|
|
|
uint8_t ExsSyncState(uint8_t device)
|
|
{
|
|
#ifdef EXS_DEBUG
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Channel %d Power Want %d, Is %d"),
|
|
device, bitRead(Exs.power, device), Exs.dimmer.channel[device].dimm);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Set Channel %d Brightness Want %d, Is %d"),
|
|
device, Exs.dimm[device], Exs.dimmer.channel[device].bright_tbl);
|
|
#endif
|
|
|
|
if (bitRead(Exs.power, device) &&
|
|
Exs.dimm[device] != Exs.dimmer.channel[device].bright_tbl) {
|
|
ExsSetBri(device, Exs.dimm[device]);
|
|
}
|
|
|
|
if (!Exs.dimm[device]) {
|
|
Exs.dimmer.channel[device].dimm = 0;
|
|
} else if (Exs.dimmer.channel[device].dimm != bitRead(Exs.power, device)) {
|
|
ExsSetPower(device, bitRead(Exs.power, device));
|
|
}
|
|
}
|
|
|
|
bool ExsSyncState()
|
|
{
|
|
#ifdef EXS_DEBUG
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Serial %p, Cmd %d"), ExsSerial, Exs.cmd_status);
|
|
#endif
|
|
|
|
if (!ExsSerial || Exs.cmd_status != 0)
|
|
return false;
|
|
|
|
ExsSyncState(0);
|
|
ExsSyncState(1);
|
|
}
|
|
|
|
void ExsDebugState()
|
|
{
|
|
#ifdef EXS_DEBUG
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: MCU v%d.%d, c0: On:%d,Dim:%d,Tbl:%d(%d%%), c1: On:%d,Dim:%d,Tbl:%d(%d%%), ChLock: %d"),
|
|
Exs.dimmer.version_major, Exs.dimmer.version_minor,
|
|
Exs.dimmer.channel[0].on, Exs.dimmer.channel[0].dimm,
|
|
Exs.dimmer.channel[0].bright_tbl,
|
|
changeUIntScale(Exs.dimmer.channel[0].bright_tbl, 0, 255, 0, 100),
|
|
Exs.dimmer.channel[1].on, Exs.dimmer.channel[1].dimm,
|
|
Exs.dimmer.channel[1].bright_tbl,
|
|
changeUIntScale(Exs.dimmer.channel[1].bright_tbl, 0, 255, 0, 100),
|
|
Exs.dimmer.gate_lock);
|
|
#endif
|
|
}
|
|
|
|
void ExsPacketProcess(void)
|
|
{
|
|
uint8_t len = Exs.buffer[1];
|
|
uint8_t cmd = Exs.buffer[2];
|
|
|
|
switch (cmd)
|
|
{
|
|
case EXS_GET_VALUES:
|
|
# 294 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino"
|
|
if (len > 9)
|
|
{
|
|
Exs.dimmer.version_major = Exs.buffer[3];
|
|
Exs.dimmer.version_minor = Exs.buffer[4];
|
|
|
|
|
|
Exs.dimmer.channel[0].on = Exs.buffer[6];
|
|
Exs.dimmer.channel[0].dimm = Exs.buffer[6];
|
|
Exs.dimmer.channel[0].bright_tbl = Exs.buffer[7];
|
|
|
|
|
|
Exs.dimmer.channel[1].on = Exs.buffer[9];
|
|
Exs.dimmer.channel[1].dimm = Exs.buffer[9];
|
|
Exs.dimmer.channel[1].bright_tbl = Exs.buffer[10];
|
|
|
|
Exs.dimmer.gate_lock = Exs.buffer[11];
|
|
}
|
|
else
|
|
# 327 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_30_exs_dimmer.ino"
|
|
{
|
|
Exs.dimmer.version_major = 1;
|
|
Exs.dimmer.version_minor = 0;
|
|
|
|
|
|
Exs.dimmer.channel[0].on = Exs.buffer[4] - 48;
|
|
Exs.dimmer.channel[0].dimm = Exs.buffer[4] - 48;
|
|
Exs.dimmer.channel[0].bright_tbl = Exs.buffer[5] - 48;
|
|
|
|
|
|
Exs.dimmer.channel[1].on = Exs.buffer[7] - 48;
|
|
Exs.dimmer.channel[1].dimm = Exs.buffer[7] - 48;
|
|
Exs.dimmer.channel[1].bright_tbl = Exs.buffer[8] - 48;
|
|
|
|
Exs.dimmer.gate_lock = Exs.buffer[9] - 48;
|
|
}
|
|
|
|
ExsDebugState();
|
|
ExsSyncState();
|
|
ExsDebugState();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool ExsModuleSelected(void)
|
|
{
|
|
Settings.light_correction = 0;
|
|
Settings.flag.mqtt_serial = 0;
|
|
Settings.flag3.pwm_multi_channels = 1;
|
|
SetSeriallog(LOG_LEVEL_NONE);
|
|
|
|
devices_present = +2;
|
|
light_type = LT_SERIAL2;
|
|
return true;
|
|
}
|
|
|
|
bool ExsSetChannels(void)
|
|
{
|
|
#ifdef EXS_DEBUG
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("EXS: SetChannels: \""));
|
|
for (int i = 0; i < XdrvMailbox.data_len; i++)
|
|
{
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, ((uint8_t *)XdrvMailbox.data)[i]);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s\""), log_data);
|
|
AddLog(LOG_LEVEL_DEBUG_MORE);
|
|
#endif
|
|
|
|
Exs.dimm[0] = ((uint8_t *)XdrvMailbox.data)[0];
|
|
Exs.dimm[1] = ((uint8_t *)XdrvMailbox.data)[1];
|
|
return ExsSyncState();
|
|
}
|
|
|
|
bool ExsSetPower(void)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Set Power, Device %d, Power 0x%02x"),
|
|
active_device, XdrvMailbox.index);
|
|
|
|
Exs.power = XdrvMailbox.index;
|
|
return ExsSyncState();
|
|
}
|
|
|
|
void EsxMcuStart(void)
|
|
{
|
|
int retries = 3;
|
|
|
|
#ifdef EXS_DEBUG
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EXS: Request MCU configuration, PIN %d to Low"), pin[GPIO_EXS_ENABLE]);
|
|
#endif
|
|
|
|
pinMode(pin[GPIO_EXS_ENABLE], OUTPUT);
|
|
digitalWrite(pin[GPIO_EXS_ENABLE], LOW);
|
|
|
|
delay(1);
|
|
|
|
while (ExsSerial->available())
|
|
{
|
|
|
|
ExsSerial->read();
|
|
}
|
|
}
|
|
|
|
void ExsInit(void)
|
|
{
|
|
#ifdef EXS_DEBUG
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Starting Tx %d Rx %d"), pin[GPIO_TXD], pin[GPIO_RXD]);
|
|
#endif
|
|
|
|
Exs.buffer = (uint8_t *)malloc(EXS_BUFFER_SIZE);
|
|
if (Exs.buffer != nullptr)
|
|
{
|
|
ExsSerial = new TasmotaSerial(pin[GPIO_RXD], pin[GPIO_TXD], 2);
|
|
if (ExsSerial->begin(9600))
|
|
{
|
|
if (ExsSerial->hardwareSerial())
|
|
{
|
|
ClaimSerial();
|
|
}
|
|
ExsSerial->flush();
|
|
EsxMcuStart();
|
|
ExsSendCmd(EXS_CH_LOCK, 0);
|
|
ExsSendCmd(EXS_GET_VALUES, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExsSerialInput(void)
|
|
{
|
|
while (ExsSerial->available())
|
|
{
|
|
yield();
|
|
uint8_t serial_in_byte = ExsSerial->read();
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("EXS: Serial In Byte 0x%02x"), serial_in_byte);
|
|
|
|
if (Exs.cmd_status == 0 &&
|
|
serial_in_byte == 0x7B)
|
|
{
|
|
Exs.cmd_status = 1;
|
|
Exs.byte_counter = 0;
|
|
}
|
|
else if (Exs.byte_counter >= EXS_BUFFER_SIZE)
|
|
{
|
|
Exs.cmd_status = 0;
|
|
}
|
|
else if (Exs.cmd_status == 1)
|
|
{
|
|
Exs.buffer[Exs.byte_counter++] = serial_in_byte;
|
|
|
|
if (Exs.byte_counter > 2 && Exs.byte_counter == Exs.buffer[1] + 2)
|
|
{
|
|
uint8_t crc = crc8(&Exs.buffer[2], Exs.buffer[1]);
|
|
|
|
|
|
Exs.cmd_status = 0;
|
|
|
|
#ifdef EXS_DEBUG
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("EXS: RX Packet: \""));
|
|
for (uint32_t i = 0; i < Exs.byte_counter; i++)
|
|
{
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%02x"), log_data, Exs.buffer[i]);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s\", CRC: 0x%02x"), log_data, crc);
|
|
AddLog(LOG_LEVEL_DEBUG_MORE);
|
|
#endif
|
|
|
|
if (Exs.buffer[0] == crc)
|
|
{
|
|
ExsSerial->write(0xFF);
|
|
ExsPacketProcess();
|
|
}
|
|
else
|
|
{
|
|
ExsSerial->write(0x00);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef EXS_MCU_CMNDS
|
|
|
|
#define D_PRFX_EXS "Exs"
|
|
#define D_CMND_EXS_DIMM "Dimm"
|
|
#define D_CMND_EXS_DIMM_TBL "DimmTbl"
|
|
#define D_CMND_EXS_DIMM_VAL "DimmVal"
|
|
#define D_CMND_EXS_DIMMS "Dimms"
|
|
#define D_CMND_EXS_CH_LOCK "ChLock"
|
|
#define D_CMND_EXS_STATE "State"
|
|
|
|
const char kExsCommands[] PROGMEM = D_PRFX_EXS "|"
|
|
D_CMND_EXS_DIMM "|" D_CMND_EXS_DIMM_TBL "|" D_CMND_EXS_DIMM_VAL "|"
|
|
D_CMND_EXS_DIMMS "|" D_CMND_EXS_CH_LOCK "|"
|
|
D_CMND_EXS_STATE;
|
|
|
|
void (* const ExsCommand[])(void) PROGMEM = {
|
|
&CmndExsDimm, &CmndExsDimmTbl, &CmndExsDimmVal,
|
|
&CmndExsDimms, &CmndExsChLock,
|
|
&CmndExsState };
|
|
|
|
void CmndExsDimm(void)
|
|
{
|
|
if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) &&
|
|
(XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1)) {
|
|
ExsSendCmd(EXS_DIMM_1_ON + 0x10 * (XdrvMailbox.index - 1) +
|
|
XdrvMailbox.payload ^ 1, 0);
|
|
}
|
|
CmndExsState();
|
|
}
|
|
|
|
void CmndExsDimmTbl(void)
|
|
{
|
|
if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) &&
|
|
(XdrvMailbox.payload > 0 || XdrvMailbox.payload <= 255)) {
|
|
ExsSendCmd(EXS_DIMM_1_TBL + 0x10 * (XdrvMailbox.index - 1),
|
|
XdrvMailbox.payload);
|
|
}
|
|
CmndExsState();
|
|
}
|
|
|
|
void CmndExsDimmVal(void)
|
|
{
|
|
if ((XdrvMailbox.index == 1 || XdrvMailbox.index == 2) &&
|
|
(XdrvMailbox.payload > 0 || XdrvMailbox.payload <= 255)) {
|
|
ExsSendCmd(EXS_DIMM_1_VAL + 0x10 * (XdrvMailbox.index - 1),
|
|
XdrvMailbox.payload);
|
|
}
|
|
CmndExsState();
|
|
}
|
|
|
|
void CmndExsDimms(void)
|
|
{
|
|
if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1) {
|
|
ExsSendCmd(EXS_DIMMS_ON + XdrvMailbox.payload ^ 1, 0);
|
|
}
|
|
CmndExsState();
|
|
}
|
|
|
|
void CmndExsChLock(void)
|
|
{
|
|
if (XdrvMailbox.payload == 0 || XdrvMailbox.payload == 1) {
|
|
ExsSendCmd(EXS_CH_LOCK, XdrvMailbox.payload);
|
|
}
|
|
CmndExsState();
|
|
}
|
|
|
|
void CmndExsState(void)
|
|
{
|
|
ExsSendCmd(EXS_GET_VALUES, 0);
|
|
|
|
|
|
uint32_t snd_time = millis();
|
|
while ((TimePassedSince(snd_time) < EXS_ACK_TIMEOUT) &&
|
|
(!ExsSerial->available()))
|
|
;
|
|
ExsSerialInput();
|
|
|
|
Response_P(PSTR("{\"" D_CMND_EXS_STATE "\":{"));
|
|
ResponseAppend_P(PSTR("\"McuVersion\":\"%d.%d\","
|
|
"\"Channels\":["),
|
|
Exs.dimmer.version_major, Exs.dimmer.version_minor);
|
|
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
if (i != 0) {
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
ResponseAppend_P(PSTR("{\"On\":\"%d\","
|
|
"\"BrightProz\":\"%d\","
|
|
"\"BrightTab\":\"%d\","
|
|
"\"Dimm\":\"%d\"}"),
|
|
Exs.dimmer.channel[i].on,
|
|
changeUIntScale(Exs.dimmer.channel[i].bright_tbl, 0, 255, 0, 100),
|
|
Exs.dimmer.channel[i].bright_tbl,
|
|
Exs.dimmer.channel[i].dimm);
|
|
}
|
|
ResponseAppend_P(PSTR("],"));
|
|
ResponseAppend_P(PSTR("\"GateLock\":\"%d\""), Exs.dimmer.gate_lock);
|
|
ResponseJsonEndEnd();
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv30(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (EXS_DIMMER == my_module_type)
|
|
{
|
|
switch (function)
|
|
{
|
|
case FUNC_LOOP:
|
|
if (ExsSerial)
|
|
ExsSerialInput();
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
result = ExsModuleSelected();
|
|
break;
|
|
case FUNC_INIT:
|
|
ExsInit();
|
|
break;
|
|
case FUNC_SET_DEVICE_POWER:
|
|
result = ExsSetPower();
|
|
break;
|
|
case FUNC_SET_CHANNELS:
|
|
result = ExsSetChannels();
|
|
break;
|
|
#ifdef EXS_MCU_CMNDS
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kExsCommands, ExsCommand);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_31_tasmota_slave.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_31_tasmota_slave.ino"
|
|
#ifdef USE_TASMOTA_SLAVE
|
|
|
|
|
|
|
|
|
|
#define XDRV_31 31
|
|
|
|
#define CONST_STK_CRC_EOP 0x20
|
|
|
|
#define CMND_STK_GET_SYNC 0x30
|
|
#define CMND_STK_SET_DEVICE 0x42
|
|
#define CMND_STK_SET_DEVICE_EXT 0x45
|
|
#define CMND_STK_ENTER_PROGMODE 0x50
|
|
#define CMND_STK_LEAVE_PROGMODE 0x51
|
|
#define CMND_STK_LOAD_ADDRESS 0x55
|
|
#define CMND_STK_PROG_PAGE 0x64
|
|
|
|
|
|
|
|
|
|
|
|
#define CMND_START 0xFC
|
|
#define CMND_END 0xFD
|
|
|
|
#define CMND_FEATURES 0x01
|
|
#define CMND_JSON 0x02
|
|
#define CMND_FUNC_EVERY_SECOND 0x03
|
|
#define CMND_FUNC_EVERY_100_MSECOND 0x04
|
|
#define CMND_SLAVE_SEND 0x05
|
|
#define CMND_PUBLISH_TELE 0x06
|
|
#define CMND_EXECUTE_CMND 0x07
|
|
|
|
#define PARAM_DATA_START 0xFE
|
|
#define PARAM_DATA_END 0xFF
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
|
|
|
|
|
|
|
|
class SimpleHexParse {
|
|
public:
|
|
SimpleHexParse(void);
|
|
uint8_t parseLine(char *hexline);
|
|
uint8_t ptr_l = 0;
|
|
uint8_t ptr_h = 0;
|
|
bool PageIsReady = false;
|
|
bool firstrun = true;
|
|
bool EndOfFile = false;
|
|
uint8_t FlashPage[128];
|
|
uint8_t FlashPageIdx = 0;
|
|
uint8_t layoverBuffer[16];
|
|
uint8_t layoverIdx = 0;
|
|
uint8_t getByte(char *hexline, uint8_t idx);
|
|
};
|
|
|
|
SimpleHexParse::SimpleHexParse(void)
|
|
{
|
|
|
|
}
|
|
|
|
uint8_t SimpleHexParse::parseLine(char *hexline)
|
|
{
|
|
if (layoverIdx) {
|
|
memcpy(&FlashPage[0], &layoverBuffer[0], layoverIdx);
|
|
FlashPageIdx = layoverIdx;
|
|
layoverIdx = 0;
|
|
}
|
|
uint8_t len = getByte(hexline, 1);
|
|
uint8_t addr_h = getByte(hexline, 2);
|
|
uint8_t addr_l = getByte(hexline, 3);
|
|
uint8_t rectype = getByte(hexline, 4);
|
|
for (uint8_t idx = 0; idx < len; idx++) {
|
|
if (FlashPageIdx < 128) {
|
|
FlashPage[FlashPageIdx] = getByte(hexline, idx+5);
|
|
FlashPageIdx++;
|
|
} else {
|
|
layoverBuffer[layoverIdx] = getByte(hexline, idx+5);
|
|
layoverIdx++;
|
|
}
|
|
}
|
|
if (1 == rectype) {
|
|
EndOfFile = true;
|
|
while (FlashPageIdx < 128) {
|
|
FlashPage[FlashPageIdx] = 0xFF;
|
|
FlashPageIdx++;
|
|
}
|
|
}
|
|
if (FlashPageIdx == 128) {
|
|
if (firstrun) {
|
|
firstrun = false;
|
|
} else {
|
|
ptr_l += 0x40;
|
|
if (ptr_l == 0) {
|
|
ptr_l = 0;
|
|
ptr_h++;
|
|
}
|
|
}
|
|
firstrun = false;
|
|
PageIsReady = true;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t SimpleHexParse::getByte(char* hexline, uint8_t idx)
|
|
{
|
|
char buff[3];
|
|
buff[3] = '\0';
|
|
memcpy(&buff, &hexline[(idx*2)-1], 2);
|
|
return strtol(buff, 0, 16);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct TSLAVE {
|
|
uint32_t spi_hex_size = 0;
|
|
uint32_t spi_sector_counter = 0;
|
|
uint8_t spi_sector_cursor = 0;
|
|
uint8_t inverted = LOW;
|
|
bool type = false;
|
|
bool flashing = false;
|
|
bool SerialEnabled = false;
|
|
uint8_t waitstate = 0;
|
|
bool unsupported = false;
|
|
} TSlave;
|
|
|
|
typedef union {
|
|
uint32_t data;
|
|
struct {
|
|
uint32_t func_json_append : 1;
|
|
uint32_t func_every_second : 1;
|
|
uint32_t func_every_100_msecond : 1;
|
|
uint32_t func_slave_send : 1;
|
|
uint32_t spare4 : 1;
|
|
uint32_t spare5 : 1;
|
|
uint32_t spare6 : 1;
|
|
uint32_t spare7 : 1;
|
|
uint32_t spare8 : 1;
|
|
uint32_t spare9 : 1;
|
|
uint32_t spare10 : 1;
|
|
uint32_t spare11 : 1;
|
|
uint32_t spare12 : 1;
|
|
uint32_t spare13 : 1;
|
|
uint32_t spare14 : 1;
|
|
uint32_t spare15 : 1;
|
|
uint32_t spare16 : 1;
|
|
uint32_t spare17 : 1;
|
|
uint32_t spare18 : 1;
|
|
uint32_t spare19 : 1;
|
|
uint32_t spare20 : 1;
|
|
uint32_t spare21 : 1;
|
|
uint32_t spare22 : 1;
|
|
uint32_t spare23 : 1;
|
|
uint32_t spare24 : 1;
|
|
uint32_t spare25 : 1;
|
|
uint32_t spare26 : 1;
|
|
uint32_t spare27 : 1;
|
|
uint32_t spare28 : 1;
|
|
uint32_t spare29 : 1;
|
|
uint32_t spare30 : 1;
|
|
uint32_t spare31 : 1;
|
|
};
|
|
} TSlaveFeatureCfg;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct TSLAVE_FEATURES {
|
|
uint32_t features_version;
|
|
TSlaveFeatureCfg features;
|
|
} TSlaveSettings;
|
|
|
|
struct TSLAVE_COMMAND {
|
|
uint8_t command;
|
|
uint8_t parameter;
|
|
uint8_t unused2;
|
|
uint8_t unused3;
|
|
} TSlaveCommand;
|
|
|
|
TasmotaSerial *TasmotaSlave_Serial;
|
|
|
|
uint32_t TasmotaSlave_FlashStart(void)
|
|
{
|
|
return (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 2;
|
|
}
|
|
|
|
uint8_t TasmotaSlave_UpdateInit(void)
|
|
{
|
|
TSlave.spi_hex_size = 0;
|
|
TSlave.spi_sector_counter = TasmotaSlave_FlashStart();
|
|
TSlave.spi_sector_cursor = 0;
|
|
return 0;
|
|
}
|
|
|
|
void TasmotaSlave_Reset(void)
|
|
{
|
|
if (TSlave.SerialEnabled) {
|
|
digitalWrite(pin[GPIO_TASMOTASLAVE_RST], !TSlave.inverted);
|
|
delay(1);
|
|
digitalWrite(pin[GPIO_TASMOTASLAVE_RST], TSlave.inverted);
|
|
delay(1);
|
|
digitalWrite(pin[GPIO_TASMOTASLAVE_RST], !TSlave.inverted);
|
|
delay(5);
|
|
}
|
|
}
|
|
|
|
uint8_t TasmotaSlave_waitForSerialData(int dataCount, int timeout)
|
|
{
|
|
int timer = 0;
|
|
while (timer < timeout) {
|
|
if (TasmotaSlave_Serial->available() >= dataCount) {
|
|
return 1;
|
|
}
|
|
delay(1);
|
|
timer++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t TasmotaSlave_sendBytes(uint8_t* bytes, int count)
|
|
{
|
|
TasmotaSlave_Serial->write(bytes, count);
|
|
TasmotaSlave_waitForSerialData(2, 250);
|
|
uint8_t sync = TasmotaSlave_Serial->read();
|
|
uint8_t ok = TasmotaSlave_Serial->read();
|
|
if ((sync == 0x14) && (ok == 0x10)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t TasmotaSlave_execCmd(uint8_t cmd)
|
|
{
|
|
uint8_t bytes[] = { cmd, CONST_STK_CRC_EOP };
|
|
return TasmotaSlave_sendBytes(bytes, 2);
|
|
}
|
|
|
|
uint8_t TasmotaSlave_execParam(uint8_t cmd, uint8_t* params, int count)
|
|
{
|
|
uint8_t bytes[32];
|
|
bytes[0] = cmd;
|
|
int i = 0;
|
|
while (i < count) {
|
|
bytes[i + 1] = params[i];
|
|
i++;
|
|
}
|
|
bytes[i + 1] = CONST_STK_CRC_EOP;
|
|
return TasmotaSlave_sendBytes(bytes, i + 2);
|
|
}
|
|
|
|
uint8_t TasmotaSlave_exitProgMode(void)
|
|
{
|
|
return TasmotaSlave_execCmd(CMND_STK_LEAVE_PROGMODE);
|
|
}
|
|
|
|
uint8_t TasmotaSlave_SetupFlash(void)
|
|
{
|
|
uint8_t ProgParams[] = {0x86, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x80, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00};
|
|
uint8_t ExtProgParams[] = {0x05, 0x04, 0xd7, 0xc2, 0x00};
|
|
TasmotaSlave_Serial->begin(USE_TASMOTA_SLAVE_FLASH_SPEED);
|
|
if (TasmotaSlave_Serial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
|
|
TasmotaSlave_Reset();
|
|
|
|
uint8_t timeout = 0;
|
|
uint8_t no_error = 0;
|
|
while (50 > timeout) {
|
|
if (TasmotaSlave_execCmd(CMND_STK_GET_SYNC)) {
|
|
timeout = 200;
|
|
no_error = 1;
|
|
}
|
|
timeout++;
|
|
delay(1);
|
|
}
|
|
if (no_error) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Found bootloader"));
|
|
} else {
|
|
no_error = 0;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Bootloader could not be found"));
|
|
}
|
|
if (no_error) {
|
|
if (TasmotaSlave_execParam(CMND_STK_SET_DEVICE, ProgParams, sizeof(ProgParams))) {
|
|
} else {
|
|
no_error = 0;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Could not configure device for programming (1)"));
|
|
}
|
|
}
|
|
if (no_error) {
|
|
if (TasmotaSlave_execParam(CMND_STK_SET_DEVICE_EXT, ExtProgParams, sizeof(ExtProgParams))) {
|
|
} else {
|
|
no_error = 0;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Could not configure device for programming (2)"));
|
|
}
|
|
}
|
|
if (no_error) {
|
|
if (TasmotaSlave_execCmd(CMND_STK_ENTER_PROGMODE)) {
|
|
} else {
|
|
no_error = 0;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Failed to put bootloader into programming mode"));
|
|
}
|
|
}
|
|
return no_error;
|
|
}
|
|
|
|
uint8_t TasmotaSlave_loadAddress(uint8_t adrHi, uint8_t adrLo)
|
|
{
|
|
uint8_t params[] = { adrLo, adrHi };
|
|
return TasmotaSlave_execParam(CMND_STK_LOAD_ADDRESS, params, sizeof(params));
|
|
}
|
|
|
|
void TasmotaSlave_FlashPage(uint8_t addr_h, uint8_t addr_l, uint8_t* data)
|
|
{
|
|
uint8_t Header[] = {CMND_STK_PROG_PAGE, 0x00, 0x80, 0x46};
|
|
TasmotaSlave_loadAddress(addr_h, addr_l);
|
|
TasmotaSlave_Serial->write(Header, 4);
|
|
for (int i = 0; i < 128; i++) {
|
|
TasmotaSlave_Serial->write(data[i]);
|
|
}
|
|
TasmotaSlave_Serial->write(CONST_STK_CRC_EOP);
|
|
TasmotaSlave_waitForSerialData(2, 250);
|
|
TasmotaSlave_Serial->read();
|
|
TasmotaSlave_Serial->read();
|
|
}
|
|
|
|
void TasmotaSlave_Flash(void)
|
|
{
|
|
bool reading = true;
|
|
uint32_t read = 0;
|
|
uint32_t processed = 0;
|
|
char thishexline[50];
|
|
uint8_t position = 0;
|
|
char* flash_buffer;
|
|
|
|
SimpleHexParse hexParse = SimpleHexParse();
|
|
|
|
if (!TasmotaSlave_SetupFlash()) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Flashing aborted!"));
|
|
TSlave.flashing = false;
|
|
restart_flag = 2;
|
|
return;
|
|
}
|
|
|
|
flash_buffer = new char[SPI_FLASH_SEC_SIZE];
|
|
uint32_t flash_start = TasmotaSlave_FlashStart() * SPI_FLASH_SEC_SIZE;
|
|
while (reading) {
|
|
ESP.flashRead(flash_start + read, (uint32_t*)flash_buffer, SPI_FLASH_SEC_SIZE);
|
|
read = read + SPI_FLASH_SEC_SIZE;
|
|
if (read >= TSlave.spi_hex_size) {
|
|
reading = false;
|
|
}
|
|
for (uint32_t ca = 0; ca < SPI_FLASH_SEC_SIZE; ca++) {
|
|
processed++;
|
|
if ((processed <= TSlave.spi_hex_size) && (!hexParse.EndOfFile)) {
|
|
if (':' == flash_buffer[ca]) {
|
|
position = 0;
|
|
}
|
|
if (0x0D == flash_buffer[ca]) {
|
|
thishexline[position] = 0;
|
|
hexParse.parseLine(thishexline);
|
|
if (hexParse.PageIsReady) {
|
|
TasmotaSlave_FlashPage(hexParse.ptr_h, hexParse.ptr_l, hexParse.FlashPage);
|
|
hexParse.PageIsReady = false;
|
|
hexParse.FlashPageIdx = 0;
|
|
}
|
|
} else {
|
|
if (0x0A != flash_buffer[ca]) {
|
|
thishexline[position] = flash_buffer[ca];
|
|
position++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TasmotaSlave_exitProgMode();
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TasmotaSlave: Flash done!"));
|
|
TSlave.flashing = false;
|
|
restart_flag = 2;
|
|
}
|
|
|
|
void TasmotaSlave_SetFlagFlashing(bool value)
|
|
{
|
|
TSlave.flashing = value;
|
|
}
|
|
|
|
bool TasmotaSlave_GetFlagFlashing(void)
|
|
{
|
|
return TSlave.flashing;
|
|
}
|
|
|
|
void TasmotaSlave_WriteBuffer(uint8_t *buf, size_t size)
|
|
{
|
|
if (0 == TSlave.spi_sector_cursor) {
|
|
ESP.flashEraseSector(TSlave.spi_sector_counter);
|
|
}
|
|
TSlave.spi_sector_cursor++;
|
|
ESP.flashWrite((TSlave.spi_sector_counter * SPI_FLASH_SEC_SIZE) + ((TSlave.spi_sector_cursor-1)*2048), (uint32_t*)buf, size);
|
|
TSlave.spi_hex_size = TSlave.spi_hex_size + size;
|
|
if (2 == TSlave.spi_sector_cursor) {
|
|
TSlave.spi_sector_cursor = 0;
|
|
TSlave.spi_sector_counter++;
|
|
}
|
|
}
|
|
|
|
void TasmotaSlave_Init(void)
|
|
{
|
|
if (TSlave.type) {
|
|
return;
|
|
}
|
|
if (10 > TSlave.waitstate) {
|
|
TSlave.waitstate++;
|
|
return;
|
|
}
|
|
if (!TSlave.SerialEnabled) {
|
|
if ((pin[GPIO_TASMOTASLAVE_RXD] < 99) && (pin[GPIO_TASMOTASLAVE_TXD] < 99) &&
|
|
((pin[GPIO_TASMOTASLAVE_RST] < 99) || (pin[GPIO_TASMOTASLAVE_RST_INV] < 99))) {
|
|
TasmotaSlave_Serial = new TasmotaSerial(pin[GPIO_TASMOTASLAVE_RXD], pin[GPIO_TASMOTASLAVE_TXD], 1, 0, 200);
|
|
if (TasmotaSlave_Serial->begin(USE_TASMOTA_SLAVE_SERIAL_SPEED)) {
|
|
if (TasmotaSlave_Serial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
TasmotaSlave_Serial->setTimeout(50);
|
|
if (pin[GPIO_TASMOTASLAVE_RST_INV] < 99) {
|
|
pin[GPIO_TASMOTASLAVE_RST] = pin[GPIO_TASMOTASLAVE_RST_INV];
|
|
pin[GPIO_TASMOTASLAVE_RST_INV] = 99;
|
|
TSlave.inverted = HIGH;
|
|
}
|
|
pinMode(pin[GPIO_TASMOTASLAVE_RST], OUTPUT);
|
|
TSlave.SerialEnabled = true;
|
|
TasmotaSlave_Reset();
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Enabled"));
|
|
}
|
|
}
|
|
}
|
|
if (TSlave.SerialEnabled) {
|
|
TasmotaSlave_sendCmnd(CMND_FEATURES, 0);
|
|
char buffer[32];
|
|
TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_START), buffer, sizeof(buffer));
|
|
uint8_t len = TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_END), buffer, sizeof(buffer));
|
|
memcpy(&TSlaveSettings, &buffer, sizeof(TSlaveSettings));
|
|
if (20191129 == TSlaveSettings.features_version) {
|
|
TSlave.type = true;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Version %u"), TSlaveSettings.features_version);
|
|
} else {
|
|
if ((!TSlave.unsupported) && (TSlaveSettings.features_version > 0)) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Tasmota Slave Version %u not supported!"), TSlaveSettings.features_version);
|
|
TSlave.unsupported = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TasmotaSlave_Show(void)
|
|
{
|
|
if ((TSlave.type) && (TSlaveSettings.features.func_json_append)) {
|
|
char buffer[100];
|
|
TasmotaSlave_sendCmnd(CMND_JSON, 0);
|
|
TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_START), buffer, sizeof(buffer)-1);
|
|
uint8_t len = TasmotaSlave_Serial->readBytesUntil(char(PARAM_DATA_END), buffer, sizeof(buffer)-1);
|
|
buffer[len] = '\0';
|
|
ResponseAppend_P(PSTR(",\"TasmotaSlave\":%s"), buffer);
|
|
}
|
|
}
|
|
|
|
void TasmotaSlave_sendCmnd(uint8_t cmnd, uint8_t param)
|
|
{
|
|
TSlaveCommand.command = cmnd;
|
|
TSlaveCommand.parameter = param;
|
|
char buffer[sizeof(TSlaveCommand)+2];
|
|
buffer[0] = CMND_START;
|
|
memcpy(&buffer[1], &TSlaveCommand, sizeof(TSlaveCommand));
|
|
buffer[sizeof(TSlaveCommand)+1] = CMND_END;
|
|
for (uint8_t ca = 0; ca < sizeof(buffer); ca++) {
|
|
TasmotaSlave_Serial->write(buffer[ca]);
|
|
}
|
|
}
|
|
|
|
#define D_PRFX_SLAVE "Slave"
|
|
#define D_CMND_SLAVE_RESET "Reset"
|
|
#define D_CMND_SLAVE_SEND "Send"
|
|
|
|
const char kTasmotaSlaveCommands[] PROGMEM = D_PRFX_SLAVE "|"
|
|
D_CMND_SLAVE_RESET "|" D_CMND_SLAVE_SEND;
|
|
|
|
void (* const TasmotaSlaveCommand[])(void) PROGMEM = {
|
|
&CmndTasmotaSlaveReset, &CmndTasmotaSlaveSend };
|
|
|
|
void CmndTasmotaSlaveReset(void)
|
|
{
|
|
TasmotaSlave_Reset();
|
|
TSlave.type = false;
|
|
TSlave.waitstate = 7;
|
|
TSlave.unsupported = false;
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndTasmotaSlaveSend(void)
|
|
{
|
|
if (0 < XdrvMailbox.data_len) {
|
|
TasmotaSlave_sendCmnd(CMND_SLAVE_SEND, XdrvMailbox.data_len);
|
|
TasmotaSlave_Serial->write(char(PARAM_DATA_START));
|
|
for (uint8_t idx = 0; idx < XdrvMailbox.data_len; idx++) {
|
|
TasmotaSlave_Serial->write(XdrvMailbox.data[idx]);
|
|
}
|
|
TasmotaSlave_Serial->write(char(PARAM_DATA_END));
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void TasmotaSlave_ProcessIn(void)
|
|
{
|
|
uint8_t cmnd = TasmotaSlave_Serial->read();
|
|
switch (cmnd) {
|
|
case CMND_START:
|
|
TasmotaSlave_waitForSerialData(sizeof(TSlaveCommand),50);
|
|
uint8_t buffer[sizeof(TSlaveCommand)];
|
|
for (uint8_t idx = 0; idx < sizeof(TSlaveCommand); idx++) {
|
|
buffer[idx] = TasmotaSlave_Serial->read();
|
|
}
|
|
TasmotaSlave_Serial->read();
|
|
memcpy(&TSlaveCommand, &buffer, sizeof(TSlaveCommand));
|
|
char inbuf[TSlaveCommand.parameter+1];
|
|
TasmotaSlave_waitForSerialData(TSlaveCommand.parameter, 50);
|
|
TasmotaSlave_Serial->read();
|
|
for (uint8_t idx = 0; idx < TSlaveCommand.parameter; idx++) {
|
|
inbuf[idx] = TasmotaSlave_Serial->read();
|
|
}
|
|
TasmotaSlave_Serial->read();
|
|
inbuf[TSlaveCommand.parameter] = '\0';
|
|
|
|
if (CMND_PUBLISH_TELE == TSlaveCommand.command) {
|
|
Response_P(PSTR("{\"TasmotaSlave\":"));
|
|
ResponseAppend_P("%s", inbuf);
|
|
ResponseJsonEnd();
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
|
|
XdrvRulesProcess();
|
|
}
|
|
if (CMND_EXECUTE_CMND == TSlaveCommand.command) {
|
|
ExecuteCommand(inbuf, SRC_IGNORE);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv31(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_100_MSECOND:
|
|
if (TSlave.type) {
|
|
if (TasmotaSlave_Serial->available()) {
|
|
TasmotaSlave_ProcessIn();
|
|
}
|
|
if (TSlaveSettings.features.func_every_100_msecond) {
|
|
TasmotaSlave_sendCmnd(CMND_FUNC_EVERY_100_MSECOND, 0);
|
|
}
|
|
}
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
if ((TSlave.type) && (TSlaveSettings.features.func_every_second)) {
|
|
TasmotaSlave_sendCmnd(CMND_FUNC_EVERY_SECOND, 0);
|
|
}
|
|
TasmotaSlave_Init();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
if ((TSlave.type) && (TSlaveSettings.features.func_json_append)) {
|
|
TasmotaSlave_Show();
|
|
}
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kTasmotaSlaveCommands, TasmotaSlaveCommand);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_32_hotplug.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_32_hotplug.ino"
|
|
#ifdef USE_HOTPLUG
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_32 32
|
|
|
|
const uint32_t HOTPLUG_MAX = 254;
|
|
|
|
const char kHotPlugCommands[] PROGMEM = "|"
|
|
D_CMND_HOTPLUG;
|
|
|
|
void (* const HotPlugCommand[])(void) PROGMEM = {
|
|
&CmndHotPlugTime };
|
|
|
|
struct {
|
|
|
|
bool enabled = false;
|
|
uint8_t timeout = 0;
|
|
} Hotplug;
|
|
|
|
void HotPlugInit(void)
|
|
{
|
|
|
|
if (Settings.hotplug_scan == 0xFF) { Settings.hotplug_scan = 0; }
|
|
if (Settings.hotplug_scan != 0) {
|
|
Hotplug.enabled = true;
|
|
Hotplug.timeout = 1;
|
|
} else
|
|
Hotplug.enabled = false;
|
|
}
|
|
|
|
void HotPlugEverySecond(void)
|
|
{
|
|
if (Hotplug.enabled) {
|
|
if (Hotplug.timeout == 0) {
|
|
XsnsCall(FUNC_HOTPLUG_SCAN);
|
|
Hotplug.timeout = Settings.hotplug_scan;
|
|
}
|
|
Hotplug.timeout--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndHotPlugTime(void)
|
|
{
|
|
if (XdrvMailbox.payload <= HOTPLUG_MAX) {
|
|
Settings.hotplug_scan = XdrvMailbox.payload;
|
|
HotPlugInit();
|
|
}
|
|
ResponseCmndNumber(Settings.hotplug_scan);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv32(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
HotPlugEverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kHotPlugCommands, HotPlugCommand);
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
HotPlugInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_33_nrf24l01.ino"
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_33_nrf24l01.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_NRF24
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_33 33
|
|
|
|
#define MOSI 13
|
|
#define MISO 12
|
|
#define SCK 14
|
|
|
|
#include <SPI.h>
|
|
#include <RF24.h>
|
|
|
|
const char NRF24type[] PROGMEM = "NRF24";
|
|
|
|
const char HTTP_NRF24[] PROGMEM =
|
|
"{s}%sL01%c: " "{m}started{e}";
|
|
|
|
struct {
|
|
uint8_t chipType = 0;
|
|
} NRF24;
|
|
|
|
|
|
|
|
RF24 NRF24radio;
|
|
|
|
bool NRF24initRadio()
|
|
{
|
|
NRF24radio.begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_DC]);
|
|
NRF24radio.powerUp();
|
|
|
|
if(NRF24radio.isChipConnected()){
|
|
DEBUG_DRIVER_LOG(PSTR("NRF24 chip connected"));
|
|
return true;
|
|
}
|
|
DEBUG_DRIVER_LOG(PSTR("NRF24 chip NOT !!!! connected"));
|
|
return false;
|
|
}
|
|
|
|
bool NRF24Detect(void)
|
|
{
|
|
if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_DC]<99)){
|
|
SPI.pins(SCK,MOSI,MISO,-1);
|
|
if(NRF24initRadio()){
|
|
NRF24.chipType = 32;
|
|
AddLog_P2(LOG_LEVEL_INFO,PSTR("NRF24L01 initialized"));
|
|
if(NRF24radio.isPVariant()){
|
|
NRF24.chipType = 43;
|
|
AddLog_P2(LOG_LEVEL_INFO,PSTR("NRF24L01+ detected"));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv33(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
result = NRF24Detect();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
|
|
# 22 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
|
|
#ifdef DEBUG_THEO
|
|
#ifndef USE_DEBUG_DRIVER
|
|
#define USE_DEBUG_DRIVER
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef USE_DEBUG_DRIVER
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XDRV_99 99
|
|
|
|
#ifndef CPU_LOAD_CHECK
|
|
#define CPU_LOAD_CHECK 1
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#define D_CMND_CFGDUMP "CfgDump"
|
|
#define D_CMND_CFGPEEK "CfgPeek"
|
|
#define D_CMND_CFGPOKE "CfgPoke"
|
|
#define D_CMND_CFGSHOW "CfgShow"
|
|
#define D_CMND_CFGXOR "CfgXor"
|
|
#define D_CMND_CPUCHECK "CpuChk"
|
|
#define D_CMND_EXCEPTION "Exception"
|
|
#define D_CMND_FLASHDUMP "FlashDump"
|
|
#define D_CMND_FLASHMODE "FlashMode"
|
|
#define D_CMND_FREEMEM "FreeMem"
|
|
#define D_CMND_HELP "Help"
|
|
#define D_CMND_RTCDUMP "RtcDump"
|
|
#define D_CMND_SETSENSOR "SetSensor"
|
|
#define D_CMND_I2CWRITE "I2CWrite"
|
|
#define D_CMND_I2CREAD "I2CRead"
|
|
#define D_CMND_I2CSTRETCH "I2CStretch"
|
|
#define D_CMND_I2CCLOCK "I2CClock"
|
|
|
|
const char kDebugCommands[] PROGMEM = "|"
|
|
D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|"
|
|
#ifdef USE_WEBSERVER
|
|
D_CMND_CFGXOR "|"
|
|
#endif
|
|
D_CMND_CPUCHECK "|"
|
|
#ifdef DEBUG_THEO
|
|
D_CMND_EXCEPTION "|"
|
|
#endif
|
|
D_CMND_FLASHDUMP "|" D_CMND_FLASHMODE "|" D_CMND_FREEMEM"|" D_CMND_HELP "|" D_CMND_RTCDUMP "|" D_CMND_SETSENSOR "|"
|
|
#ifdef USE_I2C
|
|
D_CMND_I2CWRITE "|" D_CMND_I2CREAD "|" D_CMND_I2CSTRETCH "|" D_CMND_I2CCLOCK
|
|
#endif
|
|
;
|
|
|
|
void (* const DebugCommand[])(void) PROGMEM = {
|
|
&CmndCfgDump, &CmndCfgPeek, &CmndCfgPoke,
|
|
#ifdef USE_WEBSERVER
|
|
&CmndCfgXor,
|
|
#endif
|
|
&CmndCpuCheck,
|
|
#ifdef DEBUG_THEO
|
|
&CmndException,
|
|
#endif
|
|
&CmndFlashDump, &CmndFlashMode, &CmndFreemem, &CmndHelp, &CmndRtcDump, &CmndSetSensor,
|
|
#ifdef USE_I2C
|
|
&CmndI2cWrite, &CmndI2cRead, &CmndI2cStretch, &CmndI2cClock
|
|
#endif
|
|
};
|
|
|
|
uint32_t CPU_loops = 0;
|
|
uint32_t CPU_last_millis = 0;
|
|
uint32_t CPU_last_loop_time = 0;
|
|
uint8_t CPU_load_check = 0;
|
|
uint8_t CPU_show_freemem = 0;
|
|
|
|
|
|
|
|
#ifdef DEBUG_THEO
|
|
void ExceptionTest(uint8_t type)
|
|
{
|
|
# 145 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
|
|
if (1 == type) {
|
|
char svalue[10];
|
|
snprintf_P(svalue, sizeof(svalue), PSTR("%s"), 7);
|
|
}
|
|
# 159 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
|
|
if (2 == type) {
|
|
while(1) delay(1000);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void CpuLoadLoop(void)
|
|
{
|
|
CPU_last_loop_time = millis();
|
|
if (CPU_load_check && CPU_last_millis) {
|
|
CPU_loops ++;
|
|
if ((CPU_last_millis + (CPU_load_check *1000)) <= CPU_last_loop_time) {
|
|
#if defined(F_CPU) && (F_CPU == 160000000L)
|
|
int CPU_load = 100 - ( (CPU_loops*(1 + 30*sleep)) / (CPU_load_check *800) );
|
|
CPU_loops = CPU_loops / CPU_load_check;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, CPU %d%%(160MHz), Loops/sec %d"), ESP.getFreeHeap(), CPU_load, CPU_loops);
|
|
#else
|
|
int CPU_load = 100 - ( (CPU_loops*(1 + 30*sleep)) / (CPU_load_check *400) );
|
|
CPU_loops = CPU_loops / CPU_load_check;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, CPU %d%%(80MHz), Loops/sec %d"), ESP.getFreeHeap(), CPU_load, CPU_loops);
|
|
#endif
|
|
CPU_last_millis = CPU_last_loop_time;
|
|
CPU_loops = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1)
|
|
|
|
|
|
|
|
extern "C" {
|
|
#include <cont.h>
|
|
extern cont_t g_cont;
|
|
}
|
|
|
|
void DebugFreeMem(void)
|
|
{
|
|
register uint32_t *sp asm("a1");
|
|
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, FreeStack %d (%s)"), ESP.getFreeHeap(), 4 * (sp - g_cont.stack), XdrvMailbox.data);
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
#include <cont.h>
|
|
extern cont_t* g_pcont;
|
|
}
|
|
|
|
void DebugFreeMem(void)
|
|
{
|
|
register uint32_t *sp asm("a1");
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "FreeRam %d, FreeStack %d (%s)"), ESP.getFreeHeap(), 4 * (sp - g_pcont->stack), XdrvMailbox.data);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void DebugRtcDump(char* parms)
|
|
{
|
|
#define CFG_COLS 16
|
|
|
|
uint16_t idx;
|
|
uint16_t maxrow;
|
|
uint16_t row;
|
|
uint16_t col;
|
|
char *p;
|
|
# 246 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_99_debug.ino"
|
|
uint8_t buffer[768];
|
|
|
|
system_rtc_mem_read(0, (uint32_t*)&buffer, sizeof(buffer));
|
|
|
|
maxrow = ((sizeof(buffer)+CFG_COLS)/CFG_COLS);
|
|
|
|
uint16_t srow = strtol(parms, &p, 16) / CFG_COLS;
|
|
uint16_t mrow = strtol(p, &p, 10);
|
|
|
|
|
|
|
|
if (0 == mrow) {
|
|
mrow = 8;
|
|
}
|
|
if (srow > maxrow) {
|
|
srow = maxrow - mrow;
|
|
}
|
|
if (mrow < (maxrow - srow)) {
|
|
maxrow = srow + mrow;
|
|
}
|
|
|
|
for (row = srow; row < maxrow; row++) {
|
|
idx = row * CFG_COLS;
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), idx);
|
|
for (col = 0; col < CFG_COLS; col++) {
|
|
if (!(col%4)) {
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s "), log_data);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[idx + col]);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data);
|
|
for (col = 0; col < CFG_COLS; col++) {
|
|
|
|
|
|
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' ');
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s|"), log_data);
|
|
AddLog(LOG_LEVEL_INFO);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DebugCfgDump(char* parms)
|
|
{
|
|
#define CFG_COLS 16
|
|
|
|
uint16_t idx;
|
|
uint16_t maxrow;
|
|
uint16_t row;
|
|
uint16_t col;
|
|
char *p;
|
|
|
|
uint8_t *buffer = (uint8_t *) &Settings;
|
|
maxrow = ((sizeof(SYSCFG)+CFG_COLS)/CFG_COLS);
|
|
|
|
uint16_t srow = strtol(parms, &p, 16) / CFG_COLS;
|
|
uint16_t mrow = strtol(p, &p, 10);
|
|
|
|
|
|
|
|
if (0 == mrow) {
|
|
mrow = 8;
|
|
}
|
|
if (srow > maxrow) {
|
|
srow = maxrow - mrow;
|
|
}
|
|
if (mrow < (maxrow - srow)) {
|
|
maxrow = srow + mrow;
|
|
}
|
|
|
|
for (row = srow; row < maxrow; row++) {
|
|
idx = row * CFG_COLS;
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), idx);
|
|
for (col = 0; col < CFG_COLS; col++) {
|
|
if (!(col%4)) {
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s "), log_data);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[idx + col]);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data);
|
|
for (col = 0; col < CFG_COLS; col++) {
|
|
|
|
|
|
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' ');
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s|"), log_data);
|
|
AddLog(LOG_LEVEL_INFO);
|
|
delay(1);
|
|
}
|
|
}
|
|
|
|
void DebugCfgPeek(char* parms)
|
|
{
|
|
char *p;
|
|
|
|
uint16_t address = strtol(parms, &p, 16);
|
|
if (address > sizeof(SYSCFG)) address = sizeof(SYSCFG) -4;
|
|
address = (address >> 2) << 2;
|
|
|
|
uint8_t *buffer = (uint8_t *) &Settings;
|
|
uint8_t data8 = buffer[address];
|
|
uint16_t data16 = (buffer[address +1] << 8) + buffer[address];
|
|
uint32_t data32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + data16;
|
|
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%03X:"), address);
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, buffer[address +i]);
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s |"), log_data);
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s%c"), log_data, ((buffer[address +i] > 0x20) && (buffer[address +i] < 0x7F)) ? (char)buffer[address +i] : ' ');
|
|
}
|
|
snprintf_P(log_data, sizeof(log_data), PSTR("%s| 0x%02X (%d), 0x%04X (%d), 0x%0LX (%lu)"), log_data, data8, data8, data16, data16, data32, data32);
|
|
AddLog(LOG_LEVEL_INFO);
|
|
}
|
|
|
|
void DebugCfgPoke(char* parms)
|
|
{
|
|
char *p;
|
|
|
|
uint16_t address = strtol(parms, &p, 16);
|
|
if (address > sizeof(SYSCFG)) address = sizeof(SYSCFG) -4;
|
|
address = (address >> 2) << 2;
|
|
|
|
uint32_t data = strtol(p, &p, 16);
|
|
|
|
uint8_t *buffer = (uint8_t *) &Settings;
|
|
uint32_t data32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + (buffer[address +1] << 8) + buffer[address];
|
|
|
|
uint8_t *nbuffer = (uint8_t *) &data;
|
|
for (uint32_t i = 0; i < 4; i++) { buffer[address +i] = nbuffer[+i]; }
|
|
|
|
uint32_t ndata32 = (buffer[address +3] << 24) + (buffer[address +2] << 16) + (buffer[address +1] << 8) + buffer[address];
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: 0x%0LX (%lu) poked to 0x%0LX (%lu)"), address, data32, data32, ndata32, ndata32);
|
|
}
|
|
|
|
void SetFlashMode(uint8_t mode)
|
|
{
|
|
uint8_t *_buffer;
|
|
uint32_t address;
|
|
|
|
address = 0;
|
|
_buffer = new uint8_t[FLASH_SECTOR_SIZE];
|
|
|
|
if (ESP.flashRead(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) {
|
|
if (_buffer[2] != mode) {
|
|
_buffer[2] = mode;
|
|
if (ESP.flashEraseSector(address / FLASH_SECTOR_SIZE)) {
|
|
ESP.flashWrite(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE);
|
|
}
|
|
}
|
|
}
|
|
delete[] _buffer;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndHelp(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("HLP: "), kDebugCommands);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndRtcDump(void)
|
|
{
|
|
DebugRtcDump(XdrvMailbox.data);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndCfgDump(void)
|
|
{
|
|
DebugCfgDump(XdrvMailbox.data);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndCfgPeek(void)
|
|
{
|
|
DebugCfgPeek(XdrvMailbox.data);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndCfgPoke(void)
|
|
{
|
|
DebugCfgPoke(XdrvMailbox.data);
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
void CmndCfgXor(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
Web.config_xor_on_set = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Web.config_xor_on_set);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_THEO
|
|
void CmndException(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) { ExceptionTest(XdrvMailbox.payload); }
|
|
ResponseCmndDone();
|
|
}
|
|
#endif
|
|
|
|
void CmndCpuCheck(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
CPU_load_check = XdrvMailbox.payload;
|
|
CPU_last_millis = CPU_last_loop_time;
|
|
}
|
|
ResponseCmndNumber(CPU_load_check);
|
|
}
|
|
|
|
void CmndFreemem(void)
|
|
{
|
|
if (XdrvMailbox.data_len > 0) {
|
|
CPU_show_freemem = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(CPU_show_freemem);
|
|
}
|
|
|
|
void CmndSetSensor(void)
|
|
{
|
|
if (XdrvMailbox.index < MAX_XSNS_DRIVERS) {
|
|
if (XdrvMailbox.payload >= 0) {
|
|
bitWrite(Settings.sensors[XdrvMailbox.index / 32], XdrvMailbox.index % 32, XdrvMailbox.payload &1);
|
|
if (1 == XdrvMailbox.payload) {
|
|
restart_flag = 2;
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"" D_CMND_SETSENSOR "\":"));
|
|
XsnsSensorState();
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
void CmndFlashMode(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
|
|
SetFlashMode(XdrvMailbox.payload);
|
|
}
|
|
ResponseCmndNumber(ESP.getFlashChipMode());
|
|
}
|
|
|
|
uint32_t DebugSwap32(uint32_t x) {
|
|
return ((x << 24) & 0xff000000 ) |
|
|
((x << 8) & 0x00ff0000 ) |
|
|
((x >> 8) & 0x0000ff00 ) |
|
|
((x >> 24) & 0x000000ff );
|
|
}
|
|
|
|
void CmndFlashDump(void)
|
|
{
|
|
|
|
|
|
|
|
const uint32_t flash_start = 0x40200000;
|
|
const uint8_t bytes_per_cols = 0x20;
|
|
const uint32_t max = (SPIFFS_END + 5) * SPI_FLASH_SEC_SIZE;
|
|
|
|
uint32_t start = flash_start;
|
|
uint32_t rows = 8;
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= (max - bytes_per_cols))) {
|
|
start += (XdrvMailbox.payload &0x7FFFFFFC);
|
|
|
|
char *p;
|
|
uint32_t is_payload = strtol(XdrvMailbox.data, &p, 16);
|
|
rows = strtol(p, &p, 10);
|
|
if (0 == rows) { rows = 8; }
|
|
}
|
|
uint32_t end = start + (rows * bytes_per_cols);
|
|
if ((end - flash_start) > max) {
|
|
end = flash_start + max;
|
|
}
|
|
|
|
for (uint32_t pos = start; pos < end; pos += bytes_per_cols) {
|
|
uint32_t* values = (uint32_t*)(pos);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%06X: %08X %08X %08X %08X %08X %08X %08X %08X"), pos - flash_start,
|
|
DebugSwap32(values[0]), DebugSwap32(values[1]), DebugSwap32(values[2]), DebugSwap32(values[3]),
|
|
DebugSwap32(values[4]), DebugSwap32(values[5]), DebugSwap32(values[6]), DebugSwap32(values[7]));
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
#ifdef USE_I2C
|
|
void CmndI2cWrite(void)
|
|
{
|
|
|
|
if (i2c_flg) {
|
|
char* parms = XdrvMailbox.data;
|
|
uint8_t buffer[100];
|
|
uint32_t index = 0;
|
|
|
|
char *p;
|
|
char *data = strtok_r(parms, " ,", &p);
|
|
while (data != NULL && index < sizeof(buffer)) {
|
|
buffer[index++] = strtol(data, nullptr, 16);
|
|
data = strtok_r(nullptr, " ,", &p);
|
|
}
|
|
|
|
if (index > 1) {
|
|
AddLogBuffer(LOG_LEVEL_INFO, buffer, index);
|
|
|
|
Wire.beginTransmission(buffer[0]);
|
|
for (uint32_t i = 1; i < index; i++) {
|
|
Wire.write(buffer[i]);
|
|
}
|
|
int result = Wire.endTransmission();
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("I2C: Result %d"), result);
|
|
}
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndI2cRead(void)
|
|
{
|
|
|
|
if (i2c_flg) {
|
|
char* parms = XdrvMailbox.data;
|
|
uint8_t buffer[100];
|
|
uint32_t index = 0;
|
|
|
|
char *p;
|
|
char *data = strtok_r(parms, " ,", &p);
|
|
while (data != NULL && index < sizeof(buffer)) {
|
|
buffer[index++] = strtol(data, nullptr, 16);
|
|
data = strtok_r(nullptr, " ,", &p);
|
|
}
|
|
|
|
if (index > 0) {
|
|
uint8_t size = 1;
|
|
if (index > 1) {
|
|
size = buffer[1];
|
|
}
|
|
Wire.requestFrom(buffer[0], size);
|
|
index = 0;
|
|
while (Wire.available() && index < sizeof(buffer)) {
|
|
buffer[index++] = Wire.read();
|
|
}
|
|
if (index > 0) {
|
|
AddLogBuffer(LOG_LEVEL_INFO, buffer, index);
|
|
}
|
|
}
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndI2cStretch(void)
|
|
{
|
|
if (i2c_flg && (XdrvMailbox.payload > 0)) {
|
|
Wire.setClockStretchLimit(XdrvMailbox.payload);
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
|
|
void CmndI2cClock(void)
|
|
{
|
|
if (i2c_flg && (XdrvMailbox.payload > 0)) {
|
|
Wire.setClock(XdrvMailbox.payload);
|
|
}
|
|
ResponseCmndDone();
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdrv99(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
CpuLoadLoop();
|
|
break;
|
|
case FUNC_FREE_MEM:
|
|
if (CPU_show_freemem) { DebugFreeMem(); }
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
CPU_last_millis = millis();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kDebugCommands, DebugCommand);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_interface.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdrv_interface.ino"
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
bool (* const xdrv_func_ptr[])(uint8_t) PROGMEM = {
|
|
#else
|
|
bool (* const xdrv_func_ptr[])(uint8_t) = {
|
|
#endif
|
|
|
|
#ifdef XDRV_01
|
|
&Xdrv01,
|
|
#endif
|
|
|
|
#ifdef XDRV_02
|
|
&Xdrv02,
|
|
#endif
|
|
|
|
#ifdef XDRV_03
|
|
&Xdrv03,
|
|
#endif
|
|
|
|
#ifdef XDRV_04
|
|
&Xdrv04,
|
|
#endif
|
|
|
|
#ifdef XDRV_05
|
|
&Xdrv05,
|
|
#endif
|
|
|
|
#ifdef XDRV_06
|
|
&Xdrv06,
|
|
#endif
|
|
|
|
#ifdef XDRV_07
|
|
&Xdrv07,
|
|
#endif
|
|
|
|
#ifdef XDRV_08
|
|
&Xdrv08,
|
|
#endif
|
|
|
|
#ifdef XDRV_09
|
|
&Xdrv09,
|
|
#endif
|
|
|
|
#ifdef XDRV_10
|
|
&Xdrv10,
|
|
#endif
|
|
|
|
#ifdef XDRV_11
|
|
&Xdrv11,
|
|
#endif
|
|
|
|
#ifdef XDRV_12
|
|
&Xdrv12,
|
|
#endif
|
|
|
|
#ifdef XDRV_13
|
|
&Xdrv13,
|
|
#endif
|
|
|
|
#ifdef XDRV_14
|
|
&Xdrv14,
|
|
#endif
|
|
|
|
#ifdef XDRV_15
|
|
&Xdrv15,
|
|
#endif
|
|
|
|
#ifdef XDRV_16
|
|
&Xdrv16,
|
|
#endif
|
|
|
|
#ifdef XDRV_17
|
|
&Xdrv17,
|
|
#endif
|
|
|
|
#ifdef XDRV_18
|
|
&Xdrv18,
|
|
#endif
|
|
|
|
#ifdef XDRV_19
|
|
&Xdrv19,
|
|
#endif
|
|
|
|
#ifdef XDRV_20
|
|
&Xdrv20,
|
|
#endif
|
|
|
|
#ifdef XDRV_21
|
|
&Xdrv21,
|
|
#endif
|
|
|
|
#ifdef XDRV_22
|
|
&Xdrv22,
|
|
#endif
|
|
|
|
#ifdef XDRV_23
|
|
&Xdrv23,
|
|
#endif
|
|
|
|
#ifdef XDRV_24
|
|
&Xdrv24,
|
|
#endif
|
|
|
|
#ifdef XDRV_25
|
|
&Xdrv25,
|
|
#endif
|
|
|
|
#ifdef XDRV_26
|
|
&Xdrv26,
|
|
#endif
|
|
|
|
#ifdef XDRV_27
|
|
&Xdrv27,
|
|
#endif
|
|
|
|
#ifdef XDRV_28
|
|
&Xdrv28,
|
|
#endif
|
|
|
|
#ifdef XDRV_29
|
|
&Xdrv29,
|
|
#endif
|
|
|
|
#ifdef XDRV_30
|
|
&Xdrv30,
|
|
#endif
|
|
|
|
#ifdef XDRV_31
|
|
&Xdrv31,
|
|
#endif
|
|
|
|
#ifdef XDRV_32
|
|
&Xdrv32,
|
|
#endif
|
|
|
|
#ifdef XDRV_33
|
|
&Xdrv33,
|
|
#endif
|
|
|
|
#ifdef XDRV_34
|
|
&Xdrv34,
|
|
#endif
|
|
|
|
#ifdef XDRV_35
|
|
&Xdrv35,
|
|
#endif
|
|
|
|
#ifdef XDRV_36
|
|
&Xdrv36,
|
|
#endif
|
|
|
|
#ifdef XDRV_37
|
|
&Xdrv37,
|
|
#endif
|
|
|
|
#ifdef XDRV_38
|
|
&Xdrv38,
|
|
#endif
|
|
|
|
#ifdef XDRV_39
|
|
&Xdrv39,
|
|
#endif
|
|
|
|
#ifdef XDRV_40
|
|
&Xdrv40,
|
|
#endif
|
|
|
|
#ifdef XDRV_41
|
|
&Xdrv41,
|
|
#endif
|
|
|
|
#ifdef XDRV_42
|
|
&Xdrv42,
|
|
#endif
|
|
|
|
#ifdef XDRV_43
|
|
&Xdrv43,
|
|
#endif
|
|
|
|
#ifdef XDRV_44
|
|
&Xdrv44,
|
|
#endif
|
|
|
|
#ifdef XDRV_45
|
|
&Xdrv45,
|
|
#endif
|
|
|
|
#ifdef XDRV_46
|
|
&Xdrv46,
|
|
#endif
|
|
|
|
#ifdef XDRV_47
|
|
&Xdrv47,
|
|
#endif
|
|
|
|
#ifdef XDRV_48
|
|
&Xdrv48,
|
|
#endif
|
|
|
|
#ifdef XDRV_49
|
|
&Xdrv49,
|
|
#endif
|
|
|
|
#ifdef XDRV_50
|
|
&Xdrv50,
|
|
#endif
|
|
|
|
#ifdef XDRV_51
|
|
&Xdrv51,
|
|
#endif
|
|
|
|
#ifdef XDRV_52
|
|
&Xdrv52,
|
|
#endif
|
|
|
|
#ifdef XDRV_53
|
|
&Xdrv53,
|
|
#endif
|
|
|
|
#ifdef XDRV_54
|
|
&Xdrv54,
|
|
#endif
|
|
|
|
#ifdef XDRV_55
|
|
&Xdrv55,
|
|
#endif
|
|
|
|
#ifdef XDRV_56
|
|
&Xdrv56,
|
|
#endif
|
|
|
|
#ifdef XDRV_57
|
|
&Xdrv57,
|
|
#endif
|
|
|
|
#ifdef XDRV_58
|
|
&Xdrv58,
|
|
#endif
|
|
|
|
#ifdef XDRV_59
|
|
&Xdrv59,
|
|
#endif
|
|
|
|
#ifdef XDRV_60
|
|
&Xdrv60,
|
|
#endif
|
|
|
|
#ifdef XDRV_61
|
|
&Xdrv61,
|
|
#endif
|
|
|
|
#ifdef XDRV_62
|
|
&Xdrv62,
|
|
#endif
|
|
|
|
#ifdef XDRV_63
|
|
&Xdrv63,
|
|
#endif
|
|
|
|
#ifdef XDRV_64
|
|
&Xdrv64,
|
|
#endif
|
|
|
|
#ifdef XDRV_65
|
|
&Xdrv65,
|
|
#endif
|
|
|
|
#ifdef XDRV_66
|
|
&Xdrv66,
|
|
#endif
|
|
|
|
#ifdef XDRV_67
|
|
&Xdrv67,
|
|
#endif
|
|
|
|
#ifdef XDRV_68
|
|
&Xdrv68,
|
|
#endif
|
|
|
|
#ifdef XDRV_69
|
|
&Xdrv69,
|
|
#endif
|
|
|
|
#ifdef XDRV_70
|
|
&Xdrv70,
|
|
#endif
|
|
|
|
#ifdef XDRV_71
|
|
&Xdrv71,
|
|
#endif
|
|
|
|
#ifdef XDRV_72
|
|
&Xdrv72,
|
|
#endif
|
|
|
|
#ifdef XDRV_73
|
|
&Xdrv73,
|
|
#endif
|
|
|
|
#ifdef XDRV_74
|
|
&Xdrv74,
|
|
#endif
|
|
|
|
#ifdef XDRV_75
|
|
&Xdrv75,
|
|
#endif
|
|
|
|
#ifdef XDRV_76
|
|
&Xdrv76,
|
|
#endif
|
|
|
|
#ifdef XDRV_77
|
|
&Xdrv77,
|
|
#endif
|
|
|
|
#ifdef XDRV_78
|
|
&Xdrv78,
|
|
#endif
|
|
|
|
#ifdef XDRV_79
|
|
&Xdrv79,
|
|
#endif
|
|
|
|
#ifdef XDRV_80
|
|
&Xdrv80,
|
|
#endif
|
|
|
|
#ifdef XDRV_81
|
|
&Xdrv81,
|
|
#endif
|
|
|
|
#ifdef XDRV_82
|
|
&Xdrv82,
|
|
#endif
|
|
|
|
#ifdef XDRV_83
|
|
&Xdrv83,
|
|
#endif
|
|
|
|
#ifdef XDRV_84
|
|
&Xdrv84,
|
|
#endif
|
|
|
|
#ifdef XDRV_85
|
|
&Xdrv85,
|
|
#endif
|
|
|
|
#ifdef XDRV_86
|
|
&Xdrv86,
|
|
#endif
|
|
|
|
#ifdef XDRV_87
|
|
&Xdrv87,
|
|
#endif
|
|
|
|
#ifdef XDRV_88
|
|
&Xdrv88,
|
|
#endif
|
|
|
|
#ifdef XDRV_89
|
|
&Xdrv89,
|
|
#endif
|
|
|
|
#ifdef XDRV_90
|
|
&Xdrv90,
|
|
#endif
|
|
|
|
#ifdef XDRV_91
|
|
&Xdrv91,
|
|
#endif
|
|
|
|
#ifdef XDRV_92
|
|
&Xdrv92,
|
|
#endif
|
|
|
|
#ifdef XDRV_93
|
|
&Xdrv93,
|
|
#endif
|
|
|
|
#ifdef XDRV_94
|
|
&Xdrv94,
|
|
#endif
|
|
|
|
#ifdef XDRV_95
|
|
&Xdrv95,
|
|
#endif
|
|
|
|
#ifdef XDRV_96
|
|
&Xdrv96,
|
|
#endif
|
|
|
|
#ifdef XDRV_97
|
|
&Xdrv97,
|
|
#endif
|
|
|
|
#ifdef XDRV_98
|
|
&Xdrv98,
|
|
#endif
|
|
|
|
#ifdef XDRV_99
|
|
&Xdrv99
|
|
#endif
|
|
};
|
|
|
|
const uint8_t xdrv_present = sizeof(xdrv_func_ptr) / sizeof(xdrv_func_ptr[0]);
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
const uint8_t kXdrvList[] PROGMEM = {
|
|
#else
|
|
const uint8_t kXdrvList[] = {
|
|
#endif
|
|
|
|
#ifdef XDRV_01
|
|
XDRV_01,
|
|
#endif
|
|
|
|
#ifdef XDRV_02
|
|
XDRV_02,
|
|
#endif
|
|
|
|
#ifdef XDRV_03
|
|
XDRV_03,
|
|
#endif
|
|
|
|
#ifdef XDRV_04
|
|
XDRV_04,
|
|
#endif
|
|
|
|
#ifdef XDRV_05
|
|
XDRV_05,
|
|
#endif
|
|
|
|
#ifdef XDRV_06
|
|
XDRV_06,
|
|
#endif
|
|
|
|
#ifdef XDRV_07
|
|
XDRV_07,
|
|
#endif
|
|
|
|
#ifdef XDRV_08
|
|
XDRV_08,
|
|
#endif
|
|
|
|
#ifdef XDRV_09
|
|
XDRV_09,
|
|
#endif
|
|
|
|
#ifdef XDRV_10
|
|
XDRV_10,
|
|
#endif
|
|
|
|
#ifdef XDRV_11
|
|
XDRV_11,
|
|
#endif
|
|
|
|
#ifdef XDRV_12
|
|
XDRV_12,
|
|
#endif
|
|
|
|
#ifdef XDRV_13
|
|
XDRV_13,
|
|
#endif
|
|
|
|
#ifdef XDRV_14
|
|
XDRV_14,
|
|
#endif
|
|
|
|
#ifdef XDRV_15
|
|
XDRV_15,
|
|
#endif
|
|
|
|
#ifdef XDRV_16
|
|
XDRV_16,
|
|
#endif
|
|
|
|
#ifdef XDRV_17
|
|
XDRV_17,
|
|
#endif
|
|
|
|
#ifdef XDRV_18
|
|
XDRV_18,
|
|
#endif
|
|
|
|
#ifdef XDRV_19
|
|
XDRV_19,
|
|
#endif
|
|
|
|
#ifdef XDRV_20
|
|
XDRV_20,
|
|
#endif
|
|
|
|
#ifdef XDRV_21
|
|
XDRV_21,
|
|
#endif
|
|
|
|
#ifdef XDRV_22
|
|
XDRV_22,
|
|
#endif
|
|
|
|
#ifdef XDRV_23
|
|
XDRV_23,
|
|
#endif
|
|
|
|
#ifdef XDRV_24
|
|
XDRV_24,
|
|
#endif
|
|
|
|
#ifdef XDRV_25
|
|
XDRV_25,
|
|
#endif
|
|
|
|
#ifdef XDRV_26
|
|
XDRV_26,
|
|
#endif
|
|
|
|
#ifdef XDRV_27
|
|
XDRV_27,
|
|
#endif
|
|
|
|
#ifdef XDRV_28
|
|
XDRV_28,
|
|
#endif
|
|
|
|
#ifdef XDRV_29
|
|
XDRV_29,
|
|
#endif
|
|
|
|
#ifdef XDRV_30
|
|
XDRV_30,
|
|
#endif
|
|
|
|
#ifdef XDRV_31
|
|
XDRV_31,
|
|
#endif
|
|
|
|
#ifdef XDRV_32
|
|
XDRV_32,
|
|
#endif
|
|
|
|
#ifdef XDRV_33
|
|
XDRV_33,
|
|
#endif
|
|
|
|
#ifdef XDRV_34
|
|
XDRV_34,
|
|
#endif
|
|
|
|
#ifdef XDRV_35
|
|
XDRV_35,
|
|
#endif
|
|
|
|
#ifdef XDRV_36
|
|
XDRV_36,
|
|
#endif
|
|
|
|
#ifdef XDRV_37
|
|
XDRV_37,
|
|
#endif
|
|
|
|
#ifdef XDRV_38
|
|
XDRV_38,
|
|
#endif
|
|
|
|
#ifdef XDRV_39
|
|
XDRV_39,
|
|
#endif
|
|
|
|
#ifdef XDRV_40
|
|
XDRV_40,
|
|
#endif
|
|
|
|
#ifdef XDRV_41
|
|
XDRV_41,
|
|
#endif
|
|
|
|
#ifdef XDRV_42
|
|
XDRV_42,
|
|
#endif
|
|
|
|
#ifdef XDRV_43
|
|
XDRV_43,
|
|
#endif
|
|
|
|
#ifdef XDRV_44
|
|
XDRV_44,
|
|
#endif
|
|
|
|
#ifdef XDRV_45
|
|
XDRV_45,
|
|
#endif
|
|
|
|
#ifdef XDRV_46
|
|
XDRV_46,
|
|
#endif
|
|
|
|
#ifdef XDRV_47
|
|
XDRV_47,
|
|
#endif
|
|
|
|
#ifdef XDRV_48
|
|
XDRV_48,
|
|
#endif
|
|
|
|
#ifdef XDRV_49
|
|
XDRV_49,
|
|
#endif
|
|
|
|
#ifdef XDRV_50
|
|
XDRV_50,
|
|
#endif
|
|
|
|
#ifdef XDRV_51
|
|
XDRV_51,
|
|
#endif
|
|
|
|
#ifdef XDRV_52
|
|
XDRV_52,
|
|
#endif
|
|
|
|
#ifdef XDRV_53
|
|
XDRV_53,
|
|
#endif
|
|
|
|
#ifdef XDRV_54
|
|
XDRV_54,
|
|
#endif
|
|
|
|
#ifdef XDRV_55
|
|
XDRV_55,
|
|
#endif
|
|
|
|
#ifdef XDRV_56
|
|
XDRV_56,
|
|
#endif
|
|
|
|
#ifdef XDRV_57
|
|
XDRV_57,
|
|
#endif
|
|
|
|
#ifdef XDRV_58
|
|
XDRV_58,
|
|
#endif
|
|
|
|
#ifdef XDRV_59
|
|
XDRV_59,
|
|
#endif
|
|
|
|
#ifdef XDRV_60
|
|
XDRV_60,
|
|
#endif
|
|
|
|
#ifdef XDRV_61
|
|
XDRV_61,
|
|
#endif
|
|
|
|
#ifdef XDRV_62
|
|
XDRV_62,
|
|
#endif
|
|
|
|
#ifdef XDRV_63
|
|
XDRV_63,
|
|
#endif
|
|
|
|
#ifdef XDRV_64
|
|
XDRV_64,
|
|
#endif
|
|
|
|
#ifdef XDRV_65
|
|
XDRV_65,
|
|
#endif
|
|
|
|
#ifdef XDRV_66
|
|
XDRV_66,
|
|
#endif
|
|
|
|
#ifdef XDRV_67
|
|
XDRV_67,
|
|
#endif
|
|
|
|
#ifdef XDRV_68
|
|
XDRV_68,
|
|
#endif
|
|
|
|
#ifdef XDRV_69
|
|
XDRV_69,
|
|
#endif
|
|
|
|
#ifdef XDRV_70
|
|
XDRV_70,
|
|
#endif
|
|
|
|
#ifdef XDRV_71
|
|
XDRV_71,
|
|
#endif
|
|
|
|
#ifdef XDRV_72
|
|
XDRV_72,
|
|
#endif
|
|
|
|
#ifdef XDRV_73
|
|
XDRV_73,
|
|
#endif
|
|
|
|
#ifdef XDRV_74
|
|
XDRV_74,
|
|
#endif
|
|
|
|
#ifdef XDRV_75
|
|
XDRV_75,
|
|
#endif
|
|
|
|
#ifdef XDRV_76
|
|
XDRV_76,
|
|
#endif
|
|
|
|
#ifdef XDRV_77
|
|
XDRV_77,
|
|
#endif
|
|
|
|
#ifdef XDRV_78
|
|
XDRV_78,
|
|
#endif
|
|
|
|
#ifdef XDRV_79
|
|
XDRV_79,
|
|
#endif
|
|
|
|
#ifdef XDRV_80
|
|
XDRV_80,
|
|
#endif
|
|
|
|
#ifdef XDRV_81
|
|
XDRV_81,
|
|
#endif
|
|
|
|
#ifdef XDRV_82
|
|
XDRV_82,
|
|
#endif
|
|
|
|
#ifdef XDRV_83
|
|
XDRV_83,
|
|
#endif
|
|
|
|
#ifdef XDRV_84
|
|
XDRV_84,
|
|
#endif
|
|
|
|
#ifdef XDRV_85
|
|
XDRV_85,
|
|
#endif
|
|
|
|
#ifdef XDRV_86
|
|
XDRV_86,
|
|
#endif
|
|
|
|
#ifdef XDRV_87
|
|
XDRV_87,
|
|
#endif
|
|
|
|
#ifdef XDRV_88
|
|
XDRV_88,
|
|
#endif
|
|
|
|
#ifdef XDRV_89
|
|
XDRV_89,
|
|
#endif
|
|
|
|
#ifdef XDRV_90
|
|
XDRV_90,
|
|
#endif
|
|
|
|
#ifdef XDRV_91
|
|
XDRV_91,
|
|
#endif
|
|
|
|
#ifdef XDRV_92
|
|
XDRV_92,
|
|
#endif
|
|
|
|
#ifdef XDRV_93
|
|
XDRV_93,
|
|
#endif
|
|
|
|
#ifdef XDRV_94
|
|
XDRV_94,
|
|
#endif
|
|
|
|
#ifdef XDRV_95
|
|
XDRV_95,
|
|
#endif
|
|
|
|
#ifdef XDRV_96
|
|
XDRV_96,
|
|
#endif
|
|
|
|
#ifdef XDRV_97
|
|
XDRV_97,
|
|
#endif
|
|
|
|
#ifdef XDRV_98
|
|
XDRV_98,
|
|
#endif
|
|
|
|
#ifdef XDRV_99
|
|
XDRV_99
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
void XsnsDriverState(void)
|
|
{
|
|
ResponseAppend_P(PSTR(",\"Drivers\":\""));
|
|
for (uint32_t i = 0; i < sizeof(kXdrvList); i++) {
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
uint32_t driverid = pgm_read_byte(kXdrvList + i);
|
|
#else
|
|
uint32_t driverid = kXdrvList[i];
|
|
#endif
|
|
ResponseAppend_P(PSTR("%s%d"), (i) ? "," : "", driverid);
|
|
}
|
|
ResponseAppend_P(PSTR("\""));
|
|
}
|
|
|
|
|
|
|
|
bool XdrvRulesProcess(void)
|
|
{
|
|
return XdrvCallDriver(10, FUNC_RULES_PROCESS);
|
|
}
|
|
|
|
#ifdef USE_DEBUG_DRIVER
|
|
void ShowFreeMem(const char *where)
|
|
{
|
|
char stemp[30];
|
|
snprintf_P(stemp, sizeof(stemp), where);
|
|
XdrvMailbox.data = stemp;
|
|
XdrvCall(FUNC_FREE_MEM);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool XdrvCallDriver(uint32_t driver, uint8_t Function)
|
|
{
|
|
for (uint32_t x = 0; x < xdrv_present; x++) {
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
uint32_t listed = pgm_read_byte(kXdrvList + x);
|
|
#else
|
|
uint32_t listed = kXdrvList[x];
|
|
#endif
|
|
if (driver == listed) {
|
|
return xdrv_func_ptr[x](Function);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool XdrvCall(uint8_t Function)
|
|
{
|
|
bool result = false;
|
|
|
|
DEBUG_TRACE_LOG(PSTR("DRV: %d"), Function);
|
|
|
|
for (uint32_t x = 0; x < xdrv_present; x++) {
|
|
result = xdrv_func_ptr[x](Function);
|
|
|
|
if (result && ((FUNC_COMMAND == Function) ||
|
|
(FUNC_COMMAND_DRIVER == Function) ||
|
|
(FUNC_MQTT_DATA == Function) ||
|
|
(FUNC_RULES_PROCESS == Function) ||
|
|
(FUNC_BUTTON_PRESSED == Function) ||
|
|
(FUNC_SERIAL == Function) ||
|
|
(FUNC_MODULE_INIT == Function) ||
|
|
(FUNC_SET_CHANNELS == Function) ||
|
|
(FUNC_PIN_STATE == Function) ||
|
|
(FUNC_SET_DEVICE_POWER == Function)
|
|
)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_LCD
|
|
|
|
#define XDSP_01 1
|
|
#define XI2C_03 3
|
|
|
|
#define LCD_ADDRESS1 0x27
|
|
#define LCD_ADDRESS2 0x3F
|
|
|
|
#include <Wire.h>
|
|
#include <LiquidCrystal_I2C.h>
|
|
|
|
LiquidCrystal_I2C *lcd;
|
|
|
|
|
|
|
|
void LcdInitMode(void)
|
|
{
|
|
lcd->init();
|
|
lcd->clear();
|
|
}
|
|
|
|
void LcdInit(uint8_t mode)
|
|
{
|
|
switch(mode) {
|
|
case DISPLAY_INIT_MODE:
|
|
LcdInitMode();
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
DisplayClearScreenBuffer();
|
|
#endif
|
|
break;
|
|
case DISPLAY_INIT_PARTIAL:
|
|
case DISPLAY_INIT_FULL:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LcdInitDriver(void)
|
|
{
|
|
if (!Settings.display_model) {
|
|
if (I2cSetDevice(LCD_ADDRESS1)) {
|
|
Settings.display_address[0] = LCD_ADDRESS1;
|
|
Settings.display_model = XDSP_01;
|
|
}
|
|
else if (I2cSetDevice(LCD_ADDRESS2)) {
|
|
Settings.display_address[0] = LCD_ADDRESS2;
|
|
Settings.display_model = XDSP_01;
|
|
}
|
|
}
|
|
|
|
if (XDSP_01 == Settings.display_model) {
|
|
I2cSetActiveFound(Settings.display_address[0], "LCD");
|
|
|
|
Settings.display_width = Settings.display_cols[0];
|
|
Settings.display_height = Settings.display_rows;
|
|
lcd = new LiquidCrystal_I2C(Settings.display_address[0], Settings.display_cols[0], Settings.display_rows);
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
DisplayAllocScreenBuffer();
|
|
#endif
|
|
|
|
LcdInitMode();
|
|
}
|
|
}
|
|
|
|
void LcdDrawStringAt(void)
|
|
{
|
|
if (dsp_flag) {
|
|
dsp_x--;
|
|
dsp_y--;
|
|
}
|
|
lcd->setCursor(dsp_x, dsp_y);
|
|
lcd->print(dsp_str);
|
|
}
|
|
|
|
void LcdDisplayOnOff(uint8_t on)
|
|
{
|
|
if (on) {
|
|
lcd->backlight();
|
|
} else {
|
|
lcd->noBacklight();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void LcdCenter(uint8_t row, char* txt)
|
|
{
|
|
char line[Settings.display_cols[0] +2];
|
|
|
|
int len = strlen(txt);
|
|
int offset = 0;
|
|
if (len >= Settings.display_cols[0]) {
|
|
len = Settings.display_cols[0];
|
|
} else {
|
|
offset = (Settings.display_cols[0] - len) / 2;
|
|
}
|
|
memset(line, 0x20, Settings.display_cols[0]);
|
|
line[Settings.display_cols[0]] = 0;
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
line[offset +i] = txt[i];
|
|
}
|
|
lcd->setCursor(0, row);
|
|
lcd->print(line);
|
|
}
|
|
|
|
bool LcdPrintLog(void)
|
|
{
|
|
bool result = false;
|
|
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); }
|
|
|
|
char* txt = DisplayLogBuffer('\337');
|
|
if (txt != nullptr) {
|
|
uint8_t last_row = Settings.display_rows -1;
|
|
|
|
for (uint32_t i = 0; i < last_row; i++) {
|
|
strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols);
|
|
lcd->setCursor(0, i);
|
|
lcd->print(disp_screen_buffer[i +1]);
|
|
}
|
|
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
|
|
DisplayFillScreen(last_row);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
|
|
|
|
lcd->setCursor(0, last_row);
|
|
lcd->print(disp_screen_buffer[last_row]);
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void LcdTime(void)
|
|
{
|
|
char line[Settings.display_cols[0] +1];
|
|
|
|
snprintf_P(line, sizeof(line), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
LcdCenter(0, line);
|
|
snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year);
|
|
LcdCenter(1, line);
|
|
}
|
|
|
|
void LcdRefresh(void)
|
|
{
|
|
if (Settings.display_mode) {
|
|
switch (Settings.display_mode) {
|
|
case 1:
|
|
LcdTime();
|
|
break;
|
|
case 2:
|
|
case 4:
|
|
LcdPrintLog();
|
|
break;
|
|
case 3:
|
|
case 5: {
|
|
if (!LcdPrintLog()) { LcdTime(); }
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp01(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_03)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
LcdInitDriver();
|
|
}
|
|
else if (XDSP_01 == Settings.display_model) {
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
case FUNC_DISPLAY_INIT:
|
|
LcdInit(dsp_init);
|
|
break;
|
|
case FUNC_DISPLAY_POWER:
|
|
LcdDisplayOnOff(disp_power);
|
|
break;
|
|
case FUNC_DISPLAY_CLEAR:
|
|
lcd->clear();
|
|
break;
|
|
# 238 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_01_lcd.ino"
|
|
case FUNC_DISPLAY_DRAW_STRING:
|
|
LcdDrawStringAt();
|
|
break;
|
|
case FUNC_DISPLAY_ONOFF:
|
|
LcdDisplayOnOff(dsp_on);
|
|
break;
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
LcdRefresh();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_02_ssd1306.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_02_ssd1306.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_SSD1306
|
|
|
|
#define XDSP_02 2
|
|
#define XI2C_04 4
|
|
|
|
#define OLED_RESET 4
|
|
|
|
#define SPRINT(A) char str[32];sprintf(str,"val: %d ",A);Serial.println((char*)str);
|
|
|
|
#define OLED_ADDRESS1 0x3C
|
|
#define OLED_ADDRESS2 0x3D
|
|
|
|
#define OLED_BUFFER_COLS 40
|
|
#define OLED_BUFFER_ROWS 16
|
|
|
|
#define OLED_FONT_WIDTH 6
|
|
#define OLED_FONT_HEIGTH 8
|
|
|
|
#include <Wire.h>
|
|
#include <renderer.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
|
|
Adafruit_SSD1306 *oled1306;
|
|
|
|
extern uint8_t *buffer;
|
|
|
|
|
|
|
|
void SSD1306InitDriver(void)
|
|
{
|
|
if (!Settings.display_model) {
|
|
if (I2cSetDevice(OLED_ADDRESS1)) {
|
|
Settings.display_address[0] = OLED_ADDRESS1;
|
|
Settings.display_model = XDSP_02;
|
|
}
|
|
else if (I2cSetDevice(OLED_ADDRESS2)) {
|
|
Settings.display_address[0] = OLED_ADDRESS2;
|
|
Settings.display_model = XDSP_02;
|
|
}
|
|
}
|
|
|
|
if (XDSP_02 == Settings.display_model) {
|
|
I2cSetActiveFound(Settings.display_address[0], "SSD1306");
|
|
|
|
if ((Settings.display_width != 64) && (Settings.display_width != 96) && (Settings.display_width != 128)) {
|
|
Settings.display_width = 128;
|
|
}
|
|
if ((Settings.display_height != 16) && (Settings.display_height != 32) && (Settings.display_height != 48) && (Settings.display_height != 64)) {
|
|
Settings.display_height = 64;
|
|
}
|
|
|
|
uint8_t reset_pin = -1;
|
|
if (pin[GPIO_OLED_RESET] < 99) {
|
|
reset_pin = pin[GPIO_OLED_RESET];
|
|
}
|
|
|
|
|
|
if (buffer) { free(buffer); }
|
|
buffer = (unsigned char*)calloc((Settings.display_width * Settings.display_height) / 8,1);
|
|
if (!buffer) { return; }
|
|
|
|
|
|
|
|
oled1306 = new Adafruit_SSD1306(Settings.display_width, Settings.display_height, &Wire, reset_pin);
|
|
oled1306->begin(SSD1306_SWITCHCAPVCC, Settings.display_address[0], reset_pin >= 0);
|
|
renderer = oled1306;
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE, Settings.display_size, Settings.display_rotate, Settings.display_font);
|
|
renderer->setTextColor(1,0);
|
|
|
|
#ifdef SHOW_SPLASH
|
|
renderer->setTextFont(0);
|
|
renderer->setTextSize(2);
|
|
renderer->setCursor(20,20);
|
|
renderer->println(F("SSD1306"));
|
|
renderer->Updateframe();
|
|
renderer->DisplayOnff(1);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void Ssd1306PrintLog(void)
|
|
{
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); }
|
|
|
|
char* txt = DisplayLogBuffer('\370');
|
|
if (txt != NULL) {
|
|
uint8_t last_row = Settings.display_rows -1;
|
|
|
|
renderer->clearDisplay();
|
|
renderer->setTextSize(Settings.display_size);
|
|
renderer->setCursor(0,0);
|
|
for (byte i = 0; i < last_row; i++) {
|
|
strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols);
|
|
renderer->println(disp_screen_buffer[i]);
|
|
}
|
|
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
|
|
DisplayFillScreen(last_row);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
|
|
|
|
renderer->println(disp_screen_buffer[last_row]);
|
|
renderer->Updateframe();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ssd1306Time(void)
|
|
{
|
|
char line[12];
|
|
|
|
renderer->clearDisplay();
|
|
renderer->setTextSize(Settings.display_size);
|
|
renderer->setTextFont(Settings.display_font);
|
|
renderer->setCursor(0, 0);
|
|
snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
renderer->println(line);
|
|
snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year);
|
|
renderer->println(line);
|
|
renderer->Updateframe();
|
|
}
|
|
|
|
void Ssd1306Refresh(void)
|
|
{
|
|
if (!renderer) return;
|
|
|
|
if (Settings.display_mode) {
|
|
switch (Settings.display_mode) {
|
|
case 1:
|
|
Ssd1306Time();
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
Ssd1306PrintLog();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp02(byte function)
|
|
{
|
|
if (!I2cEnabled(XI2C_04)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
SSD1306InitDriver();
|
|
}
|
|
else if (XDSP_02 == Settings.display_model) {
|
|
switch (function) {
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
Ssd1306Refresh();
|
|
break;
|
|
#endif
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_03_matrix.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_03_matrix.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_MATRIX
|
|
|
|
#define XDSP_03 3
|
|
#define XI2C_05 5
|
|
|
|
#define MTX_MAX_SCREEN_BUFFER 80
|
|
|
|
#include <Wire.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_LEDBackpack.h>
|
|
|
|
Adafruit_8x8matrix *matrix[8];
|
|
uint8_t mtx_matrices = 0;
|
|
uint8_t mtx_state = 0;
|
|
uint8_t mtx_counter = 0;
|
|
int16_t mtx_x = 0;
|
|
int16_t mtx_y = 0;
|
|
|
|
|
|
char *mtx_buffer = nullptr;
|
|
|
|
uint8_t mtx_mode = 0;
|
|
uint8_t mtx_loop = 0;
|
|
uint8_t mtx_done = 0;
|
|
|
|
|
|
|
|
void MatrixWrite(void)
|
|
{
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->writeDisplay();
|
|
}
|
|
}
|
|
|
|
void MatrixClear(void)
|
|
{
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->clear();
|
|
}
|
|
MatrixWrite();
|
|
}
|
|
|
|
void MatrixFixed(char* txt)
|
|
{
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->clear();
|
|
matrix[i]->setCursor(-i *8, 0);
|
|
matrix[i]->print(txt);
|
|
matrix[i]->setBrightness(Settings.display_dimmer);
|
|
}
|
|
MatrixWrite();
|
|
}
|
|
|
|
void MatrixCenter(char* txt)
|
|
{
|
|
int offset;
|
|
|
|
int len = strlen(txt);
|
|
offset = (len < 8) ? offset = ((mtx_matrices *8) - (len *6)) / 2 : 0;
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->clear();
|
|
matrix[i]->setCursor(-(i *8)+offset, 0);
|
|
matrix[i]->print(txt);
|
|
matrix[i]->setBrightness(Settings.display_dimmer);
|
|
}
|
|
MatrixWrite();
|
|
}
|
|
|
|
void MatrixScrollLeft(char* txt, int loop)
|
|
{
|
|
switch (mtx_state) {
|
|
case 1:
|
|
mtx_state = 2;
|
|
|
|
mtx_x = 8 * mtx_matrices;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), txt);
|
|
|
|
disp_refresh = Settings.display_refresh;
|
|
case 2:
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->clear();
|
|
matrix[i]->setCursor(mtx_x - i *8, 0);
|
|
matrix[i]->print(txt);
|
|
matrix[i]->setBrightness(Settings.display_dimmer);
|
|
}
|
|
MatrixWrite();
|
|
|
|
mtx_x--;
|
|
int16_t len = strlen(txt);
|
|
if (mtx_x < -(len *6)) { mtx_state = loop; }
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MatrixScrollUp(char* txt, int loop)
|
|
{
|
|
int wordcounter = 0;
|
|
char tmpbuf[200];
|
|
char *words[100];
|
|
|
|
|
|
|
|
char separators[] = " /";
|
|
|
|
switch (mtx_state) {
|
|
case 1:
|
|
mtx_state = 2;
|
|
|
|
mtx_y = 8;
|
|
mtx_counter = 0;
|
|
disp_refresh = Settings.display_refresh;
|
|
case 2:
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
strlcpy(tmpbuf, txt, sizeof(tmpbuf));
|
|
char *p = strtok(tmpbuf, separators);
|
|
while (p != nullptr && wordcounter < 40) {
|
|
words[wordcounter++] = p;
|
|
p = strtok(nullptr, separators);
|
|
}
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->clear();
|
|
for (uint32_t j = 0; j < wordcounter; j++) {
|
|
matrix[i]->setCursor(-i *8, mtx_y + (j *8));
|
|
matrix[i]->println(words[j]);
|
|
}
|
|
matrix[i]->setBrightness(Settings.display_dimmer);
|
|
}
|
|
MatrixWrite();
|
|
if (((mtx_y %8) == 0) && mtx_counter) {
|
|
mtx_counter--;
|
|
} else {
|
|
mtx_y--;
|
|
mtx_counter = STATES * 1;
|
|
}
|
|
if (mtx_y < -(wordcounter *8)) { mtx_state = loop; }
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void MatrixInitMode(void)
|
|
{
|
|
for (uint32_t i = 0; i < mtx_matrices; i++) {
|
|
matrix[i]->setRotation(Settings.display_rotate);
|
|
matrix[i]->setBrightness(Settings.display_dimmer);
|
|
matrix[i]->blinkRate(0);
|
|
matrix[i]->setTextWrap(false);
|
|
|
|
|
|
matrix[i]->cp437(true);
|
|
}
|
|
MatrixClear();
|
|
}
|
|
|
|
void MatrixInit(uint8_t mode)
|
|
{
|
|
switch(mode) {
|
|
case DISPLAY_INIT_MODE:
|
|
MatrixInitMode();
|
|
break;
|
|
case DISPLAY_INIT_PARTIAL:
|
|
case DISPLAY_INIT_FULL:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MatrixInitDriver(void)
|
|
{
|
|
mtx_buffer = (char*)(malloc(MTX_MAX_SCREEN_BUFFER));
|
|
if (mtx_buffer != nullptr) {
|
|
if (!Settings.display_model) {
|
|
if (I2cSetDevice(Settings.display_address[1])) {
|
|
Settings.display_model = XDSP_03;
|
|
}
|
|
}
|
|
|
|
if (XDSP_03 == Settings.display_model) {
|
|
mtx_state = 1;
|
|
for (mtx_matrices = 0; mtx_matrices < 8; mtx_matrices++) {
|
|
if (Settings.display_address[mtx_matrices]) {
|
|
I2cSetActiveFound(Settings.display_address[mtx_matrices], "8x8Matrix");
|
|
matrix[mtx_matrices] = new Adafruit_8x8matrix();
|
|
matrix[mtx_matrices]->begin(Settings.display_address[mtx_matrices]);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Settings.display_width = mtx_matrices * 8;
|
|
Settings.display_height = 8;
|
|
|
|
MatrixInitMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MatrixOnOff(void)
|
|
{
|
|
if (!disp_power) { MatrixClear(); }
|
|
}
|
|
|
|
void MatrixDrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag)
|
|
{
|
|
strlcpy(mtx_buffer, str, MTX_MAX_SCREEN_BUFFER);
|
|
mtx_mode = x &1;
|
|
mtx_loop = y &1;
|
|
if (!mtx_state) { mtx_state = 1; }
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void MatrixPrintLog(uint8_t direction)
|
|
{
|
|
char* txt = (!mtx_done) ? DisplayLogBuffer('\370') : mtx_buffer;
|
|
if (txt != nullptr) {
|
|
if (!mtx_state) { mtx_state = 1; }
|
|
|
|
if (!mtx_done) {
|
|
|
|
uint8_t space = 0;
|
|
uint8_t max_cols = (disp_log_buffer_cols < MTX_MAX_SCREEN_BUFFER) ? disp_log_buffer_cols : MTX_MAX_SCREEN_BUFFER;
|
|
mtx_buffer[0] = '\0';
|
|
uint8_t i = 0;
|
|
while ((txt[i] != '\0') && (i < max_cols)) {
|
|
if (txt[i] == ' ') {
|
|
space++;
|
|
} else {
|
|
space = 0;
|
|
}
|
|
if (space < 2) {
|
|
strncat(mtx_buffer, (const char*)txt +i, (strlen(mtx_buffer) < MTX_MAX_SCREEN_BUFFER -1) ? 1 : 0);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), mtx_buffer);
|
|
|
|
mtx_done = 1;
|
|
}
|
|
|
|
if (direction) {
|
|
MatrixScrollUp(mtx_buffer, 0);
|
|
} else {
|
|
MatrixScrollLeft(mtx_buffer, 0);
|
|
}
|
|
if (!mtx_state) { mtx_done = 0; }
|
|
} else {
|
|
char disp_time[9];
|
|
|
|
snprintf_P(disp_time, sizeof(disp_time), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
MatrixFixed(disp_time);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void MatrixRefresh(void)
|
|
{
|
|
if (disp_power) {
|
|
switch (Settings.display_mode) {
|
|
case 0: {
|
|
switch (mtx_mode) {
|
|
case 0:
|
|
MatrixScrollLeft(mtx_buffer, mtx_loop);
|
|
break;
|
|
case 1:
|
|
MatrixScrollUp(mtx_buffer, mtx_loop);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case 2: {
|
|
char disp_date[9];
|
|
snprintf_P(disp_date, sizeof(disp_date), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year -2000);
|
|
MatrixFixed(disp_date);
|
|
break;
|
|
}
|
|
case 3: {
|
|
char disp_day[10];
|
|
snprintf_P(disp_day, sizeof(disp_day), PSTR("%d %s"), RtcTime.day_of_month, RtcTime.name_of_month);
|
|
MatrixCenter(disp_day);
|
|
break;
|
|
}
|
|
case 4:
|
|
MatrixPrintLog(0);
|
|
break;
|
|
case 1:
|
|
case 5:
|
|
MatrixPrintLog(1);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp03(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_05)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
MatrixInitDriver();
|
|
}
|
|
else if (XDSP_03 == Settings.display_model) {
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
case FUNC_DISPLAY_INIT:
|
|
MatrixInit(dsp_init);
|
|
break;
|
|
case FUNC_DISPLAY_EVERY_50_MSECOND:
|
|
MatrixRefresh();
|
|
break;
|
|
case FUNC_DISPLAY_POWER:
|
|
MatrixOnOff();
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_STRING:
|
|
MatrixDrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_04_ili9341.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_04_ili9341.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_ILI9341
|
|
|
|
#define XDSP_04 4
|
|
|
|
#define TFT_TOP 16
|
|
#define TFT_BOTTOM 16
|
|
#define TFT_FONT_WIDTH 6
|
|
#define TFT_FONT_HEIGTH 8
|
|
|
|
#include <SPI.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_ILI9341.h>
|
|
|
|
Adafruit_ILI9341 *tft;
|
|
|
|
uint16_t tft_scroll;
|
|
|
|
|
|
|
|
void Ili9341InitMode(void)
|
|
{
|
|
tft->setRotation(Settings.display_rotate);
|
|
tft->invertDisplay(0);
|
|
tft->fillScreen(ILI9341_BLACK);
|
|
tft->setTextWrap(false);
|
|
tft->cp437(true);
|
|
if (!Settings.display_mode) {
|
|
tft->setCursor(0, 0);
|
|
tft->setTextColor(ILI9341_WHITE, ILI9341_BLACK);
|
|
tft->setTextSize(1);
|
|
} else {
|
|
tft->setScrollMargins(TFT_TOP, TFT_BOTTOM);
|
|
tft->setCursor(0, 0);
|
|
tft->setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
|
|
tft->setTextSize(2);
|
|
|
|
|
|
tft_scroll = TFT_TOP;
|
|
}
|
|
}
|
|
|
|
void Ili9341Init(uint8_t mode)
|
|
{
|
|
switch(mode) {
|
|
case DISPLAY_INIT_MODE:
|
|
Ili9341InitMode();
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
if (Settings.display_rotate) {
|
|
DisplayClearScreenBuffer();
|
|
}
|
|
#endif
|
|
break;
|
|
case DISPLAY_INIT_PARTIAL:
|
|
case DISPLAY_INIT_FULL:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Ili9341InitDriver(void)
|
|
{
|
|
if (!Settings.display_model) {
|
|
Settings.display_model = XDSP_04;
|
|
}
|
|
|
|
if (XDSP_04 == Settings.display_model) {
|
|
if (Settings.display_width != ILI9341_TFTWIDTH) {
|
|
Settings.display_width = ILI9341_TFTWIDTH;
|
|
}
|
|
if (Settings.display_height != ILI9341_TFTHEIGHT) {
|
|
Settings.display_height = ILI9341_TFTHEIGHT;
|
|
}
|
|
tft = new Adafruit_ILI9341(pin[GPIO_SPI_CS], pin[GPIO_SPI_DC]);
|
|
tft->begin();
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
if (Settings.display_rotate) {
|
|
DisplayAllocScreenBuffer();
|
|
}
|
|
#endif
|
|
|
|
Ili9341InitMode();
|
|
}
|
|
}
|
|
|
|
void Ili9341Clear(void)
|
|
{
|
|
tft->fillScreen(ILI9341_BLACK);
|
|
tft->setCursor(0, 0);
|
|
}
|
|
|
|
void Ili9341DrawStringAt(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t flag)
|
|
{
|
|
uint16_t active_color = ILI9341_WHITE;
|
|
|
|
tft->setTextSize(Settings.display_size);
|
|
if (!flag) {
|
|
tft->setCursor(x, y);
|
|
} else {
|
|
tft->setCursor((x-1) * TFT_FONT_WIDTH * Settings.display_size, (y-1) * TFT_FONT_HEIGTH * Settings.display_size);
|
|
}
|
|
if (color) { active_color = color; }
|
|
tft->setTextColor(active_color, ILI9341_BLACK);
|
|
tft->println(str);
|
|
}
|
|
|
|
void Ili9341DisplayOnOff(uint8_t on)
|
|
{
|
|
|
|
|
|
if (pin[GPIO_BACKLIGHT] < 99) {
|
|
pinMode(pin[GPIO_BACKLIGHT], OUTPUT);
|
|
digitalWrite(pin[GPIO_BACKLIGHT], on);
|
|
}
|
|
}
|
|
|
|
void Ili9341OnOff(void)
|
|
{
|
|
Ili9341DisplayOnOff(disp_power);
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void Ili9341PrintLog(void)
|
|
{
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
if (Settings.display_rotate) {
|
|
if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); }
|
|
}
|
|
|
|
char* txt = DisplayLogBuffer('\370');
|
|
if (txt != nullptr) {
|
|
uint8_t size = Settings.display_size;
|
|
uint16_t theight = size * TFT_FONT_HEIGTH;
|
|
|
|
tft->setTextSize(size);
|
|
tft->setTextColor(ILI9341_CYAN, ILI9341_BLACK);
|
|
if (!Settings.display_rotate) {
|
|
tft->setCursor(0, tft_scroll);
|
|
tft->fillRect(0, tft_scroll, tft->width(), theight, ILI9341_BLACK);
|
|
tft->print(txt);
|
|
tft_scroll += theight;
|
|
if (tft_scroll >= (tft->height() - TFT_BOTTOM)) {
|
|
tft_scroll = TFT_TOP;
|
|
}
|
|
tft->scrollTo(tft_scroll);
|
|
} else {
|
|
uint8_t last_row = Settings.display_rows -1;
|
|
|
|
tft_scroll = theight;
|
|
tft->setCursor(0, tft_scroll);
|
|
for (uint32_t i = 0; i < last_row; i++) {
|
|
strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols);
|
|
|
|
tft->print(disp_screen_buffer[i]);
|
|
tft_scroll += theight;
|
|
tft->setCursor(0, tft_scroll);
|
|
delay(1);
|
|
}
|
|
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
|
|
DisplayFillScreen(last_row);
|
|
tft->print(disp_screen_buffer[last_row]);
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), txt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ili9341Refresh(void)
|
|
{
|
|
if (Settings.display_mode) {
|
|
char tftdt[Settings.display_cols[0] +1];
|
|
char date4[11];
|
|
char space[Settings.display_cols[0] - 17];
|
|
char time[9];
|
|
|
|
tft->setTextSize(2);
|
|
tft->setTextColor(ILI9341_YELLOW, ILI9341_RED);
|
|
tft->setCursor(0, 0);
|
|
|
|
snprintf_P(date4, sizeof(date4), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year);
|
|
memset(space, 0x20, sizeof(space));
|
|
space[sizeof(space) -1] = '\0';
|
|
snprintf_P(time, sizeof(time), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
snprintf_P(tftdt, sizeof(tftdt), PSTR("%s%s%s"), date4, space, time);
|
|
|
|
tft->print(tftdt);
|
|
|
|
switch (Settings.display_mode) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
Ili9341PrintLog();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp04(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (spi_flg) {
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
Ili9341InitDriver();
|
|
}
|
|
else if (XDSP_04 == Settings.display_model) {
|
|
|
|
if (!dsp_color) { dsp_color = ILI9341_WHITE; }
|
|
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
case FUNC_DISPLAY_INIT:
|
|
Ili9341Init(dsp_init);
|
|
break;
|
|
case FUNC_DISPLAY_POWER:
|
|
Ili9341OnOff();
|
|
break;
|
|
case FUNC_DISPLAY_CLEAR:
|
|
Ili9341Clear();
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_HLINE:
|
|
tft->writeFastHLine(dsp_x, dsp_y, dsp_len, dsp_color);
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_VLINE:
|
|
tft->writeFastVLine(dsp_x, dsp_y, dsp_len, dsp_color);
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_LINE:
|
|
tft->writeLine(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color);
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_CIRCLE:
|
|
tft->drawCircle(dsp_x, dsp_y, dsp_rad, dsp_color);
|
|
break;
|
|
case FUNC_DISPLAY_FILL_CIRCLE:
|
|
tft->fillCircle(dsp_x, dsp_y, dsp_rad, dsp_color);
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_RECTANGLE:
|
|
tft->drawRect(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color);
|
|
break;
|
|
case FUNC_DISPLAY_FILL_RECTANGLE:
|
|
tft->fillRect(dsp_x, dsp_y, dsp_x2, dsp_y2, dsp_color);
|
|
break;
|
|
|
|
|
|
|
|
case FUNC_DISPLAY_TEXT_SIZE:
|
|
tft->setTextSize(Settings.display_size);
|
|
break;
|
|
case FUNC_DISPLAY_FONT_SIZE:
|
|
|
|
break;
|
|
case FUNC_DISPLAY_DRAW_STRING:
|
|
Ili9341DrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag);
|
|
break;
|
|
case FUNC_DISPLAY_ONOFF:
|
|
Ili9341DisplayOnOff(dsp_on);
|
|
break;
|
|
case FUNC_DISPLAY_ROTATION:
|
|
tft->setRotation(Settings.display_rotate);
|
|
break;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
Ili9341Refresh();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_EPAPER_29
|
|
|
|
#define XDSP_05 5
|
|
|
|
#define EPD_TOP 12
|
|
#define EPD_FONT_HEIGTH 12
|
|
|
|
#define COLORED 1
|
|
#define UNCOLORED 0
|
|
|
|
|
|
|
|
#define USE_TINY_FONT
|
|
|
|
#include <epd2in9.h>
|
|
#include <epdpaint.h>
|
|
|
|
|
|
extern uint8_t *buffer;
|
|
uint16_t epd_scroll;
|
|
|
|
Epd *epd;
|
|
|
|
|
|
|
|
void EpdInitDriver29()
|
|
{
|
|
if (!Settings.display_model) {
|
|
Settings.display_model = XDSP_05;
|
|
}
|
|
|
|
if (XDSP_05 == Settings.display_model) {
|
|
if (Settings.display_width != EPD_WIDTH) {
|
|
Settings.display_width = EPD_WIDTH;
|
|
}
|
|
if (Settings.display_height != EPD_HEIGHT) {
|
|
Settings.display_height = EPD_HEIGHT;
|
|
}
|
|
|
|
|
|
if (buffer) free(buffer);
|
|
buffer=(unsigned char*)calloc((EPD_WIDTH * EPD_HEIGHT) / 8,1);
|
|
if (!buffer) return;
|
|
|
|
|
|
epd = new Epd(EPD_WIDTH,EPD_HEIGHT);
|
|
|
|
|
|
if ((pin[GPIO_SPI_CS] < 99) && (pin[GPIO_SPI_CLK] < 99) && (pin[GPIO_SPI_MOSI] < 99)) {
|
|
epd->Begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EPD: HardSPI CS %d, CLK %d, MOSI %d"),pin[GPIO_SPI_CS], pin[GPIO_SPI_CLK], pin[GPIO_SPI_MOSI]);
|
|
}
|
|
else if ((pin[GPIO_SSPI_CS] < 99) && (pin[GPIO_SSPI_SCLK] < 99) && (pin[GPIO_SSPI_MOSI] < 99)) {
|
|
epd->Begin(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EPD: SoftSPI CS %d, CLK %d, MOSI %d"),pin[GPIO_SSPI_CS], pin[GPIO_SSPI_SCLK], pin[GPIO_SSPI_MOSI]);
|
|
} else {
|
|
free(buffer);
|
|
return;
|
|
}
|
|
|
|
renderer = epd;
|
|
epd->Init(DISPLAY_INIT_FULL);
|
|
epd->Init(DISPLAY_INIT_PARTIAL);
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font);
|
|
|
|
renderer->setTextColor(1,0);
|
|
|
|
#ifdef SHOW_SPLASH
|
|
|
|
renderer->setTextFont(1);
|
|
renderer->DrawStringAt(50, 50, "Waveshare E-Paper Display!", COLORED,0);
|
|
renderer->Updateframe();
|
|
delay(1000);
|
|
renderer->fillScreen(0);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
#define EPD_FONT_HEIGTH 12
|
|
void EpdPrintLog29(void)
|
|
{
|
|
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
|
|
if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); }
|
|
|
|
|
|
char* txt = DisplayLogBuffer('\040');
|
|
if (txt != nullptr) {
|
|
uint8_t size = Settings.display_size;
|
|
uint16_t theight = size * EPD_FONT_HEIGTH;
|
|
|
|
renderer->setTextFont(size);
|
|
uint8_t last_row = Settings.display_rows -1;
|
|
|
|
|
|
epd_scroll = 0;
|
|
for (uint32_t i = 0; i < last_row; i++) {
|
|
strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols);
|
|
renderer->DrawStringAt(0, epd_scroll, disp_screen_buffer[i], COLORED, 0);
|
|
epd_scroll += theight;
|
|
}
|
|
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
|
|
DisplayFillScreen(last_row);
|
|
renderer->DrawStringAt(0, epd_scroll, disp_screen_buffer[last_row], COLORED, 0);
|
|
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "[%s]"), txt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EpdRefresh29(void)
|
|
{
|
|
if (Settings.display_mode) {
|
|
|
|
if (!renderer) return;
|
|
# 165 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_05_epaper_29.ino"
|
|
switch (Settings.display_mode) {
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
EpdPrintLog29();
|
|
renderer->Updateframe();
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp05(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
EpdInitDriver29();
|
|
}
|
|
else if (XDSP_05 == Settings.display_model) {
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
EpdRefresh29();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_06_epaper_42.ino"
|
|
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_06_epaper_42.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_EPAPER_42
|
|
|
|
#define XDSP_06 6
|
|
|
|
#define COLORED42 1
|
|
#define UNCOLORED42 0
|
|
|
|
|
|
|
|
#define USE_TINY_FONT
|
|
|
|
#include <epd4in2.h>
|
|
#include <epdpaint.h>
|
|
|
|
extern uint8_t *buffer;
|
|
|
|
Epd42 *epd42;
|
|
|
|
|
|
|
|
|
|
void EpdInitDriver42()
|
|
{
|
|
if (!Settings.display_model) {
|
|
Settings.display_model = XDSP_06;
|
|
}
|
|
|
|
if (XDSP_06 == Settings.display_model) {
|
|
|
|
if (Settings.display_width != EPD_WIDTH42) {
|
|
Settings.display_width = EPD_WIDTH42;
|
|
}
|
|
if (Settings.display_height != EPD_HEIGHT42) {
|
|
Settings.display_height = EPD_HEIGHT42;
|
|
}
|
|
|
|
|
|
if (buffer) free(buffer);
|
|
buffer=(unsigned char*)calloc((EPD_WIDTH42 * EPD_HEIGHT42) / 8,1);
|
|
if (!buffer) return;
|
|
|
|
|
|
epd42 = new Epd42(EPD_WIDTH42,EPD_HEIGHT42);
|
|
|
|
#ifdef USE_SPI
|
|
if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)) {
|
|
epd42->Begin(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]);
|
|
} else {
|
|
free(buffer);
|
|
return;
|
|
}
|
|
#else
|
|
if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)) {
|
|
epd42->Begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]);
|
|
} else {
|
|
free(buffer);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
renderer = epd42;
|
|
|
|
epd42->Init();
|
|
|
|
renderer->fillScreen(0);
|
|
|
|
|
|
epd42->Init(DISPLAY_INIT_FULL);
|
|
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font);
|
|
|
|
epd42->ClearFrame();
|
|
renderer->Updateframe();
|
|
delay(3000);
|
|
renderer->setTextColor(1,0);
|
|
|
|
#ifdef SHOW_SPLASH
|
|
|
|
renderer->setTextFont(2);
|
|
renderer->DrawStringAt(50, 140, "Waveshare E-Paper!", COLORED42,0);
|
|
renderer->Updateframe();
|
|
delay(350);
|
|
renderer->fillScreen(0);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void EpdRefresh42()
|
|
{
|
|
if (Settings.display_mode) {
|
|
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
EpdInitDriver42();
|
|
}
|
|
else if (XDSP_06 == Settings.display_model) {
|
|
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
EpdRefresh42();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_07_sh1106.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_07_sh1106.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_SH1106
|
|
|
|
#define OLED_RESET 4
|
|
|
|
#define SPRINT(A) char str[32];sprintf(str,"val: %d ",A);Serial.println((char*)str);
|
|
|
|
extern uint8_t *buffer;
|
|
|
|
#define XDSP_07 7
|
|
#define XI2C_06 6
|
|
|
|
#define OLED_ADDRESS1 0x3C
|
|
#define OLED_ADDRESS2 0x3D
|
|
|
|
#define OLED_BUFFER_COLS 40
|
|
#define OLED_BUFFER_ROWS 16
|
|
|
|
#define OLED_FONT_WIDTH 6
|
|
#define OLED_FONT_HEIGTH 8
|
|
|
|
#include <Wire.h>
|
|
#include <renderer.h>
|
|
#include <Adafruit_SH1106.h>
|
|
|
|
Adafruit_SH1106 *oled1106;
|
|
|
|
|
|
|
|
|
|
void SH1106InitDriver()
|
|
{
|
|
if (!Settings.display_model) {
|
|
if (I2cSetDevice(OLED_ADDRESS1)) {
|
|
Settings.display_address[0] = OLED_ADDRESS1;
|
|
Settings.display_model = XDSP_07;
|
|
}
|
|
else if (I2cSetDevice(OLED_ADDRESS2)) {
|
|
Settings.display_address[0] = OLED_ADDRESS2;
|
|
Settings.display_model = XDSP_07;
|
|
}
|
|
}
|
|
|
|
if (XDSP_07 == Settings.display_model) {
|
|
I2cSetActiveFound(Settings.display_address[0], "SH1106");
|
|
|
|
if (Settings.display_width != SH1106_LCDWIDTH) {
|
|
Settings.display_width = SH1106_LCDWIDTH;
|
|
}
|
|
if (Settings.display_height != SH1106_LCDHEIGHT) {
|
|
Settings.display_height = SH1106_LCDHEIGHT;
|
|
}
|
|
|
|
|
|
if (buffer) free(buffer);
|
|
buffer=(unsigned char*)calloc((SH1106_LCDWIDTH * SH1106_LCDHEIGHT) / 8,1);
|
|
if (!buffer) return;
|
|
|
|
|
|
oled1106 = new Adafruit_SH1106(SH1106_LCDWIDTH,SH1106_LCDHEIGHT);
|
|
renderer=oled1106;
|
|
renderer->Begin(SH1106_SWITCHCAPVCC, Settings.display_address[0],0);
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font);
|
|
renderer->setTextColor(1,0);
|
|
|
|
#ifdef SHOW_SPLASH
|
|
renderer->setTextFont(0);
|
|
renderer->setTextSize(2);
|
|
renderer->setCursor(20,20);
|
|
renderer->println(F("SH1106"));
|
|
renderer->Updateframe();
|
|
renderer->DisplayOnff(1);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void SH1106PrintLog(void)
|
|
{
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); }
|
|
|
|
char* txt = DisplayLogBuffer('\370');
|
|
if (txt != NULL) {
|
|
uint8_t last_row = Settings.display_rows -1;
|
|
|
|
renderer->clearDisplay();
|
|
renderer->setTextSize(Settings.display_size);
|
|
renderer->setCursor(0,0);
|
|
for (byte i = 0; i < last_row; i++) {
|
|
strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols);
|
|
renderer->println(disp_screen_buffer[i]);
|
|
}
|
|
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
|
|
DisplayFillScreen(last_row);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
|
|
|
|
renderer->println(disp_screen_buffer[last_row]);
|
|
renderer->Updateframe();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SH1106Time(void)
|
|
{
|
|
char line[12];
|
|
|
|
renderer->clearDisplay();
|
|
renderer->setTextSize(Settings.display_size);
|
|
renderer->setTextFont(Settings.display_font);
|
|
renderer->setCursor(0, 0);
|
|
snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
renderer->println(line);
|
|
snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year);
|
|
renderer->println(line);
|
|
renderer->Updateframe();
|
|
}
|
|
|
|
void SH1106Refresh(void)
|
|
{
|
|
if (!renderer) return;
|
|
if (Settings.display_mode) {
|
|
switch (Settings.display_mode) {
|
|
case 1:
|
|
SH1106Time();
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
SH1106PrintLog();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xdsp07(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_06)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
SH1106InitDriver();
|
|
}
|
|
else if (XDSP_07 == Settings.display_model) {
|
|
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
SH1106Refresh();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_08_ILI9488.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_08_ILI9488.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_ILI9488
|
|
|
|
#define XDSP_08 8
|
|
#define XI2C_38 38
|
|
|
|
#define COLORED 1
|
|
#define UNCOLORED 0
|
|
|
|
|
|
#define FT6236_address 0x38
|
|
|
|
|
|
|
|
#define USE_TINY_FONT
|
|
|
|
|
|
#include <ILI9488.h>
|
|
#include <FT6236.h>
|
|
|
|
TouchLocation ili9488_pLoc;
|
|
uint8_t ili9488_ctouch_counter = 0;
|
|
|
|
|
|
#define BACKPLANE_PIN 2
|
|
|
|
extern uint8_t *buffer;
|
|
extern uint8_t color_type;
|
|
ILI9488 *ili9488;
|
|
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
extern VButton *buttons[];
|
|
#endif
|
|
|
|
extern const uint16_t picture[];
|
|
uint8_t FT6236_found;
|
|
|
|
|
|
|
|
void ILI9488_InitDriver()
|
|
{
|
|
if (!Settings.display_model) {
|
|
Settings.display_model = XDSP_08;
|
|
}
|
|
|
|
if (XDSP_08 == Settings.display_model) {
|
|
|
|
if (Settings.display_width != ILI9488_TFTWIDTH) {
|
|
Settings.display_width = ILI9488_TFTWIDTH;
|
|
}
|
|
if (Settings.display_height != ILI9488_TFTHEIGHT) {
|
|
Settings.display_height = ILI9488_TFTHEIGHT;
|
|
}
|
|
|
|
|
|
buffer=NULL;
|
|
|
|
|
|
fg_color = ILI9488_WHITE;
|
|
bg_color = ILI9488_BLACK;
|
|
|
|
uint8_t bppin=BACKPLANE_PIN;
|
|
if (pin[GPIO_BACKLIGHT]<99) {
|
|
bppin=pin[GPIO_BACKLIGHT];
|
|
}
|
|
|
|
|
|
if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)){
|
|
ili9488 = new ILI9488(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK],bppin);
|
|
} else {
|
|
if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)) {
|
|
ili9488 = new ILI9488(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK],bppin);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
SPI.begin();
|
|
ili9488->begin();
|
|
renderer = ili9488;
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font);
|
|
|
|
#ifdef SHOW_SPLASH
|
|
|
|
renderer->setTextFont(2);
|
|
renderer->setTextColor(ILI9488_WHITE,ILI9488_BLACK);
|
|
renderer->DrawStringAt(50, 50, "ILI9488 TFT Display!", ILI9488_WHITE,0);
|
|
delay(1000);
|
|
|
|
|
|
#endif
|
|
|
|
color_type = COLOR_COLOR;
|
|
|
|
|
|
if (I2cEnabled(XI2C_38) && I2cSetDevice(FT6236_address)) {
|
|
FT6236begin(FT6236_address);
|
|
FT6236_found=1;
|
|
I2cSetActiveFound(FT6236_address, "FT6236");
|
|
} else {
|
|
FT6236_found=0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
void ILI9488_MQTT(uint8_t count,const char *cp) {
|
|
ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7);
|
|
MqttPublishTeleSensor();
|
|
}
|
|
|
|
void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr) {
|
|
buttons[count]->xdrawButton(pwr);
|
|
if (pwr) buttons[count]->vpower|=0x80;
|
|
else buttons[count]->vpower&=0x7f;
|
|
}
|
|
|
|
void FT6236Check() {
|
|
uint16_t temp;
|
|
uint8_t rbutt=0,vbutt=0;
|
|
ili9488_ctouch_counter++;
|
|
if (2 == ili9488_ctouch_counter) {
|
|
|
|
ili9488_ctouch_counter=0;
|
|
if (FT6236readTouchLocation(&ili9488_pLoc,1)) {
|
|
|
|
if (renderer) {
|
|
uint8_t rot=renderer->getRotation();
|
|
switch (rot) {
|
|
case 0:
|
|
temp=ili9488_pLoc.y;
|
|
ili9488_pLoc.y=renderer->height()-ili9488_pLoc.x;
|
|
ili9488_pLoc.x=temp;
|
|
break;
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
break;
|
|
case 3:
|
|
temp=ili9488_pLoc.y;
|
|
ili9488_pLoc.y=ili9488_pLoc.x;
|
|
ili9488_pLoc.x=renderer->width()-temp;
|
|
break;
|
|
}
|
|
|
|
for (uint8_t count=0; count<MAXBUTTONS; count++) {
|
|
if (buttons[count]) {
|
|
uint8_t bflags=buttons[count]->vpower&0x7f;
|
|
if (buttons[count]->contains(ili9488_pLoc.x,ili9488_pLoc.y)) {
|
|
|
|
buttons[count]->press(true);
|
|
if (buttons[count]->justPressed()) {
|
|
if (!bflags) {
|
|
uint8_t pwr=bitRead(power,rbutt);
|
|
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
|
|
ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
|
|
ILI9488_RDW_BUTT(count,!pwr);
|
|
}
|
|
} else {
|
|
|
|
const char *cp;
|
|
if (bflags==1) {
|
|
|
|
buttons[count]->vpower^=0x80;
|
|
cp="TBT";
|
|
} else {
|
|
|
|
buttons[count]->vpower|=0x80;
|
|
cp="PBT";
|
|
}
|
|
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
|
|
ILI9488_MQTT(count,cp);
|
|
}
|
|
}
|
|
}
|
|
if (!bflags) {
|
|
rbutt++;
|
|
} else {
|
|
vbutt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
for (uint8_t count=0; count<MAXBUTTONS; count++) {
|
|
if (buttons[count]) {
|
|
uint8_t bflags=buttons[count]->vpower&0x7f;
|
|
buttons[count]->press(false);
|
|
if (buttons[count]->justReleased()) {
|
|
uint8_t bflags=buttons[count]->vpower&0x7f;
|
|
if (bflags>0) {
|
|
if (bflags>1) {
|
|
|
|
buttons[count]->vpower&=0x7f;
|
|
ILI9488_MQTT(count,"PBT");
|
|
}
|
|
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
|
|
}
|
|
}
|
|
if (!bflags) {
|
|
|
|
uint8_t pwr=bitRead(power,rbutt);
|
|
uint8_t vpwr=(buttons[count]->vpower&0x80)>>7;
|
|
if (pwr!=vpwr) {
|
|
ILI9488_RDW_BUTT(count,pwr);
|
|
}
|
|
rbutt++;
|
|
}
|
|
}
|
|
}
|
|
ili9488_pLoc.x=0;
|
|
ili9488_pLoc.y=0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
bool Xdsp08(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
ILI9488_InitDriver();
|
|
}
|
|
else if (XDSP_08 == Settings.display_model) {
|
|
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
case FUNC_DISPLAY_EVERY_50_MSECOND:
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
if (FT6236_found) FT6236Check();
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_09_SSD1351.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_09_SSD1351.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_SSD1351
|
|
|
|
#define XDSP_09 9
|
|
|
|
#define COLORED 1
|
|
#define UNCOLORED 0
|
|
|
|
|
|
|
|
|
|
#define USE_TINY_FONT
|
|
|
|
#include <SSD1351.h>
|
|
|
|
extern uint8_t *buffer;
|
|
extern uint8_t color_type;
|
|
SSD1351 *ssd1351;
|
|
|
|
|
|
|
|
void SSD1351_InitDriver() {
|
|
if (!Settings.display_model) {
|
|
Settings.display_model = XDSP_09;
|
|
}
|
|
|
|
if (XDSP_09 == Settings.display_model) {
|
|
|
|
if (Settings.display_width != SSD1351_WIDTH) {
|
|
Settings.display_width = SSD1351_WIDTH;
|
|
}
|
|
if (Settings.display_height != SSD1351_HEIGHT) {
|
|
Settings.display_height = SSD1351_HEIGHT;
|
|
}
|
|
|
|
buffer=0;
|
|
|
|
|
|
fg_color = SSD1351_WHITE;
|
|
bg_color = SSD1351_BLACK;
|
|
|
|
|
|
if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]<99) && (pin[GPIO_SSPI_SCLK]<99)){
|
|
ssd1351 = new SSD1351(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_SCLK]);
|
|
} else {
|
|
if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]<99) && (pin[GPIO_SPI_CLK]<99)){
|
|
ssd1351 = new SSD1351(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_CLK]);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
delay(100);
|
|
SPI.begin();
|
|
ssd1351->begin();
|
|
renderer = ssd1351;
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font);
|
|
renderer->dim(Settings.display_dimmer);
|
|
|
|
#ifdef SHOW_SPLASH
|
|
|
|
renderer->setTextFont(2);
|
|
renderer->setTextColor(SSD1351_WHITE,SSD1351_BLACK);
|
|
renderer->DrawStringAt(10, 60, "SSD1351", SSD1351_RED,0);
|
|
delay(1000);
|
|
|
|
#endif
|
|
color_type = COLOR_COLOR;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
void SSD1351PrintLog(void)
|
|
{
|
|
disp_refresh--;
|
|
if (!disp_refresh) {
|
|
disp_refresh = Settings.display_refresh;
|
|
if (!disp_screen_buffer_cols) { DisplayAllocScreenBuffer(); }
|
|
|
|
char* txt = DisplayLogBuffer('\370');
|
|
if (txt != NULL) {
|
|
uint8_t last_row = Settings.display_rows -1;
|
|
|
|
renderer->clearDisplay();
|
|
renderer->setTextSize(Settings.display_size);
|
|
renderer->setCursor(0,0);
|
|
for (byte i = 0; i < last_row; i++) {
|
|
strlcpy(disp_screen_buffer[i], disp_screen_buffer[i +1], disp_screen_buffer_cols);
|
|
renderer->println(disp_screen_buffer[i]);
|
|
}
|
|
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
|
|
DisplayFillScreen(last_row);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
|
|
|
|
renderer->println(disp_screen_buffer[last_row]);
|
|
renderer->Updateframe();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SSD1351Time(void)
|
|
{
|
|
char line[12];
|
|
|
|
renderer->clearDisplay();
|
|
renderer->setTextSize(2);
|
|
renderer->setCursor(0, 0);
|
|
snprintf_P(line, sizeof(line), PSTR(" %02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
|
|
renderer->println(line);
|
|
snprintf_P(line, sizeof(line), PSTR("%02d" D_MONTH_DAY_SEPARATOR "%02d" D_YEAR_MONTH_SEPARATOR "%04d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year);
|
|
renderer->println(line);
|
|
renderer->Updateframe();
|
|
}
|
|
|
|
void SSD1351Refresh(void)
|
|
{
|
|
if (Settings.display_mode) {
|
|
switch (Settings.display_mode) {
|
|
case 1:
|
|
SSD1351Time();
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
SSD1351PrintLog();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
bool Xdsp09(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
SSD1351_InitDriver();
|
|
}
|
|
else if (XDSP_09 == Settings.display_model) {
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
SSD1351Refresh();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_DISPLAY
|
|
#ifdef USE_DISPLAY_RA8876
|
|
|
|
#define XDSP_10 10
|
|
#define XI2C_39 39
|
|
|
|
#define COLORED 1
|
|
#define UNCOLORED 0
|
|
|
|
|
|
#define FT5316_address 0x38
|
|
|
|
|
|
|
|
#define USE_TINY_FONT
|
|
|
|
#include <RA8876.h>
|
|
#include <FT6236.h>
|
|
|
|
TouchLocation ra8876_pLoc;
|
|
uint8_t ra8876_ctouch_counter = 0;
|
|
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
extern VButton *buttons[];
|
|
#endif
|
|
|
|
extern uint8_t *buffer;
|
|
extern uint8_t color_type;
|
|
RA8876 *ra8876;
|
|
|
|
uint8_t FT5316_found;
|
|
|
|
|
|
void RA8876_InitDriver()
|
|
{
|
|
if (!Settings.display_model) {
|
|
Settings.display_model = XDSP_10;
|
|
}
|
|
|
|
if (XDSP_10 == Settings.display_model) {
|
|
|
|
if (Settings.display_width != RA8876_TFTWIDTH) {
|
|
Settings.display_width = RA8876_TFTWIDTH;
|
|
}
|
|
if (Settings.display_height != RA8876_TFTHEIGHT) {
|
|
Settings.display_height = RA8876_TFTHEIGHT;
|
|
}
|
|
buffer=0;
|
|
|
|
|
|
fg_color = RA8876_WHITE;
|
|
bg_color = RA8876_BLACK;
|
|
|
|
|
|
if ((pin[GPIO_SSPI_CS]<99) && (pin[GPIO_SSPI_MOSI]==13) && (pin[GPIO_SSPI_MISO]==12) && (pin[GPIO_SSPI_SCLK]==14)) {
|
|
ra8876 = new RA8876(pin[GPIO_SSPI_CS],pin[GPIO_SSPI_MOSI],pin[GPIO_SSPI_MISO],pin[GPIO_SSPI_SCLK],pin[GPIO_BACKLIGHT]);
|
|
} else {
|
|
if ((pin[GPIO_SPI_CS]<99) && (pin[GPIO_SPI_MOSI]==13) && (pin[GPIO_SPI_MISO]==12) && (pin[GPIO_SPI_CLK]==14)) {
|
|
ra8876 = new RA8876(pin[GPIO_SPI_CS],pin[GPIO_SPI_MOSI],pin[GPIO_SPI_MISO],pin[GPIO_SPI_CLK],pin[GPIO_BACKLIGHT]);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ra8876->begin();
|
|
renderer = ra8876;
|
|
renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font);
|
|
renderer->dim(Settings.display_dimmer);
|
|
|
|
|
|
#ifdef SHOW_SPLASH
|
|
|
|
renderer->setTextFont(2);
|
|
renderer->setTextColor(RA8876_WHITE,RA8876_BLACK);
|
|
renderer->DrawStringAt(600, 300, "RA8876", RA8876_RED,0);
|
|
delay(1000);
|
|
|
|
#endif
|
|
color_type = COLOR_COLOR;
|
|
|
|
if (I2cEnabled(XI2C_39) && I2cSetDevice(FT5316_address)) {
|
|
FT6236begin(FT5316_address);
|
|
FT5316_found=1;
|
|
I2cSetActiveFound(FT5316_address, "FT5316");
|
|
} else {
|
|
FT5316_found=0;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
void RA8876_MQTT(uint8_t count,const char *cp) {
|
|
ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7);
|
|
MqttPublishTeleSensor();
|
|
}
|
|
|
|
void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr) {
|
|
buttons[count]->xdrawButton(pwr);
|
|
if (pwr) buttons[count]->vpower|=0x80;
|
|
else buttons[count]->vpower&=0x7f;
|
|
}
|
|
|
|
|
|
void FT5316Check() {
|
|
uint16_t temp;
|
|
uint8_t rbutt=0,vbutt=0;
|
|
ra8876_ctouch_counter++;
|
|
if (2 == ra8876_ctouch_counter) {
|
|
|
|
ra8876_ctouch_counter=0;
|
|
|
|
if (FT6236readTouchLocation(&ra8876_pLoc,1)) {
|
|
ra8876_pLoc.x=ra8876_pLoc.x*RA8876_TFTWIDTH/800;
|
|
ra8876_pLoc.y=ra8876_pLoc.y*RA8876_TFTHEIGHT/480;
|
|
|
|
|
|
if (renderer) {
|
|
|
|
|
|
ra8876_pLoc.x=RA8876_TFTWIDTH-ra8876_pLoc.x;
|
|
ra8876_pLoc.y=RA8876_TFTHEIGHT-ra8876_pLoc.y;
|
|
# 170 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino"
|
|
for (uint8_t count=0; count<MAXBUTTONS; count++) {
|
|
if (buttons[count]) {
|
|
uint8_t bflags=buttons[count]->vpower&0x7f;
|
|
if (buttons[count]->contains(ra8876_pLoc.x,ra8876_pLoc.y)) {
|
|
|
|
buttons[count]->press(true);
|
|
if (buttons[count]->justPressed()) {
|
|
if (!bflags) {
|
|
|
|
uint8_t pwr=bitRead(power,rbutt);
|
|
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
|
|
ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
|
|
RA8876_RDW_BUTT(count,!pwr);
|
|
}
|
|
} else {
|
|
|
|
const char *cp;
|
|
if (bflags==1) {
|
|
|
|
buttons[count]->vpower^=0x80;
|
|
cp="TBT";
|
|
} else {
|
|
|
|
buttons[count]->vpower|=0x80;
|
|
cp="PBT";
|
|
}
|
|
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
|
|
RA8876_MQTT(count,cp);
|
|
}
|
|
}
|
|
}
|
|
if (!bflags) {
|
|
rbutt++;
|
|
} else {
|
|
vbutt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
for (uint8_t count=0; count<MAXBUTTONS; count++) {
|
|
if (buttons[count]) {
|
|
uint8_t bflags=buttons[count]->vpower&0x7f;
|
|
buttons[count]->press(false);
|
|
if (buttons[count]->justReleased()) {
|
|
if (bflags>0) {
|
|
if (bflags>1) {
|
|
|
|
buttons[count]->vpower&=0x7f;
|
|
RA8876_MQTT(count,"PBT");
|
|
}
|
|
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
|
|
}
|
|
}
|
|
if (!bflags) {
|
|
|
|
uint8_t pwr=bitRead(power,rbutt);
|
|
uint8_t vpwr=(buttons[count]->vpower&0x80)>>7;
|
|
if (pwr!=vpwr) {
|
|
RA8876_RDW_BUTT(count,pwr);
|
|
}
|
|
rbutt++;
|
|
}
|
|
}
|
|
}
|
|
ra8876_pLoc.x=0;
|
|
ra8876_pLoc.y=0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
# 426 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_10_RA8876.ino"
|
|
bool Xdsp10(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
RA8876_InitDriver();
|
|
}
|
|
else if (XDSP_10 == Settings.display_model) {
|
|
switch (function) {
|
|
case FUNC_DISPLAY_MODEL:
|
|
result = true;
|
|
break;
|
|
case FUNC_DISPLAY_EVERY_50_MSECOND:
|
|
#ifdef USE_TOUCH_BUTTONS
|
|
if (FT5316_found) FT5316Check();
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino"
|
|
#ifdef USE_DISPLAY
|
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
bool (* const xdsp_func_ptr[])(uint8_t) PROGMEM = {
|
|
#else
|
|
bool (* const xdsp_func_ptr[])(uint8_t) = {
|
|
#endif
|
|
|
|
#ifdef XDSP_01
|
|
&Xdsp01,
|
|
#endif
|
|
|
|
#ifdef XDSP_02
|
|
&Xdsp02,
|
|
#endif
|
|
|
|
#ifdef XDSP_03
|
|
&Xdsp03,
|
|
#endif
|
|
|
|
#ifdef XDSP_04
|
|
&Xdsp04,
|
|
#endif
|
|
|
|
#ifdef XDSP_05
|
|
&Xdsp05,
|
|
#endif
|
|
|
|
#ifdef XDSP_06
|
|
&Xdsp06,
|
|
#endif
|
|
|
|
#ifdef XDSP_07
|
|
&Xdsp07,
|
|
#endif
|
|
|
|
#ifdef XDSP_08
|
|
&Xdsp08,
|
|
#endif
|
|
|
|
#ifdef XDSP_09
|
|
&Xdsp09,
|
|
#endif
|
|
|
|
#ifdef XDSP_10
|
|
&Xdsp10,
|
|
#endif
|
|
|
|
#ifdef XDSP_11
|
|
&Xdsp11,
|
|
#endif
|
|
|
|
#ifdef XDSP_12
|
|
&Xdsp12,
|
|
#endif
|
|
|
|
#ifdef XDSP_13
|
|
&Xdsp13,
|
|
#endif
|
|
|
|
#ifdef XDSP_14
|
|
&Xdsp14,
|
|
#endif
|
|
|
|
#ifdef XDSP_15
|
|
&Xdsp15,
|
|
#endif
|
|
|
|
#ifdef XDSP_16
|
|
&Xdsp16
|
|
#endif
|
|
};
|
|
|
|
const uint8_t xdsp_present = sizeof(xdsp_func_ptr) / sizeof(xdsp_func_ptr[0]);
|
|
# 117 "C:/shared/sonoff/Git/Tasmota/tasmota/xdsp_interface.ino"
|
|
uint8_t XdspPresent(void)
|
|
{
|
|
return xdsp_present;
|
|
}
|
|
|
|
bool XdspCall(uint8_t Function)
|
|
{
|
|
bool result = false;
|
|
|
|
DEBUG_TRACE_LOG(PSTR("DSP: %d"), Function);
|
|
|
|
for (uint32_t x = 0; x < xdsp_present; x++) {
|
|
result = xdsp_func_ptr[x](Function);
|
|
|
|
if (result && (FUNC_DISPLAY_MODEL == Function)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_WS2812
|
|
# 38 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_01_ws2812.ino"
|
|
#define XLGT_01 1
|
|
|
|
const uint8_t WS2812_SCHEMES = 8;
|
|
|
|
const char kWs2812Commands[] PROGMEM = "|"
|
|
D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH ;
|
|
|
|
void (* const Ws2812Command[])(void) PROGMEM = {
|
|
&CmndLed, &CmndPixels, &CmndRotation, &CmndWidth };
|
|
|
|
#include <NeoPixelBus.h>
|
|
|
|
#if (USE_WS2812_CTYPE == NEO_GRB)
|
|
typedef NeoGrbFeature selectedNeoFeatureType;
|
|
#elif (USE_WS2812_CTYPE == NEO_BRG)
|
|
typedef NeoBrgFeature selectedNeoFeatureType;
|
|
#elif (USE_WS2812_CTYPE == NEO_RBG)
|
|
typedef NeoRbgFeature selectedNeoFeatureType;
|
|
#elif (USE_WS2812_CTYPE == NEO_RGBW)
|
|
typedef NeoRgbwFeature selectedNeoFeatureType;
|
|
#elif (USE_WS2812_CTYPE == NEO_GRBW)
|
|
typedef NeoGrbwFeature selectedNeoFeatureType;
|
|
#else
|
|
typedef NeoRgbFeature selectedNeoFeatureType;
|
|
#endif
|
|
|
|
#ifdef USE_WS2812_DMA
|
|
|
|
|
|
#if (USE_WS2812_HARDWARE == NEO_HW_WS2812X)
|
|
typedef NeoEsp8266DmaWs2812xMethod selectedNeoSpeedType;
|
|
#elif (USE_WS2812_HARDWARE == NEO_HW_SK6812)
|
|
typedef NeoEsp8266DmaSk6812Method selectedNeoSpeedType;
|
|
#elif (USE_WS2812_HARDWARE == NEO_HW_APA106)
|
|
typedef NeoEsp8266DmaApa106Method selectedNeoSpeedType;
|
|
#else
|
|
typedef NeoEsp8266Dma800KbpsMethod selectedNeoSpeedType;
|
|
#endif
|
|
|
|
#else
|
|
|
|
|
|
#if (USE_WS2812_HARDWARE == NEO_HW_WS2812X)
|
|
typedef NeoEsp8266BitBangWs2812xMethod selectedNeoSpeedType;
|
|
#elif (USE_WS2812_HARDWARE == NEO_HW_SK6812)
|
|
typedef NeoEsp8266BitBangSk6812Method selectedNeoSpeedType;
|
|
#else
|
|
typedef NeoEsp8266BitBang800KbpsMethod selectedNeoSpeedType;
|
|
#endif
|
|
|
|
#endif
|
|
|
|
NeoPixelBus<selectedNeoFeatureType, selectedNeoSpeedType> *strip = nullptr;
|
|
|
|
struct WsColor {
|
|
uint8_t red, green, blue;
|
|
};
|
|
|
|
struct ColorScheme {
|
|
WsColor* colors;
|
|
uint8_t count;
|
|
};
|
|
|
|
WsColor kIncandescent[2] = { 255,140,20, 0,0,0 };
|
|
WsColor kRgb[3] = { 255,0,0, 0,255,0, 0,0,255 };
|
|
WsColor kChristmas[2] = { 255,0,0, 0,255,0 };
|
|
WsColor kHanukkah[2] = { 0,0,255, 255,255,255 };
|
|
WsColor kwanzaa[3] = { 255,0,0, 0,0,0, 0,255,0 };
|
|
WsColor kRainbow[7] = { 255,0,0, 255,128,0, 255,255,0, 0,255,0, 0,0,255, 128,0,255, 255,0,255 };
|
|
WsColor kFire[3] = { 255,0,0, 255,102,0, 255,192,0 };
|
|
ColorScheme kSchemes[WS2812_SCHEMES -1] = {
|
|
kIncandescent, 2,
|
|
kRgb, 3,
|
|
kChristmas, 2,
|
|
kHanukkah, 2,
|
|
kwanzaa, 3,
|
|
kRainbow, 7,
|
|
kFire, 3 };
|
|
|
|
uint8_t kWidth[5] = {
|
|
1,
|
|
2,
|
|
4,
|
|
8,
|
|
255 };
|
|
uint8_t kWsRepeat[5] = {
|
|
8,
|
|
6,
|
|
4,
|
|
2,
|
|
1 };
|
|
|
|
struct WS2812 {
|
|
uint8_t show_next = 1;
|
|
uint8_t scheme_offset = 0;
|
|
bool suspend_update = false;
|
|
} Ws2812;
|
|
|
|
|
|
|
|
void Ws2812StripShow(void)
|
|
{
|
|
#if (USE_WS2812_CTYPE > NEO_3LED)
|
|
RgbwColor c;
|
|
#else
|
|
RgbColor c;
|
|
#endif
|
|
|
|
if (Settings.light_correction) {
|
|
for (uint32_t i = 0; i < Settings.light_pixels; i++) {
|
|
c = strip->GetPixelColor(i);
|
|
c.R = ledGamma(c.R);
|
|
c.G = ledGamma(c.G);
|
|
c.B = ledGamma(c.B);
|
|
#if (USE_WS2812_CTYPE > NEO_3LED)
|
|
c.W = ledGamma(c.W);
|
|
#endif
|
|
strip->SetPixelColor(i, c);
|
|
}
|
|
}
|
|
strip->Show();
|
|
}
|
|
|
|
int mod(int a, int b)
|
|
{
|
|
int ret = a % b;
|
|
if (ret < 0) ret += b;
|
|
return ret;
|
|
}
|
|
|
|
void Ws2812UpdatePixelColor(int position, struct WsColor hand_color, float offset)
|
|
{
|
|
#if (USE_WS2812_CTYPE > NEO_3LED)
|
|
RgbwColor color;
|
|
#else
|
|
RgbColor color;
|
|
#endif
|
|
|
|
uint32_t mod_position = mod(position, (int)Settings.light_pixels);
|
|
|
|
color = strip->GetPixelColor(mod_position);
|
|
float dimmer = 100 / (float)Settings.light_dimmer;
|
|
color.R = tmin(color.R + ((hand_color.red / dimmer) * offset), 255);
|
|
color.G = tmin(color.G + ((hand_color.green / dimmer) * offset), 255);
|
|
color.B = tmin(color.B + ((hand_color.blue / dimmer) * offset), 255);
|
|
strip->SetPixelColor(mod_position, color);
|
|
}
|
|
|
|
void Ws2812UpdateHand(int position, uint32_t index)
|
|
{
|
|
uint32_t width = Settings.light_width;
|
|
if (index < WS_MARKER) { width = Settings.ws_width[index]; }
|
|
if (!width) { return; }
|
|
|
|
position = (position + Settings.light_rotation) % Settings.light_pixels;
|
|
|
|
if (Settings.flag.ws_clock_reverse) {
|
|
position = Settings.light_pixels -position;
|
|
}
|
|
WsColor hand_color = { Settings.ws_color[index][WS_RED], Settings.ws_color[index][WS_GREEN], Settings.ws_color[index][WS_BLUE] };
|
|
|
|
Ws2812UpdatePixelColor(position, hand_color, 1);
|
|
|
|
uint32_t range = ((width -1) / 2) +1;
|
|
for (uint32_t h = 1; h < range; h++) {
|
|
float offset = (float)(range - h) / (float)range;
|
|
Ws2812UpdatePixelColor(position -h, hand_color, offset);
|
|
Ws2812UpdatePixelColor(position +h, hand_color, offset);
|
|
}
|
|
}
|
|
|
|
void Ws2812Clock(void)
|
|
{
|
|
strip->ClearTo(0);
|
|
int clksize = 60000 / (int)Settings.light_pixels;
|
|
|
|
Ws2812UpdateHand((RtcTime.second * 1000) / clksize, WS_SECOND);
|
|
Ws2812UpdateHand((RtcTime.minute * 1000) / clksize, WS_MINUTE);
|
|
Ws2812UpdateHand((((RtcTime.hour % 12) * 5000) + ((RtcTime.minute * 1000) / 12 )) / clksize, WS_HOUR);
|
|
if (Settings.ws_color[WS_MARKER][WS_RED] + Settings.ws_color[WS_MARKER][WS_GREEN] + Settings.ws_color[WS_MARKER][WS_BLUE]) {
|
|
for (uint32_t i = 0; i < 12; i++) {
|
|
Ws2812UpdateHand((i * 5000) / clksize, WS_MARKER);
|
|
}
|
|
}
|
|
|
|
Ws2812StripShow();
|
|
}
|
|
|
|
void Ws2812GradientColor(uint32_t schemenr, struct WsColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i)
|
|
{
|
|
|
|
|
|
|
|
|
|
ColorScheme scheme = kSchemes[schemenr];
|
|
uint32_t curRange = i / range;
|
|
uint32_t rangeIndex = i % range;
|
|
uint32_t colorIndex = rangeIndex / gradRange;
|
|
uint32_t start = colorIndex;
|
|
uint32_t end = colorIndex +1;
|
|
if (curRange % 2 != 0) {
|
|
start = (scheme.count -1) - start;
|
|
end = (scheme.count -1) - end;
|
|
}
|
|
float dimmer = 100 / (float)Settings.light_dimmer;
|
|
float fmyRed = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].red, scheme.colors[end].red) / dimmer;
|
|
float fmyGrn = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].green, scheme.colors[end].green) / dimmer;
|
|
float fmyBlu = (float)map(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].blue, scheme.colors[end].blue) / dimmer;
|
|
mColor->red = (uint8_t)fmyRed;
|
|
mColor->green = (uint8_t)fmyGrn;
|
|
mColor->blue = (uint8_t)fmyBlu;
|
|
}
|
|
|
|
void Ws2812Gradient(uint32_t schemenr)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
#if (USE_WS2812_CTYPE > NEO_3LED)
|
|
RgbwColor c;
|
|
c.W = 0;
|
|
#else
|
|
RgbColor c;
|
|
#endif
|
|
|
|
ColorScheme scheme = kSchemes[schemenr];
|
|
if (scheme.count < 2) { return; }
|
|
|
|
uint32_t repeat = kWsRepeat[Settings.light_width];
|
|
uint32_t range = (uint32_t)ceil((float)Settings.light_pixels / (float)repeat);
|
|
uint32_t gradRange = (uint32_t)ceil((float)range / (float)(scheme.count - 1));
|
|
uint32_t speed = ((Settings.light_speed * 2) -1) * (STATES / 10);
|
|
uint32_t offset = speed > 0 ? Light.strip_timer_counter / speed : 0;
|
|
|
|
WsColor oldColor, currentColor;
|
|
Ws2812GradientColor(schemenr, &oldColor, range, gradRange, offset);
|
|
currentColor = oldColor;
|
|
for (uint32_t i = 0; i < Settings.light_pixels; i++) {
|
|
if (kWsRepeat[Settings.light_width] > 1) {
|
|
Ws2812GradientColor(schemenr, ¤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<selectedNeoFeatureType, selectedNeoSpeedType>(WS2812_MAX_LEDS, pin[GPIO_WS2812]);
|
|
strip->Begin();
|
|
|
|
Ws2812Clear();
|
|
|
|
Ws2812.scheme_offset = Light.max_scheme +1;
|
|
Light.max_scheme += WS2812_SCHEMES;
|
|
|
|
#if (USE_WS2812_CTYPE > NEO_3LED)
|
|
light_type = LT_RGBW;
|
|
#else
|
|
light_type = LT_RGB;
|
|
#endif
|
|
light_flg = XLGT_01;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CmndLed(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings.light_pixels)) {
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char *p;
|
|
uint16_t idx = XdrvMailbox.index;
|
|
Ws2812ForceSuspend();
|
|
for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(nullptr, " ", &p)) {
|
|
if (LightColorEntry(color, strlen(color))) {
|
|
Ws2812SetColor(idx, Light.entry_color[0], Light.entry_color[1], Light.entry_color[2], Light.entry_color[3]);
|
|
idx++;
|
|
if (idx > Settings.light_pixels) { break; }
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
Ws2812ForceUpdate();
|
|
}
|
|
char scolor[LIGHT_COLOR_SIZE];
|
|
ResponseCmndIdxChar(Ws2812GetColor(XdrvMailbox.index, scolor));
|
|
}
|
|
}
|
|
|
|
void CmndPixels(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) {
|
|
Settings.light_pixels = XdrvMailbox.payload;
|
|
Settings.light_rotation = 0;
|
|
Ws2812Clear();
|
|
Light.update = true;
|
|
}
|
|
ResponseCmndNumber(Settings.light_pixels);
|
|
}
|
|
|
|
void CmndRotation(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings.light_pixels)) {
|
|
Settings.light_rotation = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.light_rotation);
|
|
}
|
|
|
|
void CmndWidth(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
|
|
if (1 == XdrvMailbox.index) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) {
|
|
Settings.light_width = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.light_width);
|
|
} else {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32)) {
|
|
Settings.ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndIdxNumber(Settings.ws_width[XdrvMailbox.index -2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xlgt01(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_SET_CHANNELS:
|
|
result = Ws2812SetChannels();
|
|
break;
|
|
case FUNC_SET_SCHEME:
|
|
Ws2812ShowScheme();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kWs2812Commands, Ws2812Command);
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
Ws2812ModuleSelected();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_02_my92x1.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_02_my92x1.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_MY92X1
|
|
|
|
|
|
|
|
|
|
#define XLGT_02 2
|
|
|
|
struct MY92X1 {
|
|
uint8_t pdi_pin = 0;
|
|
uint8_t pdcki_pin = 0;
|
|
uint8_t model = 0;
|
|
} My92x1;
|
|
|
|
extern "C" {
|
|
void os_delay_us(unsigned int);
|
|
}
|
|
|
|
void LightDiPulse(uint8_t times)
|
|
{
|
|
for (uint32_t i = 0; i < times; i++) {
|
|
digitalWrite(My92x1.pdi_pin, HIGH);
|
|
digitalWrite(My92x1.pdi_pin, LOW);
|
|
}
|
|
}
|
|
|
|
void LightDckiPulse(uint8_t times)
|
|
{
|
|
for (uint32_t i = 0; i < times; i++) {
|
|
digitalWrite(My92x1.pdcki_pin, HIGH);
|
|
digitalWrite(My92x1.pdcki_pin, LOW);
|
|
}
|
|
}
|
|
|
|
void LightMy92x1Write(uint8_t data)
|
|
{
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
digitalWrite(My92x1.pdcki_pin, LOW);
|
|
digitalWrite(My92x1.pdi_pin, (data & 0x80));
|
|
digitalWrite(My92x1.pdcki_pin, HIGH);
|
|
data = data << 1;
|
|
digitalWrite(My92x1.pdi_pin, (data & 0x80));
|
|
digitalWrite(My92x1.pdcki_pin, LOW);
|
|
digitalWrite(My92x1.pdi_pin, LOW);
|
|
data = data << 1;
|
|
}
|
|
}
|
|
|
|
void LightMy92x1Init(void)
|
|
{
|
|
uint8_t chips[3] = { 1, 2, 2 };
|
|
|
|
LightDckiPulse(chips[My92x1.model] * 32);
|
|
os_delay_us(12);
|
|
|
|
|
|
LightDiPulse(12);
|
|
os_delay_us(12);
|
|
for (uint32_t n = 0; n < chips[My92x1.model]; n++) {
|
|
LightMy92x1Write(0x18);
|
|
}
|
|
os_delay_us(12);
|
|
|
|
|
|
LightDiPulse(16);
|
|
os_delay_us(12);
|
|
}
|
|
|
|
void LightMy92x1Duty(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b, uint8_t duty_w, uint8_t duty_c)
|
|
{
|
|
uint8_t channels[3] = { 4, 6, 6 };
|
|
|
|
uint8_t duty[3][6] = {{ duty_r, duty_g, duty_b, duty_w, 0, 0 },
|
|
{ duty_w, duty_c, 0, duty_g, duty_r, duty_b },
|
|
{ duty_r, duty_g, duty_b, duty_w, duty_w, duty_w }};
|
|
|
|
os_delay_us(12);
|
|
for (uint32_t channel = 0; channel < channels[My92x1.model]; channel++) {
|
|
LightMy92x1Write(duty[My92x1.model][channel]);
|
|
}
|
|
os_delay_us(12);
|
|
LightDiPulse(8);
|
|
os_delay_us(12);
|
|
}
|
|
|
|
|
|
|
|
bool My92x1SetChannels(void)
|
|
{
|
|
uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
|
|
|
|
LightMy92x1Duty(cur_col[0], cur_col[1], cur_col[2], cur_col[3], cur_col[4]);
|
|
|
|
return true;
|
|
}
|
|
|
|
void My92x1ModuleSelected(void)
|
|
{
|
|
if ((pin[GPIO_DCKI] < 99) && (pin[GPIO_DI] < 99)) {
|
|
My92x1.pdi_pin = pin[GPIO_DI];
|
|
My92x1.pdcki_pin = pin[GPIO_DCKI];
|
|
|
|
pinMode(My92x1.pdi_pin, OUTPUT);
|
|
pinMode(My92x1.pdcki_pin, OUTPUT);
|
|
digitalWrite(My92x1.pdi_pin, LOW);
|
|
digitalWrite(My92x1.pdcki_pin, LOW);
|
|
|
|
My92x1.model = 2;
|
|
light_type = LT_RGBW;
|
|
if (AILIGHT == my_module_type) {
|
|
My92x1.model = 0;
|
|
|
|
}
|
|
else if (SONOFF_B1 == my_module_type) {
|
|
My92x1.model = 1;
|
|
light_type = LT_RGBWC;
|
|
}
|
|
|
|
LightMy92x1Init();
|
|
|
|
light_flg = XLGT_02;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: MY29x1 Found"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xlgt02(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_SET_CHANNELS:
|
|
result = My92x1SetChannels();
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
My92x1ModuleSelected();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_SM16716
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XLGT_03 3
|
|
|
|
#define D_LOG_SM16716 "SM16716: "
|
|
|
|
struct SM16716 {
|
|
uint8_t pin_clk = 0;
|
|
uint8_t pin_dat = 0;
|
|
uint8_t pin_sel = 0;
|
|
bool enabled = false;
|
|
} Sm16716;
|
|
|
|
void SM16716_SendBit(uint8_t v)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
digitalWrite(Sm16716.pin_dat, (v != 0) ? HIGH : LOW);
|
|
|
|
digitalWrite(Sm16716.pin_clk, HIGH);
|
|
|
|
digitalWrite(Sm16716.pin_clk, LOW);
|
|
}
|
|
|
|
void SM16716_SendByte(uint8_t v)
|
|
{
|
|
uint8_t mask;
|
|
|
|
for (mask = 0x80; mask; mask >>= 1) {
|
|
SM16716_SendBit(v & mask);
|
|
}
|
|
}
|
|
|
|
void SM16716_Update(uint8_t duty_r, uint8_t duty_g, uint8_t duty_b)
|
|
{
|
|
if (Sm16716.pin_sel < 99) {
|
|
bool should_enable = (duty_r | duty_g | duty_b);
|
|
if (!Sm16716.enabled && should_enable) {
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color on"));
|
|
Sm16716.enabled = true;
|
|
digitalWrite(Sm16716.pin_sel, HIGH);
|
|
|
|
|
|
delayMicroseconds(1000);
|
|
SM16716_Init();
|
|
}
|
|
else if (Sm16716.enabled && !should_enable) {
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "turning color off"));
|
|
Sm16716.enabled = false;
|
|
digitalWrite(Sm16716.pin_sel, LOW);
|
|
}
|
|
}
|
|
DEBUG_DRIVER_LOG(PSTR(D_LOG_SM16716 "Update; rgb=%02x%02x%02x"), duty_r, duty_g, duty_b);
|
|
|
|
|
|
SM16716_SendBit(1);
|
|
SM16716_SendByte(duty_r);
|
|
SM16716_SendByte(duty_g);
|
|
SM16716_SendByte(duty_b);
|
|
|
|
|
|
|
|
|
|
|
|
SM16716_SendBit(0);
|
|
SM16716_SendByte(0);
|
|
SM16716_SendByte(0);
|
|
SM16716_SendByte(0);
|
|
}
|
|
# 111 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino"
|
|
void SM16716_Init(void)
|
|
{
|
|
for (uint32_t t_init = 0; t_init < 50; ++t_init) {
|
|
SM16716_SendBit(0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool Sm16716SetChannels(void)
|
|
{
|
|
# 132 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino"
|
|
uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
|
|
|
|
SM16716_Update(cur_col[0], cur_col[1], cur_col[2]);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Sm16716ModuleSelected(void)
|
|
{
|
|
if ((pin[GPIO_SM16716_CLK] < 99) && (pin[GPIO_SM16716_DAT] < 99)) {
|
|
Sm16716.pin_clk = pin[GPIO_SM16716_CLK];
|
|
Sm16716.pin_dat = pin[GPIO_SM16716_DAT];
|
|
Sm16716.pin_sel = pin[GPIO_SM16716_SEL];
|
|
# 157 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_03_sm16716.ino"
|
|
pinMode(Sm16716.pin_clk, OUTPUT);
|
|
digitalWrite(Sm16716.pin_clk, LOW);
|
|
|
|
pinMode(Sm16716.pin_dat, OUTPUT);
|
|
digitalWrite(Sm16716.pin_dat, LOW);
|
|
|
|
if (Sm16716.pin_sel < 99) {
|
|
pinMode(Sm16716.pin_sel, OUTPUT);
|
|
digitalWrite(Sm16716.pin_sel, LOW);
|
|
|
|
} else {
|
|
|
|
SM16716_Init();
|
|
}
|
|
|
|
LightPwmOffset(LST_RGB);
|
|
light_type += LST_RGB;
|
|
light_flg = XLGT_03;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: SM16716 Found"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xlgt03(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_SET_CHANNELS:
|
|
result = Sm16716SetChannels();
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
Sm16716ModuleSelected();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_SM2135
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XLGT_04 4
|
|
|
|
#define SM2135_ADDR_MC 0xC0
|
|
#define SM2135_ADDR_CH 0xC1
|
|
#define SM2135_ADDR_R 0xC2
|
|
#define SM2135_ADDR_G 0xC3
|
|
#define SM2135_ADDR_B 0xC4
|
|
#define SM2135_ADDR_C 0xC5
|
|
#define SM2135_ADDR_W 0xC6
|
|
|
|
#define SM2135_RGB 0x00
|
|
#define SM2135_CW 0x80
|
|
|
|
#define SM2135_10MA 0x00
|
|
#define SM2135_15MA 0x01
|
|
#define SM2135_20MA 0x02
|
|
#define SM2135_25MA 0x03
|
|
#define SM2135_30MA 0x04
|
|
#define SM2135_35MA 0x05
|
|
#define SM2135_40MA 0x06
|
|
#define SM2135_45MA 0x07
|
|
#define SM2135_50MA 0x08
|
|
#define SM2135_55MA 0x09
|
|
#define SM2135_60MA 0x0A
|
|
|
|
|
|
const uint8_t SM2135_CURRENT = (SM2135_20MA << 4) | SM2135_15MA;
|
|
|
|
struct SM2135 {
|
|
uint8_t clk = 0;
|
|
uint8_t data = 0;
|
|
} Sm2135;
|
|
|
|
uint8_t Sm2135Write(uint8_t data)
|
|
{
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
digitalWrite(Sm2135.clk, LOW);
|
|
digitalWrite(Sm2135.data, (data & 0x80));
|
|
digitalWrite(Sm2135.clk, HIGH);
|
|
data = data << 1;
|
|
}
|
|
digitalWrite(Sm2135.clk, LOW);
|
|
digitalWrite(Sm2135.data, HIGH);
|
|
pinMode(Sm2135.data, INPUT);
|
|
digitalWrite(Sm2135.clk, HIGH);
|
|
uint8_t ack = digitalRead(Sm2135.data);
|
|
pinMode(Sm2135.data, OUTPUT);
|
|
return ack;
|
|
}
|
|
|
|
void Sm2135Send(uint8_t *buffer, uint8_t size)
|
|
{
|
|
digitalWrite(Sm2135.data, LOW);
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
Sm2135Write(buffer[i]);
|
|
}
|
|
digitalWrite(Sm2135.clk, LOW);
|
|
digitalWrite(Sm2135.clk, HIGH);
|
|
digitalWrite(Sm2135.data, HIGH);
|
|
}
|
|
|
|
|
|
|
|
bool Sm2135SetChannels(void)
|
|
{
|
|
uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
|
|
uint8_t data[6];
|
|
|
|
if ((0 == cur_col[0]) && (0 == cur_col[1]) && (0 == cur_col[2])) {
|
|
# 106 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_04_sm2135.ino"
|
|
data[0] = SM2135_ADDR_MC;
|
|
data[1] = SM2135_CURRENT;
|
|
data[2] = SM2135_CW;
|
|
Sm2135Send(data, 3);
|
|
delay(1);
|
|
data[0] = SM2135_ADDR_C;
|
|
data[1] = cur_col[4];
|
|
data[2] = cur_col[3];
|
|
Sm2135Send(data, 3);
|
|
} else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data[0] = SM2135_ADDR_MC;
|
|
data[1] = SM2135_CURRENT;
|
|
data[2] = SM2135_RGB;
|
|
data[3] = cur_col[1];
|
|
data[4] = cur_col[0];
|
|
data[5] = cur_col[2];
|
|
Sm2135Send(data, 6);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Sm2135ModuleSelected(void)
|
|
{
|
|
if ((pin[GPIO_SM2135_CLK] < 99) && (pin[GPIO_SM2135_DAT] < 99)) {
|
|
Sm2135.clk = pin[GPIO_SM2135_CLK];
|
|
Sm2135.data = pin[GPIO_SM2135_DAT];
|
|
|
|
pinMode(Sm2135.data, OUTPUT);
|
|
digitalWrite(Sm2135.data, HIGH);
|
|
pinMode(Sm2135.clk, OUTPUT);
|
|
digitalWrite(Sm2135.clk, HIGH);
|
|
|
|
light_type = LT_RGBWC;
|
|
light_flg = XLGT_04;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: SM2135 Found"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xlgt04(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_SET_CHANNELS:
|
|
result = Sm2135SetChannels();
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
Sm2135ModuleSelected();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_05_sonoff_l1.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_05_sonoff_l1.ino"
|
|
#ifdef USE_LIGHT
|
|
#ifdef USE_SONOFF_L1
|
|
|
|
|
|
|
|
|
|
#define XLGT_05 5
|
|
|
|
#define SONOFF_L1_BUFFER_SIZE 140
|
|
|
|
#define SONOFF_L1_MODE_COLORFUL 1
|
|
#define SONOFF_L1_MODE_COLORFUL_GRADIENT 2
|
|
#define SONOFF_L1_MODE_COLORFUL_BREATH 3
|
|
#define SONOFF_L1_MODE_DIY_GRADIENT 4
|
|
#define SONOFF_L1_MODE_DIY_PULSE 5
|
|
#define SONOFF_L1_MODE_DIY_BREATH 6
|
|
#define SONOFF_L1_MODE_DIY_STROBE 7
|
|
#define SONOFF_L1_MODE_RGB_GRADIENT 8
|
|
#define SONOFF_L1_MODE_RGB_PULSE 9
|
|
#define SONOFF_L1_MODE_RGB_BREATH 10
|
|
#define SONOFF_L1_MODE_RGB_STROBE 11
|
|
#define SONOFF_L1_MODE_SYNC_TO_MUSIC 12
|
|
|
|
struct SNFL1 {
|
|
uint32_t unlock = 0;
|
|
bool receive_ready = true;
|
|
} Snfl1;
|
|
|
|
|
|
|
|
void SnfL1Send(const char *buffer)
|
|
{
|
|
|
|
|
|
Serial.print(buffer);
|
|
Serial.write(0x1B);
|
|
Serial.flush();
|
|
}
|
|
|
|
void SnfL1SerialSendOk(void)
|
|
{
|
|
char buffer[16];
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("AT+SEND=ok"));
|
|
|
|
SnfL1Send(buffer);
|
|
}
|
|
|
|
bool SnfL1SerialInput(void)
|
|
{
|
|
if (serial_in_byte != 0x1B) {
|
|
if (serial_in_byte_counter >= 140) {
|
|
serial_in_byte_counter = 0;
|
|
}
|
|
if (serial_in_byte_counter || (!serial_in_byte_counter && ('A' == serial_in_byte))) {
|
|
serial_in_buffer[serial_in_byte_counter++] = serial_in_byte;
|
|
}
|
|
} else {
|
|
serial_in_buffer[serial_in_byte_counter++] = 0x00;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!strncmp(serial_in_buffer +3, "RESULT", 6)) {
|
|
Snfl1.receive_ready = true;
|
|
}
|
|
else if (!strncmp(serial_in_buffer +3, "UPDATE", 6)) {
|
|
char cmnd_dimmer[20];
|
|
char cmnd_color[20];
|
|
char *end_str;
|
|
char *string = serial_in_buffer +10;
|
|
char *token = strtok_r(string, ",", &end_str);
|
|
|
|
bool color_updated[3] = { false, false, false };
|
|
uint8_t current_color[3];
|
|
memcpy(current_color, Settings.light_color, 3);
|
|
|
|
bool switch_state = false;
|
|
bool is_power_change = false;
|
|
bool is_color_change = false;
|
|
bool is_brightness_change = false;
|
|
|
|
while (token != nullptr) {
|
|
char* end_token;
|
|
char* token2 = strtok_r(token, ":", &end_token);
|
|
char* token3 = strtok_r(nullptr, ":", &end_token);
|
|
|
|
if (!strncmp(token2, "\"sequence\"", 10)) {
|
|
|
|
|
|
|
|
token = nullptr;
|
|
}
|
|
|
|
else if (!strncmp(token2, "\"switch\"", 8)) {
|
|
switch_state = !strncmp(token3, "\"on\"", 4) ? true : false;
|
|
|
|
|
|
|
|
is_power_change = (switch_state != Light.power);
|
|
}
|
|
|
|
else if (!strncmp(token2, "\"color", 6)) {
|
|
char color_channel_name = token2[6];
|
|
int color_index;
|
|
switch(color_channel_name)
|
|
{
|
|
case 'R': color_index = 0;
|
|
break;
|
|
case 'G': color_index = 1;
|
|
break;
|
|
case 'B': color_index = 2;
|
|
break;
|
|
}
|
|
int color_value = atoi(token3);
|
|
current_color[color_index] = color_value;
|
|
color_updated[color_index] = true;
|
|
|
|
bool all_color_channels_updated = color_updated[0] && color_updated[1] && color_updated[2];
|
|
if (all_color_channels_updated) {
|
|
|
|
|
|
|
|
|
|
|
|
is_color_change = (Light.power && (memcmp(current_color, Settings.light_color, 3) != 0));
|
|
}
|
|
snprintf_P(cmnd_color, sizeof(cmnd_color), PSTR(D_CMND_COLOR "2 %02x%02x%02x"), current_color[0], current_color[1], current_color[2]);
|
|
}
|
|
|
|
else if (!strncmp(token2, "\"bright\"", 8)) {
|
|
uint8_t dimmer = atoi(token3);
|
|
|
|
|
|
|
|
is_brightness_change = (Light.power && (dimmer > 0) && (dimmer != Settings.light_dimmer));
|
|
snprintf_P(cmnd_dimmer, sizeof(cmnd_dimmer), PSTR(D_CMND_DIMMER " %d"), dimmer);
|
|
}
|
|
|
|
token = strtok_r(nullptr, ",", &end_str);
|
|
}
|
|
|
|
if (is_power_change) {
|
|
if (Settings.light_scheme > 0) {
|
|
if (!switch_state) {
|
|
char cmnd_scheme[20];
|
|
snprintf_P(cmnd_scheme, sizeof(cmnd_scheme), PSTR(D_CMND_SCHEME " 0"));
|
|
ExecuteCommand(cmnd_scheme, SRC_SWITCH);
|
|
}
|
|
} else {
|
|
ExecuteCommandPower(1, switch_state, SRC_SWITCH);
|
|
}
|
|
}
|
|
else if (is_brightness_change) {
|
|
ExecuteCommand(cmnd_dimmer, SRC_SWITCH);
|
|
}
|
|
else if (Light.power && is_color_change) {
|
|
if (0 == Settings.light_scheme) {
|
|
if (Settings.light_fade) {
|
|
char cmnd_fade[20];
|
|
snprintf_P(cmnd_fade, sizeof(cmnd_fade), PSTR(D_CMND_FADE " 0"));
|
|
ExecuteCommand(cmnd_fade, SRC_SWITCH);
|
|
}
|
|
ExecuteCommand(cmnd_color, SRC_SWITCH);
|
|
}
|
|
}
|
|
}
|
|
|
|
SnfL1SerialSendOk();
|
|
|
|
return true;
|
|
}
|
|
serial_in_byte = 0;
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
bool SnfL1SetChannels(void)
|
|
{
|
|
if (Snfl1.receive_ready || TimeReached(Snfl1.unlock)) {
|
|
|
|
uint8_t *scale_col = (uint8_t*)XdrvMailbox.topic;
|
|
|
|
char buffer[140];
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"light_type\":1,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"bright\":%d,\"mode\":%d"),
|
|
LocalTime(), millis()%1000,
|
|
Light.power ? "on" : "off",
|
|
scale_col[0], scale_col[1], scale_col[2],
|
|
light_state.getDimmer(),
|
|
SONOFF_L1_MODE_COLORFUL);
|
|
|
|
SnfL1Send(buffer);
|
|
|
|
Snfl1.unlock = millis() + 500;
|
|
Snfl1.receive_ready = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SnfL1ModuleSelected(void)
|
|
{
|
|
if (SONOFF_L1 == my_module_type) {
|
|
if ((pin[GPIO_RXD] < 99) && (pin[GPIO_TXD] < 99)) {
|
|
SetSerial(19200, TS_SERIAL_8N1);
|
|
|
|
light_type = LT_RGB;
|
|
light_flg = XLGT_05;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: Sonoff L1 Found"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xlgt05(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_SERIAL:
|
|
result = SnfL1SerialInput();
|
|
break;
|
|
case FUNC_SET_CHANNELS:
|
|
result = SnfL1SetChannels();
|
|
break;
|
|
case FUNC_MODULE_INIT:
|
|
SnfL1ModuleSelected();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_interface.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xlgt_interface.ino"
|
|
#ifdef USE_LIGHT
|
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
bool (* const xlgt_func_ptr[])(uint8_t) PROGMEM = {
|
|
#else
|
|
bool (* const xlgt_func_ptr[])(uint8_t) = {
|
|
#endif
|
|
|
|
#ifdef XLGT_01
|
|
&Xlgt01,
|
|
#endif
|
|
|
|
#ifdef XLGT_02
|
|
&Xlgt02,
|
|
#endif
|
|
|
|
#ifdef XLGT_03
|
|
&Xlgt03,
|
|
#endif
|
|
|
|
#ifdef XLGT_04
|
|
&Xlgt04,
|
|
#endif
|
|
|
|
#ifdef XLGT_05
|
|
&Xlgt05,
|
|
#endif
|
|
|
|
#ifdef XLGT_06
|
|
&Xlgt06,
|
|
#endif
|
|
|
|
#ifdef XLGT_07
|
|
&Xlgt07,
|
|
#endif
|
|
|
|
#ifdef XLGT_08
|
|
&Xlgt08,
|
|
#endif
|
|
|
|
#ifdef XLGT_09
|
|
&Xlgt09,
|
|
#endif
|
|
|
|
#ifdef XLGT_10
|
|
&Xlgt10,
|
|
#endif
|
|
|
|
#ifdef XLGT_11
|
|
&Xlgt11,
|
|
#endif
|
|
|
|
#ifdef XLGT_12
|
|
&Xlgt12,
|
|
#endif
|
|
|
|
#ifdef XLGT_13
|
|
&Xlgt13,
|
|
#endif
|
|
|
|
#ifdef XLGT_14
|
|
&Xlgt14,
|
|
#endif
|
|
|
|
#ifdef XLGT_15
|
|
&Xlgt15,
|
|
#endif
|
|
|
|
#ifdef XLGT_16
|
|
&Xlgt16
|
|
#endif
|
|
};
|
|
|
|
const uint8_t xlgt_present = sizeof(xlgt_func_ptr) / sizeof(xlgt_func_ptr[0]);
|
|
|
|
uint8_t xlgt_active = 0;
|
|
|
|
bool XlgtCall(uint8_t function)
|
|
{
|
|
DEBUG_TRACE_LOG(PSTR("LGT: %d"), function);
|
|
|
|
if (FUNC_MODULE_INIT == function) {
|
|
for (uint32_t x = 0; x < xlgt_present; x++) {
|
|
xlgt_func_ptr[x](function);
|
|
if (light_flg) {
|
|
xlgt_active = x;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (light_flg) {
|
|
return xlgt_func_ptr[xlgt_active](function);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_01_hlw8012.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_01_hlw8012.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_HLW8012
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XNRG_01 1
|
|
|
|
|
|
#define HLW_PREF 10000
|
|
#define HLW_UREF 2200
|
|
#define HLW_IREF 4545
|
|
|
|
|
|
#define HJL_PREF 1362
|
|
#define HJL_UREF 822
|
|
#define HJL_IREF 3300
|
|
|
|
#define HLW_POWER_PROBE_TIME 10
|
|
#define HLW_SAMPLE_COUNT 10
|
|
|
|
|
|
|
|
struct HLW {
|
|
#ifdef HLW_DEBUG
|
|
unsigned long debug[HLW_SAMPLE_COUNT];
|
|
#endif
|
|
unsigned long cf_pulse_length = 0;
|
|
unsigned long cf_pulse_last_time = 0;
|
|
unsigned long cf_power_pulse_length = 0;
|
|
|
|
unsigned long cf1_pulse_length = 0;
|
|
unsigned long cf1_pulse_last_time = 0;
|
|
unsigned long cf1_summed_pulse_length = 0;
|
|
unsigned long cf1_pulse_counter = 0;
|
|
unsigned long cf1_voltage_pulse_length = 0;
|
|
unsigned long cf1_current_pulse_length = 0;
|
|
|
|
unsigned long energy_period_counter = 0;
|
|
|
|
unsigned long power_ratio = 0;
|
|
unsigned long voltage_ratio = 0;
|
|
unsigned long current_ratio = 0;
|
|
|
|
uint8_t model_type = 0;
|
|
uint8_t cf1_timer = 0;
|
|
uint8_t power_retry = 0;
|
|
bool select_ui_flag = false;
|
|
bool ui_flag = true;
|
|
bool load_off = true;
|
|
} Hlw;
|
|
|
|
|
|
#ifndef USE_WS2812_DMA
|
|
void HlwCfInterrupt(void) ICACHE_RAM_ATTR;
|
|
void HlwCf1Interrupt(void) ICACHE_RAM_ATTR;
|
|
#endif
|
|
|
|
void HlwCfInterrupt(void)
|
|
{
|
|
unsigned long us = micros();
|
|
|
|
if (Hlw.load_off) {
|
|
Hlw.cf_pulse_last_time = us;
|
|
Hlw.load_off = false;
|
|
} else {
|
|
Hlw.cf_pulse_length = us - Hlw.cf_pulse_last_time;
|
|
Hlw.cf_pulse_last_time = us;
|
|
Hlw.energy_period_counter++;
|
|
}
|
|
Energy.data_valid[0] = 0;
|
|
}
|
|
|
|
void HlwCf1Interrupt(void)
|
|
{
|
|
unsigned long us = micros();
|
|
|
|
Hlw.cf1_pulse_length = us - Hlw.cf1_pulse_last_time;
|
|
Hlw.cf1_pulse_last_time = us;
|
|
if ((Hlw.cf1_timer > 2) && (Hlw.cf1_timer < 8)) {
|
|
Hlw.cf1_summed_pulse_length += Hlw.cf1_pulse_length;
|
|
#ifdef HLW_DEBUG
|
|
Hlw.debug[Hlw.cf1_pulse_counter] = Hlw.cf1_pulse_length;
|
|
#endif
|
|
Hlw.cf1_pulse_counter++;
|
|
if (HLW_SAMPLE_COUNT == Hlw.cf1_pulse_counter) {
|
|
Hlw.cf1_timer = 8;
|
|
}
|
|
}
|
|
Energy.data_valid[0] = 0;
|
|
}
|
|
|
|
|
|
|
|
void HlwEvery200ms(void)
|
|
{
|
|
unsigned long cf1_pulse_length = 0;
|
|
unsigned long hlw_w = 0;
|
|
unsigned long hlw_u = 0;
|
|
unsigned long hlw_i = 0;
|
|
|
|
if (micros() - Hlw.cf_pulse_last_time > (HLW_POWER_PROBE_TIME * 1000000)) {
|
|
Hlw.cf_pulse_length = 0;
|
|
Hlw.load_off = true;
|
|
}
|
|
Hlw.cf_power_pulse_length = Hlw.cf_pulse_length;
|
|
|
|
if (Hlw.cf_power_pulse_length && Energy.power_on && !Hlw.load_off) {
|
|
hlw_w = (Hlw.power_ratio * Settings.energy_power_calibration) / Hlw.cf_power_pulse_length ;
|
|
Energy.active_power[0] = (float)hlw_w / 10;
|
|
Hlw.power_retry = 1;
|
|
} else {
|
|
if (Hlw.power_retry) {
|
|
Hlw.power_retry--;
|
|
} else {
|
|
Energy.active_power[0] = 0;
|
|
}
|
|
}
|
|
|
|
if (pin[GPIO_NRG_CF1] < 99) {
|
|
Hlw.cf1_timer++;
|
|
if (Hlw.cf1_timer >= 8) {
|
|
Hlw.cf1_timer = 0;
|
|
Hlw.select_ui_flag = (Hlw.select_ui_flag) ? false : true;
|
|
DigitalWrite(GPIO_NRG_SEL, Hlw.select_ui_flag);
|
|
|
|
if (Hlw.cf1_pulse_counter) {
|
|
cf1_pulse_length = Hlw.cf1_summed_pulse_length / Hlw.cf1_pulse_counter;
|
|
}
|
|
|
|
#ifdef HLW_DEBUG
|
|
|
|
char stemp[100];
|
|
stemp[0] = '\0';
|
|
for (uint32_t i = 0; i < Hlw.cf1_pulse_counter; i++) {
|
|
snprintf_P(stemp, sizeof(stemp), PSTR("%s %d"), stemp, Hlw.debug[i]);
|
|
}
|
|
for (uint32_t i = 0; i < Hlw.cf1_pulse_counter; i++) {
|
|
for (uint32_t j = i + 1; j < Hlw.cf1_pulse_counter; j++) {
|
|
if (Hlw.debug[i] > Hlw.debug[j]) {
|
|
std::swap(Hlw.debug[i], Hlw.debug[j]);
|
|
}
|
|
}
|
|
}
|
|
unsigned long median = Hlw.debug[(Hlw.cf1_pulse_counter +1) / 2];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NRG: power %d, ui %d, cnt %d, smpl%s, sum %d, mean %d, median %d"),
|
|
Hlw.cf_power_pulse_length , Hlw.select_ui_flag, Hlw.cf1_pulse_counter, stemp, Hlw.cf1_summed_pulse_length, cf1_pulse_length, median);
|
|
#endif
|
|
|
|
if (Hlw.select_ui_flag == Hlw.ui_flag) {
|
|
Hlw.cf1_voltage_pulse_length = cf1_pulse_length;
|
|
|
|
if (Hlw.cf1_voltage_pulse_length && Energy.power_on) {
|
|
hlw_u = (Hlw.voltage_ratio * Settings.energy_voltage_calibration) / Hlw.cf1_voltage_pulse_length ;
|
|
Energy.voltage[0] = (float)hlw_u / 10;
|
|
} else {
|
|
Energy.voltage[0] = 0;
|
|
}
|
|
|
|
} else {
|
|
Hlw.cf1_current_pulse_length = cf1_pulse_length;
|
|
|
|
if (Hlw.cf1_current_pulse_length && Energy.active_power[0]) {
|
|
hlw_i = (Hlw.current_ratio * Settings.energy_current_calibration) / Hlw.cf1_current_pulse_length;
|
|
Energy.current[0] = (float)hlw_i / 1000;
|
|
} else {
|
|
Energy.current[0] = 0;
|
|
}
|
|
|
|
}
|
|
Hlw.cf1_summed_pulse_length = 0;
|
|
Hlw.cf1_pulse_counter = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HlwEverySecond(void)
|
|
{
|
|
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
|
|
Hlw.cf1_voltage_pulse_length = 0;
|
|
Hlw.cf1_current_pulse_length = 0;
|
|
Hlw.cf_power_pulse_length = 0;
|
|
} else {
|
|
unsigned long hlw_len;
|
|
|
|
if (Hlw.energy_period_counter) {
|
|
hlw_len = 10000 / Hlw.energy_period_counter;
|
|
Hlw.energy_period_counter = 0;
|
|
if (hlw_len) {
|
|
Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) / hlw_len) / 36;
|
|
EnergyUpdateToday();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void HlwSnsInit(void)
|
|
{
|
|
if (!Settings.energy_power_calibration || (4975 == Settings.energy_power_calibration)) {
|
|
Settings.energy_power_calibration = HLW_PREF_PULSE;
|
|
Settings.energy_voltage_calibration = HLW_UREF_PULSE;
|
|
Settings.energy_current_calibration = HLW_IREF_PULSE;
|
|
}
|
|
|
|
if (Hlw.model_type) {
|
|
Hlw.power_ratio = HJL_PREF;
|
|
Hlw.voltage_ratio = HJL_UREF;
|
|
Hlw.current_ratio = HJL_IREF;
|
|
} else {
|
|
Hlw.power_ratio = HLW_PREF;
|
|
Hlw.voltage_ratio = HLW_UREF;
|
|
Hlw.current_ratio = HLW_IREF;
|
|
}
|
|
|
|
if (pin[GPIO_NRG_SEL] < 99) {
|
|
pinMode(pin[GPIO_NRG_SEL], OUTPUT);
|
|
digitalWrite(pin[GPIO_NRG_SEL], Hlw.select_ui_flag);
|
|
}
|
|
if (pin[GPIO_NRG_CF1] < 99) {
|
|
pinMode(pin[GPIO_NRG_CF1], INPUT_PULLUP);
|
|
attachInterrupt(pin[GPIO_NRG_CF1], HlwCf1Interrupt, FALLING);
|
|
}
|
|
pinMode(pin[GPIO_HLW_CF], INPUT_PULLUP);
|
|
attachInterrupt(pin[GPIO_HLW_CF], HlwCfInterrupt, FALLING);
|
|
}
|
|
|
|
void HlwDrvInit(void)
|
|
{
|
|
Hlw.model_type = 0;
|
|
if (pin[GPIO_HJL_CF] < 99) {
|
|
pin[GPIO_HLW_CF] = pin[GPIO_HJL_CF];
|
|
pin[GPIO_HJL_CF] = 99;
|
|
Hlw.model_type = 1;
|
|
}
|
|
|
|
if (pin[GPIO_HLW_CF] < 99) {
|
|
|
|
Hlw.ui_flag = true;
|
|
if (pin[GPIO_NRG_SEL_INV] < 99) {
|
|
pin[GPIO_NRG_SEL] = pin[GPIO_NRG_SEL_INV];
|
|
pin[GPIO_NRG_SEL_INV] = 99;
|
|
Hlw.ui_flag = false;
|
|
}
|
|
|
|
if (pin[GPIO_NRG_CF1] < 99) {
|
|
if (99 == pin[GPIO_NRG_SEL]) {
|
|
Energy.current_available = false;
|
|
}
|
|
} else {
|
|
Energy.current_available = false;
|
|
Energy.voltage_available = false;
|
|
}
|
|
|
|
energy_flg = XNRG_01;
|
|
}
|
|
}
|
|
|
|
bool HlwCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
if ((CMND_POWERCAL == Energy.command_code) || (CMND_VOLTAGECAL == Energy.command_code) || (CMND_CURRENTCAL == Energy.command_code)) {
|
|
|
|
}
|
|
else if (CMND_POWERSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Hlw.cf_power_pulse_length ) {
|
|
Settings.energy_power_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data) * 10) * Hlw.cf_power_pulse_length ) / Hlw.power_ratio;
|
|
}
|
|
}
|
|
else if (CMND_VOLTAGESET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Hlw.cf1_voltage_pulse_length ) {
|
|
Settings.energy_voltage_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data) * 10) * Hlw.cf1_voltage_pulse_length ) / Hlw.voltage_ratio;
|
|
}
|
|
}
|
|
else if (CMND_CURRENTSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Hlw.cf1_current_pulse_length) {
|
|
Settings.energy_current_calibration = ((unsigned long)(CharToFloat(XdrvMailbox.data)) * Hlw.cf1_current_pulse_length) / Hlw.current_ratio;
|
|
}
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg01(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_200_MSECOND:
|
|
HlwEvery200ms();
|
|
break;
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
HlwEverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = HlwCommand();
|
|
break;
|
|
case FUNC_INIT:
|
|
HlwSnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
HlwDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_02_cse7766.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_02_cse7766.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_CSE7766
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XNRG_02 2
|
|
|
|
#define CSE_MAX_INVALID_POWER 128
|
|
|
|
#define CSE_NOT_CALIBRATED 0xAA
|
|
|
|
#define CSE_PULSES_NOT_INITIALIZED -1
|
|
|
|
#define CSE_PREF 1000
|
|
#define CSE_UREF 100
|
|
|
|
#define CSE_BUFFER_SIZE 25
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *CseSerial = nullptr;
|
|
|
|
struct CSE {
|
|
long voltage_cycle = 0;
|
|
long current_cycle = 0;
|
|
long power_cycle = 0;
|
|
long power_cycle_first = 0;
|
|
long cf_pulses = 0;
|
|
long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED;
|
|
|
|
int byte_counter = 0;
|
|
uint8_t *rx_buffer = nullptr;
|
|
uint8_t power_invalid = 0;
|
|
bool received = false;
|
|
} Cse;
|
|
|
|
void CseReceived(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t header = Cse.rx_buffer[0];
|
|
if ((header & 0xFC) == 0xFC) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware"));
|
|
return;
|
|
}
|
|
|
|
|
|
if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) {
|
|
long voltage_coefficient = 191200;
|
|
if (CSE_NOT_CALIBRATED != header) {
|
|
voltage_coefficient = Cse.rx_buffer[2] << 16 | Cse.rx_buffer[3] << 8 | Cse.rx_buffer[4];
|
|
}
|
|
Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF;
|
|
}
|
|
if (HLW_IREF_PULSE == Settings.energy_current_calibration) {
|
|
long current_coefficient = 16140;
|
|
if (CSE_NOT_CALIBRATED != header) {
|
|
current_coefficient = Cse.rx_buffer[8] << 16 | Cse.rx_buffer[9] << 8 | Cse.rx_buffer[10];
|
|
}
|
|
Settings.energy_current_calibration = current_coefficient;
|
|
}
|
|
if (HLW_PREF_PULSE == Settings.energy_power_calibration) {
|
|
long power_coefficient = 5364000;
|
|
if (CSE_NOT_CALIBRATED != header) {
|
|
power_coefficient = Cse.rx_buffer[14] << 16 | Cse.rx_buffer[15] << 8 | Cse.rx_buffer[16];
|
|
}
|
|
Settings.energy_power_calibration = power_coefficient / CSE_PREF;
|
|
}
|
|
|
|
uint8_t adjustement = Cse.rx_buffer[20];
|
|
Cse.voltage_cycle = Cse.rx_buffer[5] << 16 | Cse.rx_buffer[6] << 8 | Cse.rx_buffer[7];
|
|
Cse.current_cycle = Cse.rx_buffer[11] << 16 | Cse.rx_buffer[12] << 8 | Cse.rx_buffer[13];
|
|
Cse.power_cycle = Cse.rx_buffer[17] << 16 | Cse.rx_buffer[18] << 8 | Cse.rx_buffer[19];
|
|
Cse.cf_pulses = Cse.rx_buffer[21] << 8 | Cse.rx_buffer[22];
|
|
|
|
if (Energy.power_on) {
|
|
if (adjustement & 0x40) {
|
|
Energy.voltage[0] = (float)(Settings.energy_voltage_calibration * CSE_UREF) / (float)Cse.voltage_cycle;
|
|
}
|
|
if (adjustement & 0x10) {
|
|
Cse.power_invalid = 0;
|
|
if ((header & 0xF2) == 0xF2) {
|
|
Energy.active_power[0] = 0;
|
|
} else {
|
|
if (0 == Cse.power_cycle_first) { Cse.power_cycle_first = Cse.power_cycle; }
|
|
if (Cse.power_cycle_first != Cse.power_cycle) {
|
|
Cse.power_cycle_first = -1;
|
|
Energy.active_power[0] = (float)(Settings.energy_power_calibration * CSE_PREF) / (float)Cse.power_cycle;
|
|
} else {
|
|
Energy.active_power[0] = 0;
|
|
}
|
|
}
|
|
} else {
|
|
if (Cse.power_invalid < Settings.param[P_CSE7766_INVALID_POWER]) {
|
|
Cse.power_invalid++;
|
|
} else {
|
|
Cse.power_cycle_first = 0;
|
|
Energy.active_power[0] = 0;
|
|
}
|
|
}
|
|
if (adjustement & 0x20) {
|
|
if (0 == Energy.active_power[0]) {
|
|
Energy.current[0] = 0;
|
|
} else {
|
|
Energy.current[0] = (float)Settings.energy_current_calibration / (float)Cse.current_cycle;
|
|
}
|
|
}
|
|
} else {
|
|
Cse.power_cycle_first = 0;
|
|
Energy.voltage[0] = 0;
|
|
Energy.active_power[0] = 0;
|
|
Energy.current[0] = 0;
|
|
}
|
|
}
|
|
|
|
bool CseSerialInput(void)
|
|
{
|
|
while (CseSerial->available()) {
|
|
yield();
|
|
uint8_t serial_in_byte = CseSerial->read();
|
|
|
|
if (Cse.received) {
|
|
Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte;
|
|
if (24 == Cse.byte_counter) {
|
|
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, Cse.rx_buffer, 24);
|
|
|
|
uint8_t checksum = 0;
|
|
for (uint32_t i = 2; i < 23; i++) { checksum += Cse.rx_buffer[i]; }
|
|
if (checksum == Cse.rx_buffer[23]) {
|
|
Energy.data_valid[0] = 0;
|
|
CseReceived();
|
|
Cse.received = false;
|
|
return true;
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE));
|
|
do {
|
|
memmove(Cse.rx_buffer, Cse.rx_buffer +1, 24);
|
|
Cse.byte_counter--;
|
|
} while ((Cse.byte_counter > 2) && (0x5A != Cse.rx_buffer[1]));
|
|
if (0x5A != Cse.rx_buffer[1]) {
|
|
Cse.received = false;
|
|
Cse.byte_counter = 0;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if ((0x5A == serial_in_byte) && (1 == Cse.byte_counter)) {
|
|
Cse.received = true;
|
|
} else {
|
|
Cse.byte_counter = 0;
|
|
}
|
|
Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void CseEverySecond(void)
|
|
{
|
|
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
|
|
Cse.voltage_cycle = 0;
|
|
Cse.current_cycle = 0;
|
|
Cse.power_cycle = 0;
|
|
} else {
|
|
long cf_frequency = 0;
|
|
|
|
if (CSE_PULSES_NOT_INITIALIZED == Cse.cf_pulses_last_time) {
|
|
Cse.cf_pulses_last_time = Cse.cf_pulses;
|
|
} else {
|
|
if (Cse.cf_pulses < Cse.cf_pulses_last_time) {
|
|
cf_frequency = (65536 - Cse.cf_pulses_last_time) + Cse.cf_pulses;
|
|
} else {
|
|
cf_frequency = Cse.cf_pulses - Cse.cf_pulses_last_time;
|
|
}
|
|
if (cf_frequency && Energy.active_power[0]) {
|
|
unsigned long delta = (cf_frequency * Settings.energy_power_calibration) / 36;
|
|
|
|
|
|
|
|
if (delta <= (4000*100/36) * 10 ) {
|
|
Cse.cf_pulses_last_time = Cse.cf_pulses;
|
|
Energy.kWhtoday_delta += delta;
|
|
}
|
|
else {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Load overflow"));
|
|
Cse.cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED;
|
|
}
|
|
EnergyUpdateToday();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CseSnsInit(void)
|
|
{
|
|
|
|
|
|
CseSerial = new TasmotaSerial(pin[GPIO_CSE7766_RX], -1, 1);
|
|
if (CseSerial->begin(4800, 2)) {
|
|
if (CseSerial->hardwareSerial()) {
|
|
SetSerial(4800, TS_SERIAL_8E1);
|
|
ClaimSerial();
|
|
}
|
|
if (0 == Settings.param[P_CSE7766_INVALID_POWER]) {
|
|
Settings.param[P_CSE7766_INVALID_POWER] = CSE_MAX_INVALID_POWER;
|
|
}
|
|
Cse.power_invalid = Settings.param[P_CSE7766_INVALID_POWER];
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void CseDrvInit(void)
|
|
{
|
|
Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE));
|
|
if (Cse.rx_buffer != nullptr) {
|
|
|
|
if (pin[GPIO_CSE7766_RX] < 99) {
|
|
energy_flg = XNRG_02;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CseCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
if (CMND_POWERSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Cse.power_cycle) {
|
|
Settings.energy_power_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.power_cycle) / CSE_PREF;
|
|
}
|
|
}
|
|
else if (CMND_VOLTAGESET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Cse.voltage_cycle) {
|
|
Settings.energy_voltage_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.voltage_cycle) / CSE_UREF;
|
|
}
|
|
}
|
|
else if (CMND_CURRENTSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Cse.current_cycle) {
|
|
Settings.energy_current_calibration = (unsigned long)(CharToFloat(XdrvMailbox.data) * Cse.current_cycle) / 1000;
|
|
}
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg02(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (CseSerial) { CseSerialInput(); }
|
|
break;
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
CseEverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = CseCommand();
|
|
break;
|
|
case FUNC_INIT:
|
|
CseSnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
CseDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_PZEM004T
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
|
|
#define XNRG_03 3
|
|
|
|
const uint32_t PZEM_STABILIZE = 30;
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *PzemSerial = nullptr;
|
|
|
|
#define PZEM_VOLTAGE (uint8_t)0xB0
|
|
#define RESP_VOLTAGE (uint8_t)0xA0
|
|
|
|
#define PZEM_CURRENT (uint8_t)0xB1
|
|
#define RESP_CURRENT (uint8_t)0xA1
|
|
|
|
#define PZEM_POWER (uint8_t)0xB2
|
|
#define RESP_POWER (uint8_t)0xA2
|
|
|
|
#define PZEM_ENERGY (uint8_t)0xB3
|
|
#define RESP_ENERGY (uint8_t)0xA3
|
|
|
|
#define PZEM_SET_ADDRESS (uint8_t)0xB4
|
|
#define RESP_SET_ADDRESS (uint8_t)0xA4
|
|
|
|
#define PZEM_POWER_ALARM (uint8_t)0xB5
|
|
#define RESP_POWER_ALARM (uint8_t)0xA5
|
|
|
|
#define PZEM_DEFAULT_READ_TIMEOUT 500
|
|
|
|
|
|
|
|
struct PZEM {
|
|
float energy = 0;
|
|
float last_energy = 0;
|
|
uint8_t send_retry = 0;
|
|
uint8_t read_state = 0;
|
|
uint8_t phase = 0;
|
|
uint8_t address = 0;
|
|
} Pzem;
|
|
|
|
struct PZEMCommand {
|
|
uint8_t command;
|
|
uint8_t addr[4];
|
|
uint8_t data;
|
|
uint8_t crc;
|
|
};
|
|
|
|
uint8_t PzemCrc(uint8_t *data)
|
|
{
|
|
uint16_t crc = 0;
|
|
for (uint32_t i = 0; i < sizeof(PZEMCommand) -1; i++) {
|
|
crc += *data++;
|
|
}
|
|
return (uint8_t)(crc & 0xFF);
|
|
}
|
|
|
|
void PzemSend(uint8_t cmd)
|
|
{
|
|
PZEMCommand pzem;
|
|
|
|
pzem.command = cmd;
|
|
pzem.addr[0] = 192;
|
|
pzem.addr[1] = 168;
|
|
pzem.addr[2] = 1;
|
|
pzem.addr[3] = ((PZEM_SET_ADDRESS == cmd) && Pzem.address) ? Pzem.address : 1 + Pzem.phase;
|
|
pzem.data = 0;
|
|
|
|
uint8_t *bytes = (uint8_t*)&pzem;
|
|
pzem.crc = PzemCrc(bytes);
|
|
|
|
PzemSerial->flush();
|
|
PzemSerial->write(bytes, sizeof(pzem));
|
|
|
|
Pzem.address = 0;
|
|
}
|
|
|
|
bool PzemReceiveReady(void)
|
|
{
|
|
return PzemSerial->available() >= (int)sizeof(PZEMCommand);
|
|
}
|
|
|
|
bool PzemRecieve(uint8_t resp, float *data)
|
|
{
|
|
# 124 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_03_pzem004t.ino"
|
|
uint8_t buffer[sizeof(PZEMCommand)] = { 0 };
|
|
|
|
unsigned long start = millis();
|
|
uint8_t len = 0;
|
|
while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) {
|
|
if (PzemSerial->available() > 0) {
|
|
uint8_t c = (uint8_t)PzemSerial->read();
|
|
if (!len && ((c & 0xF8) != 0xA0)) {
|
|
continue;
|
|
}
|
|
buffer[len++] = c;
|
|
}
|
|
}
|
|
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, len);
|
|
|
|
if (len != sizeof(PZEMCommand)) {
|
|
|
|
return false;
|
|
}
|
|
if (buffer[6] != PzemCrc(buffer)) {
|
|
|
|
return false;
|
|
}
|
|
if (buffer[0] != resp) {
|
|
|
|
return false;
|
|
}
|
|
|
|
switch (resp) {
|
|
case RESP_VOLTAGE:
|
|
*data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0);
|
|
break;
|
|
case RESP_CURRENT:
|
|
*data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0);
|
|
break;
|
|
case RESP_POWER:
|
|
*data = (float)(buffer[1] << 8) + buffer[2];
|
|
break;
|
|
case RESP_ENERGY:
|
|
*data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3];
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY };
|
|
const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY };
|
|
|
|
void PzemEvery250ms(void)
|
|
{
|
|
bool data_ready = PzemReceiveReady();
|
|
|
|
if (data_ready) {
|
|
float value = 0;
|
|
if (PzemRecieve(pzem_responses[Pzem.read_state], &value)) {
|
|
Energy.data_valid[Pzem.phase] = 0;
|
|
switch (Pzem.read_state) {
|
|
case 1:
|
|
Energy.voltage[Pzem.phase] = value;
|
|
break;
|
|
case 2:
|
|
Energy.current[Pzem.phase] = value;
|
|
break;
|
|
case 3:
|
|
Energy.active_power[Pzem.phase] = value;
|
|
break;
|
|
case 4:
|
|
Pzem.energy += value;
|
|
if (Pzem.phase == Energy.phase_count -1) {
|
|
if (Pzem.energy > Pzem.last_energy) {
|
|
if (uptime > PZEM_STABILIZE) {
|
|
EnergyUpdateTotal(Pzem.energy, false);
|
|
}
|
|
Pzem.last_energy = Pzem.energy;
|
|
}
|
|
Pzem.energy = 0;
|
|
}
|
|
break;
|
|
}
|
|
Pzem.read_state++;
|
|
if (5 == Pzem.read_state) {
|
|
Pzem.read_state = 1;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
if (0 == Pzem.send_retry || data_ready) {
|
|
if (1 == Pzem.read_state) {
|
|
if (0 == Pzem.phase) {
|
|
Pzem.phase = Energy.phase_count -1;
|
|
} else {
|
|
Pzem.phase--;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if (Pzem.address) {
|
|
Pzem.read_state = 0;
|
|
}
|
|
|
|
Pzem.send_retry = 5;
|
|
PzemSend(pzem_commands[Pzem.read_state]);
|
|
}
|
|
else {
|
|
Pzem.send_retry--;
|
|
if ((Energy.phase_count > 1) && (0 == Pzem.send_retry) && (uptime < PZEM_STABILIZE)) {
|
|
Energy.phase_count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PzemSnsInit(void)
|
|
{
|
|
|
|
PzemSerial = new TasmotaSerial(pin[GPIO_PZEM004_RX], pin[GPIO_PZEM0XX_TX], 1);
|
|
if (PzemSerial->begin(9600)) {
|
|
if (PzemSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
Energy.phase_count = 3;
|
|
Pzem.phase = 0;
|
|
Pzem.read_state = 1;
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void PzemDrvInit(void)
|
|
{
|
|
if ((pin[GPIO_PZEM004_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) {
|
|
energy_flg = XNRG_03;
|
|
}
|
|
}
|
|
|
|
bool PzemCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
if (CMND_MODULEADDRESS == Energy.command_code) {
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4)) {
|
|
Pzem.address = XdrvMailbox.payload;
|
|
}
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg03(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (PzemSerial && (uptime > 4)) { PzemEvery250ms(); }
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = PzemCommand();
|
|
break;
|
|
case FUNC_INIT:
|
|
PzemSnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
PzemDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_04_mcp39f501.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_04_mcp39f501.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_MCP39F501
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XNRG_04 4
|
|
|
|
#define MCP_BAUDRATE 4800
|
|
#define MCP_TIMEOUT 4
|
|
#define MCP_CALIBRATION_TIMEOUT 2
|
|
|
|
#define MCP_CALIBRATE_POWER 0x001
|
|
#define MCP_CALIBRATE_VOLTAGE 0x002
|
|
#define MCP_CALIBRATE_CURRENT 0x004
|
|
#define MCP_CALIBRATE_FREQUENCY 0x008
|
|
#define MCP_SINGLE_WIRE_FLAG 0x100
|
|
|
|
#define MCP_START_FRAME 0xA5
|
|
#define MCP_ACK_FRAME 0x06
|
|
#define MCP_ERROR_NAK 0x15
|
|
#define MCP_ERROR_CRC 0x51
|
|
|
|
#define MCP_SINGLE_WIRE 0xAB
|
|
|
|
#define MCP_SET_ADDRESS 0x41
|
|
|
|
#define MCP_READ 0x4E
|
|
#define MCP_READ_16 0x52
|
|
#define MCP_READ_32 0x44
|
|
|
|
#define MCP_WRITE 0x4D
|
|
#define MCP_WRITE_16 0x57
|
|
#define MCP_WRITE_32 0x45
|
|
|
|
#define MCP_SAVE_REGISTERS 0x53
|
|
|
|
#define MCP_CALIBRATION_BASE 0x0028
|
|
#define MCP_CALIBRATION_LEN 52
|
|
|
|
#define MCP_FREQUENCY_REF_BASE 0x0094
|
|
#define MCP_FREQUENCY_GAIN_BASE 0x00AE
|
|
#define MCP_FREQUENCY_LEN 4
|
|
|
|
#define MCP_BUFFER_SIZE 60
|
|
|
|
#include <TasmotaSerial.h>
|
|
TasmotaSerial *McpSerial = nullptr;
|
|
|
|
typedef struct mcp_cal_registers_type {
|
|
uint16_t gain_current_rms;
|
|
uint16_t gain_voltage_rms;
|
|
uint16_t gain_active_power;
|
|
uint16_t gain_reactive_power;
|
|
sint32_t offset_current_rms;
|
|
sint32_t offset_active_power;
|
|
sint32_t offset_reactive_power;
|
|
sint16_t dc_offset_current;
|
|
sint16_t phase_compensation;
|
|
uint16_t apparent_power_divisor;
|
|
|
|
uint32_t system_configuration;
|
|
uint16_t dio_configuration;
|
|
uint32_t range;
|
|
|
|
uint32_t calibration_current;
|
|
uint16_t calibration_voltage;
|
|
uint32_t calibration_active_power;
|
|
uint32_t calibration_reactive_power;
|
|
uint16_t accumulation_interval;
|
|
} mcp_cal_registers_type;
|
|
|
|
char *mcp_buffer = nullptr;
|
|
unsigned long mcp_window = 0;
|
|
unsigned long mcp_kWhcounter = 0;
|
|
uint32_t mcp_system_configuration = 0x03000000;
|
|
uint32_t mcp_active_power;
|
|
|
|
|
|
uint32_t mcp_current_rms;
|
|
uint16_t mcp_voltage_rms;
|
|
uint16_t mcp_line_frequency;
|
|
|
|
uint8_t mcp_address = 0;
|
|
uint8_t mcp_calibration_active = 0;
|
|
uint8_t mcp_init = 0;
|
|
uint8_t mcp_timeout = 0;
|
|
uint8_t mcp_calibrate = 0;
|
|
uint8_t mcp_byte_counter = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t McpChecksum(uint8_t *data)
|
|
{
|
|
uint8_t checksum = 0;
|
|
uint8_t offset = 0;
|
|
uint8_t len = data[1] -1;
|
|
|
|
for (uint32_t i = offset; i < len; i++) { checksum += data[i]; }
|
|
return checksum;
|
|
}
|
|
|
|
unsigned long McpExtractInt(char *data, uint8_t offset, uint8_t size)
|
|
{
|
|
unsigned long result = 0;
|
|
unsigned long pow = 1;
|
|
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
result = result + (uint8_t)data[offset + i] * pow;
|
|
pow = pow * 256;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void McpSetInt(unsigned long value, uint8_t *data, uint8_t offset, size_t size)
|
|
{
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
data[offset + i] = ((value >> (i * 8)) & 0xFF);
|
|
}
|
|
}
|
|
|
|
void McpSend(uint8_t *data)
|
|
{
|
|
if (mcp_timeout) { return; }
|
|
mcp_timeout = MCP_TIMEOUT;
|
|
|
|
data[0] = MCP_START_FRAME;
|
|
data[data[1] -1] = McpChecksum(data);
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < data[1]; i++) {
|
|
McpSerial->write(data[i]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void McpGetAddress(void)
|
|
{
|
|
uint8_t data[] = { MCP_START_FRAME, 7, MCP_SET_ADDRESS, 0x00, 0x26, MCP_READ_16, 0x00 };
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
void McpAddressReceive(void)
|
|
{
|
|
|
|
mcp_address = mcp_buffer[3];
|
|
}
|
|
|
|
|
|
|
|
void McpGetCalibration(void)
|
|
{
|
|
if (mcp_calibration_active) { return; }
|
|
mcp_calibration_active = MCP_CALIBRATION_TIMEOUT;
|
|
|
|
uint8_t data[] = { MCP_START_FRAME, 8, MCP_SET_ADDRESS, (MCP_CALIBRATION_BASE >> 8) & 0xFF, MCP_CALIBRATION_BASE & 0xFF, MCP_READ, MCP_CALIBRATION_LEN, 0x00 };
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
void McpParseCalibration(void)
|
|
{
|
|
bool action = false;
|
|
mcp_cal_registers_type cal_registers;
|
|
|
|
|
|
cal_registers.gain_current_rms = McpExtractInt(mcp_buffer, 2, 2);
|
|
cal_registers.gain_voltage_rms = McpExtractInt(mcp_buffer, 4, 2);
|
|
cal_registers.gain_active_power = McpExtractInt(mcp_buffer, 6, 2);
|
|
cal_registers.gain_reactive_power = McpExtractInt(mcp_buffer, 8, 2);
|
|
cal_registers.offset_current_rms = McpExtractInt(mcp_buffer, 10, 4);
|
|
cal_registers.offset_active_power = McpExtractInt(mcp_buffer, 14, 4);
|
|
cal_registers.offset_reactive_power = McpExtractInt(mcp_buffer, 18, 4);
|
|
cal_registers.dc_offset_current = McpExtractInt(mcp_buffer, 22, 2);
|
|
cal_registers.phase_compensation = McpExtractInt(mcp_buffer, 24, 2);
|
|
cal_registers.apparent_power_divisor = McpExtractInt(mcp_buffer, 26, 2);
|
|
|
|
cal_registers.system_configuration = McpExtractInt(mcp_buffer, 28, 4);
|
|
cal_registers.dio_configuration = McpExtractInt(mcp_buffer, 32, 2);
|
|
cal_registers.range = McpExtractInt(mcp_buffer, 34, 4);
|
|
|
|
cal_registers.calibration_current = McpExtractInt(mcp_buffer, 38, 4);
|
|
cal_registers.calibration_voltage = McpExtractInt(mcp_buffer, 42, 2);
|
|
cal_registers.calibration_active_power = McpExtractInt(mcp_buffer, 44, 4);
|
|
cal_registers.calibration_reactive_power = McpExtractInt(mcp_buffer, 48, 4);
|
|
cal_registers.accumulation_interval = McpExtractInt(mcp_buffer, 52, 2);
|
|
|
|
if (mcp_calibrate & MCP_CALIBRATE_POWER) {
|
|
cal_registers.calibration_active_power = Settings.energy_power_calibration;
|
|
if (McpCalibrationCalc(&cal_registers, 16)) { action = true; }
|
|
}
|
|
if (mcp_calibrate & MCP_CALIBRATE_VOLTAGE) {
|
|
cal_registers.calibration_voltage = Settings.energy_voltage_calibration;
|
|
if (McpCalibrationCalc(&cal_registers, 0)) { action = true; }
|
|
}
|
|
if (mcp_calibrate & MCP_CALIBRATE_CURRENT) {
|
|
cal_registers.calibration_current = Settings.energy_current_calibration;
|
|
if (McpCalibrationCalc(&cal_registers, 8)) { action = true; }
|
|
}
|
|
mcp_timeout = 0;
|
|
if (action) { McpSetCalibration(&cal_registers); }
|
|
|
|
mcp_calibrate = 0;
|
|
|
|
Settings.energy_power_calibration = cal_registers.calibration_active_power;
|
|
Settings.energy_voltage_calibration = cal_registers.calibration_voltage;
|
|
Settings.energy_current_calibration = cal_registers.calibration_current;
|
|
|
|
mcp_system_configuration = cal_registers.system_configuration;
|
|
|
|
if (mcp_system_configuration & MCP_SINGLE_WIRE_FLAG) {
|
|
mcp_system_configuration &= ~MCP_SINGLE_WIRE_FLAG;
|
|
McpSetSystemConfiguration(2);
|
|
}
|
|
}
|
|
|
|
bool McpCalibrationCalc(struct mcp_cal_registers_type *cal_registers, uint8_t range_shift)
|
|
{
|
|
uint32_t measured;
|
|
uint32_t expected;
|
|
uint16_t *gain;
|
|
uint32_t new_gain;
|
|
|
|
if (range_shift == 0) {
|
|
measured = mcp_voltage_rms;
|
|
expected = cal_registers->calibration_voltage;
|
|
gain = &(cal_registers->gain_voltage_rms);
|
|
} else if (range_shift == 8) {
|
|
measured = mcp_current_rms;
|
|
expected = cal_registers->calibration_current;
|
|
gain = &(cal_registers->gain_current_rms);
|
|
} else if (range_shift == 16) {
|
|
measured = mcp_active_power;
|
|
expected = cal_registers->calibration_active_power;
|
|
gain = &(cal_registers->gain_active_power);
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (measured == 0) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t range = (cal_registers->range >> range_shift) & 0xFF;
|
|
|
|
calc:
|
|
new_gain = (*gain) * expected / measured;
|
|
|
|
if (new_gain < 25000) {
|
|
range++;
|
|
if (measured > 6) {
|
|
measured = measured / 2;
|
|
goto calc;
|
|
}
|
|
}
|
|
|
|
if (new_gain > 55000) {
|
|
range--;
|
|
measured = measured * 2;
|
|
goto calc;
|
|
}
|
|
|
|
*gain = new_gain;
|
|
uint32_t old_range = (cal_registers->range >> range_shift) & 0xFF;
|
|
cal_registers->range = cal_registers->range ^ (old_range << range_shift);
|
|
cal_registers->range = cal_registers->range | (range << range_shift);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void McpSetCalibration(struct mcp_cal_registers_type *cal_registers)
|
|
{
|
|
uint8_t data[7 + MCP_CALIBRATION_LEN + 2 + 1];
|
|
|
|
data[1] = sizeof(data);
|
|
data[2] = MCP_SET_ADDRESS;
|
|
data[3] = (MCP_CALIBRATION_BASE >> 8) & 0xFF;
|
|
data[4] = (MCP_CALIBRATION_BASE >> 0) & 0xFF;
|
|
|
|
data[5] = MCP_WRITE;
|
|
data[6] = MCP_CALIBRATION_LEN;
|
|
|
|
McpSetInt(cal_registers->gain_current_rms, data, 0+7, 2);
|
|
McpSetInt(cal_registers->gain_voltage_rms, data, 2+7, 2);
|
|
McpSetInt(cal_registers->gain_active_power, data, 4+7, 2);
|
|
McpSetInt(cal_registers->gain_reactive_power, data, 6+7, 2);
|
|
McpSetInt(cal_registers->offset_current_rms, data, 8+7, 4);
|
|
McpSetInt(cal_registers->offset_active_power, data, 12+7, 4);
|
|
McpSetInt(cal_registers->offset_reactive_power, data, 16+7, 4);
|
|
McpSetInt(cal_registers->dc_offset_current, data, 20+7, 2);
|
|
McpSetInt(cal_registers->phase_compensation, data, 22+7, 2);
|
|
McpSetInt(cal_registers->apparent_power_divisor, data, 24+7, 2);
|
|
|
|
McpSetInt(cal_registers->system_configuration, data, 26+7, 4);
|
|
McpSetInt(cal_registers->dio_configuration, data, 30+7, 2);
|
|
McpSetInt(cal_registers->range, data, 32+7, 4);
|
|
|
|
McpSetInt(cal_registers->calibration_current, data, 36+7, 4);
|
|
McpSetInt(cal_registers->calibration_voltage, data, 40+7, 2);
|
|
McpSetInt(cal_registers->calibration_active_power, data, 42+7, 4);
|
|
McpSetInt(cal_registers->calibration_reactive_power, data, 46+7, 4);
|
|
McpSetInt(cal_registers->accumulation_interval, data, 50+7, 2);
|
|
|
|
data[MCP_CALIBRATION_LEN+7] = MCP_SAVE_REGISTERS;
|
|
data[MCP_CALIBRATION_LEN+8] = mcp_address;
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
|
|
|
|
void McpSetSystemConfiguration(uint16 interval)
|
|
{
|
|
|
|
uint8_t data[17];
|
|
|
|
data[ 1] = sizeof(data);
|
|
data[ 2] = MCP_SET_ADDRESS;
|
|
data[ 3] = 0x00;
|
|
data[ 4] = 0x42;
|
|
data[ 5] = MCP_WRITE_32;
|
|
data[ 6] = (mcp_system_configuration >> 24) & 0xFF;
|
|
data[ 7] = (mcp_system_configuration >> 16) & 0xFF;
|
|
data[ 8] = (mcp_system_configuration >> 8) & 0xFF;
|
|
data[ 9] = (mcp_system_configuration >> 0) & 0xFF;
|
|
data[10] = MCP_SET_ADDRESS;
|
|
data[11] = 0x00;
|
|
data[12] = 0x5A;
|
|
data[13] = MCP_WRITE_16;
|
|
data[14] = (interval >> 8) & 0xFF;
|
|
data[15] = (interval >> 0) & 0xFF;
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
|
|
|
|
void McpGetFrequency(void)
|
|
{
|
|
if (mcp_calibration_active) { return; }
|
|
mcp_calibration_active = MCP_CALIBRATION_TIMEOUT;
|
|
|
|
uint8_t data[] = { MCP_START_FRAME, 11, MCP_SET_ADDRESS, (MCP_FREQUENCY_REF_BASE >> 8) & 0xFF, MCP_FREQUENCY_REF_BASE & 0xFF, MCP_READ_16,
|
|
MCP_SET_ADDRESS, (MCP_FREQUENCY_GAIN_BASE >> 8) & 0xFF, MCP_FREQUENCY_GAIN_BASE & 0xFF, MCP_READ_16, 0x00 };
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
void McpParseFrequency(void)
|
|
{
|
|
|
|
uint16_t line_frequency_ref = mcp_buffer[2] * 256 + mcp_buffer[3];
|
|
uint16_t gain_line_frequency = mcp_buffer[4] * 256 + mcp_buffer[5];
|
|
|
|
if (mcp_calibrate & MCP_CALIBRATE_FREQUENCY) {
|
|
line_frequency_ref = Settings.energy_frequency_calibration;
|
|
|
|
if ((0xFFFF == mcp_line_frequency) || (0 == gain_line_frequency)) {
|
|
mcp_line_frequency = 50000;
|
|
gain_line_frequency = 0x8000;
|
|
}
|
|
gain_line_frequency = gain_line_frequency * line_frequency_ref / mcp_line_frequency;
|
|
|
|
mcp_timeout = 0;
|
|
McpSetFrequency(line_frequency_ref, gain_line_frequency);
|
|
}
|
|
|
|
Settings.energy_frequency_calibration = line_frequency_ref;
|
|
|
|
mcp_calibrate = 0;
|
|
}
|
|
|
|
void McpSetFrequency(uint16_t line_frequency_ref, uint16_t gain_line_frequency)
|
|
{
|
|
|
|
uint8_t data[17];
|
|
|
|
data[ 1] = sizeof(data);
|
|
data[ 2] = MCP_SET_ADDRESS;
|
|
data[ 3] = (MCP_FREQUENCY_REF_BASE >> 8) & 0xFF;
|
|
data[ 4] = (MCP_FREQUENCY_REF_BASE >> 0) & 0xFF;
|
|
|
|
data[ 5] = MCP_WRITE_16;
|
|
data[ 6] = (line_frequency_ref >> 8) & 0xFF;
|
|
data[ 7] = (line_frequency_ref >> 0) & 0xFF;
|
|
|
|
data[ 8] = MCP_SET_ADDRESS;
|
|
data[ 9] = (MCP_FREQUENCY_GAIN_BASE >> 8) & 0xFF;
|
|
data[10] = (MCP_FREQUENCY_GAIN_BASE >> 0) & 0xFF;
|
|
|
|
data[11] = MCP_WRITE_16;
|
|
data[12] = (gain_line_frequency >> 8) & 0xFF;
|
|
data[13] = (gain_line_frequency >> 0) & 0xFF;
|
|
|
|
data[14] = MCP_SAVE_REGISTERS;
|
|
data[15] = mcp_address;
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
|
|
|
|
void McpGetData(void)
|
|
{
|
|
uint8_t data[] = { MCP_START_FRAME, 8, MCP_SET_ADDRESS, 0x00, 0x04, MCP_READ, 22, 0x00 };
|
|
|
|
McpSend(data);
|
|
}
|
|
|
|
void McpParseData(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
mcp_current_rms = McpExtractInt(mcp_buffer, 2, 4);
|
|
mcp_voltage_rms = McpExtractInt(mcp_buffer, 6, 2);
|
|
mcp_active_power = McpExtractInt(mcp_buffer, 8, 4);
|
|
|
|
|
|
mcp_line_frequency = McpExtractInt(mcp_buffer, 22, 2);
|
|
|
|
if (Energy.power_on) {
|
|
Energy.data_valid[0] = 0;
|
|
Energy.frequency[0] = (float)mcp_line_frequency / 1000;
|
|
Energy.voltage[0] = (float)mcp_voltage_rms / 10;
|
|
Energy.active_power[0] = (float)mcp_active_power / 100;
|
|
if (0 == Energy.active_power[0]) {
|
|
Energy.current[0] = 0;
|
|
} else {
|
|
Energy.current[0] = (float)mcp_current_rms / 10000;
|
|
}
|
|
} else {
|
|
Energy.data_valid[0] = ENERGY_WATCHDOG;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void McpSerialInput(void)
|
|
{
|
|
while ((McpSerial->available()) && (mcp_byte_counter < MCP_BUFFER_SIZE)) {
|
|
yield();
|
|
mcp_buffer[mcp_byte_counter++] = McpSerial->read();
|
|
mcp_window = millis();
|
|
}
|
|
|
|
|
|
if ((mcp_byte_counter) && (millis() - mcp_window > (24000 / MCP_BAUDRATE) +1)) {
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, (uint8_t*)mcp_buffer, mcp_byte_counter);
|
|
|
|
if (MCP_BUFFER_SIZE == mcp_byte_counter) {
|
|
|
|
}
|
|
else if (1 == mcp_byte_counter) {
|
|
if (MCP_ERROR_CRC == mcp_buffer[0]) {
|
|
|
|
mcp_timeout = 0;
|
|
}
|
|
else if (MCP_ERROR_NAK == mcp_buffer[0]) {
|
|
|
|
mcp_timeout = 0;
|
|
}
|
|
}
|
|
else if (MCP_ACK_FRAME == mcp_buffer[0]) {
|
|
if (mcp_byte_counter == mcp_buffer[1]) {
|
|
|
|
if (McpChecksum((uint8_t *)mcp_buffer) != mcp_buffer[mcp_byte_counter -1]) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("MCP: " D_CHECKSUM_FAILURE));
|
|
} else {
|
|
if (5 == mcp_buffer[1]) { McpAddressReceive(); }
|
|
if (25 == mcp_buffer[1]) { McpParseData(); }
|
|
if (MCP_CALIBRATION_LEN + 3 == mcp_buffer[1]) { McpParseCalibration(); }
|
|
if (MCP_FREQUENCY_LEN + 3 == mcp_buffer[1]) { McpParseFrequency(); }
|
|
}
|
|
|
|
}
|
|
mcp_timeout = 0;
|
|
}
|
|
else if (MCP_SINGLE_WIRE == mcp_buffer[0]) {
|
|
mcp_timeout = 0;
|
|
}
|
|
|
|
mcp_byte_counter = 0;
|
|
McpSerial->flush();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void McpEverySecond(void)
|
|
{
|
|
if (Energy.data_valid[0] > ENERGY_WATCHDOG) {
|
|
mcp_voltage_rms = 0;
|
|
mcp_current_rms = 0;
|
|
mcp_active_power = 0;
|
|
mcp_line_frequency = 0;
|
|
}
|
|
|
|
if (mcp_active_power) {
|
|
Energy.kWhtoday_delta += ((mcp_active_power * 10) / 36);
|
|
EnergyUpdateToday();
|
|
}
|
|
|
|
if (mcp_timeout) {
|
|
mcp_timeout--;
|
|
}
|
|
else if (mcp_calibration_active) {
|
|
mcp_calibration_active--;
|
|
}
|
|
else if (mcp_init) {
|
|
if (2 == mcp_init) {
|
|
McpGetCalibration();
|
|
}
|
|
else if (1 == mcp_init) {
|
|
McpGetFrequency();
|
|
}
|
|
mcp_init--;
|
|
}
|
|
else if (!mcp_address) {
|
|
McpGetAddress();
|
|
}
|
|
else {
|
|
McpGetData();
|
|
}
|
|
}
|
|
|
|
void McpSnsInit(void)
|
|
{
|
|
|
|
McpSerial = new TasmotaSerial(pin[GPIO_MCP39F5_RX], pin[GPIO_MCP39F5_TX], 1);
|
|
if (McpSerial->begin(MCP_BAUDRATE)) {
|
|
if (McpSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
mcp_buffer = serial_in_buffer;
|
|
} else {
|
|
mcp_buffer = (char*)(malloc(MCP_BUFFER_SIZE));
|
|
}
|
|
DigitalWrite(GPIO_MCP39F5_RST, 1);
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void McpDrvInit(void)
|
|
{
|
|
if ((pin[GPIO_MCP39F5_RX] < 99) && (pin[GPIO_MCP39F5_TX] < 99)) {
|
|
if (pin[GPIO_MCP39F5_RST] < 99) {
|
|
pinMode(pin[GPIO_MCP39F5_RST], OUTPUT);
|
|
digitalWrite(pin[GPIO_MCP39F5_RST], 0);
|
|
}
|
|
mcp_calibrate = 0;
|
|
mcp_timeout = 2;
|
|
mcp_init = 2;
|
|
energy_flg = XNRG_04;
|
|
}
|
|
}
|
|
|
|
bool McpCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
unsigned long value = 0;
|
|
|
|
if (CMND_POWERSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && mcp_active_power) {
|
|
value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 100);
|
|
if ((value > 100) && (value < 200000)) {
|
|
Settings.energy_power_calibration = value;
|
|
mcp_calibrate |= MCP_CALIBRATE_POWER;
|
|
McpGetCalibration();
|
|
}
|
|
}
|
|
}
|
|
else if (CMND_VOLTAGESET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && mcp_voltage_rms) {
|
|
value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 10);
|
|
if ((value > 1000) && (value < 2600)) {
|
|
Settings.energy_voltage_calibration = value;
|
|
mcp_calibrate |= MCP_CALIBRATE_VOLTAGE;
|
|
McpGetCalibration();
|
|
}
|
|
}
|
|
}
|
|
else if (CMND_CURRENTSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && mcp_current_rms) {
|
|
value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 10);
|
|
if ((value > 100) && (value < 80000)) {
|
|
Settings.energy_current_calibration = value;
|
|
mcp_calibrate |= MCP_CALIBRATE_CURRENT;
|
|
McpGetCalibration();
|
|
}
|
|
}
|
|
}
|
|
else if (CMND_FREQUENCYSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && mcp_line_frequency) {
|
|
value = (unsigned long)(CharToFloat(XdrvMailbox.data) * 1000);
|
|
if ((value > 45000) && (value < 65000)) {
|
|
Settings.energy_frequency_calibration = value;
|
|
mcp_calibrate |= MCP_CALIBRATE_FREQUENCY;
|
|
McpGetFrequency();
|
|
}
|
|
}
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg04(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if (McpSerial) { McpSerialInput(); }
|
|
break;
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
if (McpSerial) { McpEverySecond(); }
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = McpCommand();
|
|
break;
|
|
case FUNC_INIT:
|
|
McpSnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
McpDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_PZEM_AC
|
|
# 33 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_05_pzem_ac.ino"
|
|
#define XNRG_05 5
|
|
|
|
const uint8_t PZEM_AC_DEVICE_ADDRESS = 0x01;
|
|
const uint32_t PZEM_AC_STABILIZE = 30;
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *PzemAcModbus;
|
|
|
|
struct PZEMAC {
|
|
float energy = 0;
|
|
float last_energy = 0;
|
|
uint8_t send_retry = 0;
|
|
uint8_t phase = 0;
|
|
uint8_t address = 0;
|
|
uint8_t address_step = ADDR_IDLE;
|
|
} PzemAc;
|
|
|
|
void PzemAcEverySecond(void)
|
|
{
|
|
bool data_ready = PzemAcModbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[30];
|
|
|
|
uint8_t registers = 10;
|
|
if (ADDR_RECEIVE == PzemAc.address_step) {
|
|
registers = 2;
|
|
PzemAc.address_step--;
|
|
}
|
|
uint8_t error = PzemAcModbus->ReceiveBuffer(buffer, registers);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, PzemAcModbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAC: PzemAc %d error %d"), PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, error);
|
|
} else {
|
|
Energy.data_valid[PzemAc.phase] = 0;
|
|
if (10 == registers) {
|
|
|
|
|
|
|
|
|
|
|
|
Energy.voltage[PzemAc.phase] = (float)((buffer[3] << 8) + buffer[4]) / 10.0;
|
|
Energy.current[PzemAc.phase] = (float)((buffer[7] << 24) + (buffer[8] << 16) + (buffer[5] << 8) + buffer[6]) / 1000.0;
|
|
Energy.active_power[PzemAc.phase] = (float)((buffer[11] << 24) + (buffer[12] << 16) + (buffer[9] << 8) + buffer[10]) / 10.0;
|
|
Energy.frequency[PzemAc.phase] = (float)((buffer[17] << 8) + buffer[18]) / 10.0;
|
|
Energy.power_factor[PzemAc.phase] = (float)((buffer[19] << 8) + buffer[20]) / 100.0;
|
|
|
|
PzemAc.energy += (float)((buffer[15] << 24) + (buffer[16] << 16) + (buffer[13] << 8) + buffer[14]);
|
|
if (PzemAc.phase == Energy.phase_count -1) {
|
|
if (PzemAc.energy > PzemAc.last_energy) {
|
|
if (uptime > PZEM_AC_STABILIZE) {
|
|
EnergyUpdateTotal(PzemAc.energy, false);
|
|
}
|
|
PzemAc.last_energy = PzemAc.energy;
|
|
}
|
|
PzemAc.energy = 0;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == PzemAc.send_retry || data_ready) {
|
|
if (0 == PzemAc.phase) {
|
|
PzemAc.phase = Energy.phase_count -1;
|
|
} else {
|
|
PzemAc.phase--;
|
|
}
|
|
PzemAc.send_retry = ENERGY_WATCHDOG;
|
|
if (ADDR_SEND == PzemAc.address_step) {
|
|
PzemAcModbus->Send(0xF8, 0x06, 0x0002, (uint16_t)PzemAc.address);
|
|
PzemAc.address_step--;
|
|
} else {
|
|
PzemAcModbus->Send(PZEM_AC_DEVICE_ADDRESS + PzemAc.phase, 0x04, 0, 10);
|
|
}
|
|
}
|
|
else {
|
|
PzemAc.send_retry--;
|
|
if ((Energy.phase_count > 1) && (0 == PzemAc.send_retry) && (uptime < PZEM_AC_STABILIZE)) {
|
|
Energy.phase_count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PzemAcSnsInit(void)
|
|
{
|
|
PzemAcModbus = new TasmotaModbus(pin[GPIO_PZEM016_RX], pin[GPIO_PZEM0XX_TX]);
|
|
uint8_t result = PzemAcModbus->Begin(9600);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
Energy.phase_count = 3;
|
|
PzemAc.phase = 0;
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void PzemAcDrvInit(void)
|
|
{
|
|
if ((pin[GPIO_PZEM016_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) {
|
|
energy_flg = XNRG_05;
|
|
}
|
|
}
|
|
|
|
bool PzemAcCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
if (CMND_MODULEADDRESS == Energy.command_code) {
|
|
PzemAc.address = XdrvMailbox.payload;
|
|
PzemAc.address_step = ADDR_SEND;
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg05(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
if (uptime > 4) { PzemAcEverySecond(); }
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = PzemAcCommand();
|
|
break;
|
|
case FUNC_INIT:
|
|
PzemAcSnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
PzemAcDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_PZEM_DC
|
|
# 32 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_06_pzem_dc.ino"
|
|
#define XNRG_06 6
|
|
|
|
const uint8_t PZEM_DC_DEVICE_ADDRESS = 0x01;
|
|
const uint32_t PZEM_DC_STABILIZE = 30;
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *PzemDcModbus;
|
|
|
|
struct PZEMDC {
|
|
float energy = 0;
|
|
float last_energy = 0;
|
|
uint8_t send_retry = 0;
|
|
uint8_t channel = 0;
|
|
uint8_t address = 0;
|
|
uint8_t address_step = ADDR_IDLE;
|
|
} PzemDc;
|
|
|
|
void PzemDcEverySecond(void)
|
|
{
|
|
bool data_ready = PzemDcModbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[26];
|
|
|
|
uint8_t registers = 8;
|
|
if (ADDR_RECEIVE == PzemDc.address_step) {
|
|
registers = 2;
|
|
PzemDc.address_step--;
|
|
}
|
|
uint8_t error = PzemDcModbus->ReceiveBuffer(buffer, registers);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, PzemDcModbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PDC: PzemDc %d error %d"), PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, error);
|
|
} else {
|
|
Energy.data_valid[PzemDc.channel] = 0;
|
|
if (8 == registers) {
|
|
|
|
|
|
|
|
|
|
|
|
Energy.voltage[PzemDc.channel] = (float)((buffer[3] << 8) + buffer[4]) / 100.0;
|
|
Energy.current[PzemDc.channel] = (float)((buffer[5] << 8) + buffer[6]) / 100.0;
|
|
Energy.active_power[PzemDc.channel] = (float)((buffer[9] << 24) + (buffer[10] << 16) + (buffer[7] << 8) + buffer[8]) / 10.0;
|
|
|
|
PzemDc.energy += (float)((buffer[13] << 24) + (buffer[14] << 16) + (buffer[11] << 8) + buffer[12]);
|
|
if (PzemDc.channel == Energy.phase_count -1) {
|
|
if (PzemDc.energy > PzemDc.last_energy) {
|
|
if (uptime > PZEM_DC_STABILIZE) {
|
|
EnergyUpdateTotal(PzemDc.energy, false);
|
|
}
|
|
PzemDc.last_energy = PzemDc.energy;
|
|
}
|
|
PzemDc.energy = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == PzemDc.send_retry || data_ready) {
|
|
if (0 == PzemDc.channel) {
|
|
PzemDc.channel = Energy.phase_count -1;
|
|
} else {
|
|
PzemDc.channel--;
|
|
}
|
|
PzemDc.send_retry = ENERGY_WATCHDOG;
|
|
if (ADDR_SEND == PzemDc.address_step) {
|
|
PzemDcModbus->Send(0xF8, 0x06, 0x0002, (uint16_t)PzemDc.address);
|
|
PzemDc.address_step--;
|
|
} else {
|
|
PzemDcModbus->Send(PZEM_DC_DEVICE_ADDRESS + PzemDc.channel, 0x04, 0, 8);
|
|
}
|
|
}
|
|
else {
|
|
PzemDc.send_retry--;
|
|
if ((Energy.phase_count > 1) && (0 == PzemDc.send_retry) && (uptime < PZEM_DC_STABILIZE)) {
|
|
Energy.phase_count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PzemDcSnsInit(void)
|
|
{
|
|
PzemDcModbus = new TasmotaModbus(pin[GPIO_PZEM017_RX], pin[GPIO_PZEM0XX_TX]);
|
|
uint8_t result = PzemDcModbus->Begin(9600, 2);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
Energy.type_dc = true;
|
|
Energy.phase_count = 3;
|
|
PzemDc.channel = 0;
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void PzemDcDrvInit(void)
|
|
{
|
|
if ((pin[GPIO_PZEM017_RX] < 99) && (pin[GPIO_PZEM0XX_TX] < 99)) {
|
|
energy_flg = XNRG_06;
|
|
}
|
|
}
|
|
|
|
bool PzemDcCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
if (CMND_MODULEADDRESS == Energy.command_code) {
|
|
PzemDc.address = XdrvMailbox.payload;
|
|
PzemDc.address_step = ADDR_SEND;
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
if (uptime > 4) { PzemDcEverySecond(); }
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = PzemDcCommand();
|
|
break;
|
|
case FUNC_INIT:
|
|
PzemDcSnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
PzemDcDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_ADE7953
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_07_ade7953.ino"
|
|
#define XNRG_07 7
|
|
#define XI2C_07 7
|
|
|
|
#define ADE7953_PREF 1540
|
|
#define ADE7953_UREF 26000
|
|
#define ADE7953_IREF 10000
|
|
|
|
#define ADE7953_ADDR 0x38
|
|
|
|
const uint16_t Ade7953Registers[] {
|
|
0x31B,
|
|
0x313,
|
|
0x311,
|
|
0x315,
|
|
0x31A,
|
|
0x312,
|
|
0x310,
|
|
0x314,
|
|
0x31C,
|
|
0x10E
|
|
};
|
|
|
|
struct Ade7953 {
|
|
uint32_t voltage_rms = 0;
|
|
uint32_t period = 0;
|
|
uint32_t current_rms[2] = { 0, 0 };
|
|
uint32_t active_power[2] = { 0, 0 };
|
|
uint8_t init_step = 0;
|
|
} Ade7953;
|
|
|
|
int Ade7953RegSize(uint16_t reg)
|
|
{
|
|
int size = 0;
|
|
switch ((reg >> 8) & 0x0F) {
|
|
case 0x03:
|
|
size++;
|
|
case 0x02:
|
|
size++;
|
|
case 0x01:
|
|
size++;
|
|
case 0x00:
|
|
case 0x07:
|
|
case 0x08:
|
|
size++;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void Ade7953Write(uint16_t reg, uint32_t val)
|
|
{
|
|
int size = Ade7953RegSize(reg);
|
|
if (size) {
|
|
Wire.beginTransmission(ADE7953_ADDR);
|
|
Wire.write((reg >> 8) & 0xFF);
|
|
Wire.write(reg & 0xFF);
|
|
while (size--) {
|
|
Wire.write((val >> (8 * size)) & 0xFF);
|
|
}
|
|
Wire.endTransmission();
|
|
delayMicroseconds(5);
|
|
}
|
|
}
|
|
|
|
int32_t Ade7953Read(uint16_t reg)
|
|
{
|
|
uint32_t response = 0;
|
|
|
|
int size = Ade7953RegSize(reg);
|
|
if (size) {
|
|
Wire.beginTransmission(ADE7953_ADDR);
|
|
Wire.write((reg >> 8) & 0xFF);
|
|
Wire.write(reg & 0xFF);
|
|
Wire.endTransmission(0);
|
|
Wire.requestFrom(ADE7953_ADDR, size);
|
|
if (size <= Wire.available()) {
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
response = response << 8 | Wire.read();
|
|
}
|
|
}
|
|
}
|
|
return response;
|
|
}
|
|
|
|
void Ade7953Init(void)
|
|
{
|
|
Ade7953Write(0x102, 0x0004);
|
|
Ade7953Write(0x0FE, 0x00AD);
|
|
Ade7953Write(0x120, 0x0030);
|
|
}
|
|
|
|
void Ade7953GetData(void)
|
|
{
|
|
int32_t reg[2][4];
|
|
for (uint32_t i = 0; i < sizeof(Ade7953Registers)/sizeof(uint16_t); i++) {
|
|
int32_t value = Ade7953Read(Ade7953Registers[i]);
|
|
if (8 == i) {
|
|
Ade7953.voltage_rms = value;
|
|
} else if (9 == i) {
|
|
Ade7953.period = value;
|
|
} else {
|
|
reg[i >> 2][i &3] = value;
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ADE: %d, %d, [%d, %d, %d, %d], [%d, %d, %d, %d]"),
|
|
Ade7953.voltage_rms, Ade7953.period,
|
|
reg[0][0], reg[0][1], reg[0][2], reg[0][3],
|
|
reg[1][0], reg[1][1], reg[1][2], reg[1][3]);
|
|
|
|
uint32_t apparent_power[2] = { 0, 0 };
|
|
uint32_t reactive_power[2] = { 0, 0 };
|
|
|
|
for (uint32_t channel = 0; channel < 2; channel++) {
|
|
Ade7953.current_rms[channel] = reg[channel][0];
|
|
if (Ade7953.current_rms[channel] < 2000) {
|
|
Ade7953.current_rms[channel] = 0;
|
|
Ade7953.active_power[channel] = 0;
|
|
} else {
|
|
Ade7953.active_power[channel] = abs(reg[channel][1]);
|
|
apparent_power[channel] = abs(reg[channel][2]);
|
|
reactive_power[channel] = abs(reg[channel][3]);
|
|
}
|
|
}
|
|
|
|
uint32_t current_rms_sum = Ade7953.current_rms[0] + Ade7953.current_rms[1];
|
|
uint32_t active_power_sum = Ade7953.active_power[0] + Ade7953.active_power[1];
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ADE: U %d, C %d, I %d + %d = %d, P %d + %d = %d"),
|
|
Ade7953.voltage_rms, Ade7953.period,
|
|
Ade7953.current_rms[0], Ade7953.current_rms[1], current_rms_sum,
|
|
Ade7953.active_power[0], Ade7953.active_power[1], active_power_sum);
|
|
|
|
if (Energy.power_on) {
|
|
Energy.voltage[0] = (float)Ade7953.voltage_rms / Settings.energy_voltage_calibration;
|
|
Energy.frequency[0] = 223750.0f / ( (float)Ade7953.period + 1);
|
|
|
|
for (uint32_t channel = 0; channel < 2; channel++) {
|
|
Energy.data_valid[channel] = 0;
|
|
Energy.active_power[channel] = (float)Ade7953.active_power[channel] / (Settings.energy_power_calibration / 10);
|
|
Energy.reactive_power[channel] = (float)reactive_power[channel] / (Settings.energy_power_calibration / 10);
|
|
Energy.apparent_power[channel] = (float)apparent_power[channel] / (Settings.energy_power_calibration / 10);
|
|
if (0 == Energy.active_power[channel]) {
|
|
Energy.current[channel] = 0;
|
|
} else {
|
|
Energy.current[channel] = (float)Ade7953.current_rms[channel] / (Settings.energy_current_calibration * 10);
|
|
}
|
|
}
|
|
} else {
|
|
Energy.data_valid[0] = ENERGY_WATCHDOG;
|
|
Energy.data_valid[1] = ENERGY_WATCHDOG;
|
|
}
|
|
|
|
if (active_power_sum) {
|
|
Energy.kWhtoday_delta += ((active_power_sum * (100000 / (Settings.energy_power_calibration / 10))) / 3600);
|
|
EnergyUpdateToday();
|
|
}
|
|
}
|
|
|
|
void Ade7953EnergyEverySecond(void)
|
|
{
|
|
if (Ade7953.init_step) {
|
|
if (1 == Ade7953.init_step) {
|
|
Ade7953Init();
|
|
}
|
|
Ade7953.init_step--;
|
|
} else {
|
|
Ade7953GetData();
|
|
}
|
|
}
|
|
|
|
void Ade7953DrvInit(void)
|
|
{
|
|
if (pin[GPIO_ADE7953_IRQ] < 99) {
|
|
delay(100);
|
|
if (I2cSetDevice(ADE7953_ADDR)) {
|
|
if (HLW_PREF_PULSE == Settings.energy_power_calibration) {
|
|
Settings.energy_power_calibration = ADE7953_PREF;
|
|
Settings.energy_voltage_calibration = ADE7953_UREF;
|
|
Settings.energy_current_calibration = ADE7953_IREF;
|
|
}
|
|
I2cSetActiveFound(ADE7953_ADDR, "ADE7953");
|
|
Ade7953.init_step = 2;
|
|
|
|
Energy.phase_count = 2;
|
|
Energy.voltage_common = true;
|
|
|
|
energy_flg = XNRG_07;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Ade7953Command(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
uint32_t channel = (2 == XdrvMailbox.index) ? 1 : 0;
|
|
uint32_t value = (uint32_t)(CharToFloat(XdrvMailbox.data) * 100);
|
|
|
|
if (CMND_POWERCAL == Energy.command_code) {
|
|
if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_PREF; }
|
|
|
|
}
|
|
else if (CMND_VOLTAGECAL == Energy.command_code) {
|
|
if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_UREF; }
|
|
|
|
}
|
|
else if (CMND_CURRENTCAL == Energy.command_code) {
|
|
if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = ADE7953_IREF; }
|
|
|
|
}
|
|
else if (CMND_POWERSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Ade7953.active_power[channel]) {
|
|
if ((value > 100) && (value < 200000)) {
|
|
Settings.energy_power_calibration = (Ade7953.active_power[channel] * 1000) / value;
|
|
}
|
|
}
|
|
}
|
|
else if (CMND_VOLTAGESET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Ade7953.voltage_rms) {
|
|
if ((value > 10000) && (value < 26000)) {
|
|
Settings.energy_voltage_calibration = (Ade7953.voltage_rms * 100) / value;
|
|
}
|
|
}
|
|
}
|
|
else if (CMND_CURRENTSET == Energy.command_code) {
|
|
if (XdrvMailbox.data_len && Ade7953.current_rms[channel]) {
|
|
if ((value > 2000) && (value < 1000000)) {
|
|
Settings.energy_current_calibration = ((Ade7953.current_rms[channel] * 100) / value) * 100;
|
|
}
|
|
}
|
|
}
|
|
else serviced = false;
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg07(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_07)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
Ade7953EnergyEverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = Ade7953Command();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
Ade7953DrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_08_sdm120.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_08_sdm120.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_SDM120
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XNRG_08 8
|
|
|
|
|
|
#ifndef SDM120_SPEED
|
|
#define SDM120_SPEED 2400
|
|
#endif
|
|
|
|
#ifndef SDM120_ADDR
|
|
#define SDM120_ADDR 1
|
|
#endif
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *Sdm120Modbus;
|
|
|
|
const uint8_t sdm120_table = 8;
|
|
const uint8_t sdm220_table = 13;
|
|
|
|
const uint16_t sdm120_start_addresses[] {
|
|
0x0000,
|
|
0x0006,
|
|
0x000C,
|
|
0x0012,
|
|
0x0018,
|
|
0x001E,
|
|
0x0046,
|
|
0x0156,
|
|
|
|
0X0048,
|
|
0X004A,
|
|
0X004C,
|
|
0X004E,
|
|
0X0024
|
|
};
|
|
|
|
struct SDM120 {
|
|
float total_active = 0;
|
|
float import_active = NAN;
|
|
float import_reactive = 0;
|
|
float export_reactive = 0;
|
|
float phase_angle = 0;
|
|
uint8_t read_state = 0;
|
|
uint8_t send_retry = 0;
|
|
uint8_t start_address_count = sdm220_table;
|
|
} Sdm120;
|
|
|
|
|
|
|
|
void SDM120Every250ms(void)
|
|
{
|
|
bool data_ready = Sdm120Modbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[14];
|
|
|
|
uint32_t error = Sdm120Modbus->ReceiveBuffer(buffer, 2);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Sdm120Modbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM120 error %d"), error);
|
|
} else {
|
|
Energy.data_valid[0] = 0;
|
|
|
|
|
|
|
|
|
|
float value;
|
|
((uint8_t*)&value)[3] = buffer[3];
|
|
((uint8_t*)&value)[2] = buffer[4];
|
|
((uint8_t*)&value)[1] = buffer[5];
|
|
((uint8_t*)&value)[0] = buffer[6];
|
|
|
|
switch(Sdm120.read_state) {
|
|
case 0:
|
|
Energy.voltage[0] = value;
|
|
break;
|
|
|
|
case 1:
|
|
Energy.current[0] = value;
|
|
break;
|
|
|
|
case 2:
|
|
Energy.active_power[0] = value;
|
|
break;
|
|
|
|
case 3:
|
|
Energy.apparent_power[0] = value;
|
|
break;
|
|
|
|
case 4:
|
|
Energy.reactive_power[0] = value;
|
|
break;
|
|
|
|
case 5:
|
|
Energy.power_factor[0] = value;
|
|
break;
|
|
|
|
case 6:
|
|
Energy.frequency[0] = value;
|
|
break;
|
|
|
|
case 7:
|
|
Sdm120.total_active = value;
|
|
break;
|
|
|
|
case 8:
|
|
Sdm120.import_active = value;
|
|
break;
|
|
|
|
case 9:
|
|
Energy.export_active = value;
|
|
break;
|
|
|
|
case 10:
|
|
Sdm120.import_reactive = value;
|
|
break;
|
|
|
|
case 11:
|
|
Sdm120.export_reactive = value;
|
|
break;
|
|
|
|
case 12:
|
|
Sdm120.phase_angle = value;
|
|
break;
|
|
}
|
|
|
|
Sdm120.read_state++;
|
|
if (Sdm120.read_state == Sdm120.start_address_count) {
|
|
Sdm120.read_state = 0;
|
|
|
|
if (Sdm120.start_address_count > sdm120_table) {
|
|
if (!isnan(Sdm120.import_active)) {
|
|
Sdm120.total_active = Sdm120.import_active;
|
|
} else {
|
|
Sdm120.start_address_count = sdm120_table;
|
|
}
|
|
}
|
|
EnergyUpdateTotal(Sdm120.total_active, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == Sdm120.send_retry || data_ready) {
|
|
Sdm120.send_retry = 5;
|
|
Sdm120Modbus->Send(SDM120_ADDR, 0x04, sdm120_start_addresses[Sdm120.read_state], 2);
|
|
} else {
|
|
Sdm120.send_retry--;
|
|
}
|
|
}
|
|
|
|
void Sdm120SnsInit(void)
|
|
{
|
|
Sdm120Modbus = new TasmotaModbus(pin[GPIO_SDM120_RX], pin[GPIO_SDM120_TX]);
|
|
uint8_t result = Sdm120Modbus->Begin(SDM120_SPEED);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void Sdm120DrvInit(void)
|
|
{
|
|
if ((pin[GPIO_SDM120_RX] < 99) && (pin[GPIO_SDM120_TX] < 99)) {
|
|
energy_flg = XNRG_08;
|
|
}
|
|
}
|
|
|
|
void Sdm220Reset(void)
|
|
{
|
|
if (isnan(Sdm120.import_active)) { return; }
|
|
|
|
Sdm120.import_active = 0;
|
|
Sdm120.import_reactive = 0;
|
|
Sdm120.export_reactive = 0;
|
|
Sdm120.phase_angle = 0;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_ENERGY_SDM220[] PROGMEM =
|
|
"{s}" D_IMPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
|
|
"{s}" D_EXPORT_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
|
|
"{s}" D_PHASE_ANGLE "{m}%s " D_UNIT_ANGLE "{e}";
|
|
#endif
|
|
|
|
void Sdm220Show(bool json)
|
|
{
|
|
if (isnan(Sdm120.import_active)) { return; }
|
|
|
|
char import_active_chr[FLOATSZ];
|
|
dtostrfd(Sdm120.import_active, Settings.flag2.energy_resolution, import_active_chr);
|
|
char import_reactive_chr[FLOATSZ];
|
|
dtostrfd(Sdm120.import_reactive, Settings.flag2.energy_resolution, import_reactive_chr);
|
|
char export_reactive_chr[FLOATSZ];
|
|
dtostrfd(Sdm120.export_reactive, Settings.flag2.energy_resolution, export_reactive_chr);
|
|
char phase_angle_chr[FLOATSZ];
|
|
dtostrfd(Sdm120.phase_angle, 2, phase_angle_chr);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_IMPORT_ACTIVE "\":%s,\"" D_JSON_IMPORT_REACTIVE "\":%s,\"" D_JSON_EXPORT_REACTIVE "\":%s,\"" D_JSON_PHASE_ANGLE "\":%s"),
|
|
import_active_chr, import_reactive_chr, export_reactive_chr, phase_angle_chr);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_ENERGY_SDM220, import_reactive_chr, export_reactive_chr, phase_angle_chr);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg08(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (uptime > 4) { SDM120Every250ms(); }
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Sdm220Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Sdm220Show(0);
|
|
break;
|
|
#endif
|
|
case FUNC_ENERGY_RESET:
|
|
Sdm220Reset();
|
|
break;
|
|
case FUNC_INIT:
|
|
Sdm120SnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
Sdm120DrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_DDS2382
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XNRG_09 9
|
|
|
|
#ifndef DDS2382_SPEED
|
|
#define DDS2382_SPEED 9600
|
|
#endif
|
|
#ifndef DDS2382_ADDR
|
|
#define DDS2382_ADDR 1
|
|
#endif
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *Dds2382Modbus;
|
|
|
|
uint8_t Dds2382_send_retry = 0;
|
|
|
|
void Dds2382EverySecond(void)
|
|
{
|
|
bool data_ready = Dds2382Modbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[46];
|
|
|
|
uint32_t error = Dds2382Modbus->ReceiveBuffer(buffer, 18);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Dds2382Modbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "DDS2382 response error %d"), error);
|
|
} else {
|
|
Energy.data_valid[0] = 0;
|
|
# 67 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_09_dds2382.ino"
|
|
Energy.voltage[0] = (float)((buffer[27] << 8) + buffer[28]) / 10.0;
|
|
Energy.current[0] = (float)((buffer[29] << 8) + buffer[30]) / 100.0;
|
|
Energy.active_power[0] = (float)((buffer[31] << 8) + buffer[32]);
|
|
Energy.reactive_power[0] = (float)((buffer[33] << 8) + buffer[34]);
|
|
Energy.power_factor[0] = (float)((buffer[35] << 8) + buffer[36]) / 1000.0;
|
|
Energy.frequency[0] = (float)((buffer[37] << 8) + buffer[38]) / 100.0;
|
|
uint8_t offset = 11;
|
|
if (Settings.flag3.dds2382_model) {
|
|
offset = 19;
|
|
}
|
|
Energy.export_active = (float)((buffer[offset] << 24) + (buffer[offset +1] << 16) + (buffer[offset +2] << 8) + buffer[offset +3]) / 100.0;
|
|
float import_active = (float)((buffer[offset +4] << 24) + (buffer[offset +5] << 16) + (buffer[offset +6] << 8) + buffer[offset +7]) / 100.0;
|
|
|
|
EnergyUpdateTotal(import_active, true);
|
|
}
|
|
}
|
|
|
|
if (0 == Dds2382_send_retry || data_ready) {
|
|
Dds2382_send_retry = 5;
|
|
Dds2382Modbus->Send(DDS2382_ADDR, 0x03, 0, 18);
|
|
} else {
|
|
Dds2382_send_retry--;
|
|
}
|
|
}
|
|
|
|
void Dds2382SnsInit(void)
|
|
{
|
|
Dds2382Modbus = new TasmotaModbus(pin[GPIO_DDS2382_RX], pin[GPIO_DDS2382_TX]);
|
|
uint8_t result = Dds2382Modbus->Begin(DDS2382_SPEED);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void Dds2382DrvInit(void)
|
|
{
|
|
if ((pin[GPIO_DDS2382_RX] < 99) && (pin[GPIO_DDS2382_TX] < 99)) {
|
|
energy_flg = XNRG_09;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg09(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_ENERGY_EVERY_SECOND:
|
|
if (uptime > 4) { Dds2382EverySecond(); }
|
|
break;
|
|
case FUNC_INIT:
|
|
Dds2382SnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
Dds2382DrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_10_sdm630.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_10_sdm630.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_SDM630
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XNRG_10 10
|
|
|
|
|
|
#ifndef SDM630_SPEED
|
|
#define SDM630_SPEED 9600
|
|
#endif
|
|
|
|
#ifndef SDM630_ADDR
|
|
#define SDM630_ADDR 1
|
|
#endif
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *Sdm630Modbus;
|
|
|
|
const uint16_t sdm630_start_addresses[] {
|
|
0x0000,
|
|
0x0002,
|
|
0x0004,
|
|
0x0006,
|
|
0x0008,
|
|
0x000A,
|
|
0x000C,
|
|
0x000E,
|
|
0x0010,
|
|
0x0018,
|
|
0x001A,
|
|
0x001C,
|
|
0x001E,
|
|
0x0020,
|
|
0x0022,
|
|
0x0156
|
|
};
|
|
|
|
struct SDM630 {
|
|
uint8_t read_state = 0;
|
|
uint8_t send_retry = 0;
|
|
} Sdm630;
|
|
|
|
|
|
|
|
void SDM630Every250ms(void)
|
|
{
|
|
bool data_ready = Sdm630Modbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[14];
|
|
|
|
uint32_t error = Sdm630Modbus->ReceiveBuffer(buffer, 2);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Sdm630Modbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: SDM630 error %d"), error);
|
|
} else {
|
|
Energy.data_valid[0] = 0;
|
|
Energy.data_valid[1] = 0;
|
|
Energy.data_valid[2] = 0;
|
|
|
|
|
|
|
|
|
|
float value;
|
|
((uint8_t*)&value)[3] = buffer[3];
|
|
((uint8_t*)&value)[2] = buffer[4];
|
|
((uint8_t*)&value)[1] = buffer[5];
|
|
((uint8_t*)&value)[0] = buffer[6];
|
|
|
|
switch(Sdm630.read_state) {
|
|
case 0:
|
|
Energy.voltage[0] = value;
|
|
break;
|
|
|
|
case 1:
|
|
Energy.voltage[1] = value;
|
|
break;
|
|
|
|
case 2:
|
|
Energy.voltage[2] = value;
|
|
break;
|
|
|
|
case 3:
|
|
Energy.current[0] = value;
|
|
break;
|
|
|
|
case 4:
|
|
Energy.current[1] = value;
|
|
break;
|
|
|
|
case 5:
|
|
Energy.current[2] = value;
|
|
break;
|
|
|
|
case 6:
|
|
Energy.active_power[0] = value;
|
|
break;
|
|
|
|
case 7:
|
|
Energy.active_power[1] = value;
|
|
break;
|
|
|
|
case 8:
|
|
Energy.active_power[2] = value;
|
|
break;
|
|
|
|
case 9:
|
|
Energy.reactive_power[0] = value;
|
|
break;
|
|
|
|
case 10:
|
|
Energy.reactive_power[1] = value;
|
|
break;
|
|
|
|
case 11:
|
|
Energy.reactive_power[2] = value;
|
|
break;
|
|
|
|
case 12:
|
|
Energy.power_factor[0] = value;
|
|
break;
|
|
|
|
case 13:
|
|
Energy.power_factor[1] = value;
|
|
break;
|
|
|
|
case 14:
|
|
Energy.power_factor[2] = value;
|
|
break;
|
|
|
|
case 15:
|
|
EnergyUpdateTotal(value, true);
|
|
break;
|
|
}
|
|
|
|
Sdm630.read_state++;
|
|
if (sizeof(sdm630_start_addresses)/2 == Sdm630.read_state) {
|
|
Sdm630.read_state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == Sdm630.send_retry || data_ready) {
|
|
Sdm630.send_retry = 5;
|
|
Sdm630Modbus->Send(SDM630_ADDR, 0x04, sdm630_start_addresses[Sdm630.read_state], 2);
|
|
} else {
|
|
Sdm630.send_retry--;
|
|
}
|
|
}
|
|
|
|
void Sdm630SnsInit(void)
|
|
{
|
|
Sdm630Modbus = new TasmotaModbus(pin[GPIO_SDM630_RX], pin[GPIO_SDM630_TX]);
|
|
uint8_t result = Sdm630Modbus->Begin(SDM630_SPEED);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
Energy.phase_count = 3;
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void Sdm630DrvInit(void)
|
|
{
|
|
if ((pin[GPIO_SDM630_RX] < 99) && (pin[GPIO_SDM630_TX] < 99)) {
|
|
energy_flg = XNRG_10;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg10(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (uptime > 4) { SDM630Every250ms(); }
|
|
break;
|
|
case FUNC_INIT:
|
|
Sdm630SnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
Sdm630DrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_11_ddsu666.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_11_ddsu666.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_DDSU666
|
|
|
|
|
|
|
|
|
|
#define XNRG_11 11
|
|
|
|
|
|
#ifndef DDSU666_SPEED
|
|
#define DDSU666_SPEED 9600
|
|
#endif
|
|
|
|
#ifndef DDSU666_ADDR
|
|
#define DDSU666_ADDR 1
|
|
#endif
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *Ddsu666Modbus;
|
|
|
|
const uint16_t Ddsu666_start_addresses[] {
|
|
0x2000,
|
|
0x2002,
|
|
0x2004,
|
|
0x2006,
|
|
0x200A,
|
|
0x200E,
|
|
0X4000,
|
|
0X400A,
|
|
};
|
|
|
|
struct DDSU666 {
|
|
float import_active = NAN;
|
|
uint8_t read_state = 0;
|
|
uint8_t send_retry = 0;
|
|
} Ddsu666;
|
|
|
|
|
|
|
|
void DDSU666Every250ms(void)
|
|
{
|
|
bool data_ready = Ddsu666Modbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[14];
|
|
|
|
uint32_t error = Ddsu666Modbus->ReceiveBuffer(buffer, 2);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, Ddsu666Modbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDM: Ddsu666 error %d"), error);
|
|
} else {
|
|
Energy.data_valid[0] = 0;
|
|
|
|
|
|
|
|
|
|
float value;
|
|
((uint8_t*)&value)[3] = buffer[3];
|
|
((uint8_t*)&value)[2] = buffer[4];
|
|
((uint8_t*)&value)[1] = buffer[5];
|
|
((uint8_t*)&value)[0] = buffer[6];
|
|
|
|
switch(Ddsu666.read_state) {
|
|
case 0:
|
|
Energy.voltage[0] = value;
|
|
break;
|
|
|
|
case 1:
|
|
Energy.current[0] = value;
|
|
break;
|
|
|
|
case 2:
|
|
Energy.active_power[0] = value * 1000;
|
|
break;
|
|
|
|
case 3:
|
|
Energy.reactive_power[0] = value * 1000;
|
|
break;
|
|
|
|
case 4:
|
|
Energy.power_factor[0] = value;
|
|
break;
|
|
|
|
case 5:
|
|
Energy.frequency[0] = value;
|
|
break;
|
|
|
|
case 6:
|
|
Ddsu666.import_active = value;
|
|
break;
|
|
|
|
case 7:
|
|
Energy.export_active = value;
|
|
break;
|
|
}
|
|
|
|
Ddsu666.read_state++;
|
|
|
|
if (Ddsu666.read_state == 8) {
|
|
Ddsu666.read_state = 0;
|
|
EnergyUpdateTotal(Ddsu666.import_active, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == Ddsu666.send_retry || data_ready) {
|
|
Ddsu666.send_retry = 5;
|
|
Ddsu666Modbus->Send(DDSU666_ADDR, 0x04, Ddsu666_start_addresses[Ddsu666.read_state], 2);
|
|
} else {
|
|
Ddsu666.send_retry--;
|
|
}
|
|
}
|
|
|
|
void Ddsu666SnsInit(void)
|
|
{
|
|
Ddsu666Modbus = new TasmotaModbus(pin[GPIO_DDSU666_RX], pin[GPIO_DDSU666_TX]);
|
|
uint8_t result = Ddsu666Modbus->Begin(DDSU666_SPEED);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void Ddsu666DrvInit(void)
|
|
{
|
|
if ((pin[GPIO_DDSU666_RX] < 99) && (pin[GPIO_DDSU666_TX] < 99)) {
|
|
energy_flg = XNRG_11;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg11(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (uptime > 4) { DDSU666Every250ms(); }
|
|
break;
|
|
case FUNC_INIT:
|
|
Ddsu666SnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
Ddsu666DrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_12_solaxX1.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_12_solaxX1.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_SOLAX_X1
|
|
|
|
|
|
|
|
|
|
#define XNRG_12 12
|
|
|
|
#ifndef SOLAXX1_SPEED
|
|
#define SOLAXX1_SPEED 9600
|
|
#endif
|
|
|
|
#define INVERTER_ADDRESS 0x0A
|
|
|
|
#define D_SOLAX_X1 "SolaxX1"
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
enum solaxX1_Error
|
|
{
|
|
solaxX1_ERR_NO_ERROR,
|
|
solaxX1_ERR_CRC_ERROR
|
|
};
|
|
|
|
union {
|
|
uint32_t ErrMessage;
|
|
struct {
|
|
|
|
uint8_t TzProtectFault:1;
|
|
uint8_t MainsLostFault:1;
|
|
uint8_t GridVoltFault:1;
|
|
uint8_t GridFreqFault:1;
|
|
uint8_t PLLLostFault:1;
|
|
uint8_t BusVoltFault:1;
|
|
uint8_t ErrBit06:1;
|
|
uint8_t OciFault:1;
|
|
|
|
uint8_t Dci_OCP_Fault:1;
|
|
uint8_t ResidualCurrentFault:1;
|
|
uint8_t PvVoltFault:1;
|
|
uint8_t Ac10Mins_Voltage_Fault:1;
|
|
uint8_t IsolationFault:1;
|
|
uint8_t TemperatureOverFault:1;
|
|
uint8_t FanFault:1;
|
|
uint8_t ErrBit15:1;
|
|
|
|
uint8_t SpiCommsFault:1;
|
|
uint8_t SciCommsFault:1;
|
|
uint8_t ErrBit18:1;
|
|
uint8_t InputConfigFault:1;
|
|
uint8_t EepromFault:1;
|
|
uint8_t RelayFault:1;
|
|
uint8_t SampleConsistenceFault:1;
|
|
uint8_t ResidualCurrent_DeviceFault:1;
|
|
|
|
uint8_t ErrBit24:1;
|
|
uint8_t ErrBit25:1;
|
|
uint8_t ErrBit26:1;
|
|
uint8_t ErrBit27:1;
|
|
uint8_t ErrBit28:1;
|
|
uint8_t DCI_DeviceFault:1;
|
|
uint8_t OtherDeviceFault:1;
|
|
uint8_t ErrBit31:1;
|
|
};
|
|
} ErrCode;
|
|
|
|
const char kSolaxMode[] PROGMEM = D_WAITING "|" D_CHECKING "|" D_WORKING "|" D_FAILURE;
|
|
|
|
const char kSolaxError[] PROGMEM =
|
|
D_SOLAX_ERROR_0 "|" D_SOLAX_ERROR_1 "|" D_SOLAX_ERROR_2 "|" D_SOLAX_ERROR_3 "|" D_SOLAX_ERROR_4 "|" D_SOLAX_ERROR_5 "|"
|
|
D_SOLAX_ERROR_6 "|" D_SOLAX_ERROR_7 "|" D_SOLAX_ERROR_8;
|
|
|
|
|
|
|
|
TasmotaSerial *solaxX1Serial;
|
|
|
|
uint8_t solaxX1_Init = 1;
|
|
|
|
struct SOLAXX1 {
|
|
float temperature = 0;
|
|
float energy_today = 0;
|
|
float dc1_voltage = 0;
|
|
float dc2_voltage = 0;
|
|
float dc1_current = 0;
|
|
float dc2_current = 0;
|
|
float energy_total = 0;
|
|
float runtime_total = 0;
|
|
float dc1_power = 0;
|
|
float dc2_power = 0;
|
|
|
|
uint8_t status = 0;
|
|
uint32_t errorCode = 0;
|
|
} solaxX1;
|
|
|
|
union {
|
|
uint8_t status;
|
|
struct {
|
|
uint8_t freeBit7:1;
|
|
uint8_t freeBit6:1;
|
|
uint8_t freeBit5:1;
|
|
uint8_t queryOffline:1;
|
|
uint8_t queryOfflineSend:1;
|
|
uint8_t hasAddress:1;
|
|
uint8_t inverterAddressSend:1;
|
|
uint8_t inverterSnReceived:1;
|
|
};
|
|
} protocolStatus;
|
|
|
|
uint8_t header[2] = {0xAA, 0x55};
|
|
uint8_t source[2] = {0x00, 0x00};
|
|
uint8_t destination[2] = {0x00, 0x00};
|
|
uint8_t controlCode[1] = {0x00};
|
|
uint8_t functionCode[1] = {0x00};
|
|
uint8_t dataLength[1] = {0x00};
|
|
uint8_t data[16] = {0};
|
|
|
|
uint8_t message[30];
|
|
|
|
|
|
|
|
bool solaxX1_RS485ReceiveReady(void)
|
|
{
|
|
return (solaxX1Serial->available() > 1);
|
|
}
|
|
|
|
void solaxX1_RS485Send(uint16_t msgLen)
|
|
{
|
|
memcpy(message, header, 2);
|
|
memcpy(message + 2, source, 2);
|
|
memcpy(message + 4, destination, 2);
|
|
memcpy(message + 6, controlCode, 1);
|
|
memcpy(message + 7, functionCode, 1);
|
|
memcpy(message + 8, dataLength, 1);
|
|
memcpy(message + 9, data, sizeof(data));
|
|
uint16_t crc = solaxX1_calculateCRC(message, msgLen);
|
|
|
|
while (solaxX1Serial->available() > 0)
|
|
{
|
|
solaxX1Serial->read();
|
|
}
|
|
|
|
solaxX1Serial->flush();
|
|
solaxX1Serial->write(message, msgLen);
|
|
solaxX1Serial->write(highByte(crc));
|
|
solaxX1Serial->write(lowByte(crc));
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, message, msgLen);
|
|
}
|
|
|
|
uint8_t solaxX1_RS485Receive(uint8_t *value)
|
|
{
|
|
uint8_t len = 0;
|
|
|
|
while (solaxX1Serial->available() > 0)
|
|
{
|
|
value[len++] = (uint8_t)solaxX1Serial->read();
|
|
}
|
|
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, value, len);
|
|
|
|
uint16_t crc = solaxX1_calculateCRC(value, len - 2);
|
|
|
|
if (value[len - 1] == lowByte(crc) && value[len - 2] == highByte(crc))
|
|
{
|
|
return solaxX1_ERR_NO_ERROR;
|
|
}
|
|
else
|
|
{
|
|
return solaxX1_ERR_CRC_ERROR;
|
|
}
|
|
}
|
|
|
|
uint16_t solaxX1_calculateCRC(uint8_t *bExternTxPackage, uint8_t bLen)
|
|
{
|
|
uint8_t i;
|
|
uint16_t wChkSum;
|
|
wChkSum = 0;
|
|
|
|
for (i = 0; i < bLen; i++)
|
|
{
|
|
wChkSum = wChkSum + bExternTxPackage[i];
|
|
}
|
|
return wChkSum;
|
|
}
|
|
|
|
void solaxX1_SendInverterAddress(void)
|
|
{
|
|
source[0] = 0x00;
|
|
destination[0] = 0x00;
|
|
destination[1] = 0x00;
|
|
controlCode[0] = 0x10;
|
|
functionCode[0] = 0x01;
|
|
dataLength[0] = 0x0F;
|
|
data[14] = INVERTER_ADDRESS;
|
|
solaxX1_RS485Send(24);
|
|
}
|
|
|
|
void solaxX1_QueryLiveData(void)
|
|
{
|
|
source[0] = 0x01;
|
|
destination[0] = 0x00;
|
|
destination[1] = INVERTER_ADDRESS;
|
|
controlCode[0] = 0x11;
|
|
functionCode[0] = 0x02;
|
|
dataLength[0] = 0x00;
|
|
solaxX1_RS485Send(9);
|
|
}
|
|
|
|
uint8_t solaxX1_ParseErrorCode(uint32_t code){
|
|
ErrCode.ErrMessage = code;
|
|
|
|
if (code == 0) return 0;
|
|
if (ErrCode.MainsLostFault) return 1;
|
|
if (ErrCode.GridVoltFault) return 2;
|
|
if (ErrCode.GridFreqFault) return 3;
|
|
if (ErrCode.PvVoltFault) return 4;
|
|
if (ErrCode.IsolationFault) return 5;
|
|
if (ErrCode.TemperatureOverFault) return 6;
|
|
if (ErrCode.FanFault) return 7;
|
|
if (ErrCode.OtherDeviceFault) return 8;
|
|
}
|
|
|
|
|
|
|
|
uint8_t solaxX1_send_retry = 0;
|
|
uint8_t solaxX1_nodata_count = 0;
|
|
|
|
void solaxX1250MSecond(void)
|
|
{
|
|
uint8_t value[61] = {0};
|
|
bool data_ready = solaxX1_RS485ReceiveReady();
|
|
|
|
if (protocolStatus.hasAddress && (data_ready || solaxX1_send_retry == 0))
|
|
{
|
|
if (data_ready)
|
|
{
|
|
uint8_t error = solaxX1_RS485Receive(value);
|
|
if (error)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Data response CRC error"));
|
|
}
|
|
else
|
|
{
|
|
solaxX1_nodata_count = 0;
|
|
solaxX1_send_retry = 12;
|
|
Energy.data_valid[0] = 0;
|
|
|
|
solaxX1.temperature = (float)((value[9] << 8) | value[10]);
|
|
solaxX1.energy_today = (float)((value[11] << 8) | value[12]) * 0.1f;
|
|
solaxX1.dc1_voltage = (float)((value[13] << 8) | value[14]) * 0.1f;
|
|
solaxX1.dc2_voltage = (float)((value[15] << 8) | value[16]) * 0.1f;
|
|
solaxX1.dc1_current = (float)((value[17] << 8) | value[18]) * 0.1f;
|
|
solaxX1.dc2_current = (float)((value[19] << 8) | value[20]) * 0.1f;
|
|
Energy.current[0] = (float)((value[21] << 8) | value[22]) * 0.1f;
|
|
Energy.voltage[0] = (float)((value[23] << 8) | value[24]) * 0.1f;
|
|
Energy.frequency[0] = (float)((value[25] << 8) | value[26]) * 0.01f;
|
|
Energy.active_power[0] = (float)((value[27] << 8) | value[28]);
|
|
|
|
solaxX1.energy_total = (float)((value[31] << 8) | (value[32] << 8) | (value[33] << 8) | value[34]) * 0.1f;
|
|
solaxX1.runtime_total = (float)((value[35] << 8) | (value[36] << 8) | (value[37] << 8) | value[38]);
|
|
solaxX1.status = (uint8_t)((value[39] << 8) | value[40]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
solaxX1.errorCode = (uint32_t)((value[58] << 8) | (value[57] << 8) | (value[56] << 8) | value[55]);
|
|
|
|
solaxX1.dc1_power = solaxX1.dc1_voltage * solaxX1.dc1_current;
|
|
solaxX1.dc2_power = solaxX1.dc2_voltage * solaxX1.dc2_current;
|
|
|
|
solaxX1_QueryLiveData();
|
|
EnergyUpdateTotal(solaxX1.energy_total, true);
|
|
}
|
|
}
|
|
|
|
if (0 == solaxX1_send_retry && 255 != solaxX1_nodata_count) {
|
|
solaxX1_send_retry = 12;
|
|
solaxX1_QueryLiveData();
|
|
}
|
|
|
|
|
|
|
|
if (255 == solaxX1_nodata_count) {
|
|
solaxX1_nodata_count = 0;
|
|
solaxX1_send_retry = 12;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((solaxX1_nodata_count % 4) == 0) { DEBUG_SENSOR_LOG(PSTR("SX1: No Data count: %d"), solaxX1_nodata_count); }
|
|
if (solaxX1_nodata_count < 10 * 4)
|
|
{
|
|
solaxX1_nodata_count++;
|
|
}
|
|
else if (255 != solaxX1_nodata_count)
|
|
{
|
|
|
|
solaxX1_nodata_count = 255;
|
|
solaxX1_send_retry = 12;
|
|
protocolStatus.status = 0b00001000;
|
|
Energy.data_valid[0] = ENERGY_WATCHDOG;
|
|
|
|
solaxX1.temperature = solaxX1.dc1_voltage = solaxX1.dc2_voltage = solaxX1.dc1_current = solaxX1.dc2_current = solaxX1.dc1_power = 0;
|
|
solaxX1.dc2_power = solaxX1.status = Energy.current[0] = Energy.voltage[0] = Energy.frequency[0] = Energy.active_power[0] = 0;
|
|
|
|
}
|
|
}
|
|
|
|
if (!protocolStatus.hasAddress && (data_ready || solaxX1_send_retry == 0))
|
|
{
|
|
if (data_ready)
|
|
{
|
|
|
|
if (protocolStatus.inverterAddressSend)
|
|
{
|
|
uint8_t error = solaxX1_RS485Receive(value);
|
|
if (error)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Address confirmation response CRC error"));
|
|
}
|
|
else
|
|
{
|
|
if (value[6] == 0x10 && value[7] == 0x81 && value[9] == 0x06)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Set hasAddress"));
|
|
protocolStatus.status = 0b00100000;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (protocolStatus.queryOfflineSend)
|
|
{
|
|
uint8_t error = solaxX1_RS485Receive(value);
|
|
if (error)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline response CRC error"));
|
|
}
|
|
else
|
|
{
|
|
|
|
if (value[6] == 0x10 && value[7] == 0x80 && protocolStatus.inverterSnReceived == false)
|
|
{
|
|
for (uint8_t i = 9; i <= 22; i++)
|
|
{
|
|
data[i - 9] = value[i];
|
|
}
|
|
solaxX1_SendInverterAddress();
|
|
protocolStatus.status = 0b1100000;
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Set inverterSnReceived and inverterAddressSend"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (solaxX1_send_retry == 0)
|
|
{
|
|
if (protocolStatus.queryOfflineSend)
|
|
{
|
|
protocolStatus.status = 0b00001000;
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Set Query Offline"));
|
|
}
|
|
solaxX1_send_retry = 12;
|
|
}
|
|
|
|
|
|
if (protocolStatus.queryOffline)
|
|
{
|
|
|
|
source[0] = 0x01;
|
|
destination[1] = 0x00;
|
|
controlCode[0] = 0x10;
|
|
functionCode[0] = 0x00;
|
|
dataLength[0] = 0x00;
|
|
solaxX1_RS485Send(9);
|
|
protocolStatus.status = 0b00010000;
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: Query Offline Send"));
|
|
}
|
|
}
|
|
|
|
if (!data_ready)
|
|
solaxX1_send_retry--;
|
|
}
|
|
|
|
void solaxX1SnsInit(void)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("SX1: Solax X1 Inverter Init"));
|
|
DEBUG_SENSOR_LOG(PSTR("SX1: RX pin: %d, TX pin: %d"), pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX]);
|
|
protocolStatus.status = 0b00100000;
|
|
|
|
solaxX1Serial = new TasmotaSerial(pin[GPIO_SOLAXX1_RX], pin[GPIO_SOLAXX1_TX], 1);
|
|
if (solaxX1Serial->begin(SOLAXX1_SPEED)) {
|
|
if (solaxX1Serial->hardwareSerial()) { ClaimSerial(); }
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void solaxX1DrvInit(void)
|
|
{
|
|
if ((pin[GPIO_SOLAXX1_RX] < 99) && (pin[GPIO_SOLAXX1_TX] < 99)) {
|
|
energy_flg = XNRG_12;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_solaxX1_DATA1[] PROGMEM =
|
|
"{s}" D_SOLAX_X1 " " D_SOLAR_POWER "{m}%s " D_UNIT_WATT "{e}"
|
|
"{s}" D_SOLAX_X1 " " D_PV1_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
|
|
"{s}" D_SOLAX_X1 " " D_PV1_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
|
|
"{s}" D_SOLAX_X1 " " D_PV1_POWER "{m}%s " D_UNIT_WATT "{e}";
|
|
#ifdef SOLAXX1_PV2
|
|
const char HTTP_SNS_solaxX1_DATA2[] PROGMEM =
|
|
"{s}" D_SOLAX_X1 " " D_PV2_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
|
|
"{s}" D_SOLAX_X1 " " D_PV2_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
|
|
"{s}" D_SOLAX_X1 " " D_PV2_POWER "{m}%s " D_UNIT_WATT "{e}";
|
|
#endif
|
|
const char HTTP_SNS_solaxX1_DATA3[] PROGMEM =
|
|
"{s}" D_SOLAX_X1 " " D_UPTIME "{m}%s " D_UNIT_HOUR "{e}"
|
|
"{s}" D_SOLAX_X1 " " D_STATUS "{m}%s"
|
|
"{s}" D_SOLAX_X1 " " D_ERROR "{m}%s";
|
|
#endif
|
|
|
|
void solaxX1Show(bool json)
|
|
{
|
|
char solar_power[33];
|
|
dtostrfd(solaxX1.dc1_power + solaxX1.dc2_power, Settings.flag2.wattage_resolution, solar_power);
|
|
char pv1_voltage[33];
|
|
dtostrfd(solaxX1.dc1_voltage, Settings.flag2.voltage_resolution, pv1_voltage);
|
|
char pv1_current[33];
|
|
dtostrfd(solaxX1.dc1_current, Settings.flag2.current_resolution, pv1_current);
|
|
char pv1_power[33];
|
|
dtostrfd(solaxX1.dc1_power, Settings.flag2.wattage_resolution, pv1_power);
|
|
#ifdef SOLAXX1_PV2
|
|
char pv2_voltage[33];
|
|
dtostrfd(solaxX1.dc2_voltage, Settings.flag2.voltage_resolution, pv2_voltage);
|
|
char pv2_current[33];
|
|
dtostrfd(solaxX1.dc2_current, Settings.flag2.current_resolution, pv2_current);
|
|
char pv2_power[33];
|
|
dtostrfd(solaxX1.dc2_power, Settings.flag2.wattage_resolution, pv2_power);
|
|
#endif
|
|
char temperature[33];
|
|
dtostrfd(solaxX1.temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char runtime[33];
|
|
dtostrfd(solaxX1.runtime_total, 0, runtime);
|
|
char status[33];
|
|
GetTextIndexed(status, sizeof(status), solaxX1.status, kSolaxMode);
|
|
|
|
if (json)
|
|
{
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_SOLAR_POWER "\":%s,\"" D_JSON_PV1_VOLTAGE "\":%s,\"" D_JSON_PV1_CURRENT "\":%s,\"" D_JSON_PV1_POWER "\":%s"),
|
|
solar_power, pv1_voltage, pv1_current, pv1_power);
|
|
#ifdef SOLAXX1_PV2
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_PV2_VOLTAGE "\":%s,\"" D_JSON_PV2_CURRENT "\":%s,\"" D_JSON_PV2_POWER "\":%s"),
|
|
pv2_voltage, pv2_current, pv2_power);
|
|
#endif
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RUNTIME "\":%s,\"" D_JSON_STATUS "\":\"%s\",\"" D_JSON_ERROR "\":%d"),
|
|
temperature, runtime, status, solaxX1.errorCode);
|
|
|
|
#ifdef USE_WEBSERVER
|
|
}
|
|
else
|
|
{
|
|
WSContentSend_PD(HTTP_SNS_solaxX1_DATA1, solar_power, pv1_voltage, pv1_current, pv1_power);
|
|
#ifdef SOLAXX1_PV2
|
|
WSContentSend_PD(HTTP_SNS_solaxX1_DATA2, pv2_voltage, pv2_current, pv2_power);
|
|
#endif
|
|
WSContentSend_PD(HTTP_SNS_TEMP, D_SOLAX_X1, temperature, TempUnit());
|
|
char errorCodeString[33];
|
|
WSContentSend_PD(HTTP_SNS_solaxX1_DATA3, runtime, status,
|
|
GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg12(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (uptime > 4) { solaxX1250MSecond(); }
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
solaxX1Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
solaxX1Show(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
solaxX1SnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
solaxX1DrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
#ifdef USE_LE01MR
|
|
# 71 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino"
|
|
#define XNRG_13 13
|
|
|
|
|
|
#ifndef LE01MR_SPEED
|
|
#define LE01MR_SPEED 2400
|
|
#endif
|
|
|
|
#ifndef LE01MR_ADDR
|
|
#define LE01MR_ADDR 1
|
|
#endif
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *FifLEModbus;
|
|
|
|
const uint8_t le01mr_table_sz = 9;
|
|
|
|
const uint16_t le01mr_register_addresses[] {
|
|
|
|
0x0130,
|
|
0x0131,
|
|
0x0158,
|
|
0x0139,
|
|
0x0140,
|
|
0x0148,
|
|
0x0150,
|
|
0xA000,
|
|
0xA01E
|
|
};
|
|
|
|
struct LE01MR {
|
|
float total_active = 0;
|
|
float total_reactive = 0;
|
|
uint8_t read_state = 0;
|
|
uint8_t send_retry = 0;
|
|
uint8_t start_address_count = le01mr_table_sz;
|
|
} Le01mr;
|
|
|
|
|
|
|
|
void FifLEEvery250ms(void)
|
|
{
|
|
bool data_ready = FifLEModbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t buffer[14];
|
|
uint8_t reg_count = 2;
|
|
if (Le01mr.read_state < 3) {
|
|
reg_count=1;
|
|
}
|
|
|
|
uint32_t error = FifLEModbus->ReceiveBuffer(buffer, reg_count);
|
|
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, FifLEModbus->ReceiveCount());
|
|
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("FiF-LE: LE01MR Modbus error %d"), error);
|
|
} else {
|
|
Energy.data_valid[0] = 0;
|
|
# 146 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_13_fif_le01mr.ino"
|
|
uint32_t value_buff = 0;
|
|
|
|
if (Le01mr.read_state >= 0 && Le01mr.read_state < 3) {
|
|
value_buff = ((uint32_t)buffer[3])<<8 | buffer[4];
|
|
} else {
|
|
value_buff = ((uint32_t)buffer[3])<<24 | ((uint32_t)buffer[4])<<16 | ((uint32_t)buffer[5])<<8 | buffer[6];
|
|
}
|
|
|
|
switch(Le01mr.read_state) {
|
|
case 0:
|
|
Energy.frequency[0] = value_buff * 0.01f;
|
|
break;
|
|
|
|
case 1:
|
|
Energy.voltage[0] = value_buff * 0.01f;
|
|
break;
|
|
|
|
case 2:
|
|
Energy.power_factor[0] = ((int16_t)value_buff) * 0.001f;
|
|
break;
|
|
|
|
case 3:
|
|
Energy.current[0] = value_buff * 0.001f;
|
|
break;
|
|
|
|
case 4:
|
|
Energy.active_power[0] = value_buff * 1.0f;
|
|
break;
|
|
|
|
case 5:
|
|
Energy.reactive_power[0] = value_buff * 1.0f;
|
|
break;
|
|
|
|
case 6:
|
|
Energy.apparent_power[0] = value_buff * 1.0f;
|
|
break;
|
|
|
|
case 7:
|
|
Le01mr.total_active = value_buff * 0.01f;
|
|
break;
|
|
|
|
case 8:
|
|
Le01mr.total_reactive = value_buff * 0.01f;
|
|
break;
|
|
}
|
|
|
|
Le01mr.read_state++;
|
|
if (Le01mr.read_state == Le01mr.start_address_count) {
|
|
Le01mr.read_state = 0;
|
|
|
|
EnergyUpdateTotal(Le01mr.total_active, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == Le01mr.send_retry || data_ready) {
|
|
uint8_t reg_count = 2;
|
|
|
|
Le01mr.send_retry = 5;
|
|
|
|
if (Le01mr.read_state < 3) reg_count=1;
|
|
|
|
FifLEModbus->Send(LE01MR_ADDR, 0x03, le01mr_register_addresses[Le01mr.read_state], reg_count);
|
|
} else {
|
|
Le01mr.send_retry--;
|
|
}
|
|
}
|
|
|
|
void FifLESnsInit(void)
|
|
{
|
|
FifLEModbus = new TasmotaModbus(pin[GPIO_LE01MR_RX], pin[GPIO_LE01MR_TX]);
|
|
uint8_t result = FifLEModbus->Begin(LE01MR_SPEED);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
} else {
|
|
energy_flg = ENERGY_NONE;
|
|
}
|
|
}
|
|
|
|
void FifLEDrvInit(void)
|
|
{
|
|
if ((pin[GPIO_LE01MR_RX] < 99) && (pin[GPIO_LE01MR_TX] < 99)) {
|
|
energy_flg = XNRG_13;
|
|
}
|
|
}
|
|
|
|
void FifLEReset(void)
|
|
{
|
|
Le01mr.total_active = 0;
|
|
Le01mr.total_reactive = 0;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_ENERGY_LE01MR[] PROGMEM =
|
|
"{s}" D_TOTAL_ACTIVE "{m}%s " D_UNIT_KILOWATTHOUR "{e}"
|
|
"{s}" D_TOTAL_REACTIVE "{m}%s " D_UNIT_KWARH "{e}"
|
|
;
|
|
#endif
|
|
|
|
void FifLEShow(bool json)
|
|
{
|
|
char total_reactive_chr[FLOATSZ];
|
|
dtostrfd(Le01mr.total_reactive, Settings.flag2.energy_resolution, total_reactive_chr);
|
|
char total_active_chr[FLOATSZ];
|
|
dtostrfd(Le01mr.total_active, Settings.flag2.energy_resolution, total_active_chr);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_TOTAL_ACTIVE "\":%s,\"" D_JSON_TOTAL_REACTIVE "\":%s"),
|
|
total_active_chr, total_reactive_chr);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_ENERGY_LE01MR, total_active_chr, total_reactive_chr);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xnrg13(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (uptime > 4) {
|
|
FifLEEvery250ms();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
FifLEShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
FifLEShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_ENERGY_RESET:
|
|
FifLEReset();
|
|
break;
|
|
case FUNC_INIT:
|
|
FifLESnsInit();
|
|
break;
|
|
case FUNC_PRE_INIT:
|
|
FifLEDrvInit();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_interface.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xnrg_interface.ino"
|
|
#ifdef USE_ENERGY_SENSOR
|
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
bool (* const xnrg_func_ptr[])(uint8_t) PROGMEM = {
|
|
#else
|
|
bool (* const xnrg_func_ptr[])(uint8_t) = {
|
|
#endif
|
|
|
|
#ifdef XNRG_01
|
|
&Xnrg01,
|
|
#endif
|
|
|
|
#ifdef XNRG_02
|
|
&Xnrg02,
|
|
#endif
|
|
|
|
#ifdef XNRG_03
|
|
&Xnrg03,
|
|
#endif
|
|
|
|
#ifdef XNRG_04
|
|
&Xnrg04,
|
|
#endif
|
|
|
|
#ifdef XNRG_05
|
|
&Xnrg05,
|
|
#endif
|
|
|
|
#ifdef XNRG_06
|
|
&Xnrg06,
|
|
#endif
|
|
|
|
#ifdef XNRG_07
|
|
&Xnrg07,
|
|
#endif
|
|
|
|
#ifdef XNRG_08
|
|
&Xnrg08,
|
|
#endif
|
|
|
|
#ifdef XNRG_09
|
|
&Xnrg09,
|
|
#endif
|
|
|
|
#ifdef XNRG_10
|
|
&Xnrg10,
|
|
#endif
|
|
|
|
#ifdef XNRG_11
|
|
&Xnrg11,
|
|
#endif
|
|
|
|
#ifdef XNRG_12
|
|
&Xnrg12,
|
|
#endif
|
|
|
|
#ifdef XNRG_13
|
|
&Xnrg13,
|
|
#endif
|
|
|
|
#ifdef XNRG_14
|
|
&Xnrg14,
|
|
#endif
|
|
|
|
#ifdef XNRG_15
|
|
&Xnrg15,
|
|
#endif
|
|
|
|
#ifdef XNRG_16
|
|
&Xnrg16
|
|
#endif
|
|
};
|
|
|
|
const uint8_t xnrg_present = sizeof(xnrg_func_ptr) / sizeof(xnrg_func_ptr[0]);
|
|
|
|
uint8_t xnrg_active = 0;
|
|
|
|
bool XnrgCall(uint8_t function)
|
|
{
|
|
DEBUG_TRACE_LOG(PSTR("NRG: %d"), function);
|
|
|
|
if (FUNC_PRE_INIT == function) {
|
|
for (uint32_t x = 0; x < xnrg_present; x++) {
|
|
xnrg_func_ptr[x](function);
|
|
if (energy_flg) {
|
|
xnrg_active = x;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (energy_flg) {
|
|
return xnrg_func_ptr[xnrg_active](function);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_01_counter.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_01_counter.ino"
|
|
#ifdef USE_COUNTER
|
|
|
|
|
|
|
|
|
|
#define XSNS_01 1
|
|
|
|
#define D_PRFX_COUNTER "Counter"
|
|
#define D_CMND_COUNTERTYPE "Type"
|
|
#define D_CMND_COUNTERDEBOUNCE "Debounce"
|
|
|
|
const char kCounterCommands[] PROGMEM = D_PRFX_COUNTER "|"
|
|
"|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE ;
|
|
|
|
void (* const CounterCommand[])(void) PROGMEM = {
|
|
&CmndCounter, &CmndCounterType, &CmndCounterDebounce };
|
|
|
|
struct COUNTER {
|
|
uint32_t timer[MAX_COUNTERS];
|
|
uint8_t no_pullup = 0;
|
|
bool any_counter = false;
|
|
} Counter;
|
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
void CounterUpdate(uint8_t index) ICACHE_RAM_ATTR;
|
|
void CounterUpdate1(void) ICACHE_RAM_ATTR;
|
|
void CounterUpdate2(void) ICACHE_RAM_ATTR;
|
|
void CounterUpdate3(void) ICACHE_RAM_ATTR;
|
|
void CounterUpdate4(void) ICACHE_RAM_ATTR;
|
|
#endif
|
|
|
|
void CounterUpdate(uint8_t index)
|
|
{
|
|
uint32_t time = micros();
|
|
uint32_t debounce_time = time - Counter.timer[index];
|
|
if (debounce_time > Settings.pulse_counter_debounce * 1000) {
|
|
Counter.timer[index] = time;
|
|
if (bitRead(Settings.pulse_counter_type, index)) {
|
|
RtcSettings.pulse_counter[index] = debounce_time;
|
|
} else {
|
|
RtcSettings.pulse_counter[index]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CounterUpdate1(void)
|
|
{
|
|
CounterUpdate(0);
|
|
}
|
|
|
|
void CounterUpdate2(void)
|
|
{
|
|
CounterUpdate(1);
|
|
}
|
|
|
|
void CounterUpdate3(void)
|
|
{
|
|
CounterUpdate(2);
|
|
}
|
|
|
|
void CounterUpdate4(void)
|
|
{
|
|
CounterUpdate(3);
|
|
}
|
|
|
|
|
|
|
|
bool CounterPinState(void)
|
|
{
|
|
if ((XdrvMailbox.index >= GPIO_CNTR1_NP) && (XdrvMailbox.index < (GPIO_CNTR1_NP + MAX_COUNTERS))) {
|
|
bitSet(Counter.no_pullup, XdrvMailbox.index - GPIO_CNTR1_NP);
|
|
XdrvMailbox.index -= (GPIO_CNTR1_NP - GPIO_CNTR1);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CounterInit(void)
|
|
{
|
|
typedef void (*function) () ;
|
|
function counter_callbacks[] = { CounterUpdate1, CounterUpdate2, CounterUpdate3, CounterUpdate4 };
|
|
|
|
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
|
|
if (pin[GPIO_CNTR1 +i] < 99) {
|
|
Counter.any_counter = true;
|
|
pinMode(pin[GPIO_CNTR1 +i], bitRead(Counter.no_pullup, i) ? INPUT : INPUT_PULLUP);
|
|
attachInterrupt(pin[GPIO_CNTR1 +i], counter_callbacks[i], FALLING);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CounterEverySecond(void)
|
|
{
|
|
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
|
|
if (pin[GPIO_CNTR1 +i] < 99) {
|
|
if (bitRead(Settings.pulse_counter_type, i)) {
|
|
uint32_t time = micros() - Counter.timer[i];
|
|
if (time > 4200000000) {
|
|
RtcSettings.pulse_counter[i] = 4200000000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CounterSaveState(void)
|
|
{
|
|
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
|
|
if (pin[GPIO_CNTR1 +i] < 99) {
|
|
Settings.pulse_counter[i] = RtcSettings.pulse_counter[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
void CounterShow(bool json)
|
|
{
|
|
bool header = false;
|
|
uint8_t dsxflg = 0;
|
|
for (uint32_t i = 0; i < MAX_COUNTERS; i++) {
|
|
if (pin[GPIO_CNTR1 +i] < 99) {
|
|
char counter[33];
|
|
if (bitRead(Settings.pulse_counter_type, i)) {
|
|
dtostrfd((double)RtcSettings.pulse_counter[i] / 1000000, 6, counter);
|
|
} else {
|
|
dsxflg++;
|
|
snprintf_P(counter, sizeof(counter), PSTR("%lu"), RtcSettings.pulse_counter[i]);
|
|
}
|
|
|
|
if (json) {
|
|
if (!header) {
|
|
ResponseAppend_P(PSTR(",\"COUNTER\":{"));
|
|
}
|
|
ResponseAppend_P(PSTR("%s\"C%d\":%s"), (header)?",":"", i +1, counter);
|
|
header = true;
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (1 == dsxflg)) {
|
|
DomoticzSensor(DZ_COUNT, RtcSettings.pulse_counter[i]);
|
|
dsxflg++;
|
|
}
|
|
#endif
|
|
if ((0 == tele_period ) && (Settings.flag3.counter_reset_on_tele)) {
|
|
RtcSettings.pulse_counter[i] = 0;
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(PSTR("{s}" D_COUNTER "%d{m}%s%s{e}"),
|
|
i +1, counter, (bitRead(Settings.pulse_counter_type, i)) ? " " D_UNIT_SECOND : "");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
if (header) {
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CmndCounter(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) {
|
|
if ((XdrvMailbox.data_len > 0) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) {
|
|
if ((XdrvMailbox.data[0] == '-') || (XdrvMailbox.data[0] == '+')) {
|
|
RtcSettings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload;
|
|
Settings.pulse_counter[XdrvMailbox.index -1] += XdrvMailbox.payload;
|
|
} else {
|
|
RtcSettings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
Settings.pulse_counter[XdrvMailbox.index -1] = XdrvMailbox.payload;
|
|
}
|
|
}
|
|
ResponseCmndIdxNumber(RtcSettings.pulse_counter[XdrvMailbox.index -1]);
|
|
}
|
|
}
|
|
|
|
void CmndCounterType(void)
|
|
{
|
|
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_COUNTERS)) {
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1) && (pin[GPIO_CNTR1 + XdrvMailbox.index -1] < 99)) {
|
|
bitWrite(Settings.pulse_counter_type, XdrvMailbox.index -1, XdrvMailbox.payload &1);
|
|
RtcSettings.pulse_counter[XdrvMailbox.index -1] = 0;
|
|
Settings.pulse_counter[XdrvMailbox.index -1] = 0;
|
|
}
|
|
ResponseCmndIdxNumber(bitRead(Settings.pulse_counter_type, XdrvMailbox.index -1));
|
|
}
|
|
}
|
|
|
|
void CmndCounterDebounce(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32001)) {
|
|
Settings.pulse_counter_debounce = XdrvMailbox.payload;
|
|
}
|
|
ResponseCmndNumber(Settings.pulse_counter_debounce);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns01(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Counter.any_counter) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
CounterEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
CounterShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
CounterShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
case FUNC_SAVE_AT_MIDNIGHT:
|
|
CounterSaveState();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kCounterCommands, CounterCommand);
|
|
break;
|
|
}
|
|
} else {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
CounterInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = CounterPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino"
|
|
#ifndef USE_ADC_VCC
|
|
|
|
|
|
|
|
|
|
#define XSNS_02 2
|
|
|
|
#define TO_CELSIUS(x) ((x) - 273.15)
|
|
#define TO_KELVIN(x) ((x) + 273.15)
|
|
|
|
|
|
#define ANALOG_V33 3.3
|
|
#define ANALOG_T0 TO_KELVIN(25.0)
|
|
|
|
|
|
|
|
|
|
|
|
#define ANALOG_NTC_BRIDGE_RESISTANCE 32000
|
|
#define ANALOG_NTC_RESISTANCE 10000
|
|
#define ANALOG_NTC_B_COEFFICIENT 3350
|
|
|
|
|
|
|
|
|
|
|
|
#define ANALOG_LDR_BRIDGE_RESISTANCE 10000
|
|
#define ANALOG_LDR_LUX_CALC_SCALAR 12518931
|
|
#define ANALOG_LDR_LUX_CALC_EXPONENT -1.4050
|
|
# 58 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_02_analog.ino"
|
|
#define ANALOG_CT_FLAGS 0
|
|
#define ANALOG_CT_MULTIPLIER 2146
|
|
#define ANALOG_CT_VOLTAGE 2300
|
|
|
|
#define CT_FLAG_ENERGY_RESET (1 << 0)
|
|
|
|
struct {
|
|
float temperature = 0;
|
|
float current = 0;
|
|
float energy = 0;
|
|
uint32_t previous_millis = 0;
|
|
uint16_t last_value = 0;
|
|
} Adc;
|
|
|
|
void AdcInit(void)
|
|
{
|
|
if ((Settings.adc_param_type != my_adc0) || (Settings.adc_param1 > 1000000)) {
|
|
if (ADC0_TEMP == my_adc0) {
|
|
|
|
Settings.adc_param_type = ADC0_TEMP;
|
|
Settings.adc_param1 = ANALOG_NTC_BRIDGE_RESISTANCE;
|
|
Settings.adc_param2 = ANALOG_NTC_RESISTANCE;
|
|
Settings.adc_param3 = ANALOG_NTC_B_COEFFICIENT * 10000;
|
|
}
|
|
else if (ADC0_LIGHT == my_adc0) {
|
|
Settings.adc_param_type = ADC0_LIGHT;
|
|
Settings.adc_param1 = ANALOG_LDR_BRIDGE_RESISTANCE;
|
|
Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR;
|
|
Settings.adc_param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000;
|
|
}
|
|
else if (ADC0_RANGE == my_adc0) {
|
|
Settings.adc_param_type = ADC0_RANGE;
|
|
Settings.adc_param1 = 0;
|
|
Settings.adc_param2 = 1023;
|
|
Settings.adc_param3 = 0;
|
|
Settings.adc_param4 = 100;
|
|
}
|
|
else if (ADC0_CT_POWER == my_adc0) {
|
|
Settings.adc_param_type = ADC0_CT_POWER;
|
|
Settings.adc_param1 = ANALOG_CT_FLAGS;
|
|
Settings.adc_param2 = ANALOG_CT_MULTIPLIER;
|
|
Settings.adc_param3 = ANALOG_CT_VOLTAGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16_t AdcRead(uint8_t factor)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t samples = 1 << factor;
|
|
uint16_t analog = 0;
|
|
for (uint32_t i = 0; i < samples; i++) {
|
|
analog += analogRead(A0);
|
|
delay(1);
|
|
}
|
|
analog >>= factor;
|
|
return analog;
|
|
}
|
|
|
|
#ifdef USE_RULES
|
|
void AdcEvery250ms(void)
|
|
{
|
|
if (ADC0_INPUT == my_adc0) {
|
|
uint16_t new_value = AdcRead(5);
|
|
if ((new_value < Adc.last_value -10) || (new_value > Adc.last_value +10)) {
|
|
Adc.last_value = new_value;
|
|
uint16_t value = Adc.last_value / 10;
|
|
Response_P(PSTR("{\"ANALOG\":{\"A0div10\":%d}}"), (value > 99) ? 100 : value);
|
|
XdrvRulesProcess();
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
uint16_t AdcGetLux(void)
|
|
{
|
|
int adc = AdcRead(2);
|
|
|
|
double resistorVoltage = ((double)adc / 1023) * ANALOG_V33;
|
|
double ldrVoltage = ANALOG_V33 - resistorVoltage;
|
|
double ldrResistance = ldrVoltage / resistorVoltage * (double)Settings.adc_param1;
|
|
double ldrLux = (double)Settings.adc_param2 * FastPrecisePow(ldrResistance, (double)Settings.adc_param3 / 10000);
|
|
|
|
return (uint16_t)ldrLux;
|
|
}
|
|
|
|
uint16_t AdcGetRange(void)
|
|
{
|
|
|
|
|
|
|
|
int adc = AdcRead(2);
|
|
double adcrange = ( ((double)Settings.adc_param2 - (double)adc) / ( ((double)Settings.adc_param2 - (double)Settings.adc_param1)) * ((double)Settings.adc_param3 - (double)Settings.adc_param4) + (double)Settings.adc_param4 );
|
|
return (uint16_t)adcrange;
|
|
}
|
|
|
|
void AdcGetCurrentPower(uint8_t factor)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t samples = 1 << factor;
|
|
uint16_t analog = 0;
|
|
uint16_t analog_min = 1023;
|
|
uint16_t analog_max = 0;
|
|
for (uint32_t i = 0; i < samples; i++) {
|
|
analog = analogRead(A0);
|
|
if (analog < analog_min) {
|
|
analog_min = analog;
|
|
}
|
|
if (analog > analog_max) {
|
|
analog_max = analog;
|
|
}
|
|
delay(1);
|
|
}
|
|
|
|
Adc.current = (float)(analog_max-analog_min) * ((float)(Settings.adc_param2) / 100000);
|
|
float power = Adc.current * (float)(Settings.adc_param3) / 10;
|
|
uint32_t current_millis = millis();
|
|
Adc.energy = Adc.energy + ((power * (current_millis - Adc.previous_millis)) / 3600000000);
|
|
Adc.previous_millis = current_millis;
|
|
}
|
|
|
|
void AdcEverySecond(void)
|
|
{
|
|
if (ADC0_TEMP == my_adc0) {
|
|
int adc = AdcRead(2);
|
|
|
|
double Rt = (adc * Settings.adc_param1) / (1024.0 * ANALOG_V33 - (double)adc);
|
|
double BC = (double)Settings.adc_param3 / 10000;
|
|
double T = BC / (BC / ANALOG_T0 + TaylorLog(Rt / (double)Settings.adc_param2));
|
|
Adc.temperature = ConvertTemp(TO_CELSIUS(T));
|
|
}
|
|
else if (ADC0_CT_POWER == my_adc0) {
|
|
AdcGetCurrentPower(5);
|
|
}
|
|
}
|
|
|
|
void AdcShow(bool json)
|
|
{
|
|
if (ADC0_INPUT == my_adc0) {
|
|
uint16_t analog = AdcRead(5);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"ANALOG\":{\"A0\":%d}"), analog);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_ANALOG, "", 0, analog);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
else if (ADC0_TEMP == my_adc0) {
|
|
char temperature[33];
|
|
dtostrfd(Adc.temperature, Settings.flag2.temperature_resolution, temperature);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMP, "ANALOG", temperature);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, Adc.temperature);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
else if (ADC0_LIGHT == my_adc0) {
|
|
uint16_t adc_light = AdcGetLux();
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_ILLUMINANCE, "ANALOG", adc_light);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_ILLUMINANCE, adc_light);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", adc_light);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
else if (ADC0_RANGE == my_adc0) {
|
|
uint16_t adc_range = AdcGetRange();
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_RANGE, "ANALOG", adc_range);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_RANGE, "", adc_range);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
else if (ADC0_CT_POWER == my_adc0) {
|
|
AdcGetCurrentPower(5);
|
|
|
|
float voltage = (float)(Settings.adc_param3) / 10;
|
|
char voltage_chr[FLOATSZ];
|
|
dtostrfd(voltage, Settings.flag2.voltage_resolution, voltage_chr);
|
|
char current_chr[FLOATSZ];
|
|
dtostrfd(Adc.current, Settings.flag2.current_resolution, current_chr);
|
|
char power_chr[FLOATSZ];
|
|
dtostrfd(voltage * Adc.current, Settings.flag2.wattage_resolution, power_chr);
|
|
char energy_chr[FLOATSZ];
|
|
dtostrfd(Adc.energy, Settings.flag2.energy_resolution, energy_chr);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"ANALOG\":{\"" D_JSON_ENERGY "\":%s,\"" D_JSON_POWERUSAGE "\":%s,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s}"),
|
|
energy_chr, power_chr, voltage_chr, current_chr);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_POWER_ENERGY, power_chr);
|
|
DomoticzSensor(DZ_VOLTAGE, voltage_chr);
|
|
DomoticzSensor(DZ_CURRENT, current_chr);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_VOLTAGE, voltage_chr);
|
|
WSContentSend_PD(HTTP_SNS_CURRENT, current_chr);
|
|
WSContentSend_PD(HTTP_SNS_POWER, power_chr);
|
|
WSContentSend_PD(HTTP_SNS_ENERGY_TOTAL, energy_chr);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char kAdcCommands[] PROGMEM = "|"
|
|
D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM;
|
|
|
|
void (* const AdcCommand[])(void) PROGMEM = {
|
|
&CmndAdc, &CmndAdcs, &CmndAdcParam };
|
|
|
|
void CmndAdc(void)
|
|
{
|
|
if (ValidAdc() && (XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < ADC0_END)) {
|
|
Settings.my_adc0 = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
char stemp1[TOPSZ];
|
|
Response_P(PSTR("{\"" D_CMND_ADC "0\":{\"%d\":\"%s\"}}"), Settings.my_adc0, GetTextIndexed(stemp1, sizeof(stemp1), Settings.my_adc0, kAdc0Names));
|
|
}
|
|
|
|
void CmndAdcs(void)
|
|
{
|
|
Response_P(PSTR("{\"" D_CMND_ADCS "\":{"));
|
|
bool jsflg = false;
|
|
char stemp1[TOPSZ];
|
|
for (uint32_t i = 0; i < ADC0_END; i++) {
|
|
if (jsflg) {
|
|
ResponseAppend_P(PSTR(","));
|
|
}
|
|
jsflg = true;
|
|
ResponseAppend_P(PSTR("\"%d\":\"%s\""), i, GetTextIndexed(stemp1, sizeof(stemp1), i, kAdc0Names));
|
|
}
|
|
ResponseJsonEndEnd();
|
|
}
|
|
|
|
void CmndAdcParam(void)
|
|
{
|
|
if (XdrvMailbox.data_len) {
|
|
if ((ADC0_TEMP == XdrvMailbox.payload) ||
|
|
(ADC0_LIGHT == XdrvMailbox.payload) ||
|
|
(ADC0_RANGE == XdrvMailbox.payload) ||
|
|
(ADC0_CT_POWER == XdrvMailbox.payload)) {
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
char sub_string[XdrvMailbox.data_len +1];
|
|
|
|
|
|
|
|
Settings.adc_param_type = XdrvMailbox.payload;
|
|
Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
|
Settings.adc_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10);
|
|
if (ADC0_RANGE == XdrvMailbox.payload) {
|
|
Settings.adc_param3 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 4), nullptr, 10));
|
|
Settings.adc_param4 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 5), nullptr, 10));
|
|
} else {
|
|
Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000);
|
|
}
|
|
if (ADC0_CT_POWER == XdrvMailbox.payload) {
|
|
if ((Settings.adc_param1 & CT_FLAG_ENERGY_RESET) > 0) {
|
|
Adc.energy = 0;
|
|
Settings.adc_param1 ^= CT_FLAG_ENERGY_RESET;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
|
|
|
|
|
|
Settings.adc_param_type = 0;
|
|
AdcInit();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d"), Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2);
|
|
if (ADC0_RANGE == my_adc0) {
|
|
ResponseAppend_P(PSTR(",%d,%d"), Settings.adc_param3, Settings.adc_param4);
|
|
} else {
|
|
int value = Settings.adc_param3;
|
|
uint8_t precision;
|
|
for (precision = 4; precision > 0; precision--) {
|
|
if (value % 10) { break; }
|
|
value /= 10;
|
|
}
|
|
char param3[33];
|
|
dtostrfd(((double)Settings.adc_param3)/10000, precision, param3);
|
|
ResponseAppend_P(PSTR(",%s"), param3);
|
|
}
|
|
ResponseAppend_P(PSTR("]}"));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns02(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_COMMAND:
|
|
result = DecodeCommand(kAdcCommands, AdcCommand);
|
|
break;
|
|
default:
|
|
if ((ADC0_INPUT == my_adc0) ||
|
|
(ADC0_TEMP == my_adc0) ||
|
|
(ADC0_LIGHT == my_adc0) ||
|
|
(ADC0_RANGE == my_adc0) ||
|
|
(ADC0_CT_POWER == my_adc0)) {
|
|
switch (function) {
|
|
#ifdef USE_RULES
|
|
case FUNC_EVERY_250_MSECOND:
|
|
AdcEvery250ms();
|
|
break;
|
|
#endif
|
|
case FUNC_EVERY_SECOND:
|
|
AdcEverySecond();
|
|
break;
|
|
case FUNC_INIT:
|
|
AdcInit();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
AdcShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
AdcShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino"
|
|
#ifdef USE_SONOFF_SC
|
|
# 57 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_04_snfsc.ino"
|
|
#define XSNS_04 4
|
|
|
|
uint16_t sc_value[5] = { 0 };
|
|
|
|
void SonoffScSend(const char *data)
|
|
{
|
|
Serial.write(data);
|
|
Serial.write('\x1B');
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_SERIAL D_TRANSMIT " %s"), data);
|
|
}
|
|
|
|
void SonoffScInit(void)
|
|
{
|
|
|
|
SonoffScSend("AT+START");
|
|
|
|
}
|
|
|
|
void SonoffScSerialInput(char *rcvstat)
|
|
{
|
|
char *p;
|
|
char *str;
|
|
uint16_t value[5] = { 0 };
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_SERIAL D_RECEIVED " %s"), rcvstat);
|
|
|
|
if (!strncasecmp_P(rcvstat, PSTR("AT+UPDATE="), 10)) {
|
|
int8_t i = -1;
|
|
for (str = strtok_r(rcvstat, ":", &p); str && i < 5; str = strtok_r(nullptr, ":", &p)) {
|
|
value[i++] = atoi(str);
|
|
}
|
|
if (value[0] > 0) {
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
sc_value[i] = value[i];
|
|
}
|
|
sc_value[2] = (11 - sc_value[2]) * 10;
|
|
sc_value[3] *= 10;
|
|
sc_value[4] = (11 - sc_value[4]) * 10;
|
|
SonoffScSend("AT+SEND=ok");
|
|
} else {
|
|
SonoffScSend("AT+SEND=fail");
|
|
}
|
|
}
|
|
else if (!strcasecmp_P(rcvstat, PSTR("AT+STATUS?"))) {
|
|
SonoffScSend("AT+STATUS=4");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_SCPLUS[] PROGMEM =
|
|
"{s}" D_LIGHT "{m}%d%%{e}{s}" D_NOISE "{m}%d%%{e}{s}" D_AIR_QUALITY "{m}%d%%{e}";
|
|
#endif
|
|
|
|
void SonoffScShow(bool json)
|
|
{
|
|
if (sc_value[0] > 0) {
|
|
float t = ConvertTemp(sc_value[1]);
|
|
float h = ConvertHumidity(sc_value[0]);
|
|
|
|
char temperature[33];
|
|
dtostrfd(t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(h, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"SonoffSC\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_LIGHT "\":%d,\"" D_JSON_NOISE "\":%d,\"" D_JSON_AIRQUALITY "\":%d}"),
|
|
temperature, humidity, sc_value[2], sc_value[3], sc_value[4]);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
DomoticzSensor(DZ_ILLUMINANCE, sc_value[2]);
|
|
DomoticzSensor(DZ_COUNT, sc_value[3]);
|
|
DomoticzSensor(DZ_AIRQUALITY, 500 + ((100 - sc_value[4]) * 20));
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, t);
|
|
KnxSensor(KNX_HUMIDITY, h);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, "", humidity);
|
|
WSContentSend_PD(HTTP_SNS_SCPLUS, sc_value[2], sc_value[3], sc_value[4]);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns04(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (SONOFF_SC == my_module_type) {
|
|
switch (function) {
|
|
case FUNC_JSON_APPEND:
|
|
SonoffScShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
SonoffScShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
SonoffScInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_05_ds18x20.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_05_ds18x20.ino"
|
|
#ifdef USE_DS18x20
|
|
|
|
|
|
|
|
|
|
#define XSNS_05 5
|
|
|
|
|
|
|
|
#define DS18S20_CHIPID 0x10
|
|
#define DS1822_CHIPID 0x22
|
|
#define DS18B20_CHIPID 0x28
|
|
#define MAX31850_CHIPID 0x3B
|
|
|
|
#define W1_SKIP_ROM 0xCC
|
|
#define W1_CONVERT_TEMP 0x44
|
|
#define W1_WRITE_EEPROM 0x48
|
|
#define W1_WRITE_SCRATCHPAD 0x4E
|
|
#define W1_READ_SCRATCHPAD 0xBE
|
|
|
|
#define DS18X20_MAX_SENSORS 8
|
|
|
|
const char kDs18x20Types[] PROGMEM = "DS18x20|DS18S20|DS1822|DS18B20|MAX31850";
|
|
|
|
uint8_t ds18x20_chipids[] = { 0, DS18S20_CHIPID, DS1822_CHIPID, DS18B20_CHIPID, MAX31850_CHIPID };
|
|
|
|
struct DS18X20STRUCT {
|
|
uint8_t address[8];
|
|
uint8_t index;
|
|
uint8_t valid;
|
|
float temperature;
|
|
} ds18x20_sensor[DS18X20_MAX_SENSORS];
|
|
uint8_t ds18x20_sensors = 0;
|
|
uint8_t ds18x20_pin = 0;
|
|
uint8_t ds18x20_pin_out = 0;
|
|
bool ds18x20_dual_mode = false;
|
|
char ds18x20_types[12];
|
|
#ifdef W1_PARASITE_POWER
|
|
uint8_t ds18x20_sensor_curr = 0;
|
|
unsigned long w1_power_until = 0;
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#define W1_MATCH_ROM 0x55
|
|
#define W1_SEARCH_ROM 0xF0
|
|
|
|
uint8_t onewire_last_discrepancy = 0;
|
|
uint8_t onewire_last_family_discrepancy = 0;
|
|
bool onewire_last_device_flag = false;
|
|
unsigned char onewire_rom_id[8] = { 0 };
|
|
|
|
|
|
|
|
uint8_t OneWireReset(void)
|
|
{
|
|
uint8_t retries = 125;
|
|
|
|
if (!ds18x20_dual_mode) {
|
|
pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT);
|
|
do {
|
|
if (--retries == 0) {
|
|
return 0;
|
|
}
|
|
delayMicroseconds(2);
|
|
} while (!digitalRead(ds18x20_pin));
|
|
pinMode(ds18x20_pin, OUTPUT);
|
|
digitalWrite(ds18x20_pin, LOW);
|
|
delayMicroseconds(480);
|
|
pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT);
|
|
} else {
|
|
digitalWrite(ds18x20_pin_out, HIGH);
|
|
do {
|
|
if (--retries == 0) {
|
|
return 0;
|
|
}
|
|
delayMicroseconds(2);
|
|
} while (!digitalRead(ds18x20_pin));
|
|
digitalWrite(ds18x20_pin_out, LOW);
|
|
delayMicroseconds(480);
|
|
digitalWrite(ds18x20_pin_out, HIGH);
|
|
}
|
|
delayMicroseconds(70);
|
|
uint8_t r = !digitalRead(ds18x20_pin);
|
|
delayMicroseconds(410);
|
|
return r;
|
|
}
|
|
|
|
void OneWireWriteBit(uint8_t v)
|
|
{
|
|
static const uint8_t delay_low[2] = { 65, 10 };
|
|
static const uint8_t delay_high[2] = { 5, 55 };
|
|
|
|
v &= 1;
|
|
if (!ds18x20_dual_mode) {
|
|
digitalWrite(ds18x20_pin, LOW);
|
|
pinMode(ds18x20_pin, OUTPUT);
|
|
delayMicroseconds(delay_low[v]);
|
|
digitalWrite(ds18x20_pin, HIGH);
|
|
} else {
|
|
digitalWrite(ds18x20_pin_out, LOW);
|
|
delayMicroseconds(delay_low[v]);
|
|
digitalWrite(ds18x20_pin_out, HIGH);
|
|
}
|
|
delayMicroseconds(delay_high[v]);
|
|
}
|
|
|
|
uint8_t OneWireReadBit(void)
|
|
{
|
|
if (!ds18x20_dual_mode) {
|
|
pinMode(ds18x20_pin, OUTPUT);
|
|
digitalWrite(ds18x20_pin, LOW);
|
|
delayMicroseconds(3);
|
|
pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT);
|
|
} else {
|
|
digitalWrite(ds18x20_pin_out, LOW);
|
|
delayMicroseconds(3);
|
|
digitalWrite(ds18x20_pin_out, HIGH);
|
|
}
|
|
delayMicroseconds(10);
|
|
uint8_t r = digitalRead(ds18x20_pin);
|
|
delayMicroseconds(53);
|
|
return r;
|
|
}
|
|
|
|
|
|
|
|
void OneWireWrite(uint8_t v)
|
|
{
|
|
for (uint8_t bit_mask = 0x01; bit_mask; bit_mask <<= 1) {
|
|
OneWireWriteBit((bit_mask & v) ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
uint8_t OneWireRead(void)
|
|
{
|
|
uint8_t r = 0;
|
|
|
|
for (uint8_t bit_mask = 0x01; bit_mask; bit_mask <<= 1) {
|
|
if (OneWireReadBit()) {
|
|
r |= bit_mask;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void OneWireSelect(const uint8_t rom[8])
|
|
{
|
|
OneWireWrite(W1_MATCH_ROM);
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
OneWireWrite(rom[i]);
|
|
}
|
|
}
|
|
|
|
void OneWireResetSearch(void)
|
|
{
|
|
onewire_last_discrepancy = 0;
|
|
onewire_last_device_flag = false;
|
|
onewire_last_family_discrepancy = 0;
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
onewire_rom_id[i] = 0;
|
|
}
|
|
}
|
|
|
|
uint8_t OneWireSearch(uint8_t *newAddr)
|
|
{
|
|
uint8_t id_bit_number = 1;
|
|
uint8_t last_zero = 0;
|
|
uint8_t rom_byte_number = 0;
|
|
uint8_t search_result = 0;
|
|
uint8_t id_bit;
|
|
uint8_t cmp_id_bit;
|
|
unsigned char rom_byte_mask = 1;
|
|
unsigned char search_direction;
|
|
|
|
if (!onewire_last_device_flag) {
|
|
if (!OneWireReset()) {
|
|
onewire_last_discrepancy = 0;
|
|
onewire_last_device_flag = false;
|
|
onewire_last_family_discrepancy = 0;
|
|
return false;
|
|
}
|
|
OneWireWrite(W1_SEARCH_ROM);
|
|
do {
|
|
id_bit = OneWireReadBit();
|
|
cmp_id_bit = OneWireReadBit();
|
|
|
|
if ((id_bit == 1) && (cmp_id_bit == 1)) {
|
|
break;
|
|
} else {
|
|
if (id_bit != cmp_id_bit) {
|
|
search_direction = id_bit;
|
|
} else {
|
|
if (id_bit_number < onewire_last_discrepancy) {
|
|
search_direction = ((onewire_rom_id[rom_byte_number] & rom_byte_mask) > 0);
|
|
} else {
|
|
search_direction = (id_bit_number == onewire_last_discrepancy);
|
|
}
|
|
if (search_direction == 0) {
|
|
last_zero = id_bit_number;
|
|
if (last_zero < 9) {
|
|
onewire_last_family_discrepancy = last_zero;
|
|
}
|
|
}
|
|
}
|
|
if (search_direction == 1) {
|
|
onewire_rom_id[rom_byte_number] |= rom_byte_mask;
|
|
} else {
|
|
onewire_rom_id[rom_byte_number] &= ~rom_byte_mask;
|
|
}
|
|
OneWireWriteBit(search_direction);
|
|
id_bit_number++;
|
|
rom_byte_mask <<= 1;
|
|
if (rom_byte_mask == 0) {
|
|
rom_byte_number++;
|
|
rom_byte_mask = 1;
|
|
}
|
|
}
|
|
} while (rom_byte_number < 8);
|
|
if (!(id_bit_number < 65)) {
|
|
onewire_last_discrepancy = last_zero;
|
|
if (onewire_last_discrepancy == 0) {
|
|
onewire_last_device_flag = true;
|
|
}
|
|
search_result = true;
|
|
}
|
|
}
|
|
if (!search_result || !onewire_rom_id[0]) {
|
|
onewire_last_discrepancy = 0;
|
|
onewire_last_device_flag = false;
|
|
onewire_last_family_discrepancy = 0;
|
|
search_result = false;
|
|
}
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
newAddr[i] = onewire_rom_id[i];
|
|
}
|
|
return search_result;
|
|
}
|
|
|
|
bool OneWireCrc8(uint8_t *addr)
|
|
{
|
|
uint8_t crc = 0;
|
|
uint8_t len = 8;
|
|
|
|
while (len--) {
|
|
uint8_t inbyte = *addr++;
|
|
for (uint32_t i = 8; i; i--) {
|
|
uint8_t mix = (crc ^ inbyte) & 0x01;
|
|
crc >>= 1;
|
|
if (mix) {
|
|
crc ^= 0x8C;
|
|
}
|
|
inbyte >>= 1;
|
|
}
|
|
}
|
|
return (crc == *addr);
|
|
}
|
|
|
|
|
|
|
|
void Ds18x20Init(void)
|
|
{
|
|
uint64_t ids[DS18X20_MAX_SENSORS];
|
|
|
|
ds18x20_pin = pin[GPIO_DSB];
|
|
if (pin[GPIO_DSB_OUT] < 99) {
|
|
ds18x20_pin_out = pin[GPIO_DSB_OUT];
|
|
ds18x20_dual_mode = true;
|
|
pinMode(ds18x20_pin_out, OUTPUT);
|
|
pinMode(ds18x20_pin, Settings.flag3.ds18x20_internal_pullup ? INPUT_PULLUP : INPUT);
|
|
}
|
|
|
|
OneWireResetSearch();
|
|
|
|
ds18x20_sensors = 0;
|
|
while (ds18x20_sensors < DS18X20_MAX_SENSORS) {
|
|
if (!OneWireSearch(ds18x20_sensor[ds18x20_sensors].address)) {
|
|
break;
|
|
}
|
|
if (OneWireCrc8(ds18x20_sensor[ds18x20_sensors].address) &&
|
|
((ds18x20_sensor[ds18x20_sensors].address[0] == DS18S20_CHIPID) ||
|
|
(ds18x20_sensor[ds18x20_sensors].address[0] == DS1822_CHIPID) ||
|
|
(ds18x20_sensor[ds18x20_sensors].address[0] == DS18B20_CHIPID) ||
|
|
(ds18x20_sensor[ds18x20_sensors].address[0] == MAX31850_CHIPID))) {
|
|
ds18x20_sensor[ds18x20_sensors].index = ds18x20_sensors;
|
|
ids[ds18x20_sensors] = ds18x20_sensor[ds18x20_sensors].address[0];
|
|
for (uint32_t j = 6; j > 0; j--) {
|
|
ids[ds18x20_sensors] = ids[ds18x20_sensors] << 8 | ds18x20_sensor[ds18x20_sensors].address[j];
|
|
}
|
|
ds18x20_sensors++;
|
|
}
|
|
}
|
|
for (uint32_t i = 0; i < ds18x20_sensors; i++) {
|
|
for (uint32_t j = i + 1; j < ds18x20_sensors; j++) {
|
|
if (ids[ds18x20_sensor[i].index] > ids[ds18x20_sensor[j].index]) {
|
|
std::swap(ds18x20_sensor[i].index, ds18x20_sensor[j].index);
|
|
}
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSORS_FOUND " %d"), ds18x20_sensors);
|
|
}
|
|
|
|
void Ds18x20Convert(void)
|
|
{
|
|
OneWireReset();
|
|
#ifdef W1_PARASITE_POWER
|
|
|
|
if (++ds18x20_sensor_curr >= ds18x20_sensors)
|
|
ds18x20_sensor_curr = 0;
|
|
OneWireSelect(ds18x20_sensor[ds18x20_sensor_curr].address);
|
|
#else
|
|
OneWireWrite(W1_SKIP_ROM);
|
|
#endif
|
|
OneWireWrite(W1_CONVERT_TEMP);
|
|
|
|
}
|
|
|
|
bool Ds18x20Read(uint8_t sensor)
|
|
{
|
|
uint8_t data[9];
|
|
int8_t sign = 1;
|
|
|
|
uint8_t index = ds18x20_sensor[sensor].index;
|
|
if (ds18x20_sensor[index].valid) { ds18x20_sensor[index].valid--; }
|
|
for (uint32_t retry = 0; retry < 3; retry++) {
|
|
OneWireReset();
|
|
OneWireSelect(ds18x20_sensor[index].address);
|
|
OneWireWrite(W1_READ_SCRATCHPAD);
|
|
for (uint32_t i = 0; i < 9; i++) {
|
|
data[i] = OneWireRead();
|
|
}
|
|
if (OneWireCrc8(data)) {
|
|
switch(ds18x20_sensor[index].address[0]) {
|
|
case DS18S20_CHIPID: {
|
|
if (data[1] > 0x80) {
|
|
data[0] = (~data[0]) +1;
|
|
sign = -1;
|
|
}
|
|
float temp9 = (float)(data[0] >> 1) * sign;
|
|
ds18x20_sensor[index].temperature = ConvertTemp((temp9 - 0.25) + ((16.0 - data[6]) / 16.0));
|
|
ds18x20_sensor[index].valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
case DS1822_CHIPID:
|
|
case DS18B20_CHIPID: {
|
|
if (data[4] != 0x7F) {
|
|
data[4] = 0x7F;
|
|
OneWireReset();
|
|
OneWireSelect(ds18x20_sensor[index].address);
|
|
OneWireWrite(W1_WRITE_SCRATCHPAD);
|
|
OneWireWrite(data[2]);
|
|
OneWireWrite(data[3]);
|
|
OneWireWrite(data[4]);
|
|
OneWireSelect(ds18x20_sensor[index].address);
|
|
OneWireWrite(W1_WRITE_EEPROM);
|
|
#ifdef W1_PARASITE_POWER
|
|
w1_power_until = millis() + 10;
|
|
#endif
|
|
}
|
|
uint16_t temp12 = (data[1] << 8) + data[0];
|
|
if (temp12 > 2047) {
|
|
temp12 = (~temp12) +1;
|
|
sign = -1;
|
|
}
|
|
ds18x20_sensor[index].temperature = ConvertTemp(sign * temp12 * 0.0625);
|
|
ds18x20_sensor[index].valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
case MAX31850_CHIPID: {
|
|
int16_t temp14 = (data[1] << 8) + (data[0] & 0xFC);
|
|
ds18x20_sensor[index].temperature = ConvertTemp(temp14 * 0.0625);
|
|
ds18x20_sensor[index].valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DSB D_SENSOR_CRC_ERROR));
|
|
return false;
|
|
}
|
|
|
|
void Ds18x20Name(uint8_t sensor)
|
|
{
|
|
uint8_t index = sizeof(ds18x20_chipids);
|
|
while (index) {
|
|
if (ds18x20_sensor[ds18x20_sensor[sensor].index].address[0] == ds18x20_chipids[index]) {
|
|
break;
|
|
}
|
|
index--;
|
|
}
|
|
GetTextIndexed(ds18x20_types, sizeof(ds18x20_types), index, kDs18x20Types);
|
|
if (ds18x20_sensors > 1) {
|
|
snprintf_P(ds18x20_types, sizeof(ds18x20_types), PSTR("%s%c%d"), ds18x20_types, IndexSeparator(), sensor +1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void Ds18x20EverySecond(void)
|
|
{
|
|
#ifdef W1_PARASITE_POWER
|
|
|
|
unsigned long now = millis();
|
|
if (now < w1_power_until)
|
|
return;
|
|
#endif
|
|
if (uptime & 1
|
|
#ifdef W1_PARASITE_POWER
|
|
|
|
|| ds18x20_sensors >= 2
|
|
#endif
|
|
) {
|
|
|
|
Ds18x20Convert();
|
|
} else {
|
|
for (uint32_t i = 0; i < ds18x20_sensors; i++) {
|
|
|
|
if (!Ds18x20Read(i)) {
|
|
Ds18x20Name(i);
|
|
AddLogMissed(ds18x20_types, ds18x20_sensor[ds18x20_sensor[i].index].valid);
|
|
#ifdef USE_DS18x20_RECONFIGURE
|
|
if (!ds18x20_sensor[ds18x20_sensor[i].index].valid) {
|
|
memset(&ds18x20_sensor, 0, sizeof(ds18x20_sensor));
|
|
Ds18x20Init();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ds18x20Show(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < ds18x20_sensors; i++) {
|
|
uint8_t index = ds18x20_sensor[i].index;
|
|
|
|
if (ds18x20_sensor[index].valid) {
|
|
char temperature[33];
|
|
dtostrfd(ds18x20_sensor[index].temperature, Settings.flag2.temperature_resolution, temperature);
|
|
|
|
Ds18x20Name(i);
|
|
|
|
if (json) {
|
|
char address[17];
|
|
for (uint32_t j = 0; j < 6; j++) {
|
|
sprintf(address+2*j, "%02X", ds18x20_sensor[index].address[6-j]);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, address, temperature);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
KnxSensor(KNX_TEMPERATURE, ds18x20_sensor[index].temperature);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, ds18x20_types, temperature, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns05(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (pin[GPIO_DSB] < 99) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
Ds18x20Init();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
Ds18x20EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Ds18x20Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Ds18x20Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino"
|
|
#ifdef USE_DHT_OLD
|
|
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_old.ino"
|
|
#define XSNS_06 6
|
|
|
|
#define DHT_MAX_SENSORS 4
|
|
#define DHT_MAX_RETRY 8
|
|
|
|
uint32_t dht_max_cycles;
|
|
uint8_t dht_data[5];
|
|
uint8_t dht_sensors = 0;
|
|
uint8_t dht_pin_out = 0;
|
|
bool dht_active = true;
|
|
bool dht_dual_mode = false;
|
|
|
|
struct DHTSTRUCT {
|
|
uint8_t pin;
|
|
uint8_t type;
|
|
char stype[12];
|
|
uint32_t lastreadtime;
|
|
uint8_t lastresult;
|
|
float t = NAN;
|
|
float h = NAN;
|
|
} Dht[DHT_MAX_SENSORS];
|
|
|
|
void DhtReadPrep(void)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[i].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t DhtExpectPulse(uint8_t sensor, bool level)
|
|
{
|
|
int32_t count = 0;
|
|
|
|
while (digitalRead(Dht[sensor].pin) == level) {
|
|
if (count++ >= (int32_t)dht_max_cycles) {
|
|
return -1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool DhtRead(uint8_t sensor)
|
|
{
|
|
int32_t cycles[80];
|
|
uint8_t error = 0;
|
|
|
|
dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0;
|
|
|
|
|
|
|
|
|
|
if (Dht[sensor].lastresult > DHT_MAX_RETRY) {
|
|
Dht[sensor].lastresult = 0;
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[sensor].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
delay(250);
|
|
}
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, OUTPUT);
|
|
digitalWrite(Dht[sensor].pin, LOW);
|
|
} else {
|
|
digitalWrite(dht_pin_out, LOW);
|
|
}
|
|
|
|
if (GPIO_SI7021 == Dht[sensor].type) {
|
|
delayMicroseconds(500);
|
|
} else {
|
|
delay(20);
|
|
}
|
|
|
|
noInterrupts();
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[sensor].pin, HIGH);
|
|
delayMicroseconds(40);
|
|
pinMode(Dht[sensor].pin, INPUT_PULLUP);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
delayMicroseconds(40);
|
|
}
|
|
delayMicroseconds(10);
|
|
if (-1 == DhtExpectPulse(sensor, LOW)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE));
|
|
error = 1;
|
|
}
|
|
else if (-1 == DhtExpectPulse(sensor, HIGH)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE));
|
|
error = 1;
|
|
}
|
|
else {
|
|
for (uint32_t i = 0; i < 80; i += 2) {
|
|
cycles[i] = DhtExpectPulse(sensor, LOW);
|
|
cycles[i+1] = DhtExpectPulse(sensor, HIGH);
|
|
}
|
|
}
|
|
interrupts();
|
|
|
|
if (error) { return false; }
|
|
|
|
for (uint32_t i = 0; i < 40; ++i) {
|
|
int32_t lowCycles = cycles[2*i];
|
|
int32_t highCycles = cycles[2*i+1];
|
|
if ((-1 == lowCycles) || (-1 == highCycles)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE));
|
|
return false;
|
|
}
|
|
dht_data[i/8] <<= 1;
|
|
if (highCycles > lowCycles) {
|
|
dht_data[i / 8] |= 1;
|
|
}
|
|
}
|
|
|
|
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
|
if (dht_data[4] != checksum) {
|
|
char hex_char[15];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"),
|
|
ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DhtReadTempHum(uint8_t sensor)
|
|
{
|
|
if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) {
|
|
Dht[sensor].t = NAN;
|
|
Dht[sensor].h = NAN;
|
|
}
|
|
if (DhtRead(sensor)) {
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
Dht[sensor].h = dht_data[0];
|
|
Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f);
|
|
break;
|
|
case GPIO_DHT22:
|
|
case GPIO_SI7021:
|
|
Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1;
|
|
Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1;
|
|
if (dht_data[2] & 0x80) {
|
|
Dht[sensor].t *= -1;
|
|
}
|
|
break;
|
|
}
|
|
Dht[sensor].t = ConvertTemp(Dht[sensor].t);
|
|
Dht[sensor].h = ConvertHumidity(Dht[sensor].h);
|
|
Dht[sensor].lastresult = 0;
|
|
} else {
|
|
Dht[sensor].lastresult++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool DhtPinState()
|
|
{
|
|
if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) {
|
|
if (dht_sensors < DHT_MAX_SENSORS) {
|
|
Dht[dht_sensors].pin = XdrvMailbox.payload;
|
|
Dht[dht_sensors].type = XdrvMailbox.index;
|
|
dht_sensors++;
|
|
XdrvMailbox.index = GPIO_DHT11;
|
|
} else {
|
|
XdrvMailbox.index = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DhtInit(void)
|
|
{
|
|
if (dht_sensors) {
|
|
dht_max_cycles = microsecondsToClockCycles(1000);
|
|
|
|
if (pin[GPIO_DHT11_OUT] < 99) {
|
|
dht_pin_out = pin[GPIO_DHT11_OUT];
|
|
dht_dual_mode = true;
|
|
dht_sensors = 1;
|
|
pinMode(dht_pin_out, OUTPUT);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
pinMode(Dht[i].pin, INPUT_PULLUP);
|
|
Dht[i].lastreadtime = 0;
|
|
Dht[i].lastresult = 0;
|
|
GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames);
|
|
if (dht_sensors > 1) {
|
|
snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_SENSORS_FOUND " %d"), dht_sensors);
|
|
} else {
|
|
dht_active = false;
|
|
}
|
|
}
|
|
|
|
void DhtEverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
DhtReadPrep();
|
|
} else {
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
|
|
DhtReadTempHum(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DhtShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
char temperature[33];
|
|
dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
KnxSensor(KNX_TEMPERATURE, Dht[i].t);
|
|
KnxSensor(KNX_HUMIDITY, Dht[i].h);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (dht_active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DhtEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
DhtShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DhtShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
DhtInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = DhtPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
|
|
#ifdef USE_DHT_V2
|
|
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
|
|
#define XSNS_06 6
|
|
|
|
#define DHT_MAX_SENSORS 4
|
|
#define DHT_MAX_RETRY 8
|
|
|
|
uint32_t dht_max_cycles;
|
|
uint8_t dht_data[5];
|
|
uint8_t dht_sensors = 0;
|
|
uint8_t dht_pin_out = 0;
|
|
bool dht_active = true;
|
|
bool dht_dual_mode = false;
|
|
|
|
struct DHTSTRUCT {
|
|
uint8_t pin;
|
|
uint8_t type;
|
|
char stype[12];
|
|
uint32_t lastreadtime;
|
|
uint8_t lastresult;
|
|
float t = NAN;
|
|
float h = NAN;
|
|
} Dht[DHT_MAX_SENSORS];
|
|
|
|
void DhtReadPrep(void)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[i].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t DhtExpectPulse(uint8_t sensor, bool level)
|
|
{
|
|
int32_t count = 0;
|
|
|
|
while (digitalRead(Dht[sensor].pin) == level) {
|
|
if (count++ >= (int32_t)dht_max_cycles) {
|
|
return -1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool DhtRead(uint8_t sensor)
|
|
{
|
|
int32_t cycles[80];
|
|
uint8_t error = 0;
|
|
|
|
dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0;
|
|
|
|
if (Dht[sensor].lastresult > DHT_MAX_RETRY) {
|
|
Dht[sensor].lastresult = 0;
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[sensor].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
delay(250);
|
|
}
|
|
|
|
|
|
noInterrupts();
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, OUTPUT);
|
|
digitalWrite(Dht[sensor].pin, LOW);
|
|
} else {
|
|
digitalWrite(dht_pin_out, LOW);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_SI7021:
|
|
# 114 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
|
|
delayMicroseconds(500);
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[sensor].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
delayMicroseconds(40);
|
|
break;
|
|
|
|
case GPIO_DHT22:
|
|
# 133 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
|
|
delayMicroseconds(1100);
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[sensor].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
delayMicroseconds(30);
|
|
break;
|
|
|
|
case GPIO_DHT11:
|
|
# 151 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v2.ino"
|
|
default:
|
|
|
|
delay(20);
|
|
if (!dht_dual_mode) {
|
|
digitalWrite(Dht[sensor].pin, HIGH);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
delayMicroseconds(30);
|
|
break;
|
|
}
|
|
|
|
|
|
pinMode(Dht[sensor].pin, INPUT_PULLUP);
|
|
|
|
if (-1 == DhtExpectPulse(sensor, LOW)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE));
|
|
error = 1;
|
|
}
|
|
else if (-1 == DhtExpectPulse(sensor, HIGH)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE));
|
|
error = 1;
|
|
}
|
|
else {
|
|
for (uint32_t i = 0; i < 80; i += 2) {
|
|
cycles[i] = DhtExpectPulse(sensor, LOW);
|
|
cycles[i+1] = DhtExpectPulse(sensor, HIGH);
|
|
}
|
|
}
|
|
interrupts();
|
|
if (error) { return false; }
|
|
|
|
|
|
for (uint32_t i = 0; i < 40; ++i) {
|
|
int32_t lowCycles = cycles[2*i];
|
|
int32_t highCycles = cycles[2*i+1];
|
|
if ((-1 == lowCycles) || (-1 == highCycles)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE));
|
|
return false;
|
|
}
|
|
dht_data[i/8] <<= 1;
|
|
if (highCycles > lowCycles) {
|
|
dht_data[i / 8] |= 1;
|
|
}
|
|
}
|
|
|
|
|
|
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
|
if (dht_data[4] != checksum) {
|
|
char hex_char[15];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"),
|
|
ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DhtReadTempHum(uint8_t sensor)
|
|
{
|
|
if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) {
|
|
Dht[sensor].t = NAN;
|
|
Dht[sensor].h = NAN;
|
|
}
|
|
if (DhtRead(sensor)) {
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
Dht[sensor].h = dht_data[0];
|
|
Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f);
|
|
break;
|
|
case GPIO_DHT22:
|
|
case GPIO_SI7021:
|
|
Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1;
|
|
Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1;
|
|
if (dht_data[2] & 0x80) {
|
|
Dht[sensor].t *= -1;
|
|
}
|
|
break;
|
|
}
|
|
Dht[sensor].t = ConvertTemp(Dht[sensor].t);
|
|
Dht[sensor].h = ConvertHumidity(Dht[sensor].h);
|
|
Dht[sensor].lastresult = 0;
|
|
} else {
|
|
Dht[sensor].lastresult++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool DhtPinState()
|
|
{
|
|
if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) {
|
|
if (dht_sensors < DHT_MAX_SENSORS) {
|
|
Dht[dht_sensors].pin = XdrvMailbox.payload;
|
|
Dht[dht_sensors].type = XdrvMailbox.index;
|
|
dht_sensors++;
|
|
XdrvMailbox.index = GPIO_DHT11;
|
|
} else {
|
|
XdrvMailbox.index = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DhtInit(void)
|
|
{
|
|
if (dht_sensors) {
|
|
dht_max_cycles = microsecondsToClockCycles(1000);
|
|
|
|
if (pin[GPIO_DHT11_OUT] < 99) {
|
|
dht_pin_out = pin[GPIO_DHT11_OUT];
|
|
dht_dual_mode = true;
|
|
dht_sensors = 1;
|
|
pinMode(dht_pin_out, OUTPUT);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
pinMode(Dht[i].pin, INPUT_PULLUP);
|
|
Dht[i].lastreadtime = 0;
|
|
Dht[i].lastresult = 0;
|
|
GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames);
|
|
if (dht_sensors > 1) {
|
|
snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v2) " D_SENSORS_FOUND " %d"), dht_sensors);
|
|
} else {
|
|
dht_active = false;
|
|
}
|
|
}
|
|
|
|
void DhtEverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
DhtReadPrep();
|
|
} else {
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
|
|
DhtReadTempHum(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DhtShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
char temperature[33];
|
|
dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
KnxSensor(KNX_TEMPERATURE, Dht[i].t);
|
|
KnxSensor(KNX_HUMIDITY, Dht[i].h);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (dht_active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DhtEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
DhtShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DhtShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
DhtInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = DhtPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino"
|
|
#ifdef USE_DHT_V3
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v3.ino"
|
|
#define XSNS_06 6
|
|
|
|
#define DHT_MAX_SENSORS 4
|
|
#define DHT_MAX_RETRY 8
|
|
|
|
uint8_t dht_data[5];
|
|
uint8_t dht_sensors = 0;
|
|
uint8_t dht_pin_out = 0;
|
|
bool dht_active = true;
|
|
bool dht_dual_mode = false;
|
|
|
|
struct DHTSTRUCT {
|
|
uint8_t pin;
|
|
uint8_t type;
|
|
char stype[12];
|
|
uint32_t lastreadtime;
|
|
uint8_t lastresult;
|
|
float t = NAN;
|
|
float h = NAN;
|
|
} Dht[DHT_MAX_SENSORS];
|
|
|
|
bool DhtExpectPulse(uint8_t sensor, int level)
|
|
{
|
|
unsigned long timeout = micros() + 100;
|
|
while (digitalRead(Dht[sensor].pin) != level) {
|
|
if (micros() > timeout) { return false; }
|
|
delayMicroseconds(1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int DhtReadDat(uint8_t sensor)
|
|
{
|
|
uint8_t result = 0;
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
if (!DhtExpectPulse(sensor, HIGH)) { return -1; }
|
|
|
|
delayMicroseconds(35);
|
|
if (digitalRead(Dht[sensor].pin)) {
|
|
result |= (1 << (7 - i));
|
|
}
|
|
|
|
if (!DhtExpectPulse(sensor, LOW)) { return -1; }
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DhtRead(uint8_t sensor)
|
|
{
|
|
dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0;
|
|
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, OUTPUT);
|
|
digitalWrite(Dht[sensor].pin, LOW);
|
|
} else {
|
|
digitalWrite(dht_pin_out, LOW);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
delay(19);
|
|
break;
|
|
case GPIO_DHT22:
|
|
delay(2);
|
|
break;
|
|
case GPIO_SI7021:
|
|
delayMicroseconds(500);
|
|
break;
|
|
}
|
|
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, INPUT_PULLUP);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
case GPIO_DHT22:
|
|
delayMicroseconds(50);
|
|
break;
|
|
case GPIO_SI7021:
|
|
|
|
delayMicroseconds(20);
|
|
break;
|
|
}
|
|
|
|
noInterrupts();
|
|
if (!DhtExpectPulse(sensor, LOW)) {
|
|
interrupts();
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE));
|
|
return false;
|
|
}
|
|
if (!DhtExpectPulse(sensor, HIGH)) {
|
|
interrupts();
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_HIGH " " D_PULSE));
|
|
return false;
|
|
}
|
|
if (!DhtExpectPulse(sensor, LOW)) {
|
|
interrupts();
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_START_SIGNAL_LOW " " D_PULSE));
|
|
return false;
|
|
}
|
|
|
|
int data = 0;
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
data = DhtReadDat(sensor);
|
|
if (-1 == data) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " " D_PULSE));
|
|
break;
|
|
}
|
|
dht_data[i] = data;
|
|
}
|
|
interrupts();
|
|
if (-1 == data) { return false; }
|
|
|
|
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
|
if (dht_data[4] != checksum) {
|
|
char hex_char[15];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"),
|
|
ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DhtReadTempHum(uint8_t sensor)
|
|
{
|
|
if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) {
|
|
Dht[sensor].t = NAN;
|
|
Dht[sensor].h = NAN;
|
|
}
|
|
if (DhtRead(sensor)) {
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
Dht[sensor].h = dht_data[0];
|
|
Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f);
|
|
break;
|
|
case GPIO_DHT22:
|
|
case GPIO_SI7021:
|
|
Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1;
|
|
Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1;
|
|
if (dht_data[2] & 0x80) {
|
|
Dht[sensor].t *= -1;
|
|
}
|
|
break;
|
|
}
|
|
Dht[sensor].t = ConvertTemp(Dht[sensor].t);
|
|
Dht[sensor].h = ConvertHumidity(Dht[sensor].h);
|
|
Dht[sensor].lastresult = 0;
|
|
} else {
|
|
Dht[sensor].lastresult++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool DhtPinState()
|
|
{
|
|
if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) {
|
|
if (dht_sensors < DHT_MAX_SENSORS) {
|
|
Dht[dht_sensors].pin = XdrvMailbox.payload;
|
|
Dht[dht_sensors].type = XdrvMailbox.index;
|
|
dht_sensors++;
|
|
XdrvMailbox.index = GPIO_DHT11;
|
|
} else {
|
|
XdrvMailbox.index = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DhtInit(void)
|
|
{
|
|
if (dht_sensors) {
|
|
if (pin[GPIO_DHT11_OUT] < 99) {
|
|
dht_pin_out = pin[GPIO_DHT11_OUT];
|
|
dht_dual_mode = true;
|
|
dht_sensors = 1;
|
|
pinMode(dht_pin_out, OUTPUT);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
pinMode(Dht[i].pin, INPUT_PULLUP);
|
|
Dht[i].lastreadtime = 0;
|
|
Dht[i].lastresult = 0;
|
|
GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames);
|
|
if (dht_sensors > 1) {
|
|
snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v3) " D_SENSORS_FOUND " %d"), dht_sensors);
|
|
} else {
|
|
dht_active = false;
|
|
}
|
|
}
|
|
|
|
void DhtEverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
|
|
} else {
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
|
|
DhtReadTempHum(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DhtShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
char temperature[33];
|
|
dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
KnxSensor(KNX_TEMPERATURE, Dht[i].t);
|
|
KnxSensor(KNX_HUMIDITY, Dht[i].h);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (dht_active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DhtEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
DhtShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DhtShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
DhtInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = DhtPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino"
|
|
#ifdef USE_DHT_V4
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v4.ino"
|
|
#define XSNS_06 6
|
|
|
|
#define DHT_MAX_SENSORS 4
|
|
#define DHT_MAX_RETRY 8
|
|
|
|
uint8_t dht_data[5];
|
|
uint8_t dht_sensors = 0;
|
|
uint8_t dht_pin_out = 0;
|
|
bool dht_active = true;
|
|
bool dht_dual_mode = false;
|
|
|
|
struct DHTSTRUCT {
|
|
uint8_t pin;
|
|
uint8_t type;
|
|
char stype[12];
|
|
uint32_t lastreadtime;
|
|
uint8_t lastresult;
|
|
float t = NAN;
|
|
float h = NAN;
|
|
} Dht[DHT_MAX_SENSORS];
|
|
|
|
bool DhtExpectPulse(uint32_t sensor, uint32_t level)
|
|
{
|
|
unsigned long timeout = micros() + 100;
|
|
while (digitalRead(Dht[sensor].pin) != level) {
|
|
if (micros() > timeout) { return false; }
|
|
delayMicroseconds(1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DhtRead(uint32_t sensor)
|
|
{
|
|
dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0;
|
|
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, OUTPUT);
|
|
digitalWrite(Dht[sensor].pin, LOW);
|
|
} else {
|
|
digitalWrite(dht_pin_out, LOW);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
delay(19);
|
|
break;
|
|
case GPIO_DHT22:
|
|
delay(2);
|
|
break;
|
|
case GPIO_SI7021:
|
|
delayMicroseconds(500);
|
|
break;
|
|
}
|
|
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, INPUT_PULLUP);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
case GPIO_DHT22:
|
|
delayMicroseconds(50);
|
|
break;
|
|
case GPIO_SI7021:
|
|
delayMicroseconds(20);
|
|
break;
|
|
}
|
|
|
|
uint32_t level = 9;
|
|
noInterrupts();
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
level = i &1;
|
|
if (!DhtExpectPulse(sensor, level)) { break; }
|
|
level = 9;
|
|
}
|
|
if (9 == level) {
|
|
int data = 0;
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
data = 0;
|
|
for (uint32_t j = 0; j < 8; j++) {
|
|
level = 1;
|
|
if (!DhtExpectPulse(sensor, level)) { break; }
|
|
|
|
delayMicroseconds(35);
|
|
if (digitalRead(Dht[sensor].pin)) {
|
|
data |= (1 << (7 - j));
|
|
}
|
|
|
|
level = 0;
|
|
if (!DhtExpectPulse(sensor, level)) { break; }
|
|
level = 9;
|
|
}
|
|
if (level < 2) { break; }
|
|
|
|
dht_data[i] = data;
|
|
}
|
|
}
|
|
interrupts();
|
|
if (level < 2) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " %s " D_PULSE), (0 == level) ? D_START_SIGNAL_LOW : D_START_SIGNAL_HIGH);
|
|
return false;
|
|
}
|
|
|
|
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
|
if (dht_data[4] != checksum) {
|
|
char hex_char[15];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"),
|
|
ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DhtReadTempHum(uint32_t sensor)
|
|
{
|
|
if ((NAN == Dht[sensor].h) || (Dht[sensor].lastresult > DHT_MAX_RETRY)) {
|
|
Dht[sensor].t = NAN;
|
|
Dht[sensor].h = NAN;
|
|
}
|
|
if (DhtRead(sensor)) {
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
Dht[sensor].h = dht_data[0];
|
|
Dht[sensor].t = dht_data[2] + ((float)dht_data[3] * 0.1f);
|
|
break;
|
|
case GPIO_DHT22:
|
|
case GPIO_SI7021:
|
|
Dht[sensor].h = ((dht_data[0] << 8) | dht_data[1]) * 0.1;
|
|
Dht[sensor].t = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1;
|
|
if (dht_data[2] & 0x80) {
|
|
Dht[sensor].t *= -1;
|
|
}
|
|
break;
|
|
}
|
|
Dht[sensor].t = ConvertTemp(Dht[sensor].t);
|
|
if (Dht[sensor].h > 100) { Dht[sensor].h = 100.0; }
|
|
if (Dht[sensor].h < 0) { Dht[sensor].h = 0.0; }
|
|
Dht[sensor].h = ConvertHumidity(Dht[sensor].h);
|
|
Dht[sensor].lastresult = 0;
|
|
} else {
|
|
Dht[sensor].lastresult++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool DhtPinState()
|
|
{
|
|
if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) {
|
|
if (dht_sensors < DHT_MAX_SENSORS) {
|
|
Dht[dht_sensors].pin = XdrvMailbox.payload;
|
|
Dht[dht_sensors].type = XdrvMailbox.index;
|
|
dht_sensors++;
|
|
XdrvMailbox.index = GPIO_DHT11;
|
|
} else {
|
|
XdrvMailbox.index = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DhtInit(void)
|
|
{
|
|
if (dht_sensors) {
|
|
if (pin[GPIO_DHT11_OUT] < 99) {
|
|
dht_pin_out = pin[GPIO_DHT11_OUT];
|
|
dht_dual_mode = true;
|
|
dht_sensors = 1;
|
|
pinMode(dht_pin_out, OUTPUT);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
pinMode(Dht[i].pin, INPUT_PULLUP);
|
|
Dht[i].lastreadtime = 0;
|
|
Dht[i].lastresult = 0;
|
|
GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames);
|
|
if (dht_sensors > 1) {
|
|
snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v4) " D_SENSORS_FOUND " %d"), dht_sensors);
|
|
} else {
|
|
dht_active = false;
|
|
}
|
|
}
|
|
|
|
void DhtEverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
} else {
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
|
|
DhtReadTempHum(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DhtShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
char temperature[33];
|
|
dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
KnxSensor(KNX_TEMPERATURE, Dht[i].t);
|
|
KnxSensor(KNX_HUMIDITY, Dht[i].h);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (dht_active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DhtEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
DhtShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DhtShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
DhtInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = DhtPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino"
|
|
#ifdef USE_DHT
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_06_dht_v5.ino"
|
|
#define XSNS_06 6
|
|
|
|
#define DHT_MAX_SENSORS 4
|
|
#define DHT_MAX_RETRY 8
|
|
|
|
uint8_t dht_data[5];
|
|
uint8_t dht_sensors = 0;
|
|
uint8_t dht_pin_out = 0;
|
|
bool dht_active = true;
|
|
bool dht_dual_mode = false;
|
|
|
|
struct DHTSTRUCT {
|
|
uint8_t pin;
|
|
uint8_t type;
|
|
uint8_t lastresult;
|
|
char stype[12];
|
|
float t = NAN;
|
|
float h = NAN;
|
|
} Dht[DHT_MAX_SENSORS];
|
|
|
|
bool DhtWaitState(uint32_t sensor, uint32_t level)
|
|
{
|
|
unsigned long timeout = micros() + 100;
|
|
while (digitalRead(Dht[sensor].pin) != level) {
|
|
if (TimeReachedUsec(timeout)) {
|
|
PrepLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_TIMEOUT_WAITING_FOR " %s " D_PULSE),
|
|
(level) ? D_START_SIGNAL_HIGH : D_START_SIGNAL_LOW);
|
|
return false;
|
|
}
|
|
delayMicroseconds(1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DhtRead(uint32_t sensor)
|
|
{
|
|
dht_data[0] = dht_data[1] = dht_data[2] = dht_data[3] = dht_data[4] = 0;
|
|
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, OUTPUT);
|
|
digitalWrite(Dht[sensor].pin, LOW);
|
|
} else {
|
|
digitalWrite(dht_pin_out, LOW);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
delay(19);
|
|
break;
|
|
case GPIO_DHT22:
|
|
delay(2);
|
|
break;
|
|
case GPIO_SI7021:
|
|
delayMicroseconds(500);
|
|
break;
|
|
}
|
|
|
|
if (!dht_dual_mode) {
|
|
pinMode(Dht[sensor].pin, INPUT_PULLUP);
|
|
} else {
|
|
digitalWrite(dht_pin_out, HIGH);
|
|
}
|
|
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
case GPIO_DHT22:
|
|
delayMicroseconds(50);
|
|
break;
|
|
case GPIO_SI7021:
|
|
delayMicroseconds(20);
|
|
break;
|
|
}
|
|
|
|
bool error = false;
|
|
noInterrupts();
|
|
if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) {
|
|
for (uint32_t i = 0; i < 5; i++) {
|
|
int data = 0;
|
|
for (uint32_t j = 0; j < 8; j++) {
|
|
if (!DhtWaitState(sensor, 1)) {
|
|
error = true;
|
|
break;
|
|
}
|
|
delayMicroseconds(35);
|
|
if (digitalRead(Dht[sensor].pin)) {
|
|
data |= (1 << (7 - j));
|
|
}
|
|
if (!DhtWaitState(sensor, 0)) {
|
|
error = true;
|
|
break;
|
|
}
|
|
}
|
|
if (error) { break; }
|
|
dht_data[i] = data;
|
|
}
|
|
} else {
|
|
error = true;
|
|
}
|
|
interrupts();
|
|
if (error) { return false; }
|
|
|
|
uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF;
|
|
if (dht_data[4] != checksum) {
|
|
char hex_char[15];
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT D_CHECKSUM_FAILURE " %s =? %02X"),
|
|
ToHex_P(dht_data, 5, hex_char, sizeof(hex_char), ' '), checksum);
|
|
return false;
|
|
}
|
|
|
|
float temperature = NAN;
|
|
float humidity = NAN;
|
|
switch (Dht[sensor].type) {
|
|
case GPIO_DHT11:
|
|
humidity = dht_data[0];
|
|
temperature = dht_data[2] + ((float)dht_data[3] * 0.1f);
|
|
break;
|
|
case GPIO_DHT22:
|
|
case GPIO_SI7021:
|
|
humidity = ((dht_data[0] << 8) | dht_data[1]) * 0.1;
|
|
temperature = (((dht_data[2] & 0x7F) << 8 ) | dht_data[3]) * 0.1;
|
|
if (dht_data[2] & 0x80) {
|
|
temperature *= -1;
|
|
}
|
|
break;
|
|
}
|
|
if (isnan(temperature) || isnan(humidity)) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "Invalid NAN reading"));
|
|
return false;
|
|
}
|
|
|
|
if (humidity > 100) { humidity = 100.0; }
|
|
if (humidity < 0) { humidity = 0.1; }
|
|
Dht[sensor].h = ConvertHumidity(humidity);
|
|
Dht[sensor].t = ConvertTemp(temperature);
|
|
Dht[sensor].lastresult = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
bool DhtPinState()
|
|
{
|
|
if ((XdrvMailbox.index >= GPIO_DHT11) && (XdrvMailbox.index <= GPIO_SI7021)) {
|
|
if (dht_sensors < DHT_MAX_SENSORS) {
|
|
Dht[dht_sensors].pin = XdrvMailbox.payload;
|
|
Dht[dht_sensors].type = XdrvMailbox.index;
|
|
dht_sensors++;
|
|
XdrvMailbox.index = GPIO_DHT11;
|
|
} else {
|
|
XdrvMailbox.index = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DhtInit(void)
|
|
{
|
|
if (dht_sensors) {
|
|
if (pin[GPIO_DHT11_OUT] < 99) {
|
|
dht_pin_out = pin[GPIO_DHT11_OUT];
|
|
dht_dual_mode = true;
|
|
dht_sensors = 1;
|
|
pinMode(dht_pin_out, OUTPUT);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
pinMode(Dht[i].pin, INPUT_PULLUP);
|
|
Dht[i].lastresult = DHT_MAX_RETRY;
|
|
GetTextIndexed(Dht[i].stype, sizeof(Dht[i].stype), Dht[i].type, kSensorNames);
|
|
if (dht_sensors > 1) {
|
|
snprintf_P(Dht[i].stype, sizeof(Dht[i].stype), PSTR("%s%c%02d"), Dht[i].stype, IndexSeparator(), Dht[i].pin);
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DHT "(v5) " D_SENSORS_FOUND " %d"), dht_sensors);
|
|
} else {
|
|
dht_active = false;
|
|
}
|
|
}
|
|
|
|
void DhtEverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
for (uint32_t sensor = 0; sensor < dht_sensors; sensor++) {
|
|
|
|
if (!DhtRead(sensor)) {
|
|
Dht[sensor].lastresult++;
|
|
if (Dht[sensor].lastresult > DHT_MAX_RETRY) {
|
|
Dht[sensor].t = NAN;
|
|
Dht[sensor].h = NAN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DhtShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < dht_sensors; i++) {
|
|
char temperature[33];
|
|
dtostrfd(Dht[i].t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Dht[i].h, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Dht[i].stype, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
KnxSensor(KNX_TEMPERATURE, Dht[i].t);
|
|
KnxSensor(KNX_HUMIDITY, Dht[i].h);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Dht[i].stype, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Dht[i].stype, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns06(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (dht_active) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DhtEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
DhtShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DhtShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
DhtInit();
|
|
break;
|
|
case FUNC_PIN_STATE:
|
|
result = DhtPinState();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_SHT
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_07_sht1x.ino"
|
|
#define XSNS_07 7
|
|
#define XI2C_08 8
|
|
|
|
enum {
|
|
SHT1X_CMD_MEASURE_TEMP = B00000011,
|
|
SHT1X_CMD_MEASURE_RH = B00000101,
|
|
SHT1X_CMD_SOFT_RESET = B00011110
|
|
};
|
|
|
|
uint8_t sht_sda_pin;
|
|
uint8_t sht_scl_pin;
|
|
uint8_t sht_type = 0;
|
|
char sht_types[] = "SHT1X";
|
|
uint8_t sht_valid = 0;
|
|
float sht_temperature = 0;
|
|
float sht_humidity = 0;
|
|
|
|
bool ShtReset(void)
|
|
{
|
|
pinMode(sht_sda_pin, INPUT_PULLUP);
|
|
pinMode(sht_scl_pin, OUTPUT);
|
|
delay(11);
|
|
for (uint32_t i = 0; i < 9; i++) {
|
|
digitalWrite(sht_scl_pin, HIGH);
|
|
digitalWrite(sht_scl_pin, LOW);
|
|
}
|
|
bool success = ShtSendCommand(SHT1X_CMD_SOFT_RESET);
|
|
delay(11);
|
|
return success;
|
|
}
|
|
|
|
bool ShtSendCommand(const uint8_t cmd)
|
|
{
|
|
pinMode(sht_sda_pin, OUTPUT);
|
|
|
|
digitalWrite(sht_sda_pin, HIGH);
|
|
digitalWrite(sht_scl_pin, HIGH);
|
|
digitalWrite(sht_sda_pin, LOW);
|
|
digitalWrite(sht_scl_pin, LOW);
|
|
digitalWrite(sht_scl_pin, HIGH);
|
|
digitalWrite(sht_sda_pin, HIGH);
|
|
digitalWrite(sht_scl_pin, LOW);
|
|
|
|
shiftOut(sht_sda_pin, sht_scl_pin, MSBFIRST, cmd);
|
|
|
|
bool ackerror = false;
|
|
digitalWrite(sht_scl_pin, HIGH);
|
|
pinMode(sht_sda_pin, INPUT_PULLUP);
|
|
if (digitalRead(sht_sda_pin) != LOW) {
|
|
ackerror = true;
|
|
}
|
|
digitalWrite(sht_scl_pin, LOW);
|
|
delayMicroseconds(1);
|
|
if (digitalRead(sht_sda_pin) != HIGH) {
|
|
ackerror = true;
|
|
}
|
|
if (ackerror) {
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_DID_NOT_ACK_COMMAND));
|
|
}
|
|
return (!ackerror);
|
|
}
|
|
|
|
bool ShtAwaitResult(void)
|
|
{
|
|
|
|
for (uint32_t i = 0; i < 16; i++) {
|
|
if (LOW == digitalRead(sht_sda_pin)) {
|
|
return true;
|
|
}
|
|
delay(20);
|
|
}
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_SHT1 D_SENSOR_BUSY));
|
|
|
|
return false;
|
|
}
|
|
|
|
int ShtReadData(void)
|
|
{
|
|
int val = 0;
|
|
|
|
|
|
val = shiftIn(sht_sda_pin, sht_scl_pin, 8);
|
|
val <<= 8;
|
|
|
|
pinMode(sht_sda_pin, OUTPUT);
|
|
digitalWrite(sht_sda_pin, LOW);
|
|
digitalWrite(sht_scl_pin, HIGH);
|
|
digitalWrite(sht_scl_pin, LOW);
|
|
pinMode(sht_sda_pin, INPUT_PULLUP);
|
|
|
|
val |= shiftIn(sht_sda_pin, sht_scl_pin, 8);
|
|
|
|
digitalWrite(sht_scl_pin, HIGH);
|
|
digitalWrite(sht_scl_pin, LOW);
|
|
return val;
|
|
}
|
|
|
|
bool ShtRead(void)
|
|
{
|
|
if (sht_valid) { sht_valid--; }
|
|
if (!ShtReset()) { return false; }
|
|
if (!ShtSendCommand(SHT1X_CMD_MEASURE_TEMP)) { return false; }
|
|
if (!ShtAwaitResult()) { return false; }
|
|
float tempRaw = ShtReadData();
|
|
if (!ShtSendCommand(SHT1X_CMD_MEASURE_RH)) { return false; }
|
|
if (!ShtAwaitResult()) { return false; }
|
|
float humRaw = ShtReadData();
|
|
|
|
|
|
const float d1 = -39.7;
|
|
const float d2 = 0.01;
|
|
sht_temperature = d1 + (tempRaw * d2);
|
|
const float c1 = -2.0468;
|
|
const float c2 = 0.0367;
|
|
const float c3 = -1.5955E-6;
|
|
const float t1 = 0.01;
|
|
const float t2 = 0.00008;
|
|
float rhLinear = c1 + c2 * humRaw + c3 * humRaw * humRaw;
|
|
sht_humidity = (sht_temperature - 25) * (t1 + t2 * humRaw) + rhLinear;
|
|
sht_temperature = ConvertTemp(sht_temperature);
|
|
ConvertHumidity(sht_humidity);
|
|
|
|
sht_valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void ShtDetect(void)
|
|
{
|
|
sht_sda_pin = pin[GPIO_I2C_SDA];
|
|
sht_scl_pin = pin[GPIO_I2C_SCL];
|
|
if (ShtRead()) {
|
|
sht_type = 1;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SHT1X_FOUND));
|
|
} else {
|
|
Wire.begin(sht_sda_pin, sht_scl_pin);
|
|
sht_type = 0;
|
|
}
|
|
}
|
|
|
|
void ShtEverySecond(void)
|
|
{
|
|
if (!(uptime %4)) {
|
|
|
|
if (!ShtRead()) {
|
|
AddLogMissed(sht_types, sht_valid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShtShow(bool json)
|
|
{
|
|
if (sht_valid) {
|
|
char temperature[33];
|
|
dtostrfd(sht_temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(sht_humidity, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, sht_types, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, sht_temperature);
|
|
KnxSensor(KNX_HUMIDITY, sht_humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, sht_types, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, sht_types, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns07(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_08)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
ShtDetect();
|
|
}
|
|
else if (sht_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
ShtEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
ShtShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
ShtShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_HTU
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_08_htu21.ino"
|
|
#define XSNS_08 8
|
|
#define XI2C_09 9
|
|
|
|
#define HTU21_ADDR 0x40
|
|
|
|
#define SI7013_CHIPID 0x0D
|
|
#define SI7020_CHIPID 0x14
|
|
#define SI7021_CHIPID 0x15
|
|
#define HTU21_CHIPID 0x32
|
|
|
|
#define HTU21_READTEMP 0xE3
|
|
#define HTU21_READHUM 0xE5
|
|
#define HTU21_WRITEREG 0xE6
|
|
#define HTU21_READREG 0xE7
|
|
#define HTU21_RESET 0xFE
|
|
#define HTU21_HEATER_WRITE 0x51
|
|
#define HTU21_HEATER_READ 0x11
|
|
#define HTU21_SERIAL2_READ1 0xFC
|
|
#define HTU21_SERIAL2_READ2 0xC9
|
|
|
|
#define HTU21_HEATER_ON 0x04
|
|
#define HTU21_HEATER_OFF 0xFB
|
|
|
|
#define HTU21_RES_RH12_T14 0x00
|
|
#define HTU21_RES_RH8_T12 0x01
|
|
#define HTU21_RES_RH10_T13 0x80
|
|
#define HTU21_RES_RH11_T11 0x81
|
|
|
|
#define HTU21_CRC8_POLYNOM 0x13100
|
|
|
|
const char kHtuTypes[] PROGMEM = "HTU21|SI7013|SI7020|SI7021|T/RH?";
|
|
|
|
uint8_t htu_address;
|
|
uint8_t htu_type = 0;
|
|
uint8_t htu_delay_temp;
|
|
uint8_t htu_delay_humidity = 50;
|
|
uint8_t htu_valid = 0;
|
|
float htu_temperature = 0;
|
|
float htu_humidity = 0;
|
|
char htu_types[7];
|
|
|
|
uint8_t HtuCheckCrc8(uint16_t data)
|
|
{
|
|
for (uint32_t bit = 0; bit < 16; bit++) {
|
|
if (data & 0x8000) {
|
|
data = (data << 1) ^ HTU21_CRC8_POLYNOM;
|
|
} else {
|
|
data <<= 1;
|
|
}
|
|
}
|
|
return data >>= 8;
|
|
}
|
|
|
|
uint8_t HtuReadDeviceId(void)
|
|
{
|
|
uint16_t deviceID = 0;
|
|
uint8_t checksum = 0;
|
|
|
|
Wire.beginTransmission(HTU21_ADDR);
|
|
Wire.write(HTU21_SERIAL2_READ1);
|
|
Wire.write(HTU21_SERIAL2_READ2);
|
|
Wire.endTransmission();
|
|
|
|
Wire.requestFrom(HTU21_ADDR, 3);
|
|
deviceID = Wire.read() << 8;
|
|
deviceID |= Wire.read();
|
|
checksum = Wire.read();
|
|
if (HtuCheckCrc8(deviceID) == checksum) {
|
|
deviceID = deviceID >> 8;
|
|
} else {
|
|
deviceID = 0;
|
|
}
|
|
return (uint8_t)deviceID;
|
|
}
|
|
|
|
void HtuSetResolution(uint8_t resolution)
|
|
{
|
|
uint8_t current = I2cRead8(HTU21_ADDR, HTU21_READREG);
|
|
current &= 0x7E;
|
|
current |= resolution;
|
|
I2cWrite8(HTU21_ADDR, HTU21_WRITEREG, current);
|
|
}
|
|
|
|
void HtuReset(void)
|
|
{
|
|
Wire.beginTransmission(HTU21_ADDR);
|
|
Wire.write(HTU21_RESET);
|
|
Wire.endTransmission();
|
|
delay(15);
|
|
}
|
|
|
|
void HtuHeater(uint8_t heater)
|
|
{
|
|
uint8_t current = I2cRead8(HTU21_ADDR, HTU21_READREG);
|
|
|
|
switch(heater)
|
|
{
|
|
case HTU21_HEATER_ON : current |= heater;
|
|
break;
|
|
case HTU21_HEATER_OFF : current &= heater;
|
|
break;
|
|
default : current &= heater;
|
|
break;
|
|
}
|
|
I2cWrite8(HTU21_ADDR, HTU21_WRITEREG, current);
|
|
}
|
|
|
|
void HtuInit(void)
|
|
{
|
|
HtuReset();
|
|
HtuHeater(HTU21_HEATER_OFF);
|
|
HtuSetResolution(HTU21_RES_RH12_T14);
|
|
}
|
|
|
|
bool HtuRead(void)
|
|
{
|
|
uint8_t checksum = 0;
|
|
uint16_t sensorval = 0;
|
|
|
|
if (htu_valid) { htu_valid--; }
|
|
|
|
Wire.beginTransmission(HTU21_ADDR);
|
|
Wire.write(HTU21_READTEMP);
|
|
if (Wire.endTransmission() != 0) { return false; }
|
|
delay(htu_delay_temp);
|
|
|
|
Wire.requestFrom(HTU21_ADDR, 3);
|
|
if (3 == Wire.available()) {
|
|
sensorval = Wire.read() << 8;
|
|
sensorval |= Wire.read();
|
|
checksum = Wire.read();
|
|
}
|
|
if (HtuCheckCrc8(sensorval) != checksum) { return false; }
|
|
|
|
htu_temperature = ConvertTemp(0.002681 * (float)sensorval - 46.85);
|
|
|
|
Wire.beginTransmission(HTU21_ADDR);
|
|
Wire.write(HTU21_READHUM);
|
|
if (Wire.endTransmission() != 0) { return false; }
|
|
delay(htu_delay_humidity);
|
|
|
|
Wire.requestFrom(HTU21_ADDR, 3);
|
|
if (3 <= Wire.available()) {
|
|
sensorval = Wire.read() << 8;
|
|
sensorval |= Wire.read();
|
|
checksum = Wire.read();
|
|
}
|
|
if (HtuCheckCrc8(sensorval) != checksum) { return false; }
|
|
|
|
sensorval ^= 0x02;
|
|
htu_humidity = 0.001907 * (float)sensorval - 6;
|
|
if (htu_humidity > 100) { htu_humidity = 100.0; }
|
|
if (htu_humidity < 0) { htu_humidity = 0.01; }
|
|
|
|
if ((0.00 == htu_humidity) && (0.00 == htu_temperature)) {
|
|
htu_humidity = 0.0;
|
|
}
|
|
if ((htu_temperature > 0.00) && (htu_temperature < 80.00)) {
|
|
htu_humidity = (-0.15) * (25 - htu_temperature) + htu_humidity;
|
|
}
|
|
ConvertHumidity(htu_humidity);
|
|
|
|
htu_valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void HtuDetect(void)
|
|
{
|
|
htu_address = HTU21_ADDR;
|
|
if (I2cActive(htu_address)) { return; }
|
|
|
|
htu_type = HtuReadDeviceId();
|
|
if (htu_type) {
|
|
uint8_t index = 0;
|
|
HtuInit();
|
|
switch (htu_type) {
|
|
case HTU21_CHIPID:
|
|
htu_delay_temp = 50;
|
|
htu_delay_humidity = 16;
|
|
break;
|
|
case SI7021_CHIPID:
|
|
index++;
|
|
case SI7020_CHIPID:
|
|
index++;
|
|
case SI7013_CHIPID:
|
|
index++;
|
|
htu_delay_temp = 12;
|
|
htu_delay_humidity = 23;
|
|
break;
|
|
default:
|
|
index = 4;
|
|
htu_delay_temp = 50;
|
|
htu_delay_humidity = 23;
|
|
}
|
|
GetTextIndexed(htu_types, sizeof(htu_types), index, kHtuTypes);
|
|
I2cSetActiveFound(htu_address, htu_types);
|
|
}
|
|
}
|
|
|
|
void HtuEverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
if (!HtuRead()) {
|
|
AddLogMissed(htu_types, htu_valid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HtuShow(bool json)
|
|
{
|
|
if (htu_valid) {
|
|
char temperature[33];
|
|
dtostrfd(htu_temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(htu_humidity, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, htu_types, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, htu_temperature);
|
|
KnxSensor(KNX_HUMIDITY, htu_humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, htu_types, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, htu_types, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns08(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_09)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
HtuDetect();
|
|
}
|
|
else if (htu_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
HtuEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
HtuShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
HtuShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_BMP
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_09_bmp.ino"
|
|
#define XSNS_09 9
|
|
#define XI2C_10 10
|
|
|
|
#define BMP_ADDR1 0x76
|
|
#define BMP_ADDR2 0x77
|
|
|
|
#define BMP180_CHIPID 0x55
|
|
#define BMP280_CHIPID 0x58
|
|
#define BME280_CHIPID 0x60
|
|
#define BME680_CHIPID 0x61
|
|
|
|
#define BMP_REGISTER_CHIPID 0xD0
|
|
|
|
#define BMP_REGISTER_RESET 0xE0
|
|
|
|
#define BMP_CMND_RESET 0xB6
|
|
|
|
#define BMP_MAX_SENSORS 2
|
|
|
|
const char kBmpTypes[] PROGMEM = "BMP180|BMP280|BME280|BME680";
|
|
|
|
typedef struct {
|
|
uint8_t bmp_address;
|
|
char bmp_name[7];
|
|
uint8_t bmp_type;
|
|
uint8_t bmp_model;
|
|
#ifdef USE_BME680
|
|
uint8_t bme680_state;
|
|
float bmp_gas_resistance;
|
|
#endif
|
|
float bmp_temperature;
|
|
float bmp_pressure;
|
|
float bmp_humidity;
|
|
} bmp_sensors_t;
|
|
|
|
uint8_t bmp_addresses[] = { BMP_ADDR1, BMP_ADDR2 };
|
|
uint8_t bmp_count = 0;
|
|
uint8_t bmp_once = 1;
|
|
|
|
bmp_sensors_t *bmp_sensors = nullptr;
|
|
|
|
|
|
|
|
|
|
|
|
#define BMP180_REG_CONTROL 0xF4
|
|
#define BMP180_REG_RESULT 0xF6
|
|
#define BMP180_TEMPERATURE 0x2E
|
|
#define BMP180_PRESSURE3 0xF4
|
|
|
|
#define BMP180_AC1 0xAA
|
|
#define BMP180_AC2 0xAC
|
|
#define BMP180_AC3 0xAE
|
|
#define BMP180_AC4 0xB0
|
|
#define BMP180_AC5 0xB2
|
|
#define BMP180_AC6 0xB4
|
|
#define BMP180_VB1 0xB6
|
|
#define BMP180_VB2 0xB8
|
|
#define BMP180_MB 0xBA
|
|
#define BMP180_MC 0xBC
|
|
#define BMP180_MD 0xBE
|
|
|
|
#define BMP180_OSS 3
|
|
|
|
typedef struct {
|
|
int16_t cal_ac1;
|
|
int16_t cal_ac2;
|
|
int16_t cal_ac3;
|
|
int16_t cal_b1;
|
|
int16_t cal_b2;
|
|
int16_t cal_mc;
|
|
int16_t cal_md;
|
|
uint16_t cal_ac4;
|
|
uint16_t cal_ac5;
|
|
uint16_t cal_ac6;
|
|
} bmp180_cal_data_t;
|
|
|
|
bmp180_cal_data_t *bmp180_cal_data = nullptr;
|
|
|
|
bool Bmp180Calibration(uint8_t bmp_idx)
|
|
{
|
|
if (!bmp180_cal_data) {
|
|
bmp180_cal_data = (bmp180_cal_data_t*)malloc(BMP_MAX_SENSORS * sizeof(bmp180_cal_data_t));
|
|
}
|
|
if (!bmp180_cal_data) { return false; }
|
|
|
|
bmp180_cal_data[bmp_idx].cal_ac1 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC1);
|
|
bmp180_cal_data[bmp_idx].cal_ac2 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC2);
|
|
bmp180_cal_data[bmp_idx].cal_ac3 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC3);
|
|
bmp180_cal_data[bmp_idx].cal_ac4 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC4);
|
|
bmp180_cal_data[bmp_idx].cal_ac5 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC5);
|
|
bmp180_cal_data[bmp_idx].cal_ac6 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_AC6);
|
|
bmp180_cal_data[bmp_idx].cal_b1 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_VB1);
|
|
bmp180_cal_data[bmp_idx].cal_b2 = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_VB2);
|
|
bmp180_cal_data[bmp_idx].cal_mc = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_MC);
|
|
bmp180_cal_data[bmp_idx].cal_md = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_MD);
|
|
|
|
|
|
if (!bmp180_cal_data[bmp_idx].cal_ac1 |
|
|
!bmp180_cal_data[bmp_idx].cal_ac2 |
|
|
!bmp180_cal_data[bmp_idx].cal_ac3 |
|
|
!bmp180_cal_data[bmp_idx].cal_ac4 |
|
|
!bmp180_cal_data[bmp_idx].cal_ac5 |
|
|
!bmp180_cal_data[bmp_idx].cal_ac6 |
|
|
!bmp180_cal_data[bmp_idx].cal_b1 |
|
|
!bmp180_cal_data[bmp_idx].cal_b2 |
|
|
!bmp180_cal_data[bmp_idx].cal_mc |
|
|
!bmp180_cal_data[bmp_idx].cal_md) {
|
|
return false;
|
|
}
|
|
|
|
if ((bmp180_cal_data[bmp_idx].cal_ac1 == (int16_t)0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_ac2 == (int16_t)0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_ac3 == (int16_t)0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_ac4 == 0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_ac5 == 0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_ac6 == 0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_b1 == (int16_t)0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_b2 == (int16_t)0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_mc == (int16_t)0xFFFF) |
|
|
(bmp180_cal_data[bmp_idx].cal_md == (int16_t)0xFFFF)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Bmp180Read(uint8_t bmp_idx)
|
|
{
|
|
if (!bmp180_cal_data) { return; }
|
|
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_CONTROL, BMP180_TEMPERATURE);
|
|
delay(5);
|
|
int ut = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_RESULT);
|
|
int32_t xt1 = (ut - (int32_t)bmp180_cal_data[bmp_idx].cal_ac6) * ((int32_t)bmp180_cal_data[bmp_idx].cal_ac5) >> 15;
|
|
int32_t xt2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_mc << 11) / (xt1 + (int32_t)bmp180_cal_data[bmp_idx].cal_md);
|
|
int32_t bmp180_b5 = xt1 + xt2;
|
|
bmp_sensors[bmp_idx].bmp_temperature = ((bmp180_b5 + 8) >> 4) / 10.0;
|
|
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_CONTROL, BMP180_PRESSURE3);
|
|
delay(2 + (4 << BMP180_OSS));
|
|
uint32_t up = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BMP180_REG_RESULT);
|
|
up >>= (8 - BMP180_OSS);
|
|
|
|
int32_t b6 = bmp180_b5 - 4000;
|
|
int32_t x1 = ((int32_t)bmp180_cal_data[bmp_idx].cal_b2 * ((b6 * b6) >> 12)) >> 11;
|
|
int32_t x2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_ac2 * b6) >> 11;
|
|
int32_t x3 = x1 + x2;
|
|
int32_t b3 = ((((int32_t)bmp180_cal_data[bmp_idx].cal_ac1 * 4 + x3) << BMP180_OSS) + 2) >> 2;
|
|
|
|
x1 = ((int32_t)bmp180_cal_data[bmp_idx].cal_ac3 * b6) >> 13;
|
|
x2 = ((int32_t)bmp180_cal_data[bmp_idx].cal_b1 * ((b6 * b6) >> 12)) >> 16;
|
|
x3 = ((x1 + x2) + 2) >> 2;
|
|
uint32_t b4 = ((uint32_t)bmp180_cal_data[bmp_idx].cal_ac4 * (uint32_t)(x3 + 32768)) >> 15;
|
|
uint32_t b7 = ((uint32_t)up - b3) * (uint32_t)(50000UL >> BMP180_OSS);
|
|
|
|
int32_t p;
|
|
if (b7 < 0x80000000) {
|
|
p = (b7 * 2) / b4;
|
|
}
|
|
else {
|
|
p = (b7 / b4) * 2;
|
|
}
|
|
x1 = (p >> 8) * (p >> 8);
|
|
x1 = (x1 * 3038) >> 16;
|
|
x2 = (-7357 * p) >> 16;
|
|
p += ((x1 + x2 + (int32_t)3791) >> 4);
|
|
bmp_sensors[bmp_idx].bmp_pressure = (float)p / 100.0;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define BME280_REGISTER_CONTROLHUMID 0xF2
|
|
#define BME280_REGISTER_CONTROL 0xF4
|
|
#define BME280_REGISTER_CONFIG 0xF5
|
|
#define BME280_REGISTER_PRESSUREDATA 0xF7
|
|
#define BME280_REGISTER_TEMPDATA 0xFA
|
|
#define BME280_REGISTER_HUMIDDATA 0xFD
|
|
|
|
#define BME280_REGISTER_DIG_T1 0x88
|
|
#define BME280_REGISTER_DIG_T2 0x8A
|
|
#define BME280_REGISTER_DIG_T3 0x8C
|
|
#define BME280_REGISTER_DIG_P1 0x8E
|
|
#define BME280_REGISTER_DIG_P2 0x90
|
|
#define BME280_REGISTER_DIG_P3 0x92
|
|
#define BME280_REGISTER_DIG_P4 0x94
|
|
#define BME280_REGISTER_DIG_P5 0x96
|
|
#define BME280_REGISTER_DIG_P6 0x98
|
|
#define BME280_REGISTER_DIG_P7 0x9A
|
|
#define BME280_REGISTER_DIG_P8 0x9C
|
|
#define BME280_REGISTER_DIG_P9 0x9E
|
|
#define BME280_REGISTER_DIG_H1 0xA1
|
|
#define BME280_REGISTER_DIG_H2 0xE1
|
|
#define BME280_REGISTER_DIG_H3 0xE3
|
|
#define BME280_REGISTER_DIG_H4 0xE4
|
|
#define BME280_REGISTER_DIG_H5 0xE5
|
|
#define BME280_REGISTER_DIG_H6 0xE7
|
|
|
|
typedef struct {
|
|
uint16_t dig_T1;
|
|
int16_t dig_T2;
|
|
int16_t dig_T3;
|
|
uint16_t dig_P1;
|
|
int16_t dig_P2;
|
|
int16_t dig_P3;
|
|
int16_t dig_P4;
|
|
int16_t dig_P5;
|
|
int16_t dig_P6;
|
|
int16_t dig_P7;
|
|
int16_t dig_P8;
|
|
int16_t dig_P9;
|
|
int16_t dig_H2;
|
|
int16_t dig_H4;
|
|
int16_t dig_H5;
|
|
uint8_t dig_H1;
|
|
uint8_t dig_H3;
|
|
int8_t dig_H6;
|
|
} Bme280CalibrationData_t;
|
|
|
|
Bme280CalibrationData_t *Bme280CalibrationData = nullptr;
|
|
|
|
bool Bmx280Calibrate(uint8_t bmp_idx)
|
|
{
|
|
|
|
|
|
if (!Bme280CalibrationData) {
|
|
Bme280CalibrationData = (Bme280CalibrationData_t*)malloc(BMP_MAX_SENSORS * sizeof(Bme280CalibrationData_t));
|
|
}
|
|
if (!Bme280CalibrationData) { return false; }
|
|
|
|
Bme280CalibrationData[bmp_idx].dig_T1 = I2cRead16LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T1);
|
|
Bme280CalibrationData[bmp_idx].dig_T2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T2);
|
|
Bme280CalibrationData[bmp_idx].dig_T3 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_T3);
|
|
Bme280CalibrationData[bmp_idx].dig_P1 = I2cRead16LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P1);
|
|
Bme280CalibrationData[bmp_idx].dig_P2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P2);
|
|
Bme280CalibrationData[bmp_idx].dig_P3 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P3);
|
|
Bme280CalibrationData[bmp_idx].dig_P4 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P4);
|
|
Bme280CalibrationData[bmp_idx].dig_P5 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P5);
|
|
Bme280CalibrationData[bmp_idx].dig_P6 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P6);
|
|
Bme280CalibrationData[bmp_idx].dig_P7 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P7);
|
|
Bme280CalibrationData[bmp_idx].dig_P8 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P8);
|
|
Bme280CalibrationData[bmp_idx].dig_P9 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_P9);
|
|
if (BME280_CHIPID == bmp_sensors[bmp_idx].bmp_type) {
|
|
Bme280CalibrationData[bmp_idx].dig_H1 = I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H1);
|
|
Bme280CalibrationData[bmp_idx].dig_H2 = I2cReadS16_LE(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H2);
|
|
Bme280CalibrationData[bmp_idx].dig_H3 = I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H3);
|
|
Bme280CalibrationData[bmp_idx].dig_H4 = (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H4) << 4) | (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H4 + 1) & 0xF);
|
|
Bme280CalibrationData[bmp_idx].dig_H5 = (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H5 + 1) << 4) | (I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H5) >> 4);
|
|
Bme280CalibrationData[bmp_idx].dig_H6 = (int8_t)I2cRead8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_DIG_H6);
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0x00);
|
|
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROLHUMID, 0x01);
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONFIG, 0xA0);
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0x27);
|
|
} else {
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_CONTROL, 0xB7);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Bme280Read(uint8_t bmp_idx)
|
|
{
|
|
if (!Bme280CalibrationData) { return; }
|
|
|
|
int32_t adc_T = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_TEMPDATA);
|
|
adc_T >>= 4;
|
|
|
|
int32_t vart1 = ((((adc_T >> 3) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1 << 1))) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_T2)) >> 11;
|
|
int32_t vart2 = (((((adc_T >> 4) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1)) * ((adc_T >> 4) - ((int32_t)Bme280CalibrationData[bmp_idx].dig_T1))) >> 12) *
|
|
((int32_t)Bme280CalibrationData[bmp_idx].dig_T3)) >> 14;
|
|
int32_t t_fine = vart1 + vart2;
|
|
float T = (t_fine * 5 + 128) >> 8;
|
|
bmp_sensors[bmp_idx].bmp_temperature = T / 100.0;
|
|
|
|
int32_t adc_P = I2cRead24(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_PRESSUREDATA);
|
|
adc_P >>= 4;
|
|
|
|
int64_t var1 = ((int64_t)t_fine) - 128000;
|
|
int64_t var2 = var1 * var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P6;
|
|
var2 = var2 + ((var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P5) << 17);
|
|
var2 = var2 + (((int64_t)Bme280CalibrationData[bmp_idx].dig_P4) << 35);
|
|
var1 = ((var1 * var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P3) >> 8) + ((var1 * (int64_t)Bme280CalibrationData[bmp_idx].dig_P2) << 12);
|
|
var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)Bme280CalibrationData[bmp_idx].dig_P1) >> 33;
|
|
if (0 == var1) {
|
|
return;
|
|
}
|
|
int64_t p = 1048576 - adc_P;
|
|
p = (((p << 31) - var2) * 3125) / var1;
|
|
var1 = (((int64_t)Bme280CalibrationData[bmp_idx].dig_P9) * (p >> 13) * (p >> 13)) >> 25;
|
|
var2 = (((int64_t)Bme280CalibrationData[bmp_idx].dig_P8) * p) >> 19;
|
|
p = ((p + var1 + var2) >> 8) + (((int64_t)Bme280CalibrationData[bmp_idx].dig_P7) << 4);
|
|
bmp_sensors[bmp_idx].bmp_pressure = (float)p / 25600.0;
|
|
|
|
if (BMP280_CHIPID == bmp_sensors[bmp_idx].bmp_type) { return; }
|
|
|
|
int32_t adc_H = I2cRead16(bmp_sensors[bmp_idx].bmp_address, BME280_REGISTER_HUMIDDATA);
|
|
|
|
int32_t v_x1_u32r = (t_fine - ((int32_t)76800));
|
|
v_x1_u32r = (((((adc_H << 14) - (((int32_t)Bme280CalibrationData[bmp_idx].dig_H4) << 20) -
|
|
(((int32_t)Bme280CalibrationData[bmp_idx].dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) *
|
|
(((((((v_x1_u32r * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H6)) >> 10) *
|
|
(((v_x1_u32r * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H3)) >> 11) + ((int32_t)32768))) >> 10) +
|
|
((int32_t)2097152)) * ((int32_t)Bme280CalibrationData[bmp_idx].dig_H2) + 8192) >> 14));
|
|
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) *
|
|
((int32_t)Bme280CalibrationData[bmp_idx].dig_H1)) >> 4));
|
|
v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r;
|
|
v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r;
|
|
float h = (v_x1_u32r >> 12);
|
|
bmp_sensors[bmp_idx].bmp_humidity = h / 1024.0;
|
|
}
|
|
|
|
#ifdef USE_BME680
|
|
|
|
|
|
|
|
|
|
#include <bme680.h>
|
|
|
|
struct bme680_dev *gas_sensor = nullptr;
|
|
|
|
static void BmeDelayMs(uint32_t ms)
|
|
{
|
|
delay(ms);
|
|
}
|
|
|
|
bool Bme680Init(uint8_t bmp_idx)
|
|
{
|
|
if (!gas_sensor) {
|
|
gas_sensor = (bme680_dev*)malloc(BMP_MAX_SENSORS * sizeof(bme680_dev));
|
|
}
|
|
if (!gas_sensor) { return false; }
|
|
|
|
gas_sensor[bmp_idx].dev_id = bmp_sensors[bmp_idx].bmp_address;
|
|
gas_sensor[bmp_idx].intf = BME680_I2C_INTF;
|
|
gas_sensor[bmp_idx].read = &I2cReadBuffer;
|
|
gas_sensor[bmp_idx].write = &I2cWriteBuffer;
|
|
gas_sensor[bmp_idx].delay_ms = BmeDelayMs;
|
|
|
|
|
|
|
|
gas_sensor[bmp_idx].amb_temp = 25;
|
|
|
|
int8_t rslt = BME680_OK;
|
|
rslt = bme680_init(&gas_sensor[bmp_idx]);
|
|
if (rslt != BME680_OK) { return false; }
|
|
|
|
|
|
gas_sensor[bmp_idx].tph_sett.os_hum = BME680_OS_2X;
|
|
gas_sensor[bmp_idx].tph_sett.os_pres = BME680_OS_4X;
|
|
gas_sensor[bmp_idx].tph_sett.os_temp = BME680_OS_8X;
|
|
gas_sensor[bmp_idx].tph_sett.filter = BME680_FILTER_SIZE_3;
|
|
|
|
|
|
gas_sensor[bmp_idx].gas_sett.run_gas = BME680_ENABLE_GAS_MEAS;
|
|
|
|
gas_sensor[bmp_idx].gas_sett.heatr_temp = 320;
|
|
gas_sensor[bmp_idx].gas_sett.heatr_dur = 150;
|
|
|
|
|
|
|
|
gas_sensor[bmp_idx].power_mode = BME680_FORCED_MODE;
|
|
|
|
|
|
uint8_t set_required_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL;
|
|
|
|
|
|
rslt = bme680_set_sensor_settings(set_required_settings,&gas_sensor[bmp_idx]);
|
|
if (rslt != BME680_OK) { return false; }
|
|
|
|
bmp_sensors[bmp_idx].bme680_state = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Bme680Read(uint8_t bmp_idx)
|
|
{
|
|
if (!gas_sensor) { return; }
|
|
|
|
int8_t rslt = BME680_OK;
|
|
|
|
if (BME680_CHIPID == bmp_sensors[bmp_idx].bmp_type) {
|
|
if (0 == bmp_sensors[bmp_idx].bme680_state) {
|
|
|
|
rslt = bme680_set_sensor_mode(&gas_sensor[bmp_idx]);
|
|
if (rslt != BME680_OK) { return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bmp_sensors[bmp_idx].bme680_state = 1;
|
|
} else {
|
|
bmp_sensors[bmp_idx].bme680_state = 0;
|
|
|
|
struct bme680_field_data data;
|
|
rslt = bme680_get_sensor_data(&data, &gas_sensor[bmp_idx]);
|
|
if (rslt != BME680_OK) { return; }
|
|
|
|
bmp_sensors[bmp_idx].bmp_temperature = data.temperature / 100.0;
|
|
bmp_sensors[bmp_idx].bmp_humidity = data.humidity / 1000.0;
|
|
bmp_sensors[bmp_idx].bmp_pressure = data.pressure / 100.0;
|
|
|
|
if (data.status & BME680_GASM_VALID_MSK) {
|
|
bmp_sensors[bmp_idx].bmp_gas_resistance = data.gas_resistance / 1000.0;
|
|
} else {
|
|
bmp_sensors[bmp_idx].bmp_gas_resistance = 0;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void BmpDetect(void)
|
|
{
|
|
int bmp_sensor_size = BMP_MAX_SENSORS * sizeof(bmp_sensors_t);
|
|
if (!bmp_sensors) {
|
|
bmp_sensors = (bmp_sensors_t*)malloc(bmp_sensor_size);
|
|
}
|
|
if (!bmp_sensors) { return; }
|
|
memset(bmp_sensors, 0, bmp_sensor_size);
|
|
|
|
for (uint32_t i = 0; i < BMP_MAX_SENSORS; i++) {
|
|
if (I2cActive(bmp_addresses[i])) { continue; }
|
|
uint8_t bmp_type = I2cRead8(bmp_addresses[i], BMP_REGISTER_CHIPID);
|
|
if (bmp_type) {
|
|
bmp_sensors[bmp_count].bmp_address = bmp_addresses[i];
|
|
bmp_sensors[bmp_count].bmp_type = bmp_type;
|
|
bmp_sensors[bmp_count].bmp_model = 0;
|
|
|
|
bool success = false;
|
|
switch (bmp_type) {
|
|
case BMP180_CHIPID:
|
|
success = Bmp180Calibration(bmp_count);
|
|
break;
|
|
case BME280_CHIPID:
|
|
bmp_sensors[bmp_count].bmp_model++;
|
|
case BMP280_CHIPID:
|
|
bmp_sensors[bmp_count].bmp_model++;
|
|
success = Bmx280Calibrate(bmp_count);
|
|
break;
|
|
#ifdef USE_BME680
|
|
case BME680_CHIPID:
|
|
bmp_sensors[bmp_count].bmp_model = 3;
|
|
success = Bme680Init(bmp_count);
|
|
break;
|
|
#endif
|
|
}
|
|
if (success) {
|
|
GetTextIndexed(bmp_sensors[bmp_count].bmp_name, sizeof(bmp_sensors[bmp_count].bmp_name), bmp_sensors[bmp_count].bmp_model, kBmpTypes);
|
|
I2cSetActiveFound(bmp_sensors[bmp_count].bmp_address, bmp_sensors[bmp_count].bmp_name);
|
|
bmp_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BmpRead(void)
|
|
{
|
|
for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) {
|
|
switch (bmp_sensors[bmp_idx].bmp_type) {
|
|
case BMP180_CHIPID:
|
|
Bmp180Read(bmp_idx);
|
|
break;
|
|
case BMP280_CHIPID:
|
|
case BME280_CHIPID:
|
|
Bme280Read(bmp_idx);
|
|
break;
|
|
#ifdef USE_BME680
|
|
case BME680_CHIPID:
|
|
Bme680Read(bmp_idx);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
ConvertTemp(bmp_sensors[0].bmp_temperature);
|
|
ConvertHumidity(bmp_sensors[0].bmp_humidity);
|
|
}
|
|
|
|
void BmpShow(bool json)
|
|
{
|
|
for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) {
|
|
if (bmp_sensors[bmp_idx].bmp_type) {
|
|
float bmp_sealevel = 0.0;
|
|
if (bmp_sensors[bmp_idx].bmp_pressure != 0.0) {
|
|
bmp_sealevel = (bmp_sensors[bmp_idx].bmp_pressure / FastPrecisePow(1.0 - ((float)Settings.altitude / 44330.0), 5.255)) - 21.6;
|
|
bmp_sealevel = ConvertPressure(bmp_sealevel);
|
|
}
|
|
float bmp_temperature = ConvertTemp(bmp_sensors[bmp_idx].bmp_temperature);
|
|
float bmp_pressure = ConvertPressure(bmp_sensors[bmp_idx].bmp_pressure);
|
|
|
|
char name[10];
|
|
strlcpy(name, bmp_sensors[bmp_idx].bmp_name, sizeof(name));
|
|
if (bmp_count > 1) {
|
|
snprintf_P(name, sizeof(name), PSTR("%s%c%02X"), name, IndexSeparator(), bmp_sensors[bmp_idx].bmp_address);
|
|
}
|
|
|
|
char temperature[33];
|
|
dtostrfd(bmp_temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char pressure[33];
|
|
dtostrfd(bmp_pressure, Settings.flag2.pressure_resolution, pressure);
|
|
char sea_pressure[33];
|
|
dtostrfd(bmp_sealevel, Settings.flag2.pressure_resolution, sea_pressure);
|
|
char humidity[33];
|
|
dtostrfd(bmp_sensors[bmp_idx].bmp_humidity, Settings.flag2.humidity_resolution, humidity);
|
|
#ifdef USE_BME680
|
|
char gas_resistance[33];
|
|
dtostrfd(bmp_sensors[bmp_idx].bmp_gas_resistance, 2, gas_resistance);
|
|
#endif
|
|
|
|
if (json) {
|
|
char json_humidity[40];
|
|
snprintf_P(json_humidity, sizeof(json_humidity), PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity);
|
|
char json_sealevel[40];
|
|
snprintf_P(json_sealevel, sizeof(json_sealevel), PSTR(",\"" D_JSON_PRESSUREATSEALEVEL "\":%s"), sea_pressure);
|
|
#ifdef USE_BME680
|
|
char json_gas[40];
|
|
snprintf_P(json_gas, sizeof(json_gas), PSTR(",\"" D_JSON_GAS "\":%s"), gas_resistance);
|
|
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s%s}"),
|
|
name,
|
|
temperature,
|
|
(bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "",
|
|
pressure,
|
|
(Settings.altitude != 0) ? json_sealevel : "",
|
|
(bmp_sensors[bmp_idx].bmp_model >= 3) ? json_gas : "");
|
|
#else
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s}"),
|
|
name, temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : "");
|
|
#endif
|
|
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == bmp_idx)) {
|
|
DomoticzTempHumPressureSensor(temperature, humidity, pressure);
|
|
#ifdef USE_BME680
|
|
if (bmp_sensors[bmp_idx].bmp_model >= 3) { DomoticzSensor(DZ_AIRQUALITY, (uint32_t)bmp_sensors[bmp_idx].bmp_gas_resistance); }
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, bmp_temperature);
|
|
KnxSensor(KNX_HUMIDITY, bmp_sensors[bmp_idx].bmp_humidity);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, name, temperature, TempUnit());
|
|
if (bmp_sensors[bmp_idx].bmp_model >= 2) {
|
|
WSContentSend_PD(HTTP_SNS_HUM, name, humidity);
|
|
}
|
|
WSContentSend_PD(HTTP_SNS_PRESSURE, name, pressure, PressureUnit().c_str());
|
|
if (Settings.altitude != 0) {
|
|
WSContentSend_PD(HTTP_SNS_SEAPRESSURE, name, sea_pressure, PressureUnit().c_str());
|
|
}
|
|
#ifdef USE_BME680
|
|
if (bmp_sensors[bmp_idx].bmp_model >= 3) {
|
|
WSContentSend_PD(PSTR("{s}%s " D_GAS "{m}%s " D_UNIT_KILOOHM "{e}"), name, gas_resistance);
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_DEEPSLEEP
|
|
|
|
void BMP_EnterSleep(void)
|
|
{
|
|
for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) {
|
|
switch (bmp_sensors[bmp_idx].bmp_type) {
|
|
case BMP180_CHIPID:
|
|
case BMP280_CHIPID:
|
|
case BME280_CHIPID:
|
|
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP_REGISTER_RESET, BMP_CMND_RESET);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns09(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_10)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
BmpDetect();
|
|
}
|
|
else if (bmp_count) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
BmpRead();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
BmpShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
BmpShow(0);
|
|
break;
|
|
#endif
|
|
#ifdef USE_DEEPSLEEP
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
BMP_EnterSleep();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_10_bh1750.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_10_bh1750.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_BH1750
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_10 10
|
|
#define XI2C_11 11
|
|
|
|
#define BH1750_ADDR1 0x23
|
|
#define BH1750_ADDR2 0x5C
|
|
|
|
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10
|
|
|
|
uint8_t bh1750_address;
|
|
uint8_t bh1750_addresses[] = { BH1750_ADDR1, BH1750_ADDR2 };
|
|
uint8_t bh1750_type = 0;
|
|
uint8_t bh1750_valid = 0;
|
|
uint16_t bh1750_illuminance = 0;
|
|
char bh1750_types[] = "BH1750";
|
|
|
|
bool Bh1750Read(void)
|
|
{
|
|
if (bh1750_valid) { bh1750_valid--; }
|
|
|
|
if (2 != Wire.requestFrom(bh1750_address, (uint8_t)2)) { return false; }
|
|
uint8_t msb = Wire.read();
|
|
uint8_t lsb = Wire.read();
|
|
bh1750_illuminance = ((msb << 8) | lsb) / 1.2;
|
|
bh1750_valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void Bh1750Detect(void)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(bh1750_addresses); i++) {
|
|
bh1750_address = bh1750_addresses[i];
|
|
if (I2cActive(bh1750_address)) { continue; }
|
|
Wire.beginTransmission(bh1750_address);
|
|
Wire.write(BH1750_CONTINUOUS_HIGH_RES_MODE);
|
|
if (!Wire.endTransmission()) {
|
|
I2cSetActiveFound(bh1750_address, bh1750_types);
|
|
bh1750_type = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Bh1750EverySecond(void)
|
|
{
|
|
|
|
if (!Bh1750Read()) {
|
|
AddLogMissed(bh1750_types, bh1750_valid);
|
|
}
|
|
}
|
|
|
|
void Bh1750Show(bool json)
|
|
{
|
|
if (bh1750_valid) {
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_ILLUMINANCE, bh1750_illuminance);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, bh1750_types, bh1750_illuminance);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns10(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_11)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Bh1750Detect();
|
|
}
|
|
else if (bh1750_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Bh1750EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Bh1750Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Bh1750Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_11_veml6070.ino"
|
|
# 89 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_11_veml6070.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_VEML6070
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_11 11
|
|
#define XI2C_12 12
|
|
|
|
#define VEML6070_ADDR_H 0x39
|
|
#define VEML6070_ADDR_L 0x38
|
|
#define VEML6070_INTEGRATION_TIME 3
|
|
#define VEML6070_ENABLE 1
|
|
#define VEML6070_DISABLE 0
|
|
#define VEML6070_RSET_DEFAULT 270000
|
|
#define VEML6070_UV_MAX_INDEX 15
|
|
#define VEML6070_UV_MAX_DEFAULT 11
|
|
#define VEML6070_POWER_COEFFCIENT 0.025
|
|
#define VEML6070_TABLE_COEFFCIENT 32.86270591
|
|
|
|
|
|
|
|
|
|
|
|
const char kVemlTypes[] PROGMEM = "VEML6070";
|
|
double uv_risk_map[VEML6070_UV_MAX_INDEX] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
|
double uvrisk = 0;
|
|
double uvpower = 0;
|
|
uint16_t uvlevel = 0;
|
|
uint8_t veml6070_addr_low = VEML6070_ADDR_L;
|
|
uint8_t veml6070_addr_high = VEML6070_ADDR_H;
|
|
uint8_t itime = VEML6070_INTEGRATION_TIME;
|
|
uint8_t veml6070_type = 0;
|
|
char veml6070_name[9];
|
|
char str_uvrisk_text[10];
|
|
|
|
|
|
|
|
void Veml6070Detect(void)
|
|
{
|
|
if (I2cActive(VEML6070_ADDR_L)) { return; }
|
|
|
|
|
|
Wire.beginTransmission(VEML6070_ADDR_L);
|
|
Wire.write((itime << 2) | 0x02);
|
|
uint8_t status = Wire.endTransmission();
|
|
|
|
if (!status) {
|
|
veml6070_type = 1;
|
|
Veml6070UvTableInit();
|
|
uint8_t veml_model = 0;
|
|
GetTextIndexed(veml6070_name, sizeof(veml6070_name), veml_model, kVemlTypes);
|
|
I2cSetActiveFound(VEML6070_ADDR_L, veml6070_name);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void Veml6070UvTableInit(void)
|
|
{
|
|
|
|
for (uint32_t i = 0; i < VEML6070_UV_MAX_INDEX; i++) {
|
|
#ifdef USE_VEML6070_RSET
|
|
if ( (USE_VEML6070_RSET >= 220000) && (USE_VEML6070_RSET <= 1000000) ) {
|
|
uv_risk_map[i] = ( (USE_VEML6070_RSET / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1);
|
|
} else {
|
|
uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 resistor error %d"), USE_VEML6070_RSET);
|
|
}
|
|
#else
|
|
uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 resistor default used %d"), VEML6070_RSET_DEFAULT);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void Veml6070EverySecond(void)
|
|
{
|
|
|
|
Veml6070ModeCmd(1);
|
|
uvlevel = Veml6070ReadUv();
|
|
uvrisk = Veml6070UvRiskLevel(uvlevel);
|
|
uvpower = Veml6070UvPower(uvrisk);
|
|
Veml6070ModeCmd(0);
|
|
}
|
|
|
|
|
|
|
|
void Veml6070ModeCmd(bool mode_cmd)
|
|
{
|
|
|
|
|
|
Wire.beginTransmission(VEML6070_ADDR_L);
|
|
Wire.write((mode_cmd << 0) | 0x02 | (itime << 2));
|
|
uint8_t status = Wire.endTransmission();
|
|
|
|
if (!status) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 mode_cmd"));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
uint16_t Veml6070ReadUv(void)
|
|
{
|
|
uint16_t uv_raw = 0;
|
|
|
|
if (Wire.requestFrom(VEML6070_ADDR_H, 1) != 1) {
|
|
return -1;
|
|
}
|
|
uv_raw = Wire.read();
|
|
uv_raw <<= 8;
|
|
|
|
if (Wire.requestFrom(VEML6070_ADDR_L, 1) != 1) {
|
|
return -1;
|
|
}
|
|
uv_raw |= Wire.read();
|
|
|
|
return uv_raw;
|
|
}
|
|
|
|
|
|
|
|
double Veml6070UvRiskLevel(uint16_t uv_level)
|
|
{
|
|
double risk = 0;
|
|
if (uv_level < uv_risk_map[VEML6070_UV_MAX_INDEX-1]) {
|
|
risk = (double)uv_level / uv_risk_map[0];
|
|
|
|
if ( (risk >= 0) && (risk <= 2.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_1); }
|
|
else if ( (risk >= 3.0) && (risk <= 5.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_2); }
|
|
else if ( (risk >= 6.0) && (risk <= 7.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_3); }
|
|
else if ( (risk >= 8.0) && (risk <= 10.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_4); }
|
|
else if ( (risk >= 11.0) && (risk <= 12.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_5); }
|
|
else if ( (risk >= 13.0) && (risk <= 25.0) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_6); }
|
|
else { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7); }
|
|
return risk;
|
|
} else {
|
|
|
|
snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7);
|
|
return ( risk = 99 );
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "VEML6070 out of range %d"), risk);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
double Veml6070UvPower(double uvrisk)
|
|
{
|
|
|
|
double power = 0;
|
|
return ( power = VEML6070_POWER_COEFFCIENT * uvrisk );
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
#ifdef USE_VEML6070_SHOW_RAW
|
|
const char HTTP_SNS_UV_LEVEL[] PROGMEM = "{s}VEML6070 " D_UV_LEVEL "{m}%s " D_UNIT_INCREMENTS "{e}";
|
|
#endif
|
|
|
|
const char HTTP_SNS_UV_INDEX[] PROGMEM = "{s}VEML6070 " D_UV_INDEX "{m}%s %s{e}";
|
|
const char HTTP_SNS_UV_POWER[] PROGMEM = "{s}VEML6070 " D_UV_POWER "{m}%s " D_UNIT_WATT_METER_QUADRAT "{e}";
|
|
#endif
|
|
|
|
|
|
|
|
void Veml6070Show(bool json)
|
|
{
|
|
|
|
char str_uvlevel[33];
|
|
dtostrfd((double)uvlevel, 0, str_uvlevel);
|
|
char str_uvrisk[33];
|
|
dtostrfd(uvrisk, 2, str_uvrisk);
|
|
char str_uvpower[33];
|
|
dtostrfd(uvpower, 3, str_uvpower);
|
|
if (json) {
|
|
#ifdef USE_VEML6070_SHOW_RAW
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_UV_LEVEL "\":%s,\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"),
|
|
veml6070_name, str_uvlevel, str_uvrisk, str_uvrisk_text, str_uvpower);
|
|
#else
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"),
|
|
veml6070_name, str_uvrisk, str_uvrisk_text, str_uvpower);
|
|
#endif
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, uvlevel); }
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
#ifdef USE_VEML6070_SHOW_RAW
|
|
WSContentSend_PD(HTTP_SNS_UV_LEVEL, str_uvlevel);
|
|
#endif
|
|
WSContentSend_PD(HTTP_SNS_UV_INDEX, str_uvrisk, str_uvrisk_text);
|
|
WSContentSend_PD(HTTP_SNS_UV_POWER, str_uvpower);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns11(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_12)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Veml6070Detect();
|
|
}
|
|
else if (veml6070_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Veml6070EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Veml6070Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Veml6070Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_ADS1115
|
|
# 43 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_12_ads1115.ino"
|
|
#define XSNS_12 12
|
|
#define XI2C_13 13
|
|
|
|
#define ADS1115_ADDRESS_ADDR_GND 0x48
|
|
#define ADS1115_ADDRESS_ADDR_VDD 0x49
|
|
#define ADS1115_ADDRESS_ADDR_SDA 0x4A
|
|
#define ADS1115_ADDRESS_ADDR_SCL 0x4B
|
|
|
|
#define ADS1115_CONVERSIONDELAY (8)
|
|
|
|
|
|
|
|
|
|
#define ADS1115_REG_POINTER_MASK (0x03)
|
|
#define ADS1115_REG_POINTER_CONVERT (0x00)
|
|
#define ADS1115_REG_POINTER_CONFIG (0x01)
|
|
#define ADS1115_REG_POINTER_LOWTHRESH (0x02)
|
|
#define ADS1115_REG_POINTER_HITHRESH (0x03)
|
|
|
|
|
|
|
|
|
|
#define ADS1115_REG_CONFIG_OS_MASK (0x8000)
|
|
#define ADS1115_REG_CONFIG_OS_SINGLE (0x8000)
|
|
#define ADS1115_REG_CONFIG_OS_BUSY (0x0000)
|
|
#define ADS1115_REG_CONFIG_OS_NOTBUSY (0x8000)
|
|
|
|
#define ADS1115_REG_CONFIG_MUX_MASK (0x7000)
|
|
#define ADS1115_REG_CONFIG_MUX_DIFF_0_1 (0x0000)
|
|
#define ADS1115_REG_CONFIG_MUX_DIFF_0_3 (0x1000)
|
|
#define ADS1115_REG_CONFIG_MUX_DIFF_1_3 (0x2000)
|
|
#define ADS1115_REG_CONFIG_MUX_DIFF_2_3 (0x3000)
|
|
#define ADS1115_REG_CONFIG_MUX_SINGLE_0 (0x4000)
|
|
#define ADS1115_REG_CONFIG_MUX_SINGLE_1 (0x5000)
|
|
#define ADS1115_REG_CONFIG_MUX_SINGLE_2 (0x6000)
|
|
#define ADS1115_REG_CONFIG_MUX_SINGLE_3 (0x7000)
|
|
|
|
#define ADS1115_REG_CONFIG_PGA_MASK (0x0E00)
|
|
#define ADS1115_REG_CONFIG_PGA_6_144V (0x0000)
|
|
#define ADS1115_REG_CONFIG_PGA_4_096V (0x0200)
|
|
#define ADS1115_REG_CONFIG_PGA_2_048V (0x0400)
|
|
#define ADS1115_REG_CONFIG_PGA_1_024V (0x0600)
|
|
#define ADS1115_REG_CONFIG_PGA_0_512V (0x0800)
|
|
#define ADS1115_REG_CONFIG_PGA_0_256V (0x0A00)
|
|
|
|
#define ADS1115_REG_CONFIG_MODE_MASK (0x0100)
|
|
#define ADS1115_REG_CONFIG_MODE_CONTIN (0x0000)
|
|
#define ADS1115_REG_CONFIG_MODE_SINGLE (0x0100)
|
|
|
|
#define ADS1115_REG_CONFIG_DR_MASK (0x00E0)
|
|
#define ADS1115_REG_CONFIG_DR_128SPS (0x0000)
|
|
#define ADS1115_REG_CONFIG_DR_250SPS (0x0020)
|
|
#define ADS1115_REG_CONFIG_DR_490SPS (0x0040)
|
|
#define ADS1115_REG_CONFIG_DR_920SPS (0x0060)
|
|
#define ADS1115_REG_CONFIG_DR_1600SPS (0x0080)
|
|
#define ADS1115_REG_CONFIG_DR_2400SPS (0x00A0)
|
|
#define ADS1115_REG_CONFIG_DR_3300SPS (0x00C0)
|
|
#define ADS1115_REG_CONFIG_DR_6000SPS (0x00E0)
|
|
|
|
#define ADS1115_REG_CONFIG_CMODE_MASK (0x0010)
|
|
#define ADS1115_REG_CONFIG_CMODE_TRAD (0x0000)
|
|
#define ADS1115_REG_CONFIG_CMODE_WINDOW (0x0010)
|
|
|
|
#define ADS1115_REG_CONFIG_CPOL_MASK (0x0008)
|
|
#define ADS1115_REG_CONFIG_CPOL_ACTVLOW (0x0000)
|
|
#define ADS1115_REG_CONFIG_CPOL_ACTVHI (0x0008)
|
|
|
|
#define ADS1115_REG_CONFIG_CLAT_MASK (0x0004)
|
|
#define ADS1115_REG_CONFIG_CLAT_NONLAT (0x0000)
|
|
#define ADS1115_REG_CONFIG_CLAT_LATCH (0x0004)
|
|
|
|
#define ADS1115_REG_CONFIG_CQUE_MASK (0x0003)
|
|
#define ADS1115_REG_CONFIG_CQUE_1CONV (0x0000)
|
|
#define ADS1115_REG_CONFIG_CQUE_2CONV (0x0001)
|
|
#define ADS1115_REG_CONFIG_CQUE_4CONV (0x0002)
|
|
#define ADS1115_REG_CONFIG_CQUE_NONE (0x0003)
|
|
|
|
struct ADS1115 {
|
|
uint8_t count = 0;
|
|
uint8_t address;
|
|
uint8_t addresses[4] = { ADS1115_ADDRESS_ADDR_GND, ADS1115_ADDRESS_ADDR_VDD, ADS1115_ADDRESS_ADDR_SDA, ADS1115_ADDRESS_ADDR_SCL };
|
|
uint8_t found[4] = {false,false,false,false};
|
|
} Ads1115;
|
|
|
|
|
|
|
|
void Ads1115StartComparator(uint8_t channel, uint16_t mode)
|
|
{
|
|
|
|
uint16_t config = mode |
|
|
ADS1115_REG_CONFIG_CQUE_NONE |
|
|
ADS1115_REG_CONFIG_CLAT_NONLAT |
|
|
ADS1115_REG_CONFIG_PGA_6_144V |
|
|
ADS1115_REG_CONFIG_CPOL_ACTVLOW |
|
|
ADS1115_REG_CONFIG_CMODE_TRAD |
|
|
ADS1115_REG_CONFIG_DR_6000SPS;
|
|
|
|
|
|
config |= (ADS1115_REG_CONFIG_MUX_SINGLE_0 + (0x1000 * channel));
|
|
|
|
|
|
I2cWrite16(Ads1115.address, ADS1115_REG_POINTER_CONFIG, config);
|
|
}
|
|
|
|
int16_t Ads1115GetConversion(uint8_t channel)
|
|
{
|
|
Ads1115StartComparator(channel, ADS1115_REG_CONFIG_MODE_SINGLE);
|
|
|
|
delay(ADS1115_CONVERSIONDELAY);
|
|
|
|
I2cRead16(Ads1115.address, ADS1115_REG_POINTER_CONVERT);
|
|
|
|
Ads1115StartComparator(channel, ADS1115_REG_CONFIG_MODE_CONTIN);
|
|
delay(ADS1115_CONVERSIONDELAY);
|
|
|
|
uint16_t res = I2cRead16(Ads1115.address, ADS1115_REG_POINTER_CONVERT);
|
|
return (int16_t)res;
|
|
}
|
|
|
|
|
|
|
|
void Ads1115Detect(void)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(Ads1115.addresses); i++) {
|
|
if (!Ads1115.found[i]) {
|
|
Ads1115.address = Ads1115.addresses[i];
|
|
if (I2cActive(Ads1115.address)) { continue; }
|
|
uint16_t buffer;
|
|
if (I2cValidRead16(&buffer, Ads1115.address, ADS1115_REG_POINTER_CONVERT) &&
|
|
I2cValidRead16(&buffer, Ads1115.address, ADS1115_REG_POINTER_CONFIG)) {
|
|
Ads1115StartComparator(i, ADS1115_REG_CONFIG_MODE_CONTIN);
|
|
I2cSetActiveFound(Ads1115.address, "ADS1115");
|
|
Ads1115.found[i] = 1;
|
|
Ads1115.count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ads1115Show(bool json)
|
|
{
|
|
int16_t values[4];
|
|
|
|
for (uint32_t t = 0; t < sizeof(Ads1115.addresses); t++) {
|
|
|
|
if (Ads1115.found[t]) {
|
|
|
|
uint8_t old_address = Ads1115.address;
|
|
Ads1115.address = Ads1115.addresses[t];
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
values[i] = Ads1115GetConversion(i);
|
|
|
|
}
|
|
Ads1115.address = old_address;
|
|
|
|
char label[15];
|
|
if (1 == Ads1115.count) {
|
|
|
|
snprintf_P(label, sizeof(label), PSTR("ADS1115"));
|
|
} else {
|
|
|
|
snprintf_P(label, sizeof(label), PSTR("ADS1115%c%02x"), IndexSeparator(), Ads1115.addresses[t]);
|
|
}
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{"), label);
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
ResponseAppend_P(PSTR("%s\"A%d\":%d"), (0 == i) ? "" : ",", i, values[i]);
|
|
}
|
|
ResponseJsonEnd();
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
else {
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
WSContentSend_PD(HTTP_SNS_ANALOG, label, i, values[i]);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns12(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_13)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Ads1115Detect();
|
|
}
|
|
else if (Ads1115.count) {
|
|
switch (function) {
|
|
case FUNC_JSON_APPEND:
|
|
Ads1115Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Ads1115Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_INA219
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
|
|
#define XSNS_13 13
|
|
#define XI2C_14 14
|
|
|
|
#define INA219_ADDRESS1 (0x40)
|
|
#define INA219_ADDRESS2 (0x41)
|
|
#define INA219_ADDRESS3 (0x44)
|
|
#define INA219_ADDRESS4 (0x45)
|
|
|
|
#define INA219_READ (0x01)
|
|
#define INA219_REG_CONFIG (0x00)
|
|
|
|
#define INA219_CONFIG_RESET (0x8000)
|
|
|
|
#define INA219_CONFIG_BVOLTAGERANGE_MASK (0x2000)
|
|
#define INA219_CONFIG_BVOLTAGERANGE_16V (0x0000)
|
|
#define INA219_CONFIG_BVOLTAGERANGE_32V (0x2000)
|
|
|
|
#define INA219_CONFIG_GAIN_MASK (0x1800)
|
|
#define INA219_CONFIG_GAIN_1_40MV (0x0000)
|
|
#define INA219_CONFIG_GAIN_2_80MV (0x0800)
|
|
#define INA219_CONFIG_GAIN_4_160MV (0x1000)
|
|
#define INA219_CONFIG_GAIN_8_320MV (0x1800)
|
|
|
|
#define INA219_CONFIG_BADCRES_MASK (0x0780)
|
|
#define INA219_CONFIG_BADCRES_9BIT (0x0080)
|
|
#define INA219_CONFIG_BADCRES_10BIT (0x0100)
|
|
#define INA219_CONFIG_BADCRES_11BIT (0x0200)
|
|
#define INA219_CONFIG_BADCRES_12BIT (0x0400)
|
|
|
|
#define INA219_CONFIG_SADCRES_MASK (0x0078)
|
|
#define INA219_CONFIG_SADCRES_9BIT_1S_84US (0x0000)
|
|
#define INA219_CONFIG_SADCRES_10BIT_1S_148US (0x0008)
|
|
#define INA219_CONFIG_SADCRES_11BIT_1S_276US (0x0010)
|
|
#define INA219_CONFIG_SADCRES_12BIT_1S_532US (0x0018)
|
|
#define INA219_CONFIG_SADCRES_12BIT_2S_1060US (0x0048)
|
|
#define INA219_CONFIG_SADCRES_12BIT_4S_2130US (0x0050)
|
|
#define INA219_CONFIG_SADCRES_12BIT_8S_4260US (0x0058)
|
|
#define INA219_CONFIG_SADCRES_12BIT_16S_8510US (0x0060)
|
|
#define INA219_CONFIG_SADCRES_12BIT_32S_17MS (0x0068)
|
|
#define INA219_CONFIG_SADCRES_12BIT_64S_34MS (0x0070)
|
|
#define INA219_CONFIG_SADCRES_12BIT_128S_69MS (0x0078)
|
|
|
|
#define INA219_CONFIG_MODE_MASK (0x0007)
|
|
#define INA219_CONFIG_MODE_POWERDOWN (0x0000)
|
|
#define INA219_CONFIG_MODE_SVOLT_TRIGGERED (0x0001)
|
|
#define INA219_CONFIG_MODE_BVOLT_TRIGGERED (0x0002)
|
|
#define INA219_CONFIG_MODE_SANDBVOLT_TRIGGERED (0x0003)
|
|
#define INA219_CONFIG_MODE_ADCOFF (0x0004)
|
|
#define INA219_CONFIG_MODE_SVOLT_CONTINUOUS (0x0005)
|
|
#define INA219_CONFIG_MODE_BVOLT_CONTINUOUS (0x0006)
|
|
#define INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS (0x0007)
|
|
|
|
#define INA219_REG_SHUNTVOLTAGE (0x01)
|
|
#define INA219_REG_BUSVOLTAGE (0x02)
|
|
#define INA219_REG_POWER (0x03)
|
|
#define INA219_REG_CURRENT (0x04)
|
|
#define INA219_REG_CALIBRATION (0x05)
|
|
|
|
uint8_t ina219_type[4] = {0,0,0,0};
|
|
uint8_t ina219_addresses[] = { INA219_ADDRESS1, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 };
|
|
|
|
uint32_t ina219_cal_value = 0;
|
|
|
|
uint32_t ina219_current_divider_ma = 0;
|
|
|
|
uint8_t ina219_valid[4] = {0,0,0,0};
|
|
float ina219_voltage[4] = {0,0,0,0};
|
|
float ina219_current[4] = {0,0,0,0};
|
|
char ina219_types[] = "INA219";
|
|
uint8_t ina219_count = 0;
|
|
|
|
bool Ina219SetCalibration(uint8_t mode, uint16_t addr)
|
|
{
|
|
uint16_t config = 0;
|
|
|
|
switch (mode &3) {
|
|
case 0:
|
|
case 3:
|
|
ina219_cal_value = 4096;
|
|
ina219_current_divider_ma = 10;
|
|
config = INA219_CONFIG_BVOLTAGERANGE_32V | INA219_CONFIG_GAIN_8_320MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
|
|
break;
|
|
case 1:
|
|
ina219_cal_value = 10240;
|
|
ina219_current_divider_ma = 25;
|
|
config |= INA219_CONFIG_BVOLTAGERANGE_32V | INA219_CONFIG_GAIN_8_320MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
|
|
break;
|
|
case 2:
|
|
ina219_cal_value = 8192;
|
|
ina219_current_divider_ma = 20;
|
|
config |= INA219_CONFIG_BVOLTAGERANGE_16V | INA219_CONFIG_GAIN_1_40MV | INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
|
|
break;
|
|
}
|
|
|
|
bool success = I2cWrite16(addr, INA219_REG_CALIBRATION, ina219_cal_value);
|
|
if (success) {
|
|
|
|
I2cWrite16(addr, INA219_REG_CONFIG, config);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
float Ina219GetShuntVoltage_mV(uint16_t addr)
|
|
{
|
|
|
|
int16_t value = I2cReadS16(addr, INA219_REG_SHUNTVOLTAGE);
|
|
|
|
return value * 0.01;
|
|
}
|
|
|
|
float Ina219GetBusVoltage_V(uint16_t addr)
|
|
{
|
|
|
|
|
|
int16_t value = (int16_t)(((uint16_t)I2cReadS16(addr, INA219_REG_BUSVOLTAGE) >> 3) * 4);
|
|
|
|
return value * 0.001;
|
|
}
|
|
|
|
float Ina219GetCurrent_mA(uint16_t addr)
|
|
{
|
|
|
|
|
|
|
|
I2cWrite16(addr, INA219_REG_CALIBRATION, ina219_cal_value);
|
|
|
|
|
|
float value = I2cReadS16(addr, INA219_REG_CURRENT);
|
|
value /= ina219_current_divider_ma;
|
|
|
|
return value;
|
|
}
|
|
|
|
bool Ina219Read(void)
|
|
{
|
|
for (int i=0; i<sizeof(ina219_type); i++) {
|
|
if (!ina219_type[i]) { continue; }
|
|
uint16_t addr = ina219_addresses[i];
|
|
ina219_voltage[i] = Ina219GetBusVoltage_V(addr) + (Ina219GetShuntVoltage_mV(addr) / 1000);
|
|
ina219_current[i] = Ina219GetCurrent_mA(addr) / 1000;
|
|
ina219_valid[i] = SENSOR_MAX_MISS;
|
|
|
|
}
|
|
return true;
|
|
}
|
|
# 184 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_13_ina219.ino"
|
|
bool Ina219CommandSensor(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
|
|
Settings.ina219_mode = XdrvMailbox.payload;
|
|
restart_flag = 2;
|
|
}
|
|
Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_13, Settings.ina219_mode);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void Ina219Detect(void)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(ina219_type); i++) {
|
|
uint16_t addr = ina219_addresses[i];
|
|
if (I2cActive(addr)) { continue; }
|
|
if (Ina219SetCalibration(Settings.ina219_mode, addr)) {
|
|
I2cSetActiveFound(addr, ina219_types);
|
|
ina219_type[i] = 1;
|
|
ina219_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ina219EverySecond(void)
|
|
{
|
|
|
|
Ina219Read();
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_INA219_DATA[] PROGMEM =
|
|
"{s}%s " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
|
|
"{s}%s " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
|
|
"{s}%s " D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}";
|
|
#endif
|
|
|
|
void Ina219Show(bool json)
|
|
{
|
|
int num_found=0;
|
|
for (int i=0; i<sizeof(ina219_type); i++)
|
|
if (ina219_type[i] && ina219_valid[i])
|
|
num_found++;
|
|
|
|
int sensor_num = 0;
|
|
for (int i=0; i<sizeof(ina219_type); i++) {
|
|
if (!ina219_type[i] || !ina219_valid[i])
|
|
continue;
|
|
sensor_num++;
|
|
|
|
char voltage[16];
|
|
dtostrfd(ina219_voltage[i], Settings.flag2.voltage_resolution, voltage);
|
|
char current[16];
|
|
dtostrfd(ina219_current[i], Settings.flag2.current_resolution, current);
|
|
char power[16];
|
|
dtostrfd(ina219_voltage[i] * ina219_current[i], Settings.flag2.wattage_resolution, power);
|
|
char name[16];
|
|
if (num_found>1)
|
|
snprintf_P(name, sizeof(name), PSTR("%s%c%d"), ina219_types, IndexSeparator(), sensor_num);
|
|
else
|
|
snprintf_P(name, sizeof(name), PSTR("%s"), ina219_types);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%02x,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"),
|
|
name, ina219_addresses[i], voltage, current, power);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_VOLTAGE, voltage);
|
|
DomoticzSensor(DZ_CURRENT, current);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_INA219_DATA, name, voltage, name, current, name, power);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns13(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_14)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Ina219Detect();
|
|
}
|
|
else if (ina219_count) {
|
|
switch (function) {
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_13 == XdrvMailbox.index) {
|
|
result = Ina219CommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
Ina219EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Ina219Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Ina219Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_14_sht3x.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_14_sht3x.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_SHT3X
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_14 14
|
|
#define XI2C_15 15
|
|
|
|
#define SHT3X_ADDR_GND 0x44
|
|
#define SHT3X_ADDR_VDD 0x45
|
|
#define SHTC3_ADDR 0x70
|
|
|
|
#define SHT3X_MAX_SENSORS 3
|
|
|
|
const char kShtTypes[] PROGMEM = "SHT3X|SHT3X|SHTC3";
|
|
uint8_t sht3x_addresses[] = { SHT3X_ADDR_GND, SHT3X_ADDR_VDD, SHTC3_ADDR };
|
|
|
|
uint8_t sht3x_count = 0;
|
|
struct SHT3XSTRUCT {
|
|
uint8_t address;
|
|
char types[6];
|
|
} sht3x_sensors[SHT3X_MAX_SENSORS];
|
|
|
|
bool Sht3xRead(float &t, float &h, uint8_t sht3x_address)
|
|
{
|
|
unsigned int data[6];
|
|
|
|
t = NAN;
|
|
h = NAN;
|
|
|
|
Wire.beginTransmission(sht3x_address);
|
|
if (SHTC3_ADDR == sht3x_address) {
|
|
Wire.write(0x35);
|
|
Wire.write(0x17);
|
|
Wire.endTransmission();
|
|
Wire.beginTransmission(sht3x_address);
|
|
Wire.write(0x78);
|
|
Wire.write(0x66);
|
|
} else {
|
|
Wire.write(0x2C);
|
|
Wire.write(0x06);
|
|
}
|
|
if (Wire.endTransmission() != 0) {
|
|
return false;
|
|
}
|
|
delay(30);
|
|
Wire.requestFrom(sht3x_address, (uint8_t)6);
|
|
for (uint32_t i = 0; i < 6; i++) {
|
|
data[i] = Wire.read();
|
|
};
|
|
t = ConvertTemp((float)((((data[0] << 8) | data[1]) * 175) / 65535.0) - 45);
|
|
h = ConvertHumidity((float)((((data[3] << 8) | data[4]) * 100) / 65535.0));
|
|
return (!isnan(t) && !isnan(h) && (h != 0));
|
|
}
|
|
|
|
|
|
|
|
void Sht3xDetect(void)
|
|
{
|
|
for (uint32_t i = 0; i < SHT3X_MAX_SENSORS; i++) {
|
|
if (I2cActive(sht3x_addresses[i])) { continue; }
|
|
float t;
|
|
float h;
|
|
if (Sht3xRead(t, h, sht3x_addresses[i])) {
|
|
sht3x_sensors[sht3x_count].address = sht3x_addresses[i];
|
|
GetTextIndexed(sht3x_sensors[sht3x_count].types, sizeof(sht3x_sensors[sht3x_count].types), i, kShtTypes);
|
|
I2cSetActiveFound(sht3x_sensors[sht3x_count].address, sht3x_sensors[sht3x_count].types);
|
|
sht3x_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Sht3xShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < sht3x_count; i++) {
|
|
float t;
|
|
float h;
|
|
if (Sht3xRead(t, h, sht3x_sensors[i].address)) {
|
|
char temperature[33];
|
|
dtostrfd(t, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(h, Settings.flag2.humidity_resolution, humidity);
|
|
char types[11];
|
|
snprintf_P(types, sizeof(types), PSTR("%s%c0x%02X"), sht3x_sensors[i].types, IndexSeparator(), sht3x_sensors[i].address);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, types, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && (0 == i)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, t);
|
|
KnxSensor(KNX_HUMIDITY, h);
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, types, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns14(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_15)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Sht3xDetect();
|
|
}
|
|
else if (sht3x_count) {
|
|
switch (function) {
|
|
case FUNC_JSON_APPEND:
|
|
Sht3xShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Sht3xShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
|
|
#ifdef USE_MHZ19
|
|
# 33 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
|
|
#define XSNS_15 15
|
|
|
|
enum MhzFilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FILTER_FAST, MHZ19_FILTER_MEDIUM, MHZ19_FILTER_SLOW};
|
|
|
|
#define MHZ19_FILTER_OPTION MHZ19_FILTER_FAST
|
|
# 58 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
|
|
#include <TasmotaSerial.h>
|
|
|
|
#ifndef CO2_LOW
|
|
#define CO2_LOW 800
|
|
#endif
|
|
#ifndef CO2_HIGH
|
|
#define CO2_HIGH 1200
|
|
#endif
|
|
|
|
#define MHZ19_READ_TIMEOUT 400
|
|
#define MHZ19_RETRY_COUNT 8
|
|
|
|
TasmotaSerial *MhzSerial;
|
|
|
|
const char kMhzModels[] PROGMEM = "|B";
|
|
|
|
const char ABC_ENABLED[] = "ABC is Enabled";
|
|
const char ABC_DISABLED[] = "ABC is Disabled";
|
|
|
|
enum MhzCommands { MHZ_CMND_READPPM, MHZ_CMND_ABCENABLE, MHZ_CMND_ABCDISABLE, MHZ_CMND_ZEROPOINT, MHZ_CMND_RESET, MHZ_CMND_RANGE_1000, MHZ_CMND_RANGE_2000, MHZ_CMND_RANGE_3000, MHZ_CMND_RANGE_5000 };
|
|
const uint8_t kMhzCommands[][4] PROGMEM = {
|
|
|
|
{0x86,0x00,0x00,0x00},
|
|
{0x79,0xA0,0x00,0x00},
|
|
{0x79,0x00,0x00,0x00},
|
|
{0x87,0x00,0x00,0x00},
|
|
{0x8D,0x00,0x00,0x00},
|
|
{0x99,0x00,0x03,0xE8},
|
|
{0x99,0x00,0x07,0xD0},
|
|
{0x99,0x00,0x0B,0xB8},
|
|
{0x99,0x00,0x13,0x88}};
|
|
|
|
uint8_t mhz_type = 1;
|
|
uint16_t mhz_last_ppm = 0;
|
|
uint8_t mhz_filter = MHZ19_FILTER_OPTION;
|
|
bool mhz_abc_must_apply = false;
|
|
|
|
float mhz_temperature = 0;
|
|
uint8_t mhz_retry = MHZ19_RETRY_COUNT;
|
|
uint8_t mhz_received = 0;
|
|
uint8_t mhz_state = 0;
|
|
|
|
|
|
|
|
uint8_t MhzCalculateChecksum(uint8_t *array)
|
|
{
|
|
uint8_t checksum = 0;
|
|
for (uint32_t i = 1; i < 8; i++) {
|
|
checksum += array[i];
|
|
}
|
|
checksum = 255 - checksum;
|
|
return (checksum +1);
|
|
}
|
|
|
|
size_t MhzSendCmd(uint8_t command_id)
|
|
{
|
|
uint8_t mhz_send[9] = { 0 };
|
|
|
|
mhz_send[0] = 0xFF;
|
|
mhz_send[1] = 0x01;
|
|
memcpy_P(&mhz_send[2], kMhzCommands[command_id], sizeof(uint16_t));
|
|
|
|
|
|
|
|
|
|
memcpy_P(&mhz_send[6], kMhzCommands[command_id] + sizeof(uint16_t), sizeof(uint16_t));
|
|
mhz_send[8] = MhzCalculateChecksum(mhz_send);
|
|
|
|
|
|
|
|
return MhzSerial->write(mhz_send, sizeof(mhz_send));
|
|
}
|
|
|
|
|
|
|
|
bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s)
|
|
{
|
|
if (1 == s) {
|
|
return false;
|
|
}
|
|
if (mhz_last_ppm < 400 || mhz_last_ppm > 5000) {
|
|
|
|
|
|
mhz_last_ppm = ppm;
|
|
return true;
|
|
}
|
|
int32_t difference = ppm - mhz_last_ppm;
|
|
if (s > 0 && s < 64 && mhz_filter != MHZ19_FILTER_OFF) {
|
|
# 154 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
|
|
difference *= s;
|
|
difference /= 64;
|
|
}
|
|
if (MHZ19_FILTER_OFF == mhz_filter) {
|
|
if (s != 0 && s != 64) {
|
|
return false;
|
|
}
|
|
} else {
|
|
difference >>= (mhz_filter -1);
|
|
}
|
|
mhz_last_ppm = static_cast<uint16_t>(mhz_last_ppm + difference);
|
|
return true;
|
|
}
|
|
|
|
void MhzEverySecond(void)
|
|
{
|
|
mhz_state++;
|
|
if (8 == mhz_state) {
|
|
mhz_state = 0;
|
|
|
|
if (mhz_retry) {
|
|
mhz_retry--;
|
|
if (!mhz_retry) {
|
|
mhz_last_ppm = 0;
|
|
mhz_temperature = 0;
|
|
}
|
|
}
|
|
|
|
MhzSerial->flush();
|
|
MhzSendCmd(MHZ_CMND_READPPM);
|
|
mhz_received = 0;
|
|
}
|
|
|
|
if ((mhz_state > 2) && !mhz_received) {
|
|
uint8_t mhz_response[9];
|
|
|
|
unsigned long start = millis();
|
|
uint8_t counter = 0;
|
|
while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
|
|
if (MhzSerial->available() > 0) {
|
|
mhz_response[counter++] = MhzSerial->read();
|
|
} else {
|
|
delay(5);
|
|
}
|
|
}
|
|
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, mhz_response, counter);
|
|
|
|
if (counter < 9) {
|
|
|
|
return;
|
|
}
|
|
|
|
uint8_t crc = MhzCalculateChecksum(mhz_response);
|
|
if (mhz_response[8] != crc) {
|
|
|
|
return;
|
|
}
|
|
if (0xFF != mhz_response[0] || 0x86 != mhz_response[1]) {
|
|
|
|
return;
|
|
}
|
|
|
|
mhz_received = 1;
|
|
|
|
uint16_t u = (mhz_response[6] << 8) | mhz_response[7];
|
|
if (15000 == u) {
|
|
if (Settings.SensorBits1.mhz19b_abc_disable) {
|
|
|
|
|
|
mhz_abc_must_apply = true;
|
|
}
|
|
} else {
|
|
uint16_t ppm = (mhz_response[2] << 8) | mhz_response[3];
|
|
mhz_temperature = ConvertTemp((float)mhz_response[4] - 40);
|
|
uint8_t s = mhz_response[5];
|
|
mhz_type = (s) ? 1 : 2;
|
|
if (MhzCheckAndApplyFilter(ppm, s)) {
|
|
mhz_retry = MHZ19_RETRY_COUNT;
|
|
LightSetSignal(CO2_LOW, CO2_HIGH, mhz_last_ppm);
|
|
|
|
if (0 == s || 64 == s) {
|
|
if (mhz_abc_must_apply) {
|
|
mhz_abc_must_apply = false;
|
|
if (!Settings.SensorBits1.mhz19b_abc_disable) {
|
|
MhzSendCmd(MHZ_CMND_ABCENABLE);
|
|
} else {
|
|
MhzSendCmd(MHZ_CMND_ABCDISABLE);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
# 266 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_15_mhz19.ino"
|
|
#define D_JSON_RANGE_1000 "1000 ppm range"
|
|
#define D_JSON_RANGE_2000 "2000 ppm range"
|
|
#define D_JSON_RANGE_3000 "3000 ppm range"
|
|
#define D_JSON_RANGE_5000 "5000 ppm range"
|
|
|
|
bool MhzCommandSensor(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
Settings.SensorBits1.mhz19b_abc_disable = true;
|
|
MhzSendCmd(MHZ_CMND_ABCDISABLE);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED);
|
|
break;
|
|
case 1:
|
|
Settings.SensorBits1.mhz19b_abc_disable = false;
|
|
MhzSendCmd(MHZ_CMND_ABCENABLE);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED);
|
|
break;
|
|
case 2:
|
|
MhzSendCmd(MHZ_CMND_ZEROPOINT);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_ZERO_POINT_CALIBRATION);
|
|
break;
|
|
case 9:
|
|
MhzSendCmd(MHZ_CMND_RESET);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RESET);
|
|
break;
|
|
case 1000:
|
|
MhzSendCmd(MHZ_CMND_RANGE_1000);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_1000);
|
|
break;
|
|
case 2000:
|
|
MhzSendCmd(MHZ_CMND_RANGE_2000);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_2000);
|
|
break;
|
|
case 3000:
|
|
MhzSendCmd(MHZ_CMND_RANGE_3000);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_3000);
|
|
break;
|
|
case 5000:
|
|
MhzSendCmd(MHZ_CMND_RANGE_5000);
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, D_JSON_RANGE_5000);
|
|
break;
|
|
default:
|
|
if (!Settings.SensorBits1.mhz19b_abc_disable) {
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_ENABLED);
|
|
} else {
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_15, ABC_DISABLED);
|
|
}
|
|
}
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
void MhzInit(void)
|
|
{
|
|
mhz_type = 0;
|
|
if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) {
|
|
MhzSerial = new TasmotaSerial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD], 1);
|
|
if (MhzSerial->begin(9600)) {
|
|
if (MhzSerial->hardwareSerial()) { ClaimSerial(); }
|
|
mhz_type = 1;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void MhzShow(bool json)
|
|
{
|
|
char types[7] = "MHZ19B";
|
|
char temperature[33];
|
|
dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char model[3];
|
|
GetTextIndexed(model, sizeof(model), mhz_type -1, kMhzModels);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s}"), types, model, mhz_last_ppm, temperature);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm);
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_CO2, types, mhz_last_ppm);
|
|
WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns15(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (mhz_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
MhzInit();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
MhzEverySecond();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_15 == XdrvMailbox.index) {
|
|
result = MhzCommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MhzShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MhzShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_TSL2561
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_16_tsl2561.ino"
|
|
#define XSNS_16 16
|
|
#define XI2C_16 16
|
|
|
|
#include <Tsl2561Util.h>
|
|
|
|
Tsl2561 Tsl(Wire);
|
|
|
|
uint8_t tsl2561_type = 0;
|
|
uint8_t tsl2561_valid = 0;
|
|
uint32_t tsl2561_milliLux = 0;
|
|
char tsl2561_types[] = "TSL2561";
|
|
|
|
bool Tsl2561Read(void)
|
|
{
|
|
if (tsl2561_valid) { tsl2561_valid--; }
|
|
|
|
uint8_t id;
|
|
bool gain;
|
|
Tsl2561::exposure_t exposure;
|
|
uint16_t scaledFull, scaledIr;
|
|
uint32_t full, ir;
|
|
|
|
if (Tsl.on()) {
|
|
if (Tsl.id(id)
|
|
&& Tsl2561Util::autoGain(Tsl, gain, exposure, scaledFull, scaledIr)
|
|
&& Tsl2561Util::normalizedLuminosity(gain, exposure, full = scaledFull, ir = scaledIr)
|
|
&& Tsl2561Util::milliLux(full, ir, tsl2561_milliLux, Tsl2561::packageCS(id))) {
|
|
} else{
|
|
tsl2561_milliLux = 0;
|
|
}
|
|
}
|
|
tsl2561_valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
void Tsl2561Detect(void)
|
|
{
|
|
if (I2cSetDevice(0x29) || I2cSetDevice(0x39) || I2cSetDevice(0x49)) {
|
|
uint8_t id;
|
|
Tsl.begin();
|
|
if (!Tsl.id(id)) return;
|
|
if (Tsl.on()) {
|
|
tsl2561_type = 1;
|
|
I2cSetActiveFound(Tsl.address(), tsl2561_types);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Tsl2561EverySecond(void)
|
|
{
|
|
if (!(uptime %2)) {
|
|
|
|
if (!Tsl2561Read()) {
|
|
AddLogMissed(tsl2561_types, tsl2561_valid);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_TSL2561[] PROGMEM =
|
|
"{s}TSL2561 " D_ILLUMINANCE "{m}%u.%03u " D_UNIT_LUX "{e}";
|
|
#endif
|
|
|
|
void Tsl2561Show(bool json)
|
|
{
|
|
if (tsl2561_valid) {
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"TSL2561\":{\"" D_JSON_ILLUMINANCE "\":%u.%03u}"),
|
|
tsl2561_milliLux / 1000, tsl2561_milliLux % 1000);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, (tsl2561_milliLux + 500) / 1000); }
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TSL2561, tsl2561_milliLux / 1000, tsl2561_milliLux % 1000);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns16(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_16)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Tsl2561Detect();
|
|
}
|
|
else if (tsl2561_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Tsl2561EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Tsl2561Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Tsl2561Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino"
|
|
#ifdef USE_SENSEAIR
|
|
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_17_senseair.ino"
|
|
#define XSNS_17 17
|
|
|
|
#define SENSEAIR_MODBUS_SPEED 9600
|
|
#define SENSEAIR_DEVICE_ADDRESS 0xFE
|
|
#define SENSEAIR_READ_REGISTER 0x04
|
|
|
|
#ifndef CO2_LOW
|
|
#define CO2_LOW 800
|
|
#endif
|
|
#ifndef CO2_HIGH
|
|
#define CO2_HIGH 1200
|
|
#endif
|
|
|
|
#include <TasmotaModbus.h>
|
|
TasmotaModbus *SenseairModbus;
|
|
|
|
const char kSenseairTypes[] PROGMEM = "Kx0|S8";
|
|
|
|
uint8_t senseair_type = 1;
|
|
char senseair_types[7];
|
|
|
|
uint16_t senseair_co2 = 0;
|
|
float senseair_temperature = 0;
|
|
float senseair_humidity = 0;
|
|
|
|
|
|
|
|
const uint8_t start_addresses[] { 0x1A, 0x00, 0x03, 0x04, 0x05, 0x1C, 0x0A };
|
|
|
|
uint8_t senseair_read_state = 0;
|
|
uint8_t senseair_send_retry = 0;
|
|
|
|
void Senseair250ms(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
uint16_t value = 0;
|
|
bool data_ready = SenseairModbus->ReceiveReady();
|
|
|
|
if (data_ready) {
|
|
uint8_t error = SenseairModbus->Receive16BitRegister(&value);
|
|
if (error) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir response error %d"), error);
|
|
} else {
|
|
switch(senseair_read_state) {
|
|
case 0:
|
|
senseair_type = 2;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir type id low %04X"), value);
|
|
break;
|
|
case 1:
|
|
if (value) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir error %04X"), value);
|
|
}
|
|
break;
|
|
case 2:
|
|
senseair_co2 = value;
|
|
LightSetSignal(CO2_LOW, CO2_HIGH, senseair_co2);
|
|
break;
|
|
case 3:
|
|
senseair_temperature = ConvertTemp((float)value / 100);
|
|
break;
|
|
case 4:
|
|
senseair_humidity = ConvertHumidity((float)value / 100);
|
|
break;
|
|
case 5:
|
|
{
|
|
bool relay_state = value >> 8 & 1;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir relay state %d"), relay_state);
|
|
break;
|
|
}
|
|
case 6:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "SenseAir temp adjustment %d"), value);
|
|
break;
|
|
}
|
|
}
|
|
senseair_read_state++;
|
|
if (2 == senseair_type) {
|
|
if (3 == senseair_read_state) {
|
|
senseair_read_state = 1;
|
|
}
|
|
} else {
|
|
if (sizeof(start_addresses) == senseair_read_state) {
|
|
senseair_read_state = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == senseair_send_retry || data_ready) {
|
|
senseair_send_retry = 5;
|
|
SenseairModbus->Send(SENSEAIR_DEVICE_ADDRESS, SENSEAIR_READ_REGISTER, (uint16_t)start_addresses[senseair_read_state], 1);
|
|
} else {
|
|
senseair_send_retry--;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SenseairInit(void)
|
|
{
|
|
senseair_type = 0;
|
|
if ((pin[GPIO_SAIR_RX] < 99) && (pin[GPIO_SAIR_TX] < 99)) {
|
|
SenseairModbus = new TasmotaModbus(pin[GPIO_SAIR_RX], pin[GPIO_SAIR_TX]);
|
|
uint8_t result = SenseairModbus->Begin(SENSEAIR_MODBUS_SPEED);
|
|
if (result) {
|
|
if (2 == result) { ClaimSerial(); }
|
|
senseair_type = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SenseairShow(bool json)
|
|
{
|
|
char temperature[33];
|
|
dtostrfd(senseair_temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(senseair_humidity, Settings.flag2.temperature_resolution, humidity);
|
|
GetTextIndexed(senseair_types, sizeof(senseair_types), senseair_type -1, kSenseairTypes);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_CO2 "\":%d"), senseair_types, senseair_co2);
|
|
if (senseair_type != 2) {
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s"), temperature, humidity);
|
|
}
|
|
ResponseJsonEnd();
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, senseair_co2);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_CO2, senseair_types, senseair_co2);
|
|
if (senseair_type != 2) {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, senseair_types, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, senseair_types, humidity);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns17(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (senseair_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
SenseairInit();
|
|
break;
|
|
case FUNC_EVERY_250_MSECOND:
|
|
Senseair250ms();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
SenseairShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
SenseairShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino"
|
|
#ifdef USE_PMS5003
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_18_pms5003.ino"
|
|
#define XSNS_18 18
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *PmsSerial;
|
|
|
|
uint8_t pms_type = 1;
|
|
uint8_t pms_valid = 0;
|
|
|
|
struct pmsX003data {
|
|
uint16_t framelen;
|
|
uint16_t pm10_standard, pm25_standard, pm100_standard;
|
|
uint16_t pm10_env, pm25_env, pm100_env;
|
|
#ifdef PMS_MODEL_PMS3003
|
|
uint16_t reserved1, reserved2, reserved3;
|
|
#else
|
|
uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
|
|
uint16_t unused;
|
|
#endif
|
|
uint16_t checksum;
|
|
} pms_data;
|
|
|
|
|
|
|
|
bool PmsReadData(void)
|
|
{
|
|
if (! PmsSerial->available()) {
|
|
return false;
|
|
}
|
|
while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) {
|
|
PmsSerial->read();
|
|
}
|
|
#ifdef PMS_MODEL_PMS3003
|
|
if (PmsSerial->available() < 22) {
|
|
#else
|
|
if (PmsSerial->available() < 32) {
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#ifdef PMS_MODEL_PMS3003
|
|
uint8_t buffer[22];
|
|
PmsSerial->readBytes(buffer, 22);
|
|
#else
|
|
uint8_t buffer[32];
|
|
PmsSerial->readBytes(buffer, 32);
|
|
#endif
|
|
uint16_t sum = 0;
|
|
PmsSerial->flush();
|
|
|
|
#ifdef PMS_MODEL_PMS3003
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 22);
|
|
#else
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, 32);
|
|
#endif
|
|
|
|
|
|
#ifdef PMS_MODEL_PMS3003
|
|
for (uint32_t i = 0; i < 20; i++) {
|
|
#else
|
|
for (uint32_t i = 0; i < 30; i++) {
|
|
#endif
|
|
sum += buffer[i];
|
|
}
|
|
|
|
#ifdef PMS_MODEL_PMS3003
|
|
uint16_t buffer_u16[10];
|
|
for (uint32_t i = 0; i < 10; i++) {
|
|
#else
|
|
uint16_t buffer_u16[15];
|
|
for (uint32_t i = 0; i < 15; i++) {
|
|
#endif
|
|
buffer_u16[i] = buffer[2 + i*2 + 1];
|
|
buffer_u16[i] += (buffer[2 + i*2] << 8);
|
|
}
|
|
#ifdef PMS_MODEL_PMS3003
|
|
if (sum != buffer_u16[9]) {
|
|
#else
|
|
if (sum != buffer_u16[14]) {
|
|
#endif
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("PMS: " D_CHECKSUM_FAILURE));
|
|
return false;
|
|
}
|
|
|
|
#ifdef PMS_MODEL_PMS3003
|
|
memcpy((void *)&pms_data, (void *)buffer_u16, 20);
|
|
#else
|
|
memcpy((void *)&pms_data, (void *)buffer_u16, 30);
|
|
#endif
|
|
pms_valid = 10;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void PmsSecond(void)
|
|
{
|
|
if (PmsReadData()) {
|
|
pms_valid = 10;
|
|
} else {
|
|
if (pms_valid) {
|
|
pms_valid--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void PmsInit(void)
|
|
{
|
|
pms_type = 0;
|
|
if (pin[GPIO_PMS5003] < 99) {
|
|
PmsSerial = new TasmotaSerial(pin[GPIO_PMS5003], -1, 1);
|
|
if (PmsSerial->begin(9600)) {
|
|
if (PmsSerial->hardwareSerial()) { ClaimSerial(); }
|
|
pms_type = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef PMS_MODEL_PMS3003
|
|
const char HTTP_PMS3003_SNS[] PROGMEM =
|
|
|
|
|
|
|
|
"{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}PMS3003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}";
|
|
#else
|
|
const char HTTP_PMS5003_SNS[] PROGMEM =
|
|
|
|
|
|
|
|
"{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}PMS5003 " D_PARTICALS_BEYOND " 0.3 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
|
|
"{s}PMS5003 " D_PARTICALS_BEYOND " 0.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
|
|
"{s}PMS5003 " D_PARTICALS_BEYOND " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
|
|
"{s}PMS5003 " D_PARTICALS_BEYOND " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
|
|
"{s}PMS5003 " D_PARTICALS_BEYOND " 5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
|
|
"{s}PMS5003 " D_PARTICALS_BEYOND " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}";
|
|
#endif
|
|
#endif
|
|
|
|
void PmsShow(bool json)
|
|
{
|
|
if (pms_valid) {
|
|
if (json) {
|
|
#ifdef PMS_MODEL_PMS3003
|
|
ResponseAppend_P(PSTR(",\"PMS3003\":{\"CF1\":%d,\"CF2.5\":%d,\"CF10\":%d,\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d}"),
|
|
pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard,
|
|
pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env);
|
|
#else
|
|
ResponseAppend_P(PSTR(",\"PMS5003\":{\"CF1\":%d,\"CF2.5\":%d,\"CF10\":%d,\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d,\"PB0.3\":%d,\"PB0.5\":%d,\"PB1\":%d,\"PB2.5\":%d,\"PB5\":%d,\"PB10\":%d}"),
|
|
pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard,
|
|
pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env,
|
|
pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um);
|
|
#endif
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_COUNT, pms_data.pm10_env);
|
|
DomoticzSensor(DZ_VOLTAGE, pms_data.pm25_env);
|
|
DomoticzSensor(DZ_CURRENT, pms_data.pm100_env);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
|
|
#ifdef PMS_MODEL_PMS3003
|
|
WSContentSend_PD(HTTP_PMS3003_SNS,
|
|
|
|
pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env);
|
|
#else
|
|
WSContentSend_PD(HTTP_PMS5003_SNS,
|
|
|
|
pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env,
|
|
pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um);
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns18(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (pms_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
PmsInit();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
PmsSecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
PmsShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
PmsShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_19_mgs.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_19_mgs.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MGS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_19 19
|
|
#define XI2C_17 17
|
|
|
|
#ifndef MGS_SENSOR_ADDR
|
|
#define MGS_SENSOR_ADDR 0x04
|
|
#endif
|
|
|
|
#include "MutichannelGasSensor.h"
|
|
|
|
bool mgs_detected = false;
|
|
|
|
void MGSInit(void) {
|
|
gas.begin(MGS_SENSOR_ADDR);
|
|
}
|
|
|
|
void MGSPrepare(void)
|
|
{
|
|
if (I2cActive(MGS_SENSOR_ADDR)) { return; }
|
|
|
|
gas.begin(MGS_SENSOR_ADDR);
|
|
if (!gas.isError()) {
|
|
I2cSetActiveFound(MGS_SENSOR_ADDR, "MultiGas");
|
|
mgs_detected = true;
|
|
}
|
|
}
|
|
|
|
char* measure_gas(int gas_type, char* buffer)
|
|
{
|
|
float f = gas.calcGas(gas_type);
|
|
dtostrfd(f, 2, buffer);
|
|
return buffer;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_MGS_GAS[] PROGMEM = "{s}MGS %s{m}%s " D_UNIT_PARTS_PER_MILLION "{e}";
|
|
#endif
|
|
|
|
void MGSShow(bool json)
|
|
{
|
|
char buffer[33];
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"MGS\":{\"NH3\":%s"), measure_gas(NH3, buffer));
|
|
ResponseAppend_P(PSTR(",\"CO\":%s"), measure_gas(CO, buffer));
|
|
ResponseAppend_P(PSTR(",\"NO2\":%s"), measure_gas(NO2, buffer));
|
|
ResponseAppend_P(PSTR(",\"C3H8\":%s"), measure_gas(C3H8, buffer));
|
|
ResponseAppend_P(PSTR(",\"C4H10\":%s"), measure_gas(C4H10, buffer));
|
|
ResponseAppend_P(PSTR(",\"CH4\":%s"), measure_gas(GAS_CH4, buffer));
|
|
ResponseAppend_P(PSTR(",\"H2\":%s"), measure_gas(H2, buffer));
|
|
ResponseAppend_P(PSTR(",\"C2H5OH\":%s}"), measure_gas(C2H5OH, buffer));
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_MGS_GAS, "NH3", measure_gas(NH3, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "CO", measure_gas(CO, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "NO2", measure_gas(NO2, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "C3H8", measure_gas(C3H8, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "C4H10", measure_gas(C4H10, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "CH4", measure_gas(GAS_CH4, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "H2", measure_gas(H2, buffer));
|
|
WSContentSend_PD(HTTP_MGS_GAS, "C2H5OH", measure_gas(C2H5OH, buffer));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns19(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_17)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
MGSPrepare();
|
|
}
|
|
else if (mgs_detected) {
|
|
switch (function) {
|
|
case FUNC_JSON_APPEND:
|
|
MGSShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MGSShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino"
|
|
#ifdef USE_NOVA_SDS
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_20_novasds.ino"
|
|
#define XSNS_20 20
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
#ifndef STARTING_OFFSET
|
|
#define STARTING_OFFSET 30
|
|
#endif
|
|
#if STARTING_OFFSET < 10
|
|
#error "Please set STARTING_OFFSET >= 10"
|
|
#endif
|
|
#ifndef NOVA_SDS_RECDATA_TIMEOUT
|
|
#define NOVA_SDS_RECDATA_TIMEOUT 150
|
|
#endif
|
|
#ifndef NOVA_SDS_DEVICE_ID
|
|
#define NOVA_SDS_DEVICE_ID 0xFFFF
|
|
#endif
|
|
|
|
TasmotaSerial *NovaSdsSerial;
|
|
|
|
uint8_t novasds_type = 1;
|
|
uint8_t novasds_valid = 0;
|
|
uint8_t cont_mode = 1;
|
|
|
|
struct sds011data {
|
|
uint16_t pm100;
|
|
uint16_t pm25;
|
|
} novasds_data;
|
|
uint16_t pm100_sum;
|
|
uint16_t pm25_sum;
|
|
|
|
|
|
#define NOVA_SDS_REPORTING_MODE 2
|
|
#define NOVA_SDS_QUERY_DATA 4
|
|
#define NOVA_SDS_SET_DEVICE_ID 5
|
|
#define NOVA_SDS_SLEEP_AND_WORK 6
|
|
#define NOVA_SDS_WORKING_PERIOD 8
|
|
#define NOVA_SDS_CHECK_FIRMWARE_VER 7
|
|
#define NOVA_SDS_QUERY_MODE 0
|
|
#define NOVA_SDS_SET_MODE 1
|
|
#define NOVA_SDS_REPORT_ACTIVE 0
|
|
#define NOVA_SDS_REPORT_QUERY 1
|
|
#define NOVA_SDS_SLEEP 0
|
|
#define NOVA_SDS_WORK 1
|
|
|
|
|
|
bool NovaSdsCommand(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint16_t sensorid, uint8_t *buffer)
|
|
{
|
|
uint8_t novasds_cmnd[19] = {0xAA, 0xB4, byte1, byte2, byte3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (uint8_t)(sensorid & 0xFF), (uint8_t)((sensorid>>8) & 0xFF), 0x00, 0xAB};
|
|
|
|
|
|
for (uint32_t i = 2; i < 17; i++) {
|
|
novasds_cmnd[17] += novasds_cmnd[i];
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
NovaSdsSerial->write(novasds_cmnd, sizeof(novasds_cmnd));
|
|
NovaSdsSerial->flush();
|
|
|
|
|
|
unsigned long cmndtime = millis();
|
|
while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( ! NovaSdsSerial->available() ) );
|
|
if ( ! NovaSdsSerial->available() ) {
|
|
|
|
return false;
|
|
}
|
|
uint8_t recbuf[10];
|
|
memset(recbuf, 0, sizeof(recbuf));
|
|
|
|
while ( (TimePassedSince(cmndtime) < NOVA_SDS_RECDATA_TIMEOUT) && ( NovaSdsSerial->available() > 0) && (0xAA != (recbuf[0] = NovaSdsSerial->read())) );
|
|
if ( 0xAA != recbuf[0] ) {
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
NovaSdsSerial->readBytes(&recbuf[1], 9);
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, recbuf, sizeof(recbuf));
|
|
|
|
if ( nullptr != buffer ) {
|
|
|
|
memcpy(buffer, recbuf, sizeof(recbuf));
|
|
}
|
|
|
|
|
|
if ((0xAB != recbuf[9] ) || (recbuf[8] != ((recbuf[2] + recbuf[3] + recbuf[4] + recbuf[5] + recbuf[6] + recbuf[7]) & 0xFF))) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("SDS: " D_CHECKSUM_FAILURE));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NovaSdsSetWorkPeriod(void)
|
|
{
|
|
|
|
NovaSdsCommand(NOVA_SDS_WORKING_PERIOD, NOVA_SDS_SET_MODE, 0, NOVA_SDS_DEVICE_ID, nullptr);
|
|
|
|
NovaSdsCommand(NOVA_SDS_REPORTING_MODE, NOVA_SDS_SET_MODE, NOVA_SDS_REPORT_QUERY, NOVA_SDS_DEVICE_ID, nullptr);
|
|
}
|
|
|
|
bool NovaSdsReadData(void)
|
|
{
|
|
uint8_t d[10];
|
|
if ( ! NovaSdsCommand(NOVA_SDS_QUERY_DATA, 0, 0, NOVA_SDS_DEVICE_ID, d) ) {
|
|
return false;
|
|
}
|
|
novasds_data.pm25 = (d[2] + 256 * d[3]);
|
|
novasds_data.pm100 = (d[4] + 256 * d[5]);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void NovaSdsSecond(void)
|
|
{
|
|
if (!novasds_valid)
|
|
{
|
|
NovaSdsSetWorkPeriod();
|
|
novasds_valid=1;
|
|
}
|
|
if((Settings.tele_period - Settings.novasds_startingoffset <= 0))
|
|
{
|
|
if(!cont_mode)
|
|
{
|
|
cont_mode = 1;
|
|
NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_WORK, NOVA_SDS_DEVICE_ID, nullptr);
|
|
}
|
|
}
|
|
else
|
|
cont_mode = 0;
|
|
|
|
if(tele_period == Settings.tele_period - Settings.novasds_startingoffset && !cont_mode)
|
|
{
|
|
NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_WORK, NOVA_SDS_DEVICE_ID, nullptr);
|
|
}
|
|
if(tele_period >= Settings.tele_period-5 && tele_period <= Settings.tele_period-2)
|
|
{
|
|
if(!(NovaSdsReadData())) novasds_valid=0;
|
|
pm100_sum += novasds_data.pm100;
|
|
pm25_sum += novasds_data.pm25;
|
|
}
|
|
if(tele_period == Settings.tele_period-1)
|
|
{
|
|
novasds_data.pm100 = pm100_sum >> 2;
|
|
novasds_data.pm25 = pm25_sum >> 2;
|
|
if(!cont_mode)
|
|
NovaSdsCommand(NOVA_SDS_SLEEP_AND_WORK, NOVA_SDS_SET_MODE, NOVA_SDS_SLEEP, NOVA_SDS_DEVICE_ID, nullptr);
|
|
pm100_sum = pm25_sum = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool NovaSdsCommandSensor(void)
|
|
{
|
|
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 256)) {
|
|
if( XdrvMailbox.payload < 10 ) Settings.novasds_startingoffset = 10;
|
|
else Settings.novasds_startingoffset = XdrvMailbox.payload;
|
|
}
|
|
Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_20, Settings.novasds_startingoffset);
|
|
|
|
return true;
|
|
}
|
|
|
|
void NovaSdsInit(void)
|
|
{
|
|
novasds_type = 0;
|
|
if (pin[GPIO_SDS0X1_RX] < 99 && pin[GPIO_SDS0X1_TX] < 99) {
|
|
NovaSdsSerial = new TasmotaSerial(pin[GPIO_SDS0X1_RX], pin[GPIO_SDS0X1_TX], 1);
|
|
if (NovaSdsSerial->begin(9600)) {
|
|
if (NovaSdsSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
novasds_type = 1;
|
|
NovaSdsSetWorkPeriod();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SDS0X1_SNS[] PROGMEM =
|
|
"{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}SDS0X1 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}";
|
|
#endif
|
|
|
|
void NovaSdsShow(bool json)
|
|
{
|
|
if (novasds_valid) {
|
|
float pm10f = (float)(novasds_data.pm100) / 10.0f;
|
|
float pm2_5f = (float)(novasds_data.pm25) / 10.0f;
|
|
char pm10[33];
|
|
dtostrfd(pm10f, 1, pm10);
|
|
char pm2_5[33];
|
|
dtostrfd(pm2_5f, 1, pm2_5);
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"SDS0X1\":{\"PM2.5\":%s,\"PM10\":%s}"), pm2_5, pm10);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_VOLTAGE, pm2_5);
|
|
DomoticzSensor(DZ_CURRENT, pm10);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SDS0X1_SNS, pm2_5, pm10);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns20(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (novasds_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
NovaSdsInit();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
NovaSdsSecond();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_20 == XdrvMailbox.index) {
|
|
result = NovaSdsCommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
NovaSdsShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
NovaSdsShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_SGP30
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_21_sgp30.ino"
|
|
#define XSNS_21 21
|
|
#define XI2C_18 18
|
|
|
|
#define SGP30_ADDRESS 0x58
|
|
|
|
#include "Adafruit_SGP30.h"
|
|
Adafruit_SGP30 sgp;
|
|
|
|
bool sgp30_type = false;
|
|
bool sgp30_ready = false;
|
|
float sgp30_abshum;
|
|
|
|
|
|
|
|
void sgp30_Init(void)
|
|
{
|
|
if (I2cActive(SGP30_ADDRESS)) { return; }
|
|
|
|
if (sgp.begin()) {
|
|
sgp30_type = true;
|
|
|
|
I2cSetActiveFound(SGP30_ADDRESS, "SGP30");
|
|
}
|
|
}
|
|
|
|
|
|
#define POW_FUNC FastPrecisePow
|
|
|
|
float sgp30_AbsoluteHumidity(float temperature, float humidity,char tempUnit) {
|
|
|
|
|
|
|
|
|
|
|
|
float temp = NAN;
|
|
const float mw = 18.01534;
|
|
const float r = 8.31447215;
|
|
|
|
if (isnan(temperature) || isnan(humidity) ) {
|
|
return NAN;
|
|
}
|
|
|
|
if (tempUnit != 'C') {
|
|
temperature = (temperature - 32.0) * (5.0 / 9.0);
|
|
}
|
|
|
|
temp = POW_FUNC(2.718281828, (17.67 * temperature) / (temperature + 243.5));
|
|
|
|
|
|
return (6.112 * temp * humidity * mw) / ((273.15 + temperature) * r);
|
|
}
|
|
|
|
#define SAVE_PERIOD 30
|
|
|
|
void Sgp30Update(void)
|
|
{
|
|
sgp30_ready = false;
|
|
if (!sgp.IAQmeasure()) {
|
|
return;
|
|
}
|
|
if (global_update && (global_humidity > 0) && (global_temperature != 9999)) {
|
|
|
|
sgp30_abshum=sgp30_AbsoluteHumidity(global_temperature,global_humidity,TempUnit());
|
|
sgp.setHumidity(sgp30_abshum*1000);
|
|
}
|
|
sgp30_ready = true;
|
|
|
|
|
|
if (!(uptime%SAVE_PERIOD)) {
|
|
|
|
uint16_t TVOC_base;
|
|
uint16_t eCO2_base;
|
|
|
|
if (!sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) return;
|
|
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_SGP30[] PROGMEM =
|
|
"{s}SGP30 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"
|
|
"{s}SGP30 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}";
|
|
const char HTTP_SNS_AHUM[] PROGMEM = "{s}SGP30 Abs Humidity{m}%s g/m3{e}";
|
|
#endif
|
|
|
|
#define D_JSON_AHUM "aHumidity"
|
|
|
|
void Sgp30Show(bool json)
|
|
{
|
|
if (sgp30_ready) {
|
|
char abs_hum[33];
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC);
|
|
if (global_update && global_humidity>0 && global_temperature!=9999) {
|
|
|
|
dtostrfd(sgp30_abshum,4,abs_hum);
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum);
|
|
}
|
|
ResponseJsonEnd();
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, sgp.eCO2);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_SGP30, sgp.eCO2, sgp.TVOC);
|
|
if (global_update) {
|
|
WSContentSend_PD(HTTP_SNS_AHUM, abs_hum);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns21(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_18)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
sgp30_Init();
|
|
}
|
|
else if (sgp30_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Sgp30Update();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Sgp30Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Sgp30Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino"
|
|
#ifdef USE_SR04
|
|
|
|
#include <NewPing.h>
|
|
#include <TasmotaSerial.h>
|
|
# 32 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_22_sr04.ino"
|
|
#define XSNS_22 22
|
|
|
|
uint8_t sr04_type = 1;
|
|
int sr04_echo_pin = 0;
|
|
int sr04_trig_pin = 0;
|
|
real64_t distance;
|
|
|
|
NewPing* sonar = nullptr;
|
|
TasmotaSerial* sonar_serial = nullptr;
|
|
|
|
|
|
|
|
uint8_t Sr04TModeDetect(void)
|
|
{
|
|
sr04_type = 0;
|
|
if (pin[GPIO_SR04_ECHO]>=99) return sr04_type;
|
|
|
|
sr04_echo_pin = pin[GPIO_SR04_ECHO];
|
|
sr04_trig_pin = (pin[GPIO_SR04_TRIG] < 99) ? pin[GPIO_SR04_TRIG] : -1;
|
|
sonar_serial = new TasmotaSerial(sr04_echo_pin, sr04_trig_pin, 1);
|
|
|
|
if (sonar_serial->begin(9600,1)) {
|
|
DEBUG_SENSOR_LOG(PSTR("SR04: Detect mode"));
|
|
|
|
if (sr04_trig_pin!=-1) {
|
|
sr04_type = (Sr04TMiddleValue(Sr04TMode3Distance(),Sr04TMode3Distance(),Sr04TMode3Distance())!=NO_ECHO)?3:1;
|
|
} else {
|
|
sr04_type = 2;
|
|
}
|
|
} else {
|
|
sr04_type = 1;
|
|
}
|
|
|
|
if (sr04_type < 2) {
|
|
delete sonar_serial;
|
|
sonar_serial = nullptr;
|
|
sonar = new NewPing(sr04_trig_pin, sr04_echo_pin, 300);
|
|
} else {
|
|
if (sonar_serial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO,PSTR("SR04: Mode %d"), sr04_type);
|
|
return sr04_type;
|
|
}
|
|
|
|
uint16_t Sr04TMiddleValue(uint16_t first, uint16_t second, uint16_t third)
|
|
{
|
|
uint16_t ret = first;
|
|
if (first > second) {
|
|
first = second;
|
|
second = ret;
|
|
}
|
|
|
|
if (third < first) {
|
|
return first;
|
|
} else if (third > second) {
|
|
return second;
|
|
} else {
|
|
return third;
|
|
}
|
|
}
|
|
|
|
uint16_t Sr04TMode3Distance() {
|
|
|
|
sonar_serial->write(0x55);
|
|
sonar_serial->flush();
|
|
|
|
return Sr04TMode2Distance();
|
|
}
|
|
|
|
uint16_t Sr04TMode2Distance(void)
|
|
{
|
|
sonar_serial->setTimeout(300);
|
|
const char startByte = 0xff;
|
|
|
|
if (!sonar_serial->find(startByte)) {
|
|
|
|
return NO_ECHO;
|
|
}
|
|
|
|
delay(5);
|
|
|
|
uint8_t crc = sonar_serial->read();
|
|
|
|
uint16_t distance = ((uint16_t)crc) << 8;
|
|
|
|
|
|
distance += sonar_serial->read();
|
|
crc += distance & 0x00ff;
|
|
crc += 0x00FF;
|
|
|
|
|
|
if (crc != sonar_serial->read()) {
|
|
AddLog_P2(LOG_LEVEL_ERROR,PSTR("SR04: Reading CRC error."));
|
|
return NO_ECHO;
|
|
}
|
|
|
|
return distance;
|
|
}
|
|
|
|
void Sr04TReading(void) {
|
|
|
|
if (sonar_serial==nullptr && sonar==nullptr) {
|
|
Sr04TModeDetect();
|
|
}
|
|
|
|
switch (sr04_type) {
|
|
case 3:
|
|
distance = (real64_t)(Sr04TMiddleValue(Sr04TMode3Distance(),Sr04TMode3Distance(),Sr04TMode3Distance()))/ 10;
|
|
break;
|
|
case 2:
|
|
|
|
while(sonar_serial->available()) sonar_serial->read();
|
|
distance = (real64_t)(Sr04TMiddleValue(Sr04TMode2Distance(),Sr04TMode2Distance(),Sr04TMode2Distance()))/10;
|
|
break;
|
|
case 1:
|
|
distance = (real64_t)(sonar->ping_median(5))/ US_ROUNDTRIP_CM;
|
|
break;
|
|
default:
|
|
distance = NO_ECHO;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_DISTANCE[] PROGMEM =
|
|
"{s}SR04 " D_DISTANCE "{m}%s" D_UNIT_CENTIMETER "{e}";
|
|
#endif
|
|
|
|
void Sr04Show(bool json)
|
|
{
|
|
|
|
if (distance != 0) {
|
|
char distance_chr[33];
|
|
dtostrfd(distance, 3, distance_chr);
|
|
|
|
if(json) {
|
|
ResponseAppend_P(PSTR(",\"SR04\":{\"" D_JSON_DISTANCE "\":%s}"), distance_chr);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_COUNT, distance_chr);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_DISTANCE, distance_chr);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns22(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (sr04_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
result = (pin[GPIO_SR04_ECHO]<99);
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
Sr04TReading();
|
|
result = true;
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Sr04Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Sr04Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_SI1145
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_24_si1145.ino"
|
|
#define XSNS_24 24
|
|
#define XI2C_19 19
|
|
|
|
#define SI114X_ADDR 0X60
|
|
|
|
|
|
|
|
#define SI114X_QUERY 0X80
|
|
#define SI114X_SET 0XA0
|
|
#define SI114X_NOP 0X00
|
|
#define SI114X_RESET 0X01
|
|
#define SI114X_BUSADDR 0X02
|
|
#define SI114X_PS_FORCE 0X05
|
|
#define SI114X_GET_CAL 0X12
|
|
#define SI114X_ALS_FORCE 0X06
|
|
#define SI114X_PSALS_FORCE 0X07
|
|
#define SI114X_PS_PAUSE 0X09
|
|
#define SI114X_ALS_PAUSE 0X0A
|
|
#define SI114X_PSALS_PAUSE 0X0B
|
|
#define SI114X_PS_AUTO 0X0D
|
|
#define SI114X_ALS_AUTO 0X0E
|
|
#define SI114X_PSALS_AUTO 0X0F
|
|
|
|
|
|
|
|
#define SI114X_PART_ID 0X00
|
|
#define SI114X_REV_ID 0X01
|
|
#define SI114X_SEQ_ID 0X02
|
|
#define SI114X_INT_CFG 0X03
|
|
#define SI114X_IRQ_ENABLE 0X04
|
|
#define SI114X_IRQ_MODE1 0x05
|
|
#define SI114X_IRQ_MODE2 0x06
|
|
#define SI114X_HW_KEY 0X07
|
|
#define SI114X_MEAS_RATE0 0X08
|
|
#define SI114X_MEAS_RATE1 0X09
|
|
#define SI114X_PS_RATE 0X0A
|
|
#define SI114X_PS_LED21 0X0F
|
|
#define SI114X_PS_LED3 0X10
|
|
#define SI114X_UCOEFF0 0X13
|
|
#define SI114X_UCOEFF1 0X14
|
|
#define SI114X_UCOEFF2 0X15
|
|
#define SI114X_UCOEFF3 0X16
|
|
#define SI114X_WR 0X17
|
|
#define SI114X_COMMAND 0X18
|
|
#define SI114X_RESPONSE 0X20
|
|
#define SI114X_IRQ_STATUS 0X21
|
|
#define SI114X_ALS_VIS_DATA0 0X22
|
|
#define SI114X_ALS_VIS_DATA1 0X23
|
|
#define SI114X_ALS_IR_DATA0 0X24
|
|
#define SI114X_ALS_IR_DATA1 0X25
|
|
#define SI114X_PS1_DATA0 0X26
|
|
#define SI114X_PS1_DATA1 0X27
|
|
#define SI114X_PS2_DATA0 0X28
|
|
#define SI114X_PS2_DATA1 0X29
|
|
#define SI114X_PS3_DATA0 0X2A
|
|
#define SI114X_PS3_DATA1 0X2B
|
|
#define SI114X_AUX_DATA0_UVINDEX0 0X2C
|
|
#define SI114X_AUX_DATA1_UVINDEX1 0X2D
|
|
#define SI114X_RD 0X2E
|
|
#define SI114X_CHIP_STAT 0X30
|
|
|
|
|
|
|
|
#define SI114X_CHLIST 0X01
|
|
#define SI114X_CHLIST_ENUV 0x80
|
|
#define SI114X_CHLIST_ENAUX 0x40
|
|
#define SI114X_CHLIST_ENALSIR 0x20
|
|
#define SI114X_CHLIST_ENALSVIS 0x10
|
|
#define SI114X_CHLIST_ENPS1 0x01
|
|
#define SI114X_CHLIST_ENPS2 0x02
|
|
#define SI114X_CHLIST_ENPS3 0x04
|
|
|
|
#define SI114X_PSLED12_SELECT 0X02
|
|
#define SI114X_PSLED3_SELECT 0X03
|
|
|
|
#define SI114X_PS_ENCODE 0X05
|
|
#define SI114X_ALS_ENCODE 0X06
|
|
|
|
#define SI114X_PS1_ADCMUX 0X07
|
|
#define SI114X_PS2_ADCMUX 0X08
|
|
#define SI114X_PS3_ADCMUX 0X09
|
|
|
|
#define SI114X_PS_ADC_COUNTER 0X0A
|
|
#define SI114X_PS_ADC_GAIN 0X0B
|
|
#define SI114X_PS_ADC_MISC 0X0C
|
|
|
|
#define SI114X_ALS_IR_ADC_MUX 0X0E
|
|
#define SI114X_AUX_ADC_MUX 0X0F
|
|
|
|
#define SI114X_ALS_VIS_ADC_COUNTER 0X10
|
|
#define SI114X_ALS_VIS_ADC_GAIN 0X11
|
|
#define SI114X_ALS_VIS_ADC_MISC 0X12
|
|
|
|
#define SI114X_LED_REC 0X1C
|
|
|
|
#define SI114X_ALS_IR_ADC_COUNTER 0X1D
|
|
#define SI114X_ALS_IR_ADC_GAIN 0X1E
|
|
#define SI114X_ALS_IR_ADC_MISC 0X1F
|
|
|
|
|
|
|
|
|
|
#define SI114X_ADCMUX_SMALL_IR 0x00
|
|
#define SI114X_ADCMUX_VISIABLE 0x02
|
|
#define SI114X_ADCMUX_LARGE_IR 0x03
|
|
#define SI114X_ADCMUX_NO 0x06
|
|
#define SI114X_ADCMUX_GND 0x25
|
|
#define SI114X_ADCMUX_TEMPERATURE 0x65
|
|
#define SI114X_ADCMUX_VDD 0x75
|
|
|
|
#define SI114X_PSLED12_SELECT_PS1_NONE 0x00
|
|
#define SI114X_PSLED12_SELECT_PS1_LED1 0x01
|
|
#define SI114X_PSLED12_SELECT_PS1_LED2 0x02
|
|
#define SI114X_PSLED12_SELECT_PS1_LED3 0x04
|
|
#define SI114X_PSLED12_SELECT_PS2_NONE 0x00
|
|
#define SI114X_PSLED12_SELECT_PS2_LED1 0x10
|
|
#define SI114X_PSLED12_SELECT_PS2_LED2 0x20
|
|
#define SI114X_PSLED12_SELECT_PS2_LED3 0x40
|
|
#define SI114X_PSLED3_SELECT_PS2_NONE 0x00
|
|
#define SI114X_PSLED3_SELECT_PS2_LED1 0x10
|
|
#define SI114X_PSLED3_SELECT_PS2_LED2 0x20
|
|
#define SI114X_PSLED3_SELECT_PS2_LED3 0x40
|
|
|
|
#define SI114X_ADC_GAIN_DIV1 0X00
|
|
#define SI114X_ADC_GAIN_DIV2 0X01
|
|
#define SI114X_ADC_GAIN_DIV4 0X02
|
|
#define SI114X_ADC_GAIN_DIV8 0X03
|
|
#define SI114X_ADC_GAIN_DIV16 0X04
|
|
#define SI114X_ADC_GAIN_DIV32 0X05
|
|
|
|
#define SI114X_LED_CURRENT_5MA 0X01
|
|
#define SI114X_LED_CURRENT_11MA 0X02
|
|
#define SI114X_LED_CURRENT_22MA 0X03
|
|
#define SI114X_LED_CURRENT_45MA 0X04
|
|
|
|
#define SI114X_ADC_COUNTER_1ADCCLK 0X00
|
|
#define SI114X_ADC_COUNTER_7ADCCLK 0X01
|
|
#define SI114X_ADC_COUNTER_15ADCCLK 0X02
|
|
#define SI114X_ADC_COUNTER_31ADCCLK 0X03
|
|
#define SI114X_ADC_COUNTER_63ADCCLK 0X04
|
|
#define SI114X_ADC_COUNTER_127ADCCLK 0X05
|
|
#define SI114X_ADC_COUNTER_255ADCCLK 0X06
|
|
#define SI114X_ADC_COUNTER_511ADCCLK 0X07
|
|
|
|
#define SI114X_ADC_MISC_LOWRANGE 0X00
|
|
#define SI114X_ADC_MISC_HIGHRANGE 0X20
|
|
#define SI114X_ADC_MISC_ADC_NORMALPROXIMITY 0X00
|
|
#define SI114X_ADC_MISC_ADC_RAWADC 0X04
|
|
|
|
#define SI114X_INT_CFG_INTOE 0X01
|
|
|
|
#define SI114X_IRQEN_ALS 0x01
|
|
#define SI114X_IRQEN_PS1 0x04
|
|
#define SI114X_IRQEN_PS2 0x08
|
|
#define SI114X_IRQEN_PS3 0x10
|
|
|
|
uint16_t si1145_visible;
|
|
uint16_t si1145_infrared;
|
|
uint16_t si1145_uvindex;
|
|
|
|
bool si1145_type = false;
|
|
uint8_t si1145_valid = 0;
|
|
|
|
|
|
|
|
uint8_t Si1145ReadByte(uint8_t reg)
|
|
{
|
|
return I2cRead8(SI114X_ADDR, reg);
|
|
}
|
|
|
|
uint16_t Si1145ReadHalfWord(uint8_t reg)
|
|
{
|
|
return I2cRead16LE(SI114X_ADDR, reg);
|
|
}
|
|
|
|
bool Si1145WriteByte(uint8_t reg, uint16_t val)
|
|
{
|
|
I2cWrite8(SI114X_ADDR, reg, val);
|
|
}
|
|
|
|
uint8_t Si1145WriteParamData(uint8_t p, uint8_t v)
|
|
{
|
|
Si1145WriteByte(SI114X_WR, v);
|
|
Si1145WriteByte(SI114X_COMMAND, p | SI114X_SET);
|
|
return Si1145ReadByte(SI114X_RD);
|
|
}
|
|
|
|
|
|
|
|
bool Si1145Present(void)
|
|
{
|
|
return (Si1145ReadByte(SI114X_PART_ID) == 0X45);
|
|
}
|
|
|
|
void Si1145Reset(void)
|
|
{
|
|
Si1145WriteByte(SI114X_MEAS_RATE0, 0);
|
|
Si1145WriteByte(SI114X_MEAS_RATE1, 0);
|
|
Si1145WriteByte(SI114X_IRQ_ENABLE, 0);
|
|
Si1145WriteByte(SI114X_IRQ_MODE1, 0);
|
|
Si1145WriteByte(SI114X_IRQ_MODE2, 0);
|
|
Si1145WriteByte(SI114X_INT_CFG, 0);
|
|
Si1145WriteByte(SI114X_IRQ_STATUS, 0xFF);
|
|
|
|
Si1145WriteByte(SI114X_COMMAND, SI114X_RESET);
|
|
delay(10);
|
|
Si1145WriteByte(SI114X_HW_KEY, 0x17);
|
|
delay(10);
|
|
}
|
|
|
|
void Si1145DeInit(void)
|
|
{
|
|
|
|
|
|
Si1145WriteByte(SI114X_UCOEFF0, 0x29);
|
|
Si1145WriteByte(SI114X_UCOEFF1, 0x89);
|
|
Si1145WriteByte(SI114X_UCOEFF2, 0x02);
|
|
Si1145WriteByte(SI114X_UCOEFF3, 0x00);
|
|
Si1145WriteParamData(SI114X_CHLIST, SI114X_CHLIST_ENUV | SI114X_CHLIST_ENALSIR | SI114X_CHLIST_ENALSVIS | SI114X_CHLIST_ENPS1);
|
|
|
|
|
|
|
|
Si1145WriteParamData(SI114X_PS1_ADCMUX, SI114X_ADCMUX_LARGE_IR);
|
|
Si1145WriteByte(SI114X_PS_LED21, SI114X_LED_CURRENT_22MA);
|
|
Si1145WriteParamData(SI114X_PSLED12_SELECT, SI114X_PSLED12_SELECT_PS1_LED1);
|
|
|
|
|
|
|
|
Si1145WriteParamData(SI114X_PS_ADC_GAIN, SI114X_ADC_GAIN_DIV1);
|
|
Si1145WriteParamData(SI114X_PS_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK);
|
|
Si1145WriteParamData(SI114X_PS_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE | SI114X_ADC_MISC_ADC_RAWADC);
|
|
|
|
|
|
|
|
Si1145WriteParamData(SI114X_ALS_VIS_ADC_GAIN, SI114X_ADC_GAIN_DIV1);
|
|
Si1145WriteParamData(SI114X_ALS_VIS_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK);
|
|
Si1145WriteParamData(SI114X_ALS_VIS_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE);
|
|
|
|
|
|
|
|
Si1145WriteParamData(SI114X_ALS_IR_ADC_GAIN, SI114X_ADC_GAIN_DIV1);
|
|
Si1145WriteParamData(SI114X_ALS_IR_ADC_COUNTER, SI114X_ADC_COUNTER_511ADCCLK);
|
|
Si1145WriteParamData(SI114X_ALS_IR_ADC_MISC, SI114X_ADC_MISC_HIGHRANGE);
|
|
|
|
|
|
|
|
Si1145WriteByte(SI114X_INT_CFG, SI114X_INT_CFG_INTOE);
|
|
Si1145WriteByte(SI114X_IRQ_ENABLE, SI114X_IRQEN_ALS);
|
|
|
|
|
|
|
|
Si1145WriteByte(SI114X_MEAS_RATE0, 0xFF);
|
|
Si1145WriteByte(SI114X_COMMAND, SI114X_PSALS_AUTO);
|
|
}
|
|
|
|
bool Si1145Begin(void)
|
|
{
|
|
if (!Si1145Present()) { return false; }
|
|
|
|
Si1145Reset();
|
|
Si1145DeInit();
|
|
return true;
|
|
}
|
|
|
|
|
|
uint16_t Si1145ReadUV(void)
|
|
{
|
|
return Si1145ReadHalfWord(SI114X_AUX_DATA0_UVINDEX0);
|
|
}
|
|
|
|
|
|
uint16_t Si1145ReadVisible(void)
|
|
{
|
|
return Si1145ReadHalfWord(SI114X_ALS_VIS_DATA0);
|
|
}
|
|
|
|
|
|
uint16_t Si1145ReadIR(void)
|
|
{
|
|
return Si1145ReadHalfWord(SI114X_ALS_IR_DATA0);
|
|
}
|
|
|
|
|
|
|
|
bool Si1145Read(void)
|
|
{
|
|
if (si1145_valid) { si1145_valid--; }
|
|
|
|
if (!Si1145Present()) { return false; }
|
|
|
|
si1145_visible = Si1145ReadVisible();
|
|
si1145_infrared = Si1145ReadIR();
|
|
si1145_uvindex = Si1145ReadUV();
|
|
si1145_valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
void Si1145Detect(void)
|
|
{
|
|
if (I2cActive(SI114X_ADDR)) { return; }
|
|
|
|
if (Si1145Begin()) {
|
|
si1145_type = true;
|
|
I2cSetActiveFound(SI114X_ADDR, "SI1145");
|
|
}
|
|
}
|
|
|
|
void Si1145Update(void)
|
|
{
|
|
if (!Si1145Read()) {
|
|
AddLogMissed("SI1145", si1145_valid);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_SI1145[] PROGMEM =
|
|
"{s}SI1145 " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}"
|
|
"{s}SI1145 " D_INFRARED "{m}%d " D_UNIT_LUX "{e}"
|
|
"{s}SI1145 " D_UV_INDEX "{m}%d.%d{e}";
|
|
#endif
|
|
|
|
void Si1145Show(bool json)
|
|
{
|
|
if (si1145_valid) {
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"SI1145\":{\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_INFRARED "\":%d,\"" D_JSON_UV_INDEX "\":%d.%d}"),
|
|
si1145_visible, si1145_infrared, si1145_uvindex /100, si1145_uvindex %100);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) DomoticzSensor(DZ_ILLUMINANCE, si1145_visible);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_SI1145, si1145_visible, si1145_infrared, si1145_uvindex /100, si1145_uvindex %100);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns24(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_19)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Si1145Detect();
|
|
}
|
|
else if (si1145_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Si1145Update();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Si1145Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Si1145Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_LM75AD
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_26_lm75ad.ino"
|
|
#define XSNS_26 26
|
|
#define XI2C_20 20
|
|
|
|
#define LM75AD_ADDRESS1 0x48
|
|
#define LM75AD_ADDRESS2 0x49
|
|
#define LM75AD_ADDRESS3 0x4A
|
|
#define LM75AD_ADDRESS4 0x4B
|
|
#define LM75AD_ADDRESS5 0x4C
|
|
#define LM75AD_ADDRESS6 0x4D
|
|
#define LM75AD_ADDRESS7 0x4E
|
|
#define LM75AD_ADDRESS8 0x4F
|
|
|
|
#define LM75_TEMP_REGISTER 0x00
|
|
#define LM75_CONF_REGISTER 0x01
|
|
#define LM75_THYST_REGISTER 0x02
|
|
#define LM75_TOS_REGISTER 0x03
|
|
|
|
bool lm75ad_type = false;
|
|
uint8_t lm75ad_address;
|
|
uint8_t lm75ad_addresses[] = { LM75AD_ADDRESS1, LM75AD_ADDRESS2, LM75AD_ADDRESS3, LM75AD_ADDRESS4, LM75AD_ADDRESS5, LM75AD_ADDRESS6, LM75AD_ADDRESS7, LM75AD_ADDRESS8 };
|
|
|
|
void LM75ADDetect(void)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(lm75ad_addresses); i++) {
|
|
lm75ad_address = lm75ad_addresses[i];
|
|
if (I2cActive(lm75ad_address)) {
|
|
continue; }
|
|
if (!I2cSetDevice(lm75ad_address)) {
|
|
break;
|
|
}
|
|
uint16_t buffer;
|
|
if (I2cValidRead16(&buffer, lm75ad_address, LM75_THYST_REGISTER)) {
|
|
if (buffer == 0x4B00) {
|
|
lm75ad_type = true;
|
|
I2cSetActiveFound(lm75ad_address, "LM75AD");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float LM75ADGetTemp(void)
|
|
{
|
|
int16_t sign = 1;
|
|
|
|
uint16_t t = I2cRead16(lm75ad_address, LM75_TEMP_REGISTER);
|
|
if (t & 0x8000) {
|
|
t = (~t) +0x20;
|
|
sign = -1;
|
|
}
|
|
t = t >> 5;
|
|
return ConvertTemp(sign * t * 0.125);
|
|
}
|
|
|
|
void LM75ADShow(bool json)
|
|
{
|
|
float t = LM75ADGetTemp();
|
|
char temperature[33];
|
|
dtostrfd(t, Settings.flag2.temperature_resolution, temperature);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"LM75AD\":{\"" D_JSON_TEMPERATURE "\":%s}"), temperature);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) DomoticzSensor(DZ_TEMP, temperature);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "LM75AD", temperature, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns26(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_20)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
LM75ADDetect();
|
|
}
|
|
else if (lm75ad_type) {
|
|
switch (function) {
|
|
case FUNC_JSON_APPEND:
|
|
LM75ADShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
LM75ADShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
# 28 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_APDS9960
|
|
# 39 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
#define XSNS_27 27
|
|
#define XI2C_21 21
|
|
|
|
#if defined(USE_SHT) || defined(USE_VEML6070) || defined(USE_TSL2561)
|
|
#warning **** Turned off conflicting drivers SHT and VEML6070 ****
|
|
#ifdef USE_SHT
|
|
#undef USE_SHT
|
|
#endif
|
|
#ifdef USE_VEML6070
|
|
#undef USE_VEML6070
|
|
#endif
|
|
#ifdef USE_TSL2561
|
|
#undef USE_TSL2561
|
|
#endif
|
|
#endif
|
|
|
|
#define APDS9960_I2C_ADDR 0x39
|
|
|
|
#define APDS9960_CHIPID_1 0xAB
|
|
#define APDS9960_CHIPID_2 0x9C
|
|
|
|
#define APDS9930_CHIPID_1 0x12
|
|
#define APDS9930_CHIPID_2 0x39
|
|
|
|
|
|
#define GESTURE_THRESHOLD_OUT 10
|
|
#define GESTURE_SENSITIVITY_1 50
|
|
#define GESTURE_SENSITIVITY_2 20
|
|
|
|
uint8_t APDS9960addr;
|
|
uint8_t APDS9960type = 0;
|
|
char APDS9960stype[] = "APDS9960";
|
|
char currentGesture[6];
|
|
uint8_t gesture_mode = 1;
|
|
|
|
|
|
volatile uint8_t recovery_loop_counter = 0;
|
|
#define APDS9960_LONG_RECOVERY 50
|
|
#define APDS9960_MAX_GESTURE_CYCLES 50
|
|
bool APDS9960_overload = false;
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_APDS_9960_SNS[] PROGMEM =
|
|
"{s}" "Red" "{m}%s{e}"
|
|
"{s}" "Green" "{m}%s{e}"
|
|
"{s}" "Blue" "{m}%s{e}"
|
|
"{s}" "Ambient" "{m}%s " D_UNIT_LUX "{e}"
|
|
"{s}" "CCT" "{m}%s " "K" "{e}"
|
|
"{s}" "Proximity" "{m}%s{e}";
|
|
#endif
|
|
# 97 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
#define FIFO_PAUSE_TIME 30
|
|
|
|
|
|
#define APDS9960_ENABLE 0x80
|
|
#define APDS9960_ATIME 0x81
|
|
#define APDS9960_WTIME 0x83
|
|
#define APDS9960_AILTL 0x84
|
|
#define APDS9960_AILTH 0x85
|
|
#define APDS9960_AIHTL 0x86
|
|
#define APDS9960_AIHTH 0x87
|
|
#define APDS9960_PILT 0x89
|
|
#define APDS9960_PIHT 0x8B
|
|
#define APDS9960_PERS 0x8C
|
|
#define APDS9960_CONFIG1 0x8D
|
|
#define APDS9960_PPULSE 0x8E
|
|
#define APDS9960_CONTROL 0x8F
|
|
#define APDS9960_CONFIG2 0x90
|
|
#define APDS9960_ID 0x92
|
|
#define APDS9960_STATUS 0x93
|
|
#define APDS9960_CDATAL 0x94
|
|
#define APDS9960_CDATAH 0x95
|
|
#define APDS9960_RDATAL 0x96
|
|
#define APDS9960_RDATAH 0x97
|
|
#define APDS9960_GDATAL 0x98
|
|
#define APDS9960_GDATAH 0x99
|
|
#define APDS9960_BDATAL 0x9A
|
|
#define APDS9960_BDATAH 0x9B
|
|
#define APDS9960_PDATA 0x9C
|
|
#define APDS9960_POFFSET_UR 0x9D
|
|
#define APDS9960_POFFSET_DL 0x9E
|
|
#define APDS9960_CONFIG3 0x9F
|
|
#define APDS9960_GPENTH 0xA0
|
|
#define APDS9960_GEXTH 0xA1
|
|
#define APDS9960_GCONF1 0xA2
|
|
#define APDS9960_GCONF2 0xA3
|
|
#define APDS9960_GOFFSET_U 0xA4
|
|
#define APDS9960_GOFFSET_D 0xA5
|
|
#define APDS9960_GOFFSET_L 0xA7
|
|
#define APDS9960_GOFFSET_R 0xA9
|
|
#define APDS9960_GPULSE 0xA6
|
|
#define APDS9960_GCONF3 0xAA
|
|
#define APDS9960_GCONF4 0xAB
|
|
#define APDS9960_GFLVL 0xAE
|
|
#define APDS9960_GSTATUS 0xAF
|
|
#define APDS9960_IFORCE 0xE4
|
|
#define APDS9960_PICLEAR 0xE5
|
|
#define APDS9960_CICLEAR 0xE6
|
|
#define APDS9960_AICLEAR 0xE7
|
|
#define APDS9960_GFIFO_U 0xFC
|
|
#define APDS9960_GFIFO_D 0xFD
|
|
#define APDS9960_GFIFO_L 0xFE
|
|
#define APDS9960_GFIFO_R 0xFF
|
|
|
|
|
|
#define APDS9960_PON 0b00000001
|
|
#define APDS9960_AEN 0b00000010
|
|
#define APDS9960_PEN 0b00000100
|
|
#define APDS9960_WEN 0b00001000
|
|
#define APSD9960_AIEN 0b00010000
|
|
#define APDS9960_PIEN 0b00100000
|
|
#define APDS9960_GEN 0b01000000
|
|
#define APDS9960_GVALID 0b00000001
|
|
|
|
|
|
#define OFF 0
|
|
#define ON 1
|
|
|
|
|
|
#define POWER 0
|
|
#define AMBIENT_LIGHT 1
|
|
#define PROXIMITY 2
|
|
#define WAIT 3
|
|
#define AMBIENT_LIGHT_INT 4
|
|
#define PROXIMITY_INT 5
|
|
#define GESTURE 6
|
|
#define ALL 7
|
|
|
|
|
|
#define LED_DRIVE_100MA 0
|
|
#define LED_DRIVE_50MA 1
|
|
#define LED_DRIVE_25MA 2
|
|
#define LED_DRIVE_12_5MA 3
|
|
|
|
|
|
#define PGAIN_1X 0
|
|
#define PGAIN_2X 1
|
|
#define PGAIN_4X 2
|
|
#define PGAIN_8X 3
|
|
|
|
|
|
#define AGAIN_1X 0
|
|
#define AGAIN_4X 1
|
|
#define AGAIN_16X 2
|
|
#define AGAIN_64X 3
|
|
|
|
|
|
#define GGAIN_1X 0
|
|
#define GGAIN_2X 1
|
|
#define GGAIN_4X 2
|
|
#define GGAIN_8X 3
|
|
|
|
|
|
#define LED_BOOST_100 0
|
|
#define LED_BOOST_150 1
|
|
#define LED_BOOST_200 2
|
|
#define LED_BOOST_300 3
|
|
|
|
|
|
#define GWTIME_0MS 0
|
|
#define GWTIME_2_8MS 1
|
|
#define GWTIME_5_6MS 2
|
|
#define GWTIME_8_4MS 3
|
|
#define GWTIME_14_0MS 4
|
|
#define GWTIME_22_4MS 5
|
|
#define GWTIME_30_8MS 6
|
|
#define GWTIME_39_2MS 7
|
|
|
|
|
|
#define DEFAULT_ATIME 0xdb
|
|
#define DEFAULT_WTIME 246
|
|
#define DEFAULT_PROX_PPULSE 0x87
|
|
#define DEFAULT_GESTURE_PPULSE 0x89
|
|
#define DEFAULT_POFFSET_UR 0
|
|
#define DEFAULT_POFFSET_DL 0
|
|
#define DEFAULT_CONFIG1 0x60
|
|
#define DEFAULT_LDRIVE LED_DRIVE_100MA
|
|
#define DEFAULT_PGAIN PGAIN_4X
|
|
#define DEFAULT_AGAIN AGAIN_4X
|
|
#define DEFAULT_PILT 0
|
|
#define DEFAULT_PIHT 50
|
|
#define DEFAULT_AILT 0xFFFF
|
|
#define DEFAULT_AIHT 0
|
|
#define DEFAULT_PERS 0x11
|
|
#define DEFAULT_CONFIG2 0x01
|
|
#define DEFAULT_CONFIG3 0
|
|
#define DEFAULT_GPENTH 40
|
|
#define DEFAULT_GEXTH 30
|
|
#define DEFAULT_GCONF1 0x40
|
|
#define DEFAULT_GGAIN GGAIN_4X
|
|
#define DEFAULT_GLDRIVE LED_DRIVE_100MA
|
|
#define DEFAULT_GWTIME GWTIME_2_8MS
|
|
#define DEFAULT_GOFFSET 0
|
|
#define DEFAULT_GPULSE 0xC9
|
|
#define DEFAULT_GCONF3 0
|
|
#define DEFAULT_GIEN 0
|
|
|
|
#define ERROR 0xFF
|
|
|
|
|
|
enum {
|
|
DIR_NONE,
|
|
DIR_LEFT,
|
|
DIR_RIGHT,
|
|
DIR_UP,
|
|
DIR_DOWN,
|
|
DIR_ALL
|
|
};
|
|
|
|
|
|
enum {
|
|
APDS9960_NA_STATE,
|
|
APDS9960_ALL_STATE
|
|
};
|
|
|
|
|
|
typedef struct gesture_data_type {
|
|
uint8_t u_data[32];
|
|
uint8_t d_data[32];
|
|
uint8_t l_data[32];
|
|
uint8_t r_data[32];
|
|
uint8_t index;
|
|
uint8_t total_gestures;
|
|
uint8_t in_threshold;
|
|
uint8_t out_threshold;
|
|
} gesture_data_type;
|
|
|
|
|
|
gesture_data_type gesture_data_;
|
|
int16_t gesture_ud_delta_ = 0;
|
|
int16_t gesture_lr_delta_ = 0;
|
|
int16_t gesture_ud_count_ = 0;
|
|
int16_t gesture_lr_count_ = 0;
|
|
int16_t gesture_state_ = 0;
|
|
int16_t gesture_motion_ = DIR_NONE;
|
|
|
|
typedef struct color_data_type {
|
|
uint16_t a;
|
|
uint16_t r;
|
|
uint16_t g;
|
|
uint16_t b;
|
|
uint8_t p;
|
|
uint16_t cct;
|
|
uint16_t lux;
|
|
} color_data_type;
|
|
|
|
color_data_type color_data;
|
|
uint8_t APDS9960_aTime = DEFAULT_ATIME;
|
|
# 306 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
bool wireWriteByte(uint8_t val)
|
|
{
|
|
Wire.beginTransmission(APDS9960_I2C_ADDR);
|
|
Wire.write(val);
|
|
if( Wire.endTransmission() != 0 ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
# 325 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
int8_t wireReadDataBlock( uint8_t reg,
|
|
uint8_t *val,
|
|
uint16_t len)
|
|
{
|
|
unsigned char i = 0;
|
|
|
|
|
|
if (!wireWriteByte(reg)) {
|
|
return -1;
|
|
}
|
|
|
|
|
|
Wire.requestFrom(APDS9960_I2C_ADDR, len);
|
|
while (Wire.available()) {
|
|
if (i >= len) {
|
|
return -1;
|
|
}
|
|
val[i] = Wire.read();
|
|
i++;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void calculateColorTemperature(void)
|
|
{
|
|
float X, Y, Z;
|
|
float xc, yc;
|
|
float n;
|
|
float cct;
|
|
|
|
|
|
|
|
|
|
|
|
X = (-0.14282F * color_data.r) + (1.54924F * color_data.g) + (-0.95641F * color_data.b);
|
|
Y = (-0.32466F * color_data.r) + (1.57837F * color_data.g) + (-0.73191F * color_data.b);
|
|
Z = (-0.68202F * color_data.r) + (0.77073F * color_data.g) + ( 0.56332F * color_data.b);
|
|
|
|
|
|
xc = (X) / (X + Y + Z);
|
|
yc = (Y) / (X + Y + Z);
|
|
|
|
|
|
n = (xc - 0.3320F) / (0.1858F - yc);
|
|
|
|
|
|
color_data.cct = (449.0F * FastPrecisePowf(n, 3)) + (3525.0F * FastPrecisePowf(n, 2)) + (6823.3F * n) + 5520.33F;
|
|
|
|
return;
|
|
}
|
|
# 392 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getProxIntLowThresh(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PILT) ;
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setProxIntLowThresh(uint8_t threshold)
|
|
{
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PILT, threshold);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getProxIntHighThresh(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PIHT) ;
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setProxIntHighThresh(uint8_t threshold)
|
|
{
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PIHT, threshold);
|
|
}
|
|
# 448 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getLEDDrive(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ;
|
|
|
|
val = (val >> 6) & 0b00000011;
|
|
|
|
return val;
|
|
}
|
|
# 471 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setLEDDrive(uint8_t drive)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL);
|
|
|
|
|
|
drive &= 0b00000011;
|
|
drive = drive << 6;
|
|
val &= 0b00111111;
|
|
val |= drive;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val);
|
|
}
|
|
# 500 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getProximityGain(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL) ;
|
|
|
|
val = (val >> 2) & 0b00000011;
|
|
|
|
return val;
|
|
}
|
|
# 523 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setProximityGain(uint8_t drive)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL);
|
|
|
|
|
|
drive &= 0b00000011;
|
|
drive = drive << 2;
|
|
val &= 0b11110011;
|
|
val |= drive;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val);
|
|
}
|
|
# 564 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setAmbientLightGain(uint8_t drive)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONTROL);
|
|
|
|
|
|
drive &= 0b00000011;
|
|
val &= 0b11111100;
|
|
val |= drive;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONTROL, val);
|
|
}
|
|
# 591 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getLEDBoost(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ;
|
|
|
|
|
|
val = (val >> 4) & 0b00000011;
|
|
|
|
return val;
|
|
}
|
|
# 615 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setLEDBoost(uint8_t boost)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG2) ;
|
|
|
|
boost &= 0b00000011;
|
|
boost = boost << 4;
|
|
val &= 0b11001111;
|
|
val |= boost;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG2, val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getProxGainCompEnable(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ;
|
|
|
|
|
|
val = (val >> 5) & 0b00000001;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setProxGainCompEnable(uint8_t enable)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ;
|
|
|
|
|
|
enable &= 0b00000001;
|
|
enable = enable << 5;
|
|
val &= 0b11011111;
|
|
val |= enable;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, val) ;
|
|
}
|
|
# 683 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getProxPhotoMask(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ;
|
|
|
|
|
|
val &= 0b00001111;
|
|
|
|
return val;
|
|
}
|
|
# 708 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setProxPhotoMask(uint8_t mask)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_CONFIG3) ;
|
|
|
|
|
|
mask &= 0b00001111;
|
|
val &= 0b11110000;
|
|
val |= mask;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getGestureEnterThresh(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GPENTH) ;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setGestureEnterThresh(uint8_t threshold)
|
|
{
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GPENTH, threshold) ;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getGestureExitThresh(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GEXTH) ;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setGestureExitThresh(uint8_t threshold)
|
|
{
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GEXTH, threshold) ;
|
|
}
|
|
# 786 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getGestureGain(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
|
|
|
|
|
|
val = (val >> 5) & 0b00000011;
|
|
|
|
return val;
|
|
}
|
|
# 810 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setGestureGain(uint8_t gain)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
|
|
|
|
|
|
gain &= 0b00000011;
|
|
gain = gain << 5;
|
|
val &= 0b10011111;
|
|
val |= gain;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ;
|
|
}
|
|
# 838 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getGestureLEDDrive(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
|
|
|
|
|
|
val = (val >> 3) & 0b00000011;
|
|
|
|
return val;
|
|
}
|
|
# 862 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setGestureLEDDrive(uint8_t drive)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
|
|
|
|
|
|
drive &= 0b00000011;
|
|
drive = drive << 3;
|
|
val &= 0b11100111;
|
|
val |= drive;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ;
|
|
}
|
|
# 894 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getGestureWaitTime(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
|
|
|
|
|
|
val &= 0b00000111;
|
|
|
|
return val;
|
|
}
|
|
# 922 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void setGestureWaitTime(uint8_t time)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF2) ;
|
|
|
|
|
|
time &= 0b00000111;
|
|
val &= 0b11111000;
|
|
val |= time;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF2, val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void getLightIntLowThreshold(uint16_t &threshold)
|
|
{
|
|
uint8_t val_byte;
|
|
threshold = 0;
|
|
|
|
|
|
val_byte = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AILTL) ;
|
|
threshold = val_byte;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTH, val_byte) ;
|
|
threshold = threshold + ((uint16_t)val_byte << 8);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setLightIntLowThreshold(uint16_t threshold)
|
|
{
|
|
uint8_t val_low;
|
|
uint8_t val_high;
|
|
|
|
|
|
val_low = threshold & 0x00FF;
|
|
val_high = (threshold & 0xFF00) >> 8;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTL, val_low) ;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AILTH, val_high) ;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void getLightIntHighThreshold(uint16_t &threshold)
|
|
{
|
|
uint8_t val_byte;
|
|
threshold = 0;
|
|
|
|
|
|
val_byte = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AIHTL);
|
|
threshold = val_byte;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTH, val_byte) ;
|
|
threshold = threshold + ((uint16_t)val_byte << 8);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setLightIntHighThreshold(uint16_t threshold)
|
|
{
|
|
uint8_t val_low;
|
|
uint8_t val_high;
|
|
|
|
|
|
val_low = threshold & 0x00FF;
|
|
val_high = (threshold & 0xFF00) >> 8;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTL, val_low);
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_AIHTH, val_high) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void getProximityIntLowThreshold(uint8_t &threshold)
|
|
{
|
|
threshold = 0;
|
|
|
|
|
|
threshold = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PILT);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setProximityIntLowThreshold(uint8_t threshold)
|
|
{
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PILT, threshold) ;
|
|
}
|
|
# 1055 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void getProximityIntHighThreshold(uint8_t &threshold)
|
|
{
|
|
threshold = 0;
|
|
|
|
|
|
threshold = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PIHT) ;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setProximityIntHighThreshold(uint8_t threshold)
|
|
{
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PIHT, threshold) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getAmbientLightIntEnable(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ;
|
|
|
|
|
|
val = (val >> 4) & 0b00000001;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setAmbientLightIntEnable(uint8_t enable)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE);
|
|
|
|
|
|
enable &= 0b00000001;
|
|
enable = enable << 4;
|
|
val &= 0b11101111;
|
|
val |= enable;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getProximityIntEnable(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ;
|
|
|
|
|
|
val = (val >> 5) & 0b00000001;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setProximityIntEnable(uint8_t enable)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ;
|
|
|
|
|
|
enable &= 0b00000001;
|
|
enable = enable << 5;
|
|
val &= 0b11011111;
|
|
val |= enable;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getGestureIntEnable(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ;
|
|
|
|
|
|
val = (val >> 1) & 0b00000001;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setGestureIntEnable(uint8_t enable)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ;
|
|
|
|
|
|
enable &= 0b00000001;
|
|
enable = enable << 1;
|
|
val &= 0b11111101;
|
|
val |= enable;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF4, val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void clearAmbientLightInt(void)
|
|
{
|
|
uint8_t throwaway;
|
|
throwaway = I2cRead8(APDS9960_I2C_ADDR, APDS9960_AICLEAR);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void clearProximityInt(void)
|
|
{
|
|
uint8_t throwaway;
|
|
throwaway = I2cRead8(APDS9960_I2C_ADDR, APDS9960_PICLEAR) ;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t getGestureMode(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ;
|
|
|
|
|
|
val &= 0b00000001;
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setGestureMode(uint8_t mode)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GCONF4) ;
|
|
|
|
|
|
mode &= 0b00000001;
|
|
val &= 0b11111110;
|
|
val |= mode;
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF4, val) ;
|
|
}
|
|
|
|
|
|
bool APDS9960_init(void)
|
|
{
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, DEFAULT_ATIME) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_WTIME, DEFAULT_WTIME) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PPULSE, DEFAULT_PROX_PPULSE) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_POFFSET_UR, DEFAULT_POFFSET_UR) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_POFFSET_DL, DEFAULT_POFFSET_DL) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG1, DEFAULT_CONFIG1) ;
|
|
|
|
setLEDDrive(DEFAULT_LDRIVE);
|
|
|
|
setProximityGain(DEFAULT_PGAIN);
|
|
|
|
setAmbientLightGain(DEFAULT_AGAIN);
|
|
|
|
setProxIntLowThresh(DEFAULT_PILT) ;
|
|
|
|
setProxIntHighThresh(DEFAULT_PIHT);
|
|
|
|
setLightIntLowThreshold(DEFAULT_AILT) ;
|
|
|
|
setLightIntHighThreshold(DEFAULT_AIHT) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PERS, DEFAULT_PERS) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG2, DEFAULT_CONFIG2) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_CONFIG3, DEFAULT_CONFIG3) ;
|
|
|
|
|
|
setGestureEnterThresh(DEFAULT_GPENTH);
|
|
|
|
setGestureExitThresh(DEFAULT_GEXTH) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF1, DEFAULT_GCONF1) ;
|
|
|
|
setGestureGain(DEFAULT_GGAIN) ;
|
|
|
|
setGestureLEDDrive(DEFAULT_GLDRIVE) ;
|
|
|
|
setGestureWaitTime(DEFAULT_GWTIME) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_U, DEFAULT_GOFFSET) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_D, DEFAULT_GOFFSET) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_L, DEFAULT_GOFFSET) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GOFFSET_R, DEFAULT_GOFFSET) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GPULSE, DEFAULT_GPULSE) ;
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_GCONF3, DEFAULT_GCONF3) ;
|
|
|
|
setGestureIntEnable(DEFAULT_GIEN);
|
|
|
|
disablePower();
|
|
|
|
return true;
|
|
}
|
|
# 1333 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
uint8_t getMode(void)
|
|
{
|
|
uint8_t enable_value;
|
|
|
|
|
|
enable_value = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ENABLE) ;
|
|
|
|
return enable_value;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void setMode(uint8_t mode, uint8_t enable)
|
|
{
|
|
uint8_t reg_val;
|
|
|
|
|
|
reg_val = getMode();
|
|
|
|
|
|
|
|
enable = enable & 0x01;
|
|
if( mode >= 0 && mode <= 6 ) {
|
|
if (enable) {
|
|
reg_val |= (1 << mode);
|
|
} else {
|
|
reg_val &= ~(1 << mode);
|
|
}
|
|
} else if( mode == ALL ) {
|
|
if (enable) {
|
|
reg_val = 0x7F;
|
|
} else {
|
|
reg_val = 0x00;
|
|
}
|
|
}
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ENABLE, reg_val) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void enableLightSensor(void)
|
|
{
|
|
|
|
setAmbientLightGain(DEFAULT_AGAIN);
|
|
setAmbientLightIntEnable(0);
|
|
enablePower() ;
|
|
setMode(AMBIENT_LIGHT, 1) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void disableLightSensor(void)
|
|
{
|
|
setAmbientLightIntEnable(0) ;
|
|
setMode(AMBIENT_LIGHT, 0) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void enableProximitySensor(void)
|
|
{
|
|
|
|
setProximityGain(DEFAULT_PGAIN);
|
|
setLEDDrive(DEFAULT_LDRIVE) ;
|
|
setProximityIntEnable(0) ;
|
|
enablePower();
|
|
setMode(PROXIMITY, 1) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void disableProximitySensor(void)
|
|
{
|
|
setProximityIntEnable(0) ;
|
|
setMode(PROXIMITY, 0) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void enableGestureSensor(void)
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resetGestureParameters();
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_WTIME, 0xFF) ;
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_PPULSE, DEFAULT_GESTURE_PPULSE) ;
|
|
setLEDBoost(LED_BOOST_100);
|
|
setGestureIntEnable(0) ;
|
|
setGestureMode(1);
|
|
enablePower() ;
|
|
setMode(WAIT, 1) ;
|
|
setMode(PROXIMITY, 1) ;
|
|
setMode(GESTURE, 1);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void disableGestureSensor(void)
|
|
{
|
|
resetGestureParameters();
|
|
setGestureIntEnable(0) ;
|
|
setGestureMode(0) ;
|
|
setMode(GESTURE, 0) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool isGestureAvailable(void)
|
|
{
|
|
uint8_t val;
|
|
|
|
|
|
val = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GSTATUS) ;
|
|
|
|
|
|
val &= APDS9960_GVALID;
|
|
|
|
|
|
if( val == 1) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int16_t readGesture(void)
|
|
{
|
|
uint8_t fifo_level = 0;
|
|
uint8_t bytes_read = 0;
|
|
uint8_t fifo_data[128];
|
|
uint8_t gstatus;
|
|
uint16_t motion;
|
|
uint16_t i;
|
|
uint8_t gesture_loop_counter = 0;
|
|
|
|
|
|
if( !isGestureAvailable() || !(getMode() & 0b01000001) ) {
|
|
return DIR_NONE;
|
|
}
|
|
|
|
|
|
while(1) {
|
|
if (gesture_loop_counter == APDS9960_MAX_GESTURE_CYCLES){
|
|
disableGestureSensor();
|
|
APDS9960_overload = true;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("Sensor overload"));
|
|
}
|
|
gesture_loop_counter += 1;
|
|
|
|
delay(FIFO_PAUSE_TIME);
|
|
|
|
|
|
gstatus = I2cRead8(APDS9960_I2C_ADDR, APDS9960_GSTATUS);
|
|
|
|
|
|
if( (gstatus & APDS9960_GVALID) == APDS9960_GVALID ) {
|
|
|
|
|
|
fifo_level = I2cRead8(APDS9960_I2C_ADDR,APDS9960_GFLVL) ;
|
|
|
|
|
|
if( fifo_level > 0) {
|
|
bytes_read = wireReadDataBlock( APDS9960_GFIFO_U,
|
|
(uint8_t*)fifo_data,
|
|
(fifo_level * 4) );
|
|
if( bytes_read == -1 ) {
|
|
return ERROR;
|
|
}
|
|
|
|
|
|
if( bytes_read >= 4 ) {
|
|
for( i = 0; i < bytes_read; i += 4 ) {
|
|
gesture_data_.u_data[gesture_data_.index] = \
|
|
fifo_data[i + 0];
|
|
gesture_data_.d_data[gesture_data_.index] = \
|
|
fifo_data[i + 1];
|
|
gesture_data_.l_data[gesture_data_.index] = \
|
|
fifo_data[i + 2];
|
|
gesture_data_.r_data[gesture_data_.index] = \
|
|
fifo_data[i + 3];
|
|
gesture_data_.index++;
|
|
gesture_data_.total_gestures++;
|
|
}
|
|
|
|
if( processGestureData() ) {
|
|
if( decodeGesture() ) {
|
|
|
|
}
|
|
}
|
|
|
|
gesture_data_.index = 0;
|
|
gesture_data_.total_gestures = 0;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
|
|
delay(FIFO_PAUSE_TIME);
|
|
decodeGesture();
|
|
motion = gesture_motion_;
|
|
resetGestureParameters();
|
|
return motion;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void enablePower(void)
|
|
{
|
|
setMode(POWER, 1) ;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void disablePower(void)
|
|
{
|
|
setMode(POWER, 0) ;
|
|
}
|
|
# 1600 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void readAllColorAndProximityData(void)
|
|
{
|
|
if (I2cReadBuffer(APDS9960_I2C_ADDR, APDS9960_CDATAL, (uint8_t *) &color_data, (uint16_t)9))
|
|
{
|
|
|
|
|
|
}
|
|
}
|
|
# 1616 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
void resetGestureParameters(void)
|
|
{
|
|
gesture_data_.index = 0;
|
|
gesture_data_.total_gestures = 0;
|
|
|
|
gesture_ud_delta_ = 0;
|
|
gesture_lr_delta_ = 0;
|
|
|
|
gesture_ud_count_ = 0;
|
|
gesture_lr_count_ = 0;
|
|
|
|
gesture_state_ = 0;
|
|
gesture_motion_ = DIR_NONE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool processGestureData(void)
|
|
{
|
|
uint8_t u_first = 0;
|
|
uint8_t d_first = 0;
|
|
uint8_t l_first = 0;
|
|
uint8_t r_first = 0;
|
|
uint8_t u_last = 0;
|
|
uint8_t d_last = 0;
|
|
uint8_t l_last = 0;
|
|
uint8_t r_last = 0;
|
|
uint16_t ud_ratio_first;
|
|
uint16_t lr_ratio_first;
|
|
uint16_t ud_ratio_last;
|
|
uint16_t lr_ratio_last;
|
|
uint16_t ud_delta;
|
|
uint16_t lr_delta;
|
|
uint16_t i;
|
|
|
|
|
|
if( gesture_data_.total_gestures <= 4 ) {
|
|
return false;
|
|
}
|
|
|
|
|
|
if( (gesture_data_.total_gestures <= 32) && \
|
|
(gesture_data_.total_gestures > 0) ) {
|
|
|
|
|
|
for( i = 0; i < gesture_data_.total_gestures; i++ ) {
|
|
if( (gesture_data_.u_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(gesture_data_.d_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(gesture_data_.l_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(gesture_data_.r_data[i] > GESTURE_THRESHOLD_OUT) ) {
|
|
|
|
u_first = gesture_data_.u_data[i];
|
|
d_first = gesture_data_.d_data[i];
|
|
l_first = gesture_data_.l_data[i];
|
|
r_first = gesture_data_.r_data[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if( (u_first == 0) || (d_first == 0) || \
|
|
(l_first == 0) || (r_first == 0) ) {
|
|
|
|
return false;
|
|
}
|
|
|
|
for( i = gesture_data_.total_gestures - 1; i >= 0; i-- ) {
|
|
|
|
if( (gesture_data_.u_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(gesture_data_.d_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(gesture_data_.l_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(gesture_data_.r_data[i] > GESTURE_THRESHOLD_OUT) ) {
|
|
|
|
u_last = gesture_data_.u_data[i];
|
|
d_last = gesture_data_.d_data[i];
|
|
l_last = gesture_data_.l_data[i];
|
|
r_last = gesture_data_.r_data[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first);
|
|
lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first);
|
|
ud_ratio_last = ((u_last - d_last) * 100) / (u_last + d_last);
|
|
lr_ratio_last = ((l_last - r_last) * 100) / (l_last + r_last);
|
|
|
|
|
|
ud_delta = ud_ratio_last - ud_ratio_first;
|
|
lr_delta = lr_ratio_last - lr_ratio_first;
|
|
|
|
|
|
gesture_ud_delta_ += ud_delta;
|
|
gesture_lr_delta_ += lr_delta;
|
|
|
|
|
|
if( gesture_ud_delta_ >= GESTURE_SENSITIVITY_1 ) {
|
|
gesture_ud_count_ = 1;
|
|
} else if( gesture_ud_delta_ <= -GESTURE_SENSITIVITY_1 ) {
|
|
gesture_ud_count_ = -1;
|
|
} else {
|
|
gesture_ud_count_ = 0;
|
|
}
|
|
|
|
|
|
if( gesture_lr_delta_ >= GESTURE_SENSITIVITY_1 ) {
|
|
gesture_lr_count_ = 1;
|
|
} else if( gesture_lr_delta_ <= -GESTURE_SENSITIVITY_1 ) {
|
|
gesture_lr_count_ = -1;
|
|
} else {
|
|
gesture_lr_count_ = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool decodeGesture(void)
|
|
{
|
|
|
|
|
|
if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == 0) ) {
|
|
gesture_motion_ = DIR_UP;
|
|
} else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == 0) ) {
|
|
gesture_motion_ = DIR_DOWN;
|
|
} else if( (gesture_ud_count_ == 0) && (gesture_lr_count_ == 1) ) {
|
|
gesture_motion_ = DIR_RIGHT;
|
|
} else if( (gesture_ud_count_ == 0) && (gesture_lr_count_ == -1) ) {
|
|
gesture_motion_ = DIR_LEFT;
|
|
} else if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == 1) ) {
|
|
if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) {
|
|
gesture_motion_ = DIR_UP;
|
|
} else {
|
|
gesture_motion_ = DIR_RIGHT;
|
|
}
|
|
} else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == -1) ) {
|
|
if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) {
|
|
gesture_motion_ = DIR_DOWN;
|
|
} else {
|
|
gesture_motion_ = DIR_LEFT;
|
|
}
|
|
} else if( (gesture_ud_count_ == -1) && (gesture_lr_count_ == -1) ) {
|
|
if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) {
|
|
gesture_motion_ = DIR_UP;
|
|
} else {
|
|
gesture_motion_ = DIR_LEFT;
|
|
}
|
|
} else if( (gesture_ud_count_ == 1) && (gesture_lr_count_ == 1) ) {
|
|
if( abs(gesture_ud_delta_) > abs(gesture_lr_delta_) ) {
|
|
gesture_motion_ = DIR_DOWN;
|
|
} else {
|
|
gesture_motion_ = DIR_RIGHT;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void handleGesture(void) {
|
|
if (isGestureAvailable() ) {
|
|
switch (readGesture()) {
|
|
case DIR_UP:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("UP"));
|
|
snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Up"));
|
|
break;
|
|
case DIR_DOWN:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("DOWN"));
|
|
snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Down"));
|
|
break;
|
|
case DIR_LEFT:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("LEFT"));
|
|
snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Left"));
|
|
break;
|
|
case DIR_RIGHT:
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("RIGHT"));
|
|
snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Right"));
|
|
break;
|
|
default:
|
|
if(APDS9960_overload)
|
|
{
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("LONG"));
|
|
snprintf_P(currentGesture, sizeof(currentGesture), PSTR("Long"));
|
|
}
|
|
else{
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR("NONE"));
|
|
snprintf_P(currentGesture, sizeof(currentGesture), PSTR("None"));
|
|
}
|
|
}
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
|
|
void APDS9960_adjustATime(void)
|
|
{
|
|
|
|
I2cValidRead16LE(&color_data.a, APDS9960_I2C_ADDR, APDS9960_CDATAL);
|
|
|
|
|
|
if (color_data.a < (uint16_t)20){
|
|
APDS9960_aTime = 0x40;
|
|
}
|
|
else if (color_data.a < (uint16_t)40){
|
|
APDS9960_aTime = 0x80;
|
|
}
|
|
else if (color_data.a < (uint16_t)50){
|
|
APDS9960_aTime = DEFAULT_ATIME;
|
|
}
|
|
else if (color_data.a < (uint16_t)70){
|
|
APDS9960_aTime = 0xc0;
|
|
}
|
|
if (color_data.a < 200){
|
|
APDS9960_aTime = 0xe9;
|
|
}
|
|
|
|
|
|
|
|
else{
|
|
APDS9960_aTime = 0xff;
|
|
}
|
|
|
|
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, APDS9960_aTime);
|
|
enablePower();
|
|
enableLightSensor();
|
|
delay(20);
|
|
}
|
|
|
|
|
|
void APDS9960_loop(void)
|
|
{
|
|
if (recovery_loop_counter > 0){
|
|
recovery_loop_counter -= 1;
|
|
}
|
|
if (recovery_loop_counter == 1 && APDS9960_overload){
|
|
enableGestureSensor();
|
|
APDS9960_overload = false;
|
|
Response_P(PSTR("{\"Gesture\":\"On\"}"));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
|
|
gesture_mode = 1;
|
|
}
|
|
|
|
if (gesture_mode) {
|
|
if (recovery_loop_counter == 0){
|
|
handleGesture();
|
|
|
|
if (APDS9960_overload)
|
|
{
|
|
disableGestureSensor();
|
|
recovery_loop_counter = APDS9960_LONG_RECOVERY;
|
|
Response_P(PSTR("{\"Gesture\":\"Off\"}"));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
|
|
gesture_mode = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void APDS9960_detect(void)
|
|
{
|
|
if (APDS9960type || I2cActive(APDS9960_I2C_ADDR)) { return; }
|
|
|
|
APDS9960type = I2cRead8(APDS9960_I2C_ADDR, APDS9960_ID);
|
|
if (APDS9960type == APDS9960_CHIPID_1 || APDS9960type == APDS9960_CHIPID_2) {
|
|
if (APDS9960_init()) {
|
|
I2cSetActiveFound(APDS9960_I2C_ADDR, APDS9960stype);
|
|
|
|
enableProximitySensor();
|
|
enableGestureSensor();
|
|
} else {
|
|
APDS9960type = 0;
|
|
}
|
|
} else {
|
|
APDS9960type = 0;
|
|
}
|
|
currentGesture[0] = '\0';
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void APDS9960_show(bool json)
|
|
{
|
|
if (!APDS9960type) { return; }
|
|
|
|
if (!gesture_mode && !APDS9960_overload) {
|
|
char red_chr[10];
|
|
char green_chr[10];
|
|
char blue_chr[10];
|
|
char ambient_chr[10];
|
|
char cct_chr[10];
|
|
char prox_chr[10];
|
|
|
|
readAllColorAndProximityData();
|
|
|
|
sprintf (ambient_chr, "%u", color_data.a/4);
|
|
sprintf (red_chr, "%u", color_data.r);
|
|
sprintf (green_chr, "%u", color_data.g);
|
|
sprintf (blue_chr, "%u", color_data.b );
|
|
sprintf (prox_chr, "%u", color_data.p );
|
|
|
|
|
|
|
|
|
|
|
|
calculateColorTemperature();
|
|
sprintf (cct_chr, "%u", color_data.cct);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"Red\":%s,\"Green\":%s,\"Blue\":%s,\"Ambient\":%s,\"CCT\":%s,\"Proximity\":%s}"),
|
|
APDS9960stype, red_chr, green_chr, blue_chr, ambient_chr, cct_chr, prox_chr);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_APDS_9960_SNS, red_chr, green_chr, blue_chr, ambient_chr, cct_chr, prox_chr );
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
if (json && (currentGesture[0] != '\0' )) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":1}"), APDS9960stype, currentGesture);
|
|
currentGesture[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
# 1961 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_27_apds9960.ino"
|
|
bool APDS9960CommandSensor(void)
|
|
{
|
|
bool serviced = true;
|
|
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
disableGestureSensor();
|
|
gesture_mode = 0;
|
|
enableLightSensor();
|
|
APDS9960_overload = false;
|
|
break;
|
|
case 1:
|
|
if (APDS9960type) {
|
|
setGestureGain(DEFAULT_GGAIN);
|
|
setProximityGain(DEFAULT_PGAIN);
|
|
disableLightSensor();
|
|
enableGestureSensor();
|
|
gesture_mode = 1;
|
|
}
|
|
break;
|
|
case 2:
|
|
if (APDS9960type) {
|
|
setGestureGain(GGAIN_2X);
|
|
setProximityGain(PGAIN_2X);
|
|
disableLightSensor();
|
|
enableGestureSensor();
|
|
gesture_mode = 1;
|
|
}
|
|
break;
|
|
default:
|
|
int temp_aTime = (uint8_t)XdrvMailbox.payload;
|
|
if (temp_aTime > 2 && temp_aTime < 256){
|
|
disablePower();
|
|
I2cWrite8(APDS9960_I2C_ADDR, APDS9960_ATIME, temp_aTime);
|
|
enablePower();
|
|
enableLightSensor();
|
|
}
|
|
break;
|
|
}
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_27, GetStateText(gesture_mode));
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns27(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_21)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
APDS9960_detect();
|
|
}
|
|
else if (APDS9960type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
APDS9960_loop();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_27 == XdrvMailbox.index) {
|
|
result = APDS9960CommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
APDS9960_show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
APDS9960_show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino"
|
|
#ifdef USE_TM1638
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_28 28
|
|
|
|
#define TM1638_COLOR_NONE 0
|
|
#define TM1638_COLOR_RED 1
|
|
#define TM1638_COLOR_GREEN 2
|
|
|
|
#define TM1638_CLOCK_DELAY 1
|
|
|
|
uint8_t tm1638_type = 1;
|
|
uint8_t tm1638_clock_pin = 0;
|
|
uint8_t tm1638_data_pin = 0;
|
|
uint8_t tm1638_strobe_pin = 0;
|
|
uint8_t tm1638_displays = 8;
|
|
uint8_t tm1638_active_display = 1;
|
|
uint8_t tm1638_intensity = 0;
|
|
uint8_t tm1638_state = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Tm16XXSend(uint8_t data)
|
|
{
|
|
for (uint32_t i = 0; i < 8; i++) {
|
|
digitalWrite(tm1638_data_pin, !!(data & (1 << i)));
|
|
digitalWrite(tm1638_clock_pin, LOW);
|
|
delayMicroseconds(TM1638_CLOCK_DELAY);
|
|
digitalWrite(tm1638_clock_pin, HIGH);
|
|
}
|
|
}
|
|
|
|
void Tm16XXSendCommand(uint8_t cmd)
|
|
{
|
|
digitalWrite(tm1638_strobe_pin, LOW);
|
|
Tm16XXSend(cmd);
|
|
digitalWrite(tm1638_strobe_pin, HIGH);
|
|
}
|
|
|
|
void TM16XXSendData(uint8_t address, uint8_t data)
|
|
{
|
|
Tm16XXSendCommand(0x44);
|
|
digitalWrite(tm1638_strobe_pin, LOW);
|
|
Tm16XXSend(0xC0 | address);
|
|
Tm16XXSend(data);
|
|
digitalWrite(tm1638_strobe_pin, HIGH);
|
|
}
|
|
|
|
uint8_t Tm16XXReceive(void)
|
|
{
|
|
uint8_t temp = 0;
|
|
|
|
|
|
pinMode(tm1638_data_pin, INPUT);
|
|
digitalWrite(tm1638_data_pin, HIGH);
|
|
|
|
for (uint32_t i = 0; i < 8; ++i) {
|
|
digitalWrite(tm1638_clock_pin, LOW);
|
|
delayMicroseconds(TM1638_CLOCK_DELAY);
|
|
temp |= digitalRead(tm1638_data_pin) << i;
|
|
digitalWrite(tm1638_clock_pin, HIGH);
|
|
}
|
|
|
|
|
|
pinMode(tm1638_data_pin, OUTPUT);
|
|
digitalWrite(tm1638_data_pin, LOW);
|
|
|
|
return temp;
|
|
}
|
|
|
|
|
|
|
|
void Tm16XXClearDisplay(void)
|
|
{
|
|
for (uint32_t i = 0; i < tm1638_displays; i++) {
|
|
TM16XXSendData(i << 1, 0);
|
|
}
|
|
}
|
|
|
|
void Tm1638SetLED(uint8_t color, uint8_t pos)
|
|
{
|
|
TM16XXSendData((pos << 1) + 1, color);
|
|
}
|
|
|
|
void Tm1638SetLEDs(word leds)
|
|
{
|
|
for (uint32_t i = 0; i < tm1638_displays; i++) {
|
|
uint8_t color = 0;
|
|
|
|
if ((leds & (1 << i)) != 0) {
|
|
color |= TM1638_COLOR_RED;
|
|
}
|
|
|
|
if ((leds & (1 << (i + 8))) != 0) {
|
|
color |= TM1638_COLOR_GREEN;
|
|
}
|
|
|
|
Tm1638SetLED(color, i);
|
|
}
|
|
}
|
|
|
|
uint8_t Tm1638GetButtons(void)
|
|
{
|
|
uint8_t keys = 0;
|
|
|
|
digitalWrite(tm1638_strobe_pin, LOW);
|
|
Tm16XXSend(0x42);
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
keys |= Tm16XXReceive() << i;
|
|
}
|
|
digitalWrite(tm1638_strobe_pin, HIGH);
|
|
|
|
return keys;
|
|
}
|
|
|
|
|
|
|
|
void TmInit(void)
|
|
{
|
|
tm1638_type = 0;
|
|
if ((pin[GPIO_TM16CLK] < 99) && (pin[GPIO_TM16DIO] < 99) && (pin[GPIO_TM16STB] < 99)) {
|
|
tm1638_clock_pin = pin[GPIO_TM16CLK];
|
|
tm1638_data_pin = pin[GPIO_TM16DIO];
|
|
tm1638_strobe_pin = pin[GPIO_TM16STB];
|
|
|
|
pinMode(tm1638_data_pin, OUTPUT);
|
|
pinMode(tm1638_clock_pin, OUTPUT);
|
|
pinMode(tm1638_strobe_pin, OUTPUT);
|
|
|
|
digitalWrite(tm1638_strobe_pin, HIGH);
|
|
digitalWrite(tm1638_clock_pin, HIGH);
|
|
|
|
Tm16XXSendCommand(0x40);
|
|
Tm16XXSendCommand(0x80 | (tm1638_active_display ? 8 : 0) | tmin(7, tm1638_intensity));
|
|
|
|
digitalWrite(tm1638_strobe_pin, LOW);
|
|
Tm16XXSend(0xC0);
|
|
for (uint32_t i = 0; i < 16; i++) {
|
|
Tm16XXSend(0x00);
|
|
}
|
|
digitalWrite(tm1638_strobe_pin, HIGH);
|
|
|
|
tm1638_type = 1;
|
|
tm1638_state = 1;
|
|
}
|
|
}
|
|
|
|
void TmLoop(void)
|
|
{
|
|
if (tm1638_state) {
|
|
uint8_t buttons = Tm1638GetButtons();
|
|
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
|
|
SwitchSetVirtual(i, (buttons &1) ^1);
|
|
uint8_t color = (SwitchGetVirtual(i)) ? TM1638_COLOR_NONE : TM1638_COLOR_RED;
|
|
Tm1638SetLED(color, i);
|
|
buttons >>= 1;
|
|
}
|
|
SwitchHandler(1);
|
|
}
|
|
}
|
|
# 201 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino"
|
|
bool Xsns28(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (tm1638_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
TmInit();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
TmLoop();
|
|
break;
|
|
# 223 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_28_tm1638.ino"
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MCP230xx
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_29_mcp230xx.ino"
|
|
#define XSNS_29 29
|
|
#define XI2C_22 22
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t MCP230xx_IODIR = 0x00;
|
|
uint8_t MCP230xx_GPINTEN = 0x02;
|
|
uint8_t MCP230xx_IOCON = 0x05;
|
|
uint8_t MCP230xx_GPPU = 0x06;
|
|
uint8_t MCP230xx_INTF = 0x07;
|
|
uint8_t MCP230xx_INTCAP = 0x08;
|
|
uint8_t MCP230xx_GPIO = 0x09;
|
|
|
|
uint8_t mcp230xx_type = 0;
|
|
uint8_t mcp230xx_pincount = 0;
|
|
uint8_t mcp230xx_int_en = 0;
|
|
uint8_t mcp230xx_int_prio_counter = 0;
|
|
uint8_t mcp230xx_int_counter_en = 0;
|
|
uint8_t mcp230xx_int_retainer_en = 0;
|
|
uint8_t mcp230xx_int_sec_counter = 0;
|
|
|
|
uint8_t mcp230xx_int_report_defer_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
|
|
|
uint16_t mcp230xx_int_counter[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
|
|
|
uint8_t mcp230xx_int_retainer[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
|
|
|
unsigned long int_millis[16];
|
|
|
|
const char MCP230XX_SENSOR_RESPONSE[] PROGMEM = "{\"Sensor29_D%i\":{\"MODE\":%i,\"PULL_UP\":\"%s\",\"INT_MODE\":\"%s\",\"STATE\":\"%s\"}}";
|
|
|
|
const char MCP230XX_INTCFG_RESPONSE[] PROGMEM = "{\"MCP230xx_INT%s\":{\"D_%i\":%i}}";
|
|
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
const char MCP230XX_CMND_RESPONSE[] PROGMEM = "{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"}}";
|
|
#endif
|
|
|
|
void MCP230xx_CheckForIntCounter(void) {
|
|
uint8_t en = 0;
|
|
for (uint32_t ca=0;ca<16;ca++) {
|
|
if (Settings.mcp230xx_config[ca].int_count_en) {
|
|
en=1;
|
|
}
|
|
}
|
|
if (!Settings.mcp230xx_int_timer) en=0;
|
|
mcp230xx_int_counter_en=en;
|
|
if (!mcp230xx_int_counter_en) {
|
|
for (uint32_t ca=0;ca<16;ca++) {
|
|
mcp230xx_int_counter[ca] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MCP230xx_CheckForIntRetainer(void) {
|
|
uint8_t en = 0;
|
|
for (uint32_t ca=0;ca<16;ca++) {
|
|
if (Settings.mcp230xx_config[ca].int_retain_flag) {
|
|
en=1;
|
|
}
|
|
}
|
|
mcp230xx_int_retainer_en=en;
|
|
if (!mcp230xx_int_retainer_en) {
|
|
for (uint32_t ca=0;ca<16;ca++) {
|
|
mcp230xx_int_retainer[ca] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* ConvertNumTxt(uint8_t statu, uint8_t pinmod=0) {
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
if ((6 == pinmod) && (statu < 2)) { statu = abs(statu-1); }
|
|
#endif
|
|
switch (statu) {
|
|
case 0:
|
|
return "OFF";
|
|
break;
|
|
case 1:
|
|
return "ON";
|
|
break;
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
case 2:
|
|
return "TOGGLE";
|
|
break;
|
|
#endif
|
|
}
|
|
return "";
|
|
}
|
|
|
|
const char* IntModeTxt(uint8_t intmo) {
|
|
switch (intmo) {
|
|
case 0:
|
|
return "ALL";
|
|
break;
|
|
case 1:
|
|
return "EVENT";
|
|
break;
|
|
case 2:
|
|
return "TELE";
|
|
break;
|
|
case 3:
|
|
return "DISABLED";
|
|
break;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
uint8_t MCP230xx_readGPIO(uint8_t port) {
|
|
return I2cRead8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port);
|
|
}
|
|
|
|
void MCP230xx_ApplySettings(void)
|
|
{
|
|
uint8_t int_en = 0;
|
|
for (uint32_t mcp230xx_port = 0; mcp230xx_port < mcp230xx_type; mcp230xx_port++) {
|
|
uint8_t reg_gppu = 0;
|
|
uint8_t reg_gpinten = 0;
|
|
uint8_t reg_iodir = 0xFF;
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
uint8_t reg_portpins = 0x00;
|
|
#endif
|
|
for (uint32_t idx = 0; idx < 8; idx++) {
|
|
switch (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode) {
|
|
case 0 ... 1:
|
|
reg_iodir |= (1 << idx);
|
|
break;
|
|
case 2 ... 4:
|
|
reg_iodir |= (1 << idx);
|
|
reg_gpinten |= (1 << idx);
|
|
int_en = 1;
|
|
break;
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
case 5 ... 6:
|
|
reg_iodir &= ~(1 << idx);
|
|
if (Settings.flag.save_state) {
|
|
reg_portpins |= (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].saved_state << idx);
|
|
} else {
|
|
if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) {
|
|
reg_portpins |= (1 << idx);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
if ((Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) && (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pinmode < 5)) {
|
|
reg_gppu |= (1 << idx);
|
|
}
|
|
#else
|
|
if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) {
|
|
reg_gppu |= (1 << idx);
|
|
}
|
|
#endif
|
|
}
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPPU+mcp230xx_port, reg_gppu);
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPINTEN+mcp230xx_port, reg_gpinten);
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IODIR+mcp230xx_port, reg_iodir);
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO+mcp230xx_port, reg_portpins);
|
|
#endif
|
|
}
|
|
for (uint32_t idx=0;idx<mcp230xx_pincount;idx++) {
|
|
int_millis[idx]=millis();
|
|
}
|
|
mcp230xx_int_en = int_en;
|
|
MCP230xx_CheckForIntCounter();
|
|
MCP230xx_CheckForIntRetainer();
|
|
}
|
|
|
|
void MCP230xx_Detect(void)
|
|
{
|
|
if (I2cActive(USE_MCP230xx_ADDR)) { return; }
|
|
|
|
uint8_t buffer;
|
|
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IOCON, 0x80);
|
|
if (I2cValidRead8(&buffer, USE_MCP230xx_ADDR, MCP230xx_IOCON)) {
|
|
if (0x00 == buffer) {
|
|
mcp230xx_type = 1;
|
|
I2cSetActiveFound(USE_MCP230xx_ADDR, "MCP23008");
|
|
mcp230xx_pincount = 8;
|
|
MCP230xx_ApplySettings();
|
|
} else {
|
|
if (0x80 == buffer) {
|
|
mcp230xx_type = 2;
|
|
I2cSetActiveFound(USE_MCP230xx_ADDR, "MCP23017");
|
|
mcp230xx_pincount = 16;
|
|
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_IOCON, 0x00);
|
|
|
|
MCP230xx_GPINTEN = 0x04;
|
|
MCP230xx_GPPU = 0x0C;
|
|
MCP230xx_INTF = 0x0E;
|
|
MCP230xx_INTCAP = 0x10;
|
|
MCP230xx_GPIO = 0x12;
|
|
MCP230xx_ApplySettings();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MCP230xx_CheckForInterrupt(void) {
|
|
uint8_t intf;
|
|
uint8_t mcp230xx_intcap = 0;
|
|
uint8_t report_int;
|
|
for (uint32_t mcp230xx_port = 0; mcp230xx_port < mcp230xx_type; mcp230xx_port++) {
|
|
if (I2cValidRead8(&intf,USE_MCP230xx_ADDR,MCP230xx_INTF+mcp230xx_port)) {
|
|
if (intf > 0) {
|
|
if (I2cValidRead8(&mcp230xx_intcap, USE_MCP230xx_ADDR, MCP230xx_INTCAP+mcp230xx_port)) {
|
|
for (uint32_t intp = 0; intp < 8; intp++) {
|
|
if ((intf >> intp) & 0x01) {
|
|
report_int = 0;
|
|
if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode > 1) {
|
|
switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].pinmode) {
|
|
case 2:
|
|
report_int = 1;
|
|
break;
|
|
case 3:
|
|
if (((mcp230xx_intcap >> intp) & 0x01) == 0) report_int = 1;
|
|
break;
|
|
case 4:
|
|
if (((mcp230xx_intcap >> intp) & 0x01) == 1) report_int = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((mcp230xx_int_counter_en) && (report_int)) {
|
|
if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) {
|
|
mcp230xx_int_counter[intp+(mcp230xx_port*8)]++;
|
|
}
|
|
}
|
|
|
|
if (report_int) {
|
|
if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) {
|
|
mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]++;
|
|
if (mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)] >= Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_defer) {
|
|
mcp230xx_int_report_defer_counter[intp+(mcp230xx_port*8)]=0;
|
|
} else {
|
|
report_int = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (report_int) {
|
|
if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_retain_flag) {
|
|
mcp230xx_int_retainer[intp+(mcp230xx_port*8)] = 1;
|
|
report_int = 0;
|
|
}
|
|
}
|
|
if (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_count_en) {
|
|
report_int = 0;
|
|
}
|
|
if (report_int) {
|
|
bool int_tele = false;
|
|
bool int_event = false;
|
|
unsigned long millis_now = millis();
|
|
unsigned long millis_since_last_int = millis_now - int_millis[intp+(mcp230xx_port*8)];
|
|
int_millis[intp+(mcp230xx_port*8)]=millis_now;
|
|
switch (Settings.mcp230xx_config[intp+(mcp230xx_port*8)].int_report_mode) {
|
|
case 0:
|
|
int_tele=true;
|
|
int_event=true;
|
|
break;
|
|
case 1:
|
|
int_event=true;
|
|
break;
|
|
case 2:
|
|
int_tele=true;
|
|
break;
|
|
}
|
|
if (int_tele) {
|
|
ResponseTime_P(PSTR(",\"MCP230XX_INT\":{\"D%i\":%i,\"MS\":%lu}}"),
|
|
intp+(mcp230xx_port*8), ((mcp230xx_intcap >> intp) & 0x01),millis_since_last_int);
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("MCP230XX_INT"));
|
|
}
|
|
if (int_event) {
|
|
char command[19];
|
|
sprintf(command,"event MCPINT_D%i=%i",intp+(mcp230xx_port*8),((mcp230xx_intcap >> intp) & 0x01));
|
|
ExecuteCommand(command, SRC_RULE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MCP230xx_Show(bool json)
|
|
{
|
|
if (json) {
|
|
uint8_t gpio = MCP230xx_readGPIO(0);
|
|
ResponseAppend_P(PSTR(",\"MCP230XX\":{\"D0\":%i,\"D1\":%i,\"D2\":%i,\"D3\":%i,\"D4\":%i,\"D5\":%i,\"D6\":%i,\"D7\":%i"),
|
|
(gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1);
|
|
if (2 == mcp230xx_type) {
|
|
gpio = MCP230xx_readGPIO(1);
|
|
ResponseAppend_P(PSTR(",\"D8\":%i,\"D9\":%i,\"D10\":%i,\"D11\":%i,\"D12\":%i,\"D13\":%i,\"D14\":%i,\"D15\":%i"),
|
|
(gpio>>0)&1,(gpio>>1)&1,(gpio>>2)&1,(gpio>>3)&1,(gpio>>4)&1,(gpio>>5)&1,(gpio>>6)&1,(gpio>>7)&1);
|
|
}
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
|
|
void MCP230xx_SetOutPin(uint8_t pin,uint8_t pinstate) {
|
|
uint8_t portpins;
|
|
uint8_t port = 0;
|
|
uint8_t pinmo = Settings.mcp230xx_config[pin].pinmode;
|
|
uint8_t interlock = Settings.flag.interlock;
|
|
int pinadd = (pin % 2)+1-(3*(pin % 2));
|
|
char cmnd[7], stt[4];
|
|
if (pin > 7) { port = 1; }
|
|
portpins = MCP230xx_readGPIO(port);
|
|
if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) {
|
|
if (pinstate < 2) {
|
|
if (6 == pinmo) {
|
|
if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins |= (1 << (pin+pinadd-(port*8))),portpins &= ~(1 << (pin-(port*8)));
|
|
} else {
|
|
if (pinstate) portpins &= ~(1 << (pin+pinadd-(port*8))),portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8)));
|
|
}
|
|
} else {
|
|
if (6 == pinmo) {
|
|
portpins |= (1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8)));
|
|
} else {
|
|
portpins &= ~(1 << (pin+pinadd-(port*8))),portpins ^= (1 << (pin-(port*8)));
|
|
}
|
|
}
|
|
} else {
|
|
if (pinstate < 2) {
|
|
if (pinstate) portpins |= (1 << (pin-(port*8))); else portpins &= ~(1 << (pin-(port*8)));
|
|
} else {
|
|
portpins ^= (1 << (pin-(port*8)));
|
|
}
|
|
}
|
|
I2cWrite8(USE_MCP230xx_ADDR, MCP230xx_GPIO + port, portpins);
|
|
if (Settings.flag.save_state) {
|
|
Settings.mcp230xx_config[pin].saved_state=portpins>>(pin-(port*8))&1;
|
|
Settings.mcp230xx_config[pin+pinadd].saved_state=portpins>>(pin+pinadd-(port*8))&1;
|
|
}
|
|
sprintf(cmnd,ConvertNumTxt(pinstate, pinmo));
|
|
sprintf(stt,ConvertNumTxt((portpins >> (pin-(port*8))&1), pinmo));
|
|
if (interlock && (pinmo == Settings.mcp230xx_config[pin+pinadd].pinmode)) {
|
|
char stt1[4];
|
|
sprintf(stt1,ConvertNumTxt((portpins >> (pin+pinadd-(port*8))&1), pinmo));
|
|
Response_P(PSTR("{\"S29cmnd_D%i\":{\"COMMAND\":\"%s\",\"STATE\":\"%s\"},\"S29cmnd_D%i\":{\"STATE\":\"%s\"}}"),pin, cmnd, stt, pin+pinadd, stt1);
|
|
} else {
|
|
Response_P(MCP230XX_CMND_RESPONSE, pin, cmnd, stt);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void MCP230xx_Reset(uint8_t pinmode) {
|
|
uint8_t pullup = 0;
|
|
if ((pinmode > 1) && (pinmode < 5)) { pullup=1; }
|
|
for (uint32_t pinx=0;pinx<16;pinx++) {
|
|
Settings.mcp230xx_config[pinx].pinmode=pinmode;
|
|
Settings.mcp230xx_config[pinx].pullup=pullup;
|
|
Settings.mcp230xx_config[pinx].saved_state=0;
|
|
if ((pinmode > 1) && (pinmode < 5)) {
|
|
Settings.mcp230xx_config[pinx].int_report_mode=0;
|
|
} else {
|
|
Settings.mcp230xx_config[pinx].int_report_mode=3;
|
|
}
|
|
Settings.mcp230xx_config[pinx].int_report_defer=0;
|
|
Settings.mcp230xx_config[pinx].int_count_en=0;
|
|
Settings.mcp230xx_config[pinx].int_retain_flag=0;
|
|
Settings.mcp230xx_config[pinx].spare13=0;
|
|
Settings.mcp230xx_config[pinx].spare14=0;
|
|
Settings.mcp230xx_config[pinx].spare15=0;
|
|
}
|
|
Settings.mcp230xx_int_prio = 0;
|
|
Settings.mcp230xx_int_timer = 0;
|
|
MCP230xx_ApplySettings();
|
|
char pulluptxt[7];
|
|
char intmodetxt[9];
|
|
sprintf(pulluptxt,ConvertNumTxt(pullup));
|
|
uint8_t intmode = 3;
|
|
if ((pinmode > 1) && (pinmode < 5)) { intmode = 0; }
|
|
sprintf(intmodetxt,IntModeTxt(intmode));
|
|
Response_P(MCP230XX_SENSOR_RESPONSE,99,pinmode,pulluptxt,intmodetxt,"");
|
|
}
|
|
|
|
bool MCP230xx_Command(void)
|
|
{
|
|
bool serviced = true;
|
|
bool validpin = false;
|
|
uint8_t paramcount = 0;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
paramcount=1;
|
|
} else {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
char sub_string[XdrvMailbox.data_len];
|
|
for (uint32_t ca=0;ca<XdrvMailbox.data_len;ca++) {
|
|
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
|
|
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
|
|
}
|
|
UpperCase(XdrvMailbox.data,XdrvMailbox.data);
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET")) { MCP230xx_Reset(1); return serviced; }
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET1")) { MCP230xx_Reset(1); return serviced; }
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET2")) { MCP230xx_Reset(2); return serviced; }
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET3")) { MCP230xx_Reset(3); return serviced; }
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET4")) { MCP230xx_Reset(4); return serviced; }
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET5")) { MCP230xx_Reset(5); return serviced; }
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"RESET6")) { MCP230xx_Reset(6); return serviced; }
|
|
#endif
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTPRI")) {
|
|
if (paramcount > 1) {
|
|
uint8_t intpri = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if ((intpri >= 0) && (intpri <= 20)) {
|
|
Settings.mcp230xx_int_prio = intpri;
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio);
|
|
return serviced;
|
|
}
|
|
} else {
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"PRI",99,Settings.mcp230xx_int_prio);
|
|
return serviced;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTTIMER")) {
|
|
if (paramcount > 1) {
|
|
uint8_t inttim = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if ((inttim >= 0) && (inttim <= 3600)) {
|
|
Settings.mcp230xx_int_timer = inttim;
|
|
MCP230xx_CheckForIntCounter();
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer);
|
|
return serviced;
|
|
}
|
|
} else {
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"TIMER",99,Settings.mcp230xx_int_timer);
|
|
return serviced;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTDEF")) {
|
|
if (paramcount > 1) {
|
|
uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if (pin < mcp230xx_pincount) {
|
|
if (pin == 0) {
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true;
|
|
} else {
|
|
validpin = true;
|
|
}
|
|
}
|
|
if (validpin) {
|
|
if (paramcount > 2) {
|
|
uint8_t intdef = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
if ((intdef >= 0) && (intdef <= 15)) {
|
|
Settings.mcp230xx_config[pin].int_report_defer=intdef;
|
|
if (Settings.mcp230xx_config[pin].int_count_en) {
|
|
Settings.mcp230xx_config[pin].int_count_en=0;
|
|
MCP230xx_CheckForIntCounter();
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled INTCNT for pin D%i"),pin);
|
|
}
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer);
|
|
return serviced;
|
|
} else {
|
|
serviced=false;
|
|
return serviced;
|
|
}
|
|
} else {
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"DEF",pin,Settings.mcp230xx_config[pin].int_report_defer);
|
|
return serviced;
|
|
}
|
|
}
|
|
serviced = false;
|
|
return serviced;
|
|
} else {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTCNT")) {
|
|
if (paramcount > 1) {
|
|
uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if (pin < mcp230xx_pincount) {
|
|
if (pin == 0) {
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true;
|
|
} else {
|
|
validpin = true;
|
|
}
|
|
}
|
|
if (validpin) {
|
|
if (paramcount > 2) {
|
|
uint8_t intcnt = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
if ((intcnt >= 0) && (intcnt <= 1)) {
|
|
Settings.mcp230xx_config[pin].int_count_en=intcnt;
|
|
if (Settings.mcp230xx_config[pin].int_report_defer) {
|
|
Settings.mcp230xx_config[pin].int_report_defer=0;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled INTDEF for pin D%i"),pin);
|
|
}
|
|
if (Settings.mcp230xx_config[pin].int_report_mode < 3) {
|
|
Settings.mcp230xx_config[pin].int_report_mode=3;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - Disabled immediate interrupt/telemetry reporting for pin D%i"),pin);
|
|
}
|
|
if ((Settings.mcp230xx_config[pin].int_count_en) && (!Settings.mcp230xx_int_timer)) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("*** WARNING *** - INTCNT enabled for pin D%i but global INTTIMER is disabled!"),pin);
|
|
}
|
|
MCP230xx_CheckForIntCounter();
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en);
|
|
return serviced;
|
|
} else {
|
|
serviced=false;
|
|
return serviced;
|
|
}
|
|
} else {
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"CNT",pin,Settings.mcp230xx_config[pin].int_count_en);
|
|
return serviced;
|
|
}
|
|
}
|
|
serviced = false;
|
|
return serviced;
|
|
} else {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
}
|
|
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"INTRETAIN")) {
|
|
if (paramcount > 1) {
|
|
uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
if (pin < mcp230xx_pincount) {
|
|
if (pin == 0) {
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0")) validpin=true;
|
|
} else {
|
|
validpin = true;
|
|
}
|
|
}
|
|
if (validpin) {
|
|
if (paramcount > 2) {
|
|
uint8_t int_retain = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
if ((int_retain >= 0) && (int_retain <= 1)) {
|
|
Settings.mcp230xx_config[pin].int_retain_flag=int_retain;
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag);
|
|
MCP230xx_CheckForIntRetainer();
|
|
return serviced;
|
|
} else {
|
|
serviced=false;
|
|
return serviced;
|
|
}
|
|
} else {
|
|
Response_P(MCP230XX_INTCFG_RESPONSE,"INT_RETAIN",pin,Settings.mcp230xx_config[pin].int_retain_flag);
|
|
return serviced;
|
|
}
|
|
}
|
|
serviced = false;
|
|
return serviced;
|
|
} else {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
}
|
|
|
|
uint8_t pin = atoi(subStr(sub_string, XdrvMailbox.data, ",", 1));
|
|
|
|
if (pin < mcp230xx_pincount) {
|
|
if (0 == pin) {
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1), "0")) validpin=true;
|
|
} else {
|
|
validpin=true;
|
|
}
|
|
}
|
|
if (validpin && (paramcount > 1)) {
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "?")) {
|
|
uint8_t port = 0;
|
|
if (pin > 7) { port = 1; }
|
|
uint8_t portdata = MCP230xx_readGPIO(port);
|
|
char pulluptxtr[7],pinstatustxtr[7];
|
|
char intmodetxt[9];
|
|
sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode));
|
|
sprintf(pulluptxtr,ConvertNumTxt(Settings.mcp230xx_config[pin].pullup));
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
uint8_t pinmod = Settings.mcp230xx_config[pin].pinmode;
|
|
sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1,pinmod));
|
|
Response_P(MCP230XX_SENSOR_RESPONSE,pin,pinmod,pulluptxtr,intmodetxt,pinstatustxtr);
|
|
#else
|
|
sprintf(pinstatustxtr,ConvertNumTxt(portdata>>(pin-(port*8))&1));
|
|
Response_P(MCP230XX_SENSOR_RESPONSE,pin,Settings.mcp230xx_config[pin].pinmode,pulluptxtr,intmodetxt,pinstatustxtr);
|
|
#endif
|
|
return serviced;
|
|
}
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
if (Settings.mcp230xx_config[pin].pinmode >= 5) {
|
|
uint8_t pincmd = Settings.mcp230xx_config[pin].pinmode - 5;
|
|
if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "ON")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "1"))) {
|
|
MCP230xx_SetOutPin(pin,abs(pincmd-1));
|
|
return serviced;
|
|
}
|
|
if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "OFF")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "0"))) {
|
|
MCP230xx_SetOutPin(pin,pincmd);
|
|
return serviced;
|
|
}
|
|
if ((!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "T")) || (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 2), "2"))) {
|
|
MCP230xx_SetOutPin(pin,2);
|
|
return serviced;
|
|
}
|
|
}
|
|
#endif
|
|
uint8_t pinmode = 0;
|
|
uint8_t pullup = 0;
|
|
uint8_t intmode = 0;
|
|
if (paramcount > 1) {
|
|
pinmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
}
|
|
if (paramcount > 2) {
|
|
pullup = atoi(subStr(sub_string, XdrvMailbox.data, ",", 3));
|
|
}
|
|
if (paramcount > 3) {
|
|
intmode = atoi(subStr(sub_string, XdrvMailbox.data, ",", 4));
|
|
}
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 7) && (pullup < 2) && (paramcount > 2)) {
|
|
#else
|
|
if ((pin < mcp230xx_pincount) && (pinmode > 0) && (pinmode < 5) && (pullup < 2) && (paramcount > 2)) {
|
|
#endif
|
|
Settings.mcp230xx_config[pin].pinmode=pinmode;
|
|
Settings.mcp230xx_config[pin].pullup=pullup;
|
|
if ((pinmode > 1) && (pinmode < 5)) {
|
|
if ((intmode >= 0) && (intmode <= 3)) {
|
|
Settings.mcp230xx_config[pin].int_report_mode=intmode;
|
|
}
|
|
} else {
|
|
Settings.mcp230xx_config[pin].int_report_mode=3;
|
|
}
|
|
MCP230xx_ApplySettings();
|
|
uint8_t port = 0;
|
|
if (pin > 7) { port = 1; }
|
|
uint8_t portdata = MCP230xx_readGPIO(port);
|
|
char pulluptxtc[7], pinstatustxtc[7];
|
|
char intmodetxt[9];
|
|
sprintf(pulluptxtc,ConvertNumTxt(pullup));
|
|
sprintf(intmodetxt,IntModeTxt(Settings.mcp230xx_config[pin].int_report_mode));
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1,Settings.mcp230xx_config[pin].pinmode));
|
|
#else
|
|
sprintf(pinstatustxtc,ConvertNumTxt(portdata>>(pin-(port*8))&1));
|
|
#endif
|
|
Response_P(MCP230XX_SENSOR_RESPONSE,pin,pinmode,pulluptxtc,intmodetxt,pinstatustxtc);
|
|
return serviced;
|
|
}
|
|
} else {
|
|
serviced=false;
|
|
return serviced;
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
#ifdef USE_MCP230xx_DISPLAYOUTPUT
|
|
|
|
const char HTTP_SNS_MCP230xx_OUTPUT[] PROGMEM = "{s}MCP230XX D%d{m}%s{e}";
|
|
|
|
void MCP230xx_UpdateWebData(void)
|
|
{
|
|
uint8_t gpio1 = MCP230xx_readGPIO(0);
|
|
uint8_t gpio2 = 0;
|
|
if (2 == mcp230xx_type) {
|
|
gpio2 = MCP230xx_readGPIO(1);
|
|
}
|
|
uint16_t gpio = (gpio2 << 8) + gpio1;
|
|
for (uint32_t pin = 0; pin < mcp230xx_pincount; pin++) {
|
|
if (Settings.mcp230xx_config[pin].pinmode >= 5) {
|
|
char stt[7];
|
|
sprintf(stt,ConvertNumTxt((gpio>>pin)&1,Settings.mcp230xx_config[pin].pinmode));
|
|
WSContentSend_PD(HTTP_SNS_MCP230xx_OUTPUT, pin, stt);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
|
|
void MCP230xx_OutputTelemetry(void)
|
|
{
|
|
uint8_t outputcount = 0;
|
|
uint16_t gpiototal = 0;
|
|
uint8_t gpioa = 0;
|
|
uint8_t gpiob = 0;
|
|
gpioa=MCP230xx_readGPIO(0);
|
|
if (2 == mcp230xx_type) { gpiob=MCP230xx_readGPIO(1); }
|
|
gpiototal=((uint16_t)gpiob << 8) | gpioa;
|
|
for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
|
|
if (Settings.mcp230xx_config[pinx].pinmode >= 5) outputcount++;
|
|
}
|
|
if (outputcount) {
|
|
char stt[7];
|
|
ResponseTime_P(PSTR(",\"MCP230_OUT\":{"));
|
|
for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
|
|
if (Settings.mcp230xx_config[pinx].pinmode >= 5) {
|
|
sprintf(stt,ConvertNumTxt(((gpiototal>>pinx)&1),Settings.mcp230xx_config[pinx].pinmode));
|
|
ResponseAppend_P(PSTR("\"OUT_D%i\":\"%s\","),pinx,stt);
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("\"END\":1}}"));
|
|
MqttPublishTeleSensor();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
void MCP230xx_Interrupt_Counter_Report(void) {
|
|
ResponseTime_P(PSTR(",\"MCP230_INTTIMER\":{"));
|
|
for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
|
|
if (Settings.mcp230xx_config[pinx].int_count_en) {
|
|
ResponseAppend_P(PSTR("\"INTCNT_D%i\":%i,"),pinx,mcp230xx_int_counter[pinx]);
|
|
mcp230xx_int_counter[pinx]=0;
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("\"END\":1}}"));
|
|
MqttPublishTeleSensor();
|
|
mcp230xx_int_sec_counter = 0;
|
|
}
|
|
|
|
void MCP230xx_Interrupt_Retain_Report(void) {
|
|
uint16_t retainresult = 0;
|
|
ResponseTime_P(PSTR(",\"MCP_INTRETAIN\":{"));
|
|
for (uint32_t pinx = 0;pinx < mcp230xx_pincount;pinx++) {
|
|
if (Settings.mcp230xx_config[pinx].int_retain_flag) {
|
|
ResponseAppend_P(PSTR("\"D%i\":%i,"),pinx,mcp230xx_int_retainer[pinx]);
|
|
retainresult |= (((mcp230xx_int_retainer[pinx])&1) << pinx);
|
|
mcp230xx_int_retainer[pinx]=0;
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("\"Value\":%u}}"),retainresult);
|
|
MqttPublishTeleSensor();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns29(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_22)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
MCP230xx_Detect();
|
|
}
|
|
else if (mcp230xx_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (mcp230xx_int_en) {
|
|
mcp230xx_int_prio_counter++;
|
|
if ((mcp230xx_int_prio_counter) >= (Settings.mcp230xx_int_prio)) {
|
|
MCP230xx_CheckForInterrupt();
|
|
mcp230xx_int_prio_counter=0;
|
|
}
|
|
}
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
if (mcp230xx_int_counter_en) {
|
|
mcp230xx_int_sec_counter++;
|
|
if (mcp230xx_int_sec_counter >= Settings.mcp230xx_int_timer) {
|
|
MCP230xx_Interrupt_Counter_Report();
|
|
}
|
|
}
|
|
if (tele_period == 0) {
|
|
if (mcp230xx_int_retainer_en) {
|
|
MCP230xx_Interrupt_Retain_Report();
|
|
}
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
MCP230xx_OutputTelemetry();
|
|
#endif
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MCP230xx_Show(1);
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_29 == XdrvMailbox.index) {
|
|
result = MCP230xx_Command();
|
|
}
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef USE_MCP230xx_OUTPUT
|
|
#ifdef USE_MCP230xx_DISPLAYOUTPUT
|
|
case FUNC_WEB_SENSOR:
|
|
MCP230xx_UpdateWebData();
|
|
break;
|
|
#endif
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
|
|
# 46 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MPR121
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_30 30
|
|
#define XI2C_23 23
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define MPR121_ELEX_REG 0x00
|
|
|
|
|
|
#define MPR121_MHDR_REG 0x2B
|
|
|
|
|
|
#define MPR121_MHDR_VAL 0x01
|
|
|
|
|
|
#define MPR121_NHDR_REG 0x2C
|
|
|
|
|
|
#define MPR121_NHDR_VAL 0x01
|
|
|
|
|
|
#define MPR121_NCLR_REG 0x2D
|
|
|
|
|
|
#define MPR121_NCLR_VAL 0x0E
|
|
|
|
|
|
#define MPR121_MHDF_REG 0x2F
|
|
|
|
|
|
#define MPR121_MHDF_VAL 0x01
|
|
|
|
|
|
#define MPR121_NHDF_REG 0x30
|
|
|
|
|
|
#define MPR121_NHDF_VAL 0x05
|
|
|
|
|
|
#define MPR121_NCLF_REG 0x31
|
|
|
|
|
|
#define MPR121_NCLF_VAL 0x01
|
|
|
|
|
|
#define MPR121_MHDPROXR_REG 0x36
|
|
|
|
|
|
#define MPR121_MHDPROXR_VAL 0x3F
|
|
|
|
|
|
#define MPR121_NHDPROXR_REG 0x37
|
|
|
|
|
|
#define MPR121_NHDPROXR_VAL 0x5F
|
|
|
|
|
|
#define MPR121_NCLPROXR_REG 0x38
|
|
|
|
|
|
#define MPR121_NCLPROXR_VAL 0x04
|
|
|
|
|
|
#define MPR121_FDLPROXR_REG 0x39
|
|
|
|
|
|
#define MPR121_FDLPROXR_VAL 0x00
|
|
|
|
|
|
#define MPR121_MHDPROXF_REG 0x3A
|
|
|
|
|
|
#define MPR121_MHDPROXF_VAL 0x01
|
|
|
|
|
|
#define MPR121_NHDPROXF_REG 0x3B
|
|
|
|
|
|
#define MPR121_NHDPROXF_VAL 0x01
|
|
|
|
|
|
#define MPR121_NCLPROXF_REG 0x3C
|
|
|
|
|
|
#define MPR121_NCLPROXF_VAL 0x1F
|
|
|
|
|
|
#define MPR121_FDLPROXF_REG 0x3D
|
|
|
|
|
|
#define MPR121_FDLPROXF_VAL 0x04
|
|
|
|
|
|
#define MPR121_E0TTH_REG 0x41
|
|
|
|
|
|
#define MPR121_E0TTH_VAL 12
|
|
|
|
|
|
#define MPR121_E0RTH_REG 0x42
|
|
|
|
|
|
#define MPR121_E0RTH_VAL 6
|
|
|
|
|
|
#define MPR121_CDT_REG 0x5D
|
|
|
|
|
|
#define MPR121_CDT_VAL 0x20
|
|
|
|
|
|
#define MPR121_ECR_REG 0x5E
|
|
|
|
|
|
#define MPR121_ECR_VAL 0x8F
|
|
|
|
|
|
|
|
#define MPR121_SRST_REG 0x80
|
|
|
|
|
|
#define MPR121_SRST_VAL 0x63
|
|
|
|
|
|
#define BITC(sensor,position) ((pS->current[sensor] >> position) & 1)
|
|
|
|
|
|
#define BITP(sensor,position) ((pS->previous[sensor] >> position) & 1)
|
|
# 195 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
|
|
typedef struct mpr121 mpr121;
|
|
struct mpr121 {
|
|
const uint8_t i2c_addr[4] = { 0x5A, 0x5B, 0x5C, 0x5D };
|
|
const char id[4] = { 'A', 'B', 'C', 'D' };
|
|
bool connected[4] = { false, false, false, false };
|
|
bool running[4] = { false, false, false, false };
|
|
uint16_t current[4] = { 0x0000, 0x0000, 0x0000, 0x0000 };
|
|
uint16_t previous[4] = { 0x0000, 0x0000, 0x0000, 0x0000 };
|
|
};
|
|
|
|
bool mpr21_found = false;
|
|
# 217 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
|
|
void Mpr121Init(struct mpr121 *pS, bool initial)
|
|
{
|
|
|
|
for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) {
|
|
|
|
if (initial && I2cActive(pS->i2c_addr[i])) { continue; }
|
|
|
|
|
|
pS->connected[i] = (I2cWrite8(pS->i2c_addr[i], MPR121_SRST_REG, MPR121_SRST_VAL)
|
|
&& (0x24 == I2cRead8(pS->i2c_addr[i], 0x5D)));
|
|
if (pS->connected[i]) {
|
|
|
|
|
|
mpr21_found = true;
|
|
char device_name[16];
|
|
snprintf_P(device_name, sizeof(device_name), PSTR("MPR121(%c)"), pS->id[i]);
|
|
I2cSetActiveFound(pS->i2c_addr[i], device_name);
|
|
|
|
|
|
for (uint32_t j = 0; j < 13; j++) {
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_E0TTH_REG + 2 * j, MPR121_E0TTH_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_E0RTH_REG + 2 * j, MPR121_E0RTH_VAL);
|
|
}
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_MHDR_REG, MPR121_MHDR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NHDR_REG, MPR121_NHDR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NCLR_REG, MPR121_NCLR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_MHDF_REG, MPR121_MHDF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NHDF_REG, MPR121_NHDF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NCLF_REG, MPR121_NCLF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXR_REG, MPR121_MHDPROXR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXR_REG, MPR121_NHDPROXR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXR_REG, MPR121_NCLPROXR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXR_REG, MPR121_FDLPROXR_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_MHDPROXF_REG, MPR121_MHDPROXF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NHDPROXF_REG, MPR121_NHDPROXF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_NCLPROXF_REG, MPR121_NCLPROXF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_FDLPROXF_REG, MPR121_FDLPROXF_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_CDT_REG, MPR121_CDT_VAL);
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_ECR_REG, MPR121_ECR_VAL);
|
|
|
|
|
|
pS->running[i] = (0x00 != I2cRead8(pS->i2c_addr[i], MPR121_ECR_REG));
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_I2C "MPR121%c: %sRunning"), pS->id[i], (pS->running[i]) ? "" : "NOT");
|
|
|
|
} else {
|
|
|
|
|
|
pS->running[i] = false;
|
|
}
|
|
}
|
|
|
|
|
|
if (!(pS->connected[0] || pS->connected[1] || pS->connected[2]
|
|
|| pS->connected[3])) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C "MPR121: No sensors found"));
|
|
}
|
|
}
|
|
# 326 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
|
|
void Mpr121Show(struct mpr121 *pS, uint8_t function)
|
|
{
|
|
|
|
|
|
for (uint32_t i = 0; i < sizeof(pS->i2c_addr[i]); i++) {
|
|
|
|
|
|
if (pS->connected[i]) {
|
|
|
|
|
|
if (!I2cValidRead16LE(&pS->current[i], pS->i2c_addr[i], MPR121_ELEX_REG)) {
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Cannot read data!"), pS->id[i]);
|
|
Mpr121Init(pS, false);
|
|
return;
|
|
}
|
|
|
|
if (BITC(i, 15)) {
|
|
|
|
|
|
I2cWrite8(pS->i2c_addr[i], MPR121_ELEX_REG, 0x00);
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_I2C "MPR121%c: ERROR: Excess current detected! Fix circuits if it happens repeatedly! Soft-resetting MPR121 ..."), pS->id[i]);
|
|
Mpr121Init(pS, false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (pS->running[i]) {
|
|
|
|
|
|
if (FUNC_JSON_APPEND == function) {
|
|
ResponseAppend_P(PSTR(",\"MPR121%c\":{"), pS->id[i]);
|
|
}
|
|
|
|
for (uint32_t j = 0; j < 13; j++) {
|
|
|
|
|
|
if ((FUNC_EVERY_50_MSECOND == function)
|
|
&& (BITC(i, j) != BITP(i, j))) {
|
|
Response_P(PSTR("{\"MPR121%c\":{\"Button%i\":%i}}"), pS->id[i], j, BITC(i, j));
|
|
MqttPublishPrefixTopic_P(RESULT_OR_STAT, mqtt_data);
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
if (FUNC_WEB_SENSOR == function) {
|
|
WSContentSend_PD(PSTR("{s}MPR121%c Button%d{m}%d{e}"), pS->id[i], j, BITC(i, j));
|
|
}
|
|
#endif
|
|
|
|
|
|
if (FUNC_JSON_APPEND == function) {
|
|
ResponseAppend_P(PSTR("%s\"Button%i\":%i"), (j > 0 ? "," : ""), j, BITC(i, j));
|
|
}
|
|
}
|
|
|
|
|
|
pS->previous[i] = pS->current[i];
|
|
|
|
|
|
if (FUNC_JSON_APPEND == function) {
|
|
ResponseJsonEnd();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
# 410 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_30_mpr121.ino"
|
|
bool Xsns30(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_23)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
|
|
static struct mpr121 mpr121;
|
|
|
|
if (FUNC_INIT == function) {
|
|
|
|
Mpr121Init(&mpr121, true);
|
|
}
|
|
else if (mpr21_found) {
|
|
|
|
switch (function) {
|
|
|
|
|
|
case FUNC_EVERY_50_MSECOND:
|
|
Mpr121Show(&mpr121, FUNC_EVERY_50_MSECOND);
|
|
break;
|
|
|
|
|
|
case FUNC_JSON_APPEND:
|
|
Mpr121Show(&mpr121, FUNC_JSON_APPEND);
|
|
break;
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
case FUNC_WEB_SENSOR:
|
|
Mpr121Show(&mpr121, FUNC_WEB_SENSOR);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_CCS811
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_31_ccs811.ino"
|
|
#define XSNS_31 31
|
|
#define XI2C_24 24
|
|
|
|
#define EVERYNSECONDS 5
|
|
|
|
#include "Adafruit_CCS811.h"
|
|
|
|
Adafruit_CCS811 ccs;
|
|
uint8_t CCS811_ready = 0;
|
|
uint8_t CCS811_type = 0;;
|
|
uint16_t eCO2;
|
|
uint16_t TVOC;
|
|
uint8_t tcnt = 0;
|
|
uint8_t ecnt = 0;
|
|
|
|
|
|
|
|
void CCS811Detect(void)
|
|
{
|
|
if (I2cActive(CCS811_ADDRESS)) { return; }
|
|
|
|
if (!ccs.begin(CCS811_ADDRESS)) {
|
|
CCS811_type = 1;
|
|
I2cSetActiveFound(CCS811_ADDRESS, "CCS811");
|
|
}
|
|
}
|
|
|
|
void CCS811Update(void)
|
|
{
|
|
tcnt++;
|
|
if (tcnt >= EVERYNSECONDS) {
|
|
tcnt = 0;
|
|
CCS811_ready = 0;
|
|
if (ccs.available()) {
|
|
if (!ccs.readData()){
|
|
TVOC = ccs.getTVOC();
|
|
eCO2 = ccs.geteCO2();
|
|
CCS811_ready = 1;
|
|
if (global_update && global_humidity>0 && global_temperature!=9999) { ccs.setEnvironmentalData((uint8_t)global_humidity, global_temperature); }
|
|
ecnt = 0;
|
|
}
|
|
} else {
|
|
|
|
ecnt++;
|
|
if (ecnt > 6) {
|
|
|
|
ccs.begin(CCS811_ADDRESS);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const char HTTP_SNS_CCS811[] PROGMEM =
|
|
"{s}CCS811 " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"
|
|
"{s}CCS811 " D_TVOC "{m}%d " D_UNIT_PARTS_PER_BILLION "{e}";
|
|
|
|
void CCS811Show(bool json)
|
|
{
|
|
if (CCS811_ready) {
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"CCS811\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d}"), eCO2,TVOC);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, eCO2);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_CCS811, eCO2, TVOC);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns31(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_24)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
CCS811Detect();
|
|
}
|
|
else if (CCS811_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
CCS811Update();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
CCS811Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
CCS811Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MPU6050
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino"
|
|
#define XSNS_32 32
|
|
#define XI2C_25 25
|
|
|
|
#define D_SENSOR_MPU6050 "MPU6050"
|
|
|
|
#define MPU_6050_ADDR_AD0_LOW 0x68
|
|
#define MPU_6050_ADDR_AD0_HIGH 0x69
|
|
|
|
uint8_t MPU_6050_address;
|
|
uint8_t MPU_6050_addresses[] = { MPU_6050_ADDR_AD0_LOW, MPU_6050_ADDR_AD0_HIGH };
|
|
uint8_t MPU_6050_found;
|
|
|
|
int16_t MPU_6050_ax = 0, MPU_6050_ay = 0, MPU_6050_az = 0;
|
|
int16_t MPU_6050_gx = 0, MPU_6050_gy = 0, MPU_6050_gz = 0;
|
|
int16_t MPU_6050_temperature = 0;
|
|
|
|
#ifdef USE_MPU6050_DMP
|
|
#include "MPU6050_6Axis_MotionApps20.h"
|
|
#include "I2Cdev.h"
|
|
#include <helper_3dmath.h>
|
|
typedef struct MPU6050_DMP{
|
|
uint8_t devStatus;
|
|
uint16_t packetSize;
|
|
uint16_t fifoCount;
|
|
uint8_t fifoBuffer[64];
|
|
Quaternion q;
|
|
VectorInt16 aa;
|
|
VectorInt16 aaReal;
|
|
VectorFloat gravity;
|
|
float euler[3];
|
|
float yawPitchRoll[3];
|
|
} MPU6050_DMP;
|
|
|
|
MPU6050_DMP MPU6050_dmp;
|
|
#else
|
|
#include <MPU6050.h>
|
|
#endif
|
|
MPU6050 mpu6050;
|
|
|
|
void MPU_6050PerformReading(void)
|
|
{
|
|
#ifdef USE_MPU6050_DMP
|
|
mpu6050.resetFIFO();
|
|
MPU6050_dmp.fifoCount = mpu6050.getFIFOCount();
|
|
while (MPU6050_dmp.fifoCount < MPU6050_dmp.packetSize) MPU6050_dmp.fifoCount = mpu6050.getFIFOCount();
|
|
mpu6050.getFIFOBytes(MPU6050_dmp.fifoBuffer, MPU6050_dmp.packetSize);
|
|
MPU6050_dmp.fifoCount -= MPU6050_dmp.packetSize;
|
|
|
|
mpu6050.dmpGetQuaternion(&MPU6050_dmp.q, MPU6050_dmp.fifoBuffer);
|
|
mpu6050.dmpGetEuler(MPU6050_dmp.euler, &MPU6050_dmp.q);
|
|
mpu6050.dmpGetAccel(&MPU6050_dmp.aa, MPU6050_dmp.fifoBuffer);
|
|
mpu6050.dmpGetGravity(&MPU6050_dmp.gravity, &MPU6050_dmp.q);
|
|
mpu6050.dmpGetLinearAccel(&MPU6050_dmp.aaReal, &MPU6050_dmp.aa, &MPU6050_dmp.gravity);
|
|
mpu6050.dmpGetYawPitchRoll(MPU6050_dmp.yawPitchRoll, &MPU6050_dmp.q, &MPU6050_dmp.gravity);
|
|
MPU_6050_gx = MPU6050_dmp.euler[0] * 180/M_PI;
|
|
MPU_6050_gy = MPU6050_dmp.euler[1] * 180/M_PI;
|
|
MPU_6050_gz = MPU6050_dmp.euler[2] * 180/M_PI;
|
|
MPU_6050_ax = MPU6050_dmp.aaReal.x;
|
|
MPU_6050_ay = MPU6050_dmp.aaReal.y;
|
|
MPU_6050_az = MPU6050_dmp.aaReal.z;
|
|
#else
|
|
mpu6050.getMotion6(
|
|
&MPU_6050_ax,
|
|
&MPU_6050_ay,
|
|
&MPU_6050_az,
|
|
&MPU_6050_gx,
|
|
&MPU_6050_gy,
|
|
&MPU_6050_gz
|
|
);
|
|
#endif
|
|
MPU_6050_temperature = mpu6050.getTemperature();
|
|
}
|
|
# 119 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_32_mpu6050.ino"
|
|
void MPU_6050Detect(void)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(MPU_6050_addresses); i++)
|
|
{
|
|
MPU_6050_address = MPU_6050_addresses[i];
|
|
if (!I2cSetDevice(MPU_6050_address)) { break; }
|
|
mpu6050.setAddr(MPU_6050_addresses[i]);
|
|
|
|
#ifdef USE_MPU6050_DMP
|
|
MPU6050_dmp.devStatus = mpu6050.dmpInitialize();
|
|
mpu6050.setXGyroOffset(220);
|
|
mpu6050.setYGyroOffset(76);
|
|
mpu6050.setZGyroOffset(-85);
|
|
mpu6050.setZAccelOffset(1788);
|
|
if (MPU6050_dmp.devStatus == 0) {
|
|
mpu6050.setDMPEnabled(true);
|
|
MPU6050_dmp.packetSize = mpu6050.dmpGetFIFOPacketSize();
|
|
MPU_6050_found = true;
|
|
}
|
|
#else
|
|
mpu6050.initialize();
|
|
MPU_6050_found = mpu6050.testConnection();
|
|
#endif
|
|
Settings.flag2.axis_resolution = 2;
|
|
}
|
|
|
|
if (MPU_6050_found) {
|
|
I2cSetActiveFound(MPU_6050_address, D_SENSOR_MPU6050);
|
|
}
|
|
}
|
|
|
|
#define D_YAW "Yaw"
|
|
#define D_PITCH "Pitch"
|
|
#define D_ROLL "Roll"
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_AXIS[] PROGMEM =
|
|
"{s}" D_SENSOR_MPU6050 " " D_AX_AXIS "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_AY_AXIS "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_AZ_AXIS "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_GX_AXIS "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_GY_AXIS "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_GZ_AXIS "{m}%s{e}";
|
|
#ifdef USE_MPU6050_DMP
|
|
const char HTTP_SNS_YPR[] PROGMEM =
|
|
"{s}" D_SENSOR_MPU6050 " " D_YAW "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_PITCH "{m}%s{e}"
|
|
"{s}" D_SENSOR_MPU6050 " " D_ROLL "{m}%s{e}";
|
|
#endif
|
|
#endif
|
|
|
|
#define D_JSON_AXIS_AX "AccelXAxis"
|
|
#define D_JSON_AXIS_AY "AccelYAxis"
|
|
#define D_JSON_AXIS_AZ "AccelZAxis"
|
|
#define D_JSON_AXIS_GX "GyroXAxis"
|
|
#define D_JSON_AXIS_GY "GyroYAxis"
|
|
#define D_JSON_AXIS_GZ "GyroZAxis"
|
|
#define D_JSON_YAW "Yaw"
|
|
#define D_JSON_PITCH "Pitch"
|
|
#define D_JSON_ROLL "Roll"
|
|
|
|
void MPU_6050Show(bool json)
|
|
{
|
|
MPU_6050PerformReading();
|
|
|
|
double tempConv = (MPU_6050_temperature / 340.0 + 35.53);
|
|
char temperature[33];
|
|
dtostrfd(tempConv, Settings.flag2.temperature_resolution, temperature);
|
|
char axis_ax[33];
|
|
dtostrfd(MPU_6050_ax, Settings.flag2.axis_resolution, axis_ax);
|
|
char axis_ay[33];
|
|
dtostrfd(MPU_6050_ay, Settings.flag2.axis_resolution, axis_ay);
|
|
char axis_az[33];
|
|
dtostrfd(MPU_6050_az, Settings.flag2.axis_resolution, axis_az);
|
|
char axis_gx[33];
|
|
dtostrfd(MPU_6050_gx, Settings.flag2.axis_resolution, axis_gx);
|
|
char axis_gy[33];
|
|
dtostrfd(MPU_6050_gy, Settings.flag2.axis_resolution, axis_gy);
|
|
char axis_gz[33];
|
|
dtostrfd(MPU_6050_gz, Settings.flag2.axis_resolution, axis_gz);
|
|
#ifdef USE_MPU6050_DMP
|
|
char axis_yaw[33];
|
|
dtostrfd(MPU6050_dmp.yawPitchRoll[0] / PI * 180.0, Settings.flag2.axis_resolution, axis_yaw);
|
|
char axis_pitch[33];
|
|
dtostrfd(MPU6050_dmp.yawPitchRoll[1] / PI * 180.0, Settings.flag2.axis_resolution, axis_pitch);
|
|
char axis_roll[33];
|
|
dtostrfd(MPU6050_dmp.yawPitchRoll[2] / PI * 180.0, Settings.flag2.axis_resolution, axis_roll);
|
|
#endif
|
|
|
|
if (json) {
|
|
char json_axis_ax[25];
|
|
snprintf_P(json_axis_ax, sizeof(json_axis_ax), PSTR(",\"" D_JSON_AXIS_AX "\":%s"), axis_ax);
|
|
char json_axis_ay[25];
|
|
snprintf_P(json_axis_ay, sizeof(json_axis_ay), PSTR(",\"" D_JSON_AXIS_AY "\":%s"), axis_ay);
|
|
char json_axis_az[25];
|
|
snprintf_P(json_axis_az, sizeof(json_axis_az), PSTR(",\"" D_JSON_AXIS_AZ "\":%s"), axis_az);
|
|
char json_axis_gx[25];
|
|
snprintf_P(json_axis_gx, sizeof(json_axis_gx), PSTR(",\"" D_JSON_AXIS_GX "\":%s"), axis_gx);
|
|
char json_axis_gy[25];
|
|
snprintf_P(json_axis_gy, sizeof(json_axis_gy), PSTR(",\"" D_JSON_AXIS_GY "\":%s"), axis_gy);
|
|
char json_axis_gz[25];
|
|
snprintf_P(json_axis_gz, sizeof(json_axis_gz), PSTR(",\"" D_JSON_AXIS_GZ "\":%s"), axis_gz);
|
|
#ifdef USE_MPU6050_DMP
|
|
char json_ypr_y[25];
|
|
snprintf_P(json_ypr_y, sizeof(json_ypr_y), PSTR(",\"" D_JSON_YAW "\":%s"), axis_yaw);
|
|
char json_ypr_p[25];
|
|
snprintf_P(json_ypr_p, sizeof(json_ypr_p), PSTR(",\"" D_JSON_PITCH "\":%s"), axis_pitch);
|
|
char json_ypr_r[25];
|
|
snprintf_P(json_ypr_r, sizeof(json_ypr_r), PSTR(",\"" D_JSON_ROLL "\":%s"), axis_roll);
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s%s%s%s}"),
|
|
D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz,
|
|
json_ypr_y, json_ypr_p, json_ypr_r);
|
|
#else
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s}"),
|
|
D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz);
|
|
#endif
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, D_SENSOR_MPU6050, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_AXIS, axis_ax, axis_ay, axis_az, axis_gx, axis_gy, axis_gz);
|
|
#ifdef USE_MPU6050_DMP
|
|
WSContentSend_PD(HTTP_SNS_YPR, axis_yaw, axis_pitch, axis_roll);
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns32(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_25)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
MPU_6050Detect();
|
|
}
|
|
else if (MPU_6050_found) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
if (tele_period == Settings.tele_period -3) {
|
|
MPU_6050PerformReading();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MPU_6050Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MPU_6050Show(0);
|
|
MPU_6050PerformReading();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DS3231
|
|
# 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_33_ds3231.ino"
|
|
#define XSNS_33 33
|
|
#define XI2C_26 26
|
|
|
|
|
|
#ifndef USE_RTC_ADDR
|
|
#define USE_RTC_ADDR 0x68
|
|
#endif
|
|
|
|
|
|
#define RTC_SECONDS 0x00
|
|
#define RTC_MINUTES 0x01
|
|
#define RTC_HOURS 0x02
|
|
#define RTC_DAY 0x03
|
|
#define RTC_DATE 0x04
|
|
#define RTC_MONTH 0x05
|
|
#define RTC_YEAR 0x06
|
|
#define RTC_CONTROL 0x0E
|
|
#define RTC_STATUS 0x0F
|
|
|
|
#define OSF 7
|
|
#define EOSC 7
|
|
#define BBSQW 6
|
|
#define CONV 5
|
|
#define RS2 4
|
|
#define RS1 3
|
|
#define INTCN 2
|
|
|
|
|
|
#define HR1224 6
|
|
#define CENTURY 7
|
|
#define DYDT 6
|
|
bool ds3231ReadStatus = false;
|
|
bool ds3231WriteStatus = false;
|
|
bool DS3231chipDetected = false;
|
|
|
|
|
|
|
|
|
|
void DS3231Detect(void)
|
|
{
|
|
if (I2cActive(USE_RTC_ADDR)) { return; }
|
|
|
|
if (I2cValidRead(USE_RTC_ADDR, RTC_STATUS, 1)) {
|
|
I2cSetActiveFound(USE_RTC_ADDR, "DS3231");
|
|
DS3231chipDetected = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t bcd2dec(uint8_t n)
|
|
{
|
|
return n - 6 * (n >> 4);
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t dec2bcd(uint8_t n)
|
|
{
|
|
return n + 6 * (n / 10);
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t ReadFromDS3231(void)
|
|
{
|
|
TIME_T tm;
|
|
tm.second = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_SECONDS));
|
|
tm.minute = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MINUTES));
|
|
tm.hour = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_HOURS) & ~_BV(HR1224));
|
|
tm.day_of_week = I2cRead8(USE_RTC_ADDR, RTC_DAY);
|
|
tm.day_of_month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_DATE));
|
|
tm.month = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_MONTH) & ~_BV(CENTURY));
|
|
tm.year = bcd2dec(I2cRead8(USE_RTC_ADDR, RTC_YEAR));
|
|
return MakeTime(tm);
|
|
}
|
|
|
|
|
|
|
|
void SetDS3231Time (uint32_t epoch_time) {
|
|
TIME_T tm;
|
|
BreakTime(epoch_time, tm);
|
|
I2cWrite8(USE_RTC_ADDR, RTC_SECONDS, dec2bcd(tm.second));
|
|
I2cWrite8(USE_RTC_ADDR, RTC_MINUTES, dec2bcd(tm.minute));
|
|
I2cWrite8(USE_RTC_ADDR, RTC_HOURS, dec2bcd(tm.hour));
|
|
I2cWrite8(USE_RTC_ADDR, RTC_DAY, tm.day_of_week);
|
|
I2cWrite8(USE_RTC_ADDR, RTC_DATE, dec2bcd(tm.day_of_month));
|
|
I2cWrite8(USE_RTC_ADDR, RTC_MONTH, dec2bcd(tm.month));
|
|
I2cWrite8(USE_RTC_ADDR, RTC_YEAR, dec2bcd(tm.year));
|
|
I2cWrite8(USE_RTC_ADDR, RTC_STATUS, I2cRead8(USE_RTC_ADDR, RTC_STATUS) & ~_BV(OSF));
|
|
}
|
|
|
|
void DS3231EverySecond(void)
|
|
{
|
|
TIME_T tmpTime;
|
|
if (!ds3231ReadStatus && Rtc.utc_time < START_VALID_TIME ) {
|
|
ntp_force_sync = true;
|
|
Rtc.utc_time = ReadFromDS3231();
|
|
|
|
|
|
BreakTime(Rtc.utc_time, tmpTime);
|
|
if (Rtc.utc_time < START_VALID_TIME ) {
|
|
ds3231ReadStatus = true;
|
|
}
|
|
RtcTime.year = tmpTime.year + 1970;
|
|
Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year);
|
|
Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year);
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Set time from DS3231 to RTC (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
|
|
GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str());
|
|
if (Rtc.local_time < START_VALID_TIME) {
|
|
rules_flag.time_init = 1;
|
|
} else {
|
|
rules_flag.time_set = 1;
|
|
}
|
|
}
|
|
else if (!ds3231WriteStatus && Rtc.utc_time > START_VALID_TIME && abs(Rtc.utc_time - ReadFromDS3231()) > 60) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("Write Time TO DS3231 from NTP (" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
|
|
GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str());
|
|
SetDS3231Time (Rtc.utc_time);
|
|
ds3231WriteStatus = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns33(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_26)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
DS3231Detect();
|
|
}
|
|
else if (DS3231chipDetected) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
DS3231EverySecond();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino"
|
|
#ifdef USE_HX711
|
|
# 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino"
|
|
#define XSNS_34 34
|
|
|
|
#ifndef HX_MAX_WEIGHT
|
|
#define HX_MAX_WEIGHT 20000
|
|
#endif
|
|
#ifndef HX_REFERENCE
|
|
#define HX_REFERENCE 250
|
|
#endif
|
|
#ifndef HX_SCALE
|
|
#define HX_SCALE 120
|
|
#endif
|
|
|
|
#define HX_TIMEOUT 120
|
|
#define HX_SAMPLES 10
|
|
#define HX_CAL_TIMEOUT 15
|
|
|
|
#define HX_GAIN_128 1
|
|
#define HX_GAIN_32 2
|
|
#define HX_GAIN_64 3
|
|
|
|
#define D_JSON_WEIGHT_REF "WeightRef"
|
|
#define D_JSON_WEIGHT_CAL "WeightCal"
|
|
#define D_JSON_WEIGHT_MAX "WeightMax"
|
|
#define D_JSON_WEIGHT_ITEM "WeightItem"
|
|
#define D_JSON_WEIGHT_CHANGE "WeightChange"
|
|
#define D_JSON_WEIGHT_RAW "WeightRaw"
|
|
#define D_JSON_WEIGHT_DELTA "WeightDelta"
|
|
|
|
enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, HX_CAL_DONE, HX_CAL_FIRST, HX_CAL_RESET, HX_CAL_START };
|
|
|
|
const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|" D_HX_CAL_REFERENCE "|" D_HX_CAL_REMOVE;
|
|
|
|
struct HX {
|
|
long weight = 0;
|
|
long raw = 0;
|
|
long last_weight = 0;
|
|
long sum_weight = 0;
|
|
long sum_raw = 0;
|
|
long offset = 0;
|
|
long scale = 1;
|
|
long weight_diff = 0;
|
|
uint8_t type = 1;
|
|
uint8_t sample_count = 0;
|
|
uint8_t calibrate_step = HX_CAL_END;
|
|
uint8_t calibrate_timer = 0;
|
|
uint8_t calibrate_msg = 0;
|
|
uint8_t pin_sck;
|
|
uint8_t pin_dout;
|
|
bool tare_flg = false;
|
|
bool weight_changed = false;
|
|
uint16_t weight_delta = 4;
|
|
} Hx;
|
|
|
|
|
|
|
|
bool HxIsReady(uint16_t timeout)
|
|
{
|
|
|
|
uint32_t start = millis();
|
|
while ((digitalRead(Hx.pin_dout) == HIGH) && (millis() - start < timeout)) { yield(); }
|
|
return (digitalRead(Hx.pin_dout) == LOW);
|
|
}
|
|
|
|
long HxRead(void)
|
|
{
|
|
if (!HxIsReady(HX_TIMEOUT)) { return -1; }
|
|
|
|
uint8_t data[3] = { 0 };
|
|
uint8_t filler = 0x00;
|
|
|
|
|
|
data[2] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST);
|
|
data[1] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST);
|
|
data[0] = shiftIn(Hx.pin_dout, Hx.pin_sck, MSBFIRST);
|
|
|
|
|
|
for (unsigned int i = 0; i < HX_GAIN_128; i++) {
|
|
digitalWrite(Hx.pin_sck, HIGH);
|
|
digitalWrite(Hx.pin_sck, LOW);
|
|
}
|
|
|
|
|
|
if (data[2] & 0x80) { filler = 0xFF; }
|
|
|
|
|
|
unsigned long value = ( static_cast<unsigned long>(filler) << 24
|
|
| static_cast<unsigned long>(data[2]) << 16
|
|
| static_cast<unsigned long>(data[1]) << 8
|
|
| static_cast<unsigned long>(data[0]) );
|
|
|
|
return static_cast<long>(value);
|
|
}
|
|
|
|
|
|
|
|
void HxResetPart(void)
|
|
{
|
|
Hx.tare_flg = true;
|
|
Hx.sum_weight = 0;
|
|
Hx.sample_count = 0;
|
|
Hx.last_weight = 0;
|
|
}
|
|
|
|
void HxReset(void)
|
|
{
|
|
HxResetPart();
|
|
Settings.energy_frequency_calibration = 0;
|
|
}
|
|
|
|
void HxCalibrationStateTextJson(uint8_t msg_id)
|
|
{
|
|
char cal_text[30];
|
|
|
|
Hx.calibrate_msg = msg_id;
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates));
|
|
|
|
if (msg_id < 3) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("Sensor34")); }
|
|
}
|
|
|
|
void SetWeightDelta()
|
|
{
|
|
|
|
if (Settings.weight_change == 0) {
|
|
Hx.weight_delta = 4;
|
|
return;
|
|
}
|
|
|
|
|
|
if (Settings.weight_change > 100) {
|
|
Hx.weight_delta = (Settings.weight_change - 100) * 10 + 100;
|
|
return;
|
|
}
|
|
|
|
|
|
Hx.weight_delta = Settings.weight_change - 1;
|
|
}
|
|
# 192 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_34_hx711.ino"
|
|
bool HxCommand(void)
|
|
{
|
|
bool serviced = true;
|
|
bool show_parms = false;
|
|
char sub_string[XdrvMailbox.data_len +1];
|
|
|
|
for (uint32_t ca = 0; ca < XdrvMailbox.data_len; ca++) {
|
|
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
|
|
}
|
|
|
|
switch (XdrvMailbox.payload) {
|
|
case 1:
|
|
HxReset();
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, "Reset");
|
|
break;
|
|
case 2:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
|
}
|
|
Hx.scale = 1;
|
|
HxReset();
|
|
Hx.calibrate_step = HX_CAL_START;
|
|
Hx.calibrate_timer = 1;
|
|
HxCalibrationStateTextJson(3);
|
|
break;
|
|
case 3:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.weight_reference = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
|
}
|
|
show_parms = true;
|
|
break;
|
|
case 4:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.weight_calibration = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
|
Hx.scale = Settings.weight_calibration;
|
|
}
|
|
show_parms = true;
|
|
break;
|
|
case 5:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.weight_max = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) / 1000;
|
|
}
|
|
show_parms = true;
|
|
break;
|
|
case 6:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.weight_item = (unsigned long)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 2)) * 10);
|
|
}
|
|
show_parms = true;
|
|
break;
|
|
case 7:
|
|
Settings.energy_frequency_calibration = Hx.weight;
|
|
Response_P(S_JSON_SENSOR_INDEX_SVALUE, XSNS_34, D_JSON_DONE);
|
|
break;
|
|
case 8:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.SensorBits1.hx711_json_weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10) & 1;
|
|
}
|
|
show_parms = true;
|
|
break;
|
|
case 9:
|
|
if (strstr(XdrvMailbox.data, ",") != nullptr) {
|
|
Settings.weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
|
|
SetWeightDelta();
|
|
}
|
|
show_parms = true;
|
|
break;
|
|
default:
|
|
show_parms = true;
|
|
}
|
|
|
|
if (show_parms) {
|
|
char item[33];
|
|
dtostrfd((float)Settings.weight_item / 10, 1, item);
|
|
Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\""
|
|
D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":%s,\"" D_JSON_WEIGHT_DELTA "\":%d}}"),
|
|
Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000,
|
|
item, GetStateText(Settings.SensorBits1.hx711_json_weight_change), Settings.weight_change);
|
|
}
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
long HxWeight(void)
|
|
{
|
|
return (Hx.calibrate_step < HX_CAL_FAIL) ? Hx.weight : 0;
|
|
}
|
|
|
|
void HxInit(void)
|
|
{
|
|
Hx.type = 0;
|
|
if ((pin[GPIO_HX711_DAT] < 99) && (pin[GPIO_HX711_SCK] < 99)) {
|
|
Hx.pin_sck = pin[GPIO_HX711_SCK];
|
|
Hx.pin_dout = pin[GPIO_HX711_DAT];
|
|
|
|
pinMode(Hx.pin_sck, OUTPUT);
|
|
pinMode(Hx.pin_dout, INPUT);
|
|
|
|
digitalWrite(Hx.pin_sck, LOW);
|
|
|
|
SetWeightDelta();
|
|
|
|
if (HxIsReady(8 * HX_TIMEOUT)) {
|
|
if (!Settings.weight_max) { Settings.weight_max = HX_MAX_WEIGHT / 1000; }
|
|
if (!Settings.weight_calibration) { Settings.weight_calibration = HX_SCALE; }
|
|
if (!Settings.weight_reference) { Settings.weight_reference = HX_REFERENCE; }
|
|
Hx.scale = Settings.weight_calibration;
|
|
HxRead();
|
|
HxResetPart();
|
|
Hx.type = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HxEvery100mSecond(void)
|
|
{
|
|
long raw = HxRead();
|
|
Hx.sum_raw += raw;
|
|
Hx.sum_weight += raw;
|
|
|
|
Hx.sample_count++;
|
|
if (HX_SAMPLES == Hx.sample_count) {
|
|
long average = Hx.sum_weight / Hx.sample_count;
|
|
long raw_average = Hx.sum_raw / Hx.sample_count;
|
|
long value = average - Hx.offset;
|
|
Hx.weight = value / Hx.scale;
|
|
Hx.raw = raw_average / Hx.scale;
|
|
if (Hx.weight < 0) {
|
|
if (Settings.energy_frequency_calibration) {
|
|
long difference = Settings.energy_frequency_calibration + Hx.weight;
|
|
Hx.last_weight = difference;
|
|
if (difference < 0) { HxReset(); }
|
|
}
|
|
Hx.weight = 0;
|
|
} else {
|
|
Hx.last_weight = Settings.energy_frequency_calibration;
|
|
}
|
|
|
|
if (Hx.tare_flg) {
|
|
Hx.tare_flg = false;
|
|
Hx.offset = average;
|
|
}
|
|
|
|
if (Hx.calibrate_step) {
|
|
Hx.calibrate_timer--;
|
|
|
|
if (HX_CAL_START == Hx.calibrate_step) {
|
|
Hx.calibrate_step--;
|
|
Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
|
|
}
|
|
else if (HX_CAL_RESET == Hx.calibrate_step) {
|
|
if (Hx.calibrate_timer) {
|
|
if (Hx.weight < (long)Settings.weight_reference) {
|
|
Hx.calibrate_step--;
|
|
Hx.calibrate_timer = HX_CAL_TIMEOUT * (10 / HX_SAMPLES);
|
|
HxCalibrationStateTextJson(2);
|
|
}
|
|
} else {
|
|
Hx.calibrate_step = HX_CAL_FAIL;
|
|
}
|
|
}
|
|
else if (HX_CAL_FIRST == Hx.calibrate_step) {
|
|
if (Hx.calibrate_timer) {
|
|
if (Hx.weight > (long)Settings.weight_reference) {
|
|
Hx.calibrate_step--;
|
|
}
|
|
} else {
|
|
Hx.calibrate_step = HX_CAL_FAIL;
|
|
}
|
|
}
|
|
else if (HX_CAL_DONE == Hx.calibrate_step) {
|
|
if (Hx.weight > (long)Settings.weight_reference) {
|
|
Hx.calibrate_step = HX_CAL_FINISH;
|
|
Settings.weight_calibration = Hx.weight / Settings.weight_reference;
|
|
Hx.weight = 0;
|
|
HxCalibrationStateTextJson(1);
|
|
} else {
|
|
Hx.calibrate_step = HX_CAL_FAIL;
|
|
}
|
|
}
|
|
|
|
if (HX_CAL_FAIL == Hx.calibrate_step) {
|
|
Hx.calibrate_step--;
|
|
Hx.tare_flg = true;
|
|
HxCalibrationStateTextJson(0);
|
|
}
|
|
if (HX_CAL_FINISH == Hx.calibrate_step) {
|
|
Hx.calibrate_step--;
|
|
Hx.calibrate_timer = 3 * (10 / HX_SAMPLES);
|
|
Hx.scale = Settings.weight_calibration;
|
|
}
|
|
|
|
if (!Hx.calibrate_timer) {
|
|
Hx.calibrate_step = HX_CAL_END;
|
|
}
|
|
} else {
|
|
Hx.weight += Hx.last_weight;
|
|
|
|
if (Settings.SensorBits1.hx711_json_weight_change) {
|
|
if (abs(Hx.weight - Hx.weight_diff) > Hx.weight_delta) {
|
|
Hx.weight_diff = Hx.weight;
|
|
Hx.weight_changed = true;
|
|
}
|
|
else if (Hx.weight_changed && (Hx.weight == Hx.weight_diff)) {
|
|
mqtt_data[0] = '\0';
|
|
ResponseAppendTime();
|
|
HxShow(true);
|
|
ResponseJsonEnd();
|
|
MqttPublishTeleSensor();
|
|
Hx.weight_changed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
Hx.sum_weight = 0;
|
|
Hx.sum_raw = 0;
|
|
Hx.sample_count = 0;
|
|
}
|
|
}
|
|
|
|
void HxSaveBeforeRestart(void)
|
|
{
|
|
Settings.energy_frequency_calibration = Hx.weight;
|
|
Hx.sample_count = HX_SAMPLES +1;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_HX711_WEIGHT[] PROGMEM =
|
|
"{s}HX711 " D_WEIGHT "{m}%s " D_UNIT_KILOGRAM "{e}";
|
|
const char HTTP_HX711_COUNT[] PROGMEM =
|
|
"{s}HX711 " D_COUNT "{m}%d{e}";
|
|
const char HTTP_HX711_CAL[] PROGMEM =
|
|
"{s}HX711 %s{m}{e}";
|
|
#endif
|
|
|
|
void HxShow(bool json)
|
|
{
|
|
char scount[30] = { 0 };
|
|
|
|
uint16_t count = 0;
|
|
float weight = 0;
|
|
if (Hx.calibrate_step < HX_CAL_FAIL) {
|
|
if (Hx.weight && Settings.weight_item) {
|
|
count = (Hx.weight * 10) / Settings.weight_item;
|
|
if (count > 1) {
|
|
snprintf_P(scount, sizeof(scount), PSTR(",\"" D_JSON_COUNT "\":%d"), count);
|
|
}
|
|
}
|
|
weight = (float)Hx.weight / 1000;
|
|
}
|
|
char weight_chr[33];
|
|
dtostrfd(weight, Settings.flag2.weight_resolution, weight_chr);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s, \"" D_JSON_WEIGHT_RAW "\":%d}"), weight_chr, scount, Hx.raw);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_HX711_WEIGHT, weight_chr);
|
|
if (count > 1) {
|
|
WSContentSend_PD(HTTP_HX711_COUNT, count);
|
|
}
|
|
if (Hx.calibrate_step) {
|
|
char cal_text[30];
|
|
WSContentSend_PD(HTTP_HX711_CAL, GetTextIndexed(cal_text, sizeof(cal_text), Hx.calibrate_msg, kHxCalibrationStates));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
#ifdef USE_HX711_GUI
|
|
|
|
|
|
|
|
|
|
#define WEB_HANDLE_HX711 "s34"
|
|
|
|
const char S_CONFIGURE_HX711[] PROGMEM = D_CONFIGURE_HX711;
|
|
|
|
const char HTTP_BTN_MENU_MAIN_HX711[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_HX711 "' method='get'><button name='reset'>" D_RESET_HX711 "</button></form></p>";
|
|
|
|
const char HTTP_BTN_MENU_HX711[] PROGMEM =
|
|
"<p><form action='" WEB_HANDLE_HX711 "' method='get'><button>" D_CONFIGURE_HX711 "</button></form></p>";
|
|
|
|
const char HTTP_FORM_HX711[] PROGMEM =
|
|
"<fieldset><legend><b> " D_CALIBRATION " </b></legend>"
|
|
"<form method='post' action='" WEB_HANDLE_HX711 "'>"
|
|
"<p><b>" D_REFERENCE_WEIGHT "</b> (" D_UNIT_KILOGRAM ")<br><input type='number' step='0.001' id='p1' placeholder='0' value='%s'></p>"
|
|
"<br><button name='calibrate' type='submit'>" D_CALIBRATE "</button>"
|
|
"</form>"
|
|
"</fieldset><br><br>"
|
|
|
|
"<fieldset><legend><b> " D_HX711_PARAMETERS " </b></legend>"
|
|
"<form method='post' action='" WEB_HANDLE_HX711 "'>"
|
|
"<p><b>" D_ITEM_WEIGHT "</b> (" D_UNIT_KILOGRAM ")<br><input type='number' max='6.5535' step='0.0001' id='p2' placeholder='0.0' value='%s'></p>";
|
|
|
|
void HandleHxAction(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_HX711);
|
|
|
|
if (WebServer->hasArg("save")) {
|
|
HxSaveSettings();
|
|
HandleConfiguration();
|
|
return;
|
|
}
|
|
|
|
char stemp1[20];
|
|
|
|
if (WebServer->hasArg("reset")) {
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR("Sensor34 1"));
|
|
ExecuteWebCommand(stemp1, SRC_WEBGUI);
|
|
|
|
HandleRoot();
|
|
return;
|
|
}
|
|
|
|
if (WebServer->hasArg("calibrate")) {
|
|
WebGetArg("p1", stemp1, sizeof(stemp1));
|
|
Settings.weight_reference = (!strlen(stemp1)) ? 0 : (unsigned long)(CharToFloat(stemp1) * 1000);
|
|
|
|
HxLogUpdates();
|
|
|
|
snprintf_P(stemp1, sizeof(stemp1), PSTR("Sensor34 2"));
|
|
ExecuteWebCommand(stemp1, SRC_WEBGUI);
|
|
|
|
HandleRoot();
|
|
return;
|
|
}
|
|
|
|
WSContentStart_P(S_CONFIGURE_HX711);
|
|
WSContentSendStyle();
|
|
dtostrfd((float)Settings.weight_reference / 1000, 3, stemp1);
|
|
char stemp2[20];
|
|
dtostrfd((float)Settings.weight_item / 10000, 4, stemp2);
|
|
WSContentSend_P(HTTP_FORM_HX711, stemp1, stemp2);
|
|
WSContentSend_P(HTTP_FORM_END);
|
|
WSContentSpaceButton(BUTTON_CONFIGURATION);
|
|
WSContentStop();
|
|
}
|
|
|
|
void HxSaveSettings(void)
|
|
{
|
|
char tmp[100];
|
|
|
|
WebGetArg("p2", tmp, sizeof(tmp));
|
|
Settings.weight_item = (!strlen(tmp)) ? 0 : (unsigned long)(CharToFloat(tmp) * 10000);
|
|
|
|
HxLogUpdates();
|
|
}
|
|
|
|
void HxLogUpdates(void)
|
|
{
|
|
char weigth_ref_chr[33];
|
|
dtostrfd((float)Settings.weight_reference / 1000, Settings.flag2.weight_resolution, weigth_ref_chr);
|
|
char weigth_item_chr[33];
|
|
dtostrfd((float)Settings.weight_item / 10000, 4, weigth_item_chr);
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_JSON_WEIGHT_REF " %s, " D_JSON_WEIGHT_ITEM " %s"), weigth_ref_chr, weigth_item_chr);
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns34(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (Hx.type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_100_MSECOND:
|
|
HxEvery100mSecond();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_34 == XdrvMailbox.index) {
|
|
result = HxCommand();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
HxShow(1);
|
|
break;
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
HxSaveBeforeRestart();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
HxShow(0);
|
|
break;
|
|
#ifdef USE_HX711_GUI
|
|
case FUNC_WEB_ADD_MAIN_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_MAIN_HX711);
|
|
break;
|
|
case FUNC_WEB_ADD_BUTTON:
|
|
WSContentSend_P(HTTP_BTN_MENU_HX711);
|
|
break;
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/" WEB_HANDLE_HX711, HandleHxAction);
|
|
break;
|
|
#endif
|
|
#endif
|
|
case FUNC_INIT:
|
|
HxInit();
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
|
|
#ifdef USE_TX20_WIND_SENSOR
|
|
# 29 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
|
|
#define XSNS_35 35
|
|
|
|
#define TX20_BIT_TIME 1220
|
|
#define TX20_RESET_VALUES 60
|
|
|
|
|
|
|
|
extern "C" {
|
|
#include "gpio.h"
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
const char HTTP_SNS_TX20[] PROGMEM =
|
|
"{s}TX20 " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
|
"{s}TX20 " D_TX20_WIND_SPEED_AVG "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
|
"{s}TX20 " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
|
"{s}TX20 " D_TX20_WIND_DIRECTION "{m}%s{e}";
|
|
|
|
#endif
|
|
|
|
const char kTx20Directions[] PROGMEM = D_TX20_NORTH "|"
|
|
D_TX20_NORTH D_TX20_NORTH D_TX20_EAST "|"
|
|
D_TX20_NORTH D_TX20_EAST "|"
|
|
D_TX20_EAST D_TX20_NORTH D_TX20_EAST "|"
|
|
D_TX20_EAST "|"
|
|
D_TX20_EAST D_TX20_SOUTH D_TX20_EAST "|"
|
|
D_TX20_SOUTH D_TX20_EAST "|"
|
|
D_TX20_SOUTH D_TX20_SOUTH D_TX20_EAST "|"
|
|
D_TX20_SOUTH "|"
|
|
D_TX20_SOUTH D_TX20_SOUTH D_TX20_WEST "|"
|
|
D_TX20_SOUTH D_TX20_WEST "|"
|
|
D_TX20_WEST D_TX20_SOUTH D_TX20_WEST "|"
|
|
D_TX20_WEST "|"
|
|
D_TX20_WEST D_TX20_NORTH D_TX20_WEST "|"
|
|
D_TX20_NORTH D_TX20_WEST "|"
|
|
D_TX20_NORTH D_TX20_NORTH D_TX20_WEST;
|
|
|
|
uint8_t tx20_sa = 0;
|
|
uint8_t tx20_sb = 0;
|
|
uint8_t tx20_sd = 0;
|
|
uint8_t tx20_se = 0;
|
|
uint16_t tx20_sc = 0;
|
|
uint16_t tx20_sf = 0;
|
|
|
|
float tx20_wind_speed_kmh = 0;
|
|
float tx20_wind_speed_max = 0;
|
|
float tx20_wind_speed_avg = 0;
|
|
float tx20_wind_sum = 0;
|
|
int tx20_count = 0;
|
|
uint8_t tx20_wind_direction = 0;
|
|
|
|
bool tx20_available = false;
|
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
void Tx20StartRead(void) ICACHE_RAM_ATTR;
|
|
#endif
|
|
|
|
void Tx20StartRead(void)
|
|
{
|
|
# 101 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_35_tx20.ino"
|
|
tx20_available = false;
|
|
|
|
tx20_sa = 0;
|
|
tx20_sb = 0;
|
|
tx20_sd = 0;
|
|
tx20_se = 0;
|
|
tx20_sc = 0;
|
|
tx20_sf = 0;
|
|
|
|
delayMicroseconds(TX20_BIT_TIME / 2);
|
|
|
|
for (int32_t bitcount = 41; bitcount > 0; bitcount--) {
|
|
uint8_t dpin = (digitalRead(pin[GPIO_TX20_TXD_BLACK]));
|
|
if (bitcount > 41 - 5) {
|
|
|
|
tx20_sa = (tx20_sa << 1) | (dpin ^ 1);
|
|
} else if (bitcount > 41 - 5 - 4) {
|
|
|
|
tx20_sb = tx20_sb >> 1 | ((dpin ^ 1) << 3);
|
|
} else if (bitcount > 41 - 5 - 4 - 12) {
|
|
|
|
tx20_sc = tx20_sc >> 1 | ((dpin ^ 1) << 11);
|
|
} else if (bitcount > 41 - 5 - 4 - 12 - 4) {
|
|
|
|
tx20_sd = tx20_sd >> 1 | ((dpin ^ 1) << 3);
|
|
} else if (bitcount > 41 - 5 - 4 - 12 - 4 - 4) {
|
|
|
|
tx20_se = tx20_se >> 1 | (dpin << 3);
|
|
} else {
|
|
|
|
tx20_sf = tx20_sf >> 1 | (dpin << 11);
|
|
}
|
|
|
|
delayMicroseconds(TX20_BIT_TIME);
|
|
}
|
|
|
|
uint8_t chk = (tx20_sb + (tx20_sc & 0xf) + ((tx20_sc >> 4) & 0xf) + ((tx20_sc >> 8) & 0xf));
|
|
chk &= 0xf;
|
|
|
|
if ((chk == tx20_sd) && (tx20_sc < 400)) {
|
|
tx20_available = true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pin[GPIO_TX20_TXD_BLACK]);
|
|
}
|
|
|
|
void Tx20Read(void)
|
|
{
|
|
if (!(uptime % TX20_RESET_VALUES)) {
|
|
tx20_count = 0;
|
|
tx20_wind_sum = 0;
|
|
tx20_wind_speed_max = 0;
|
|
}
|
|
else if (tx20_available) {
|
|
tx20_wind_speed_kmh = float(tx20_sc) * 0.36;
|
|
if (tx20_wind_speed_kmh > tx20_wind_speed_max) {
|
|
tx20_wind_speed_max = tx20_wind_speed_kmh;
|
|
}
|
|
tx20_count++;
|
|
tx20_wind_sum += tx20_wind_speed_kmh;
|
|
tx20_wind_speed_avg = tx20_wind_sum / tx20_count;
|
|
tx20_wind_direction = tx20_sb;
|
|
}
|
|
}
|
|
|
|
void Tx20Init(void) {
|
|
pinMode(pin[GPIO_TX20_TXD_BLACK], INPUT);
|
|
attachInterrupt(pin[GPIO_TX20_TXD_BLACK], Tx20StartRead, RISING);
|
|
}
|
|
|
|
void Tx20Show(bool json)
|
|
{
|
|
char wind_speed_string[33];
|
|
dtostrfd(tx20_wind_speed_kmh, 2, wind_speed_string);
|
|
char wind_speed_max_string[33];
|
|
dtostrfd(tx20_wind_speed_max, 2, wind_speed_max_string);
|
|
char wind_speed_avg_string[33];
|
|
dtostrfd(tx20_wind_speed_avg, 2, wind_speed_avg_string);
|
|
char wind_direction_string[4];
|
|
GetTextIndexed(wind_direction_string, sizeof(wind_direction_string), tx20_wind_direction, kTx20Directions);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"TX20\":{\"Speed\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\"}"),
|
|
wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TX20, wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns35(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (pin[GPIO_TX20_TXD_BLACK] < 99) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
Tx20Init();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
Tx20Read();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Tx20Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Tx20Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_36_mgc3130.ino"
|
|
# 22 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_36_mgc3130.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MGC3130
|
|
# 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_36_mgc3130.ino"
|
|
#define XSNS_36 36
|
|
#define XI2C_27 27
|
|
|
|
#warning **** MGC3130: It is recommended to disable all unneeded I2C-drivers ****
|
|
|
|
#define MGC3130_I2C_ADDR 0x42
|
|
|
|
#define MGC3130_xfer pin[GPIO_MGC3130_XFER]
|
|
#define MGC3130_reset pin[GPIO_MGC3130_RESET]
|
|
|
|
|
|
bool MGC3130_type = false;
|
|
char MGC3130stype[] = "MGC3130";
|
|
|
|
|
|
#define MGC3130_SYSTEM_STATUS 0x15
|
|
#define MGC3130_REQUEST_MSG 0x06
|
|
#define MGC3130_FW_VERSION 0x83
|
|
#define MGC3130_SET_RUNTIME 0xA2
|
|
#define MGC3130_SENSOR_DATA 0x91
|
|
|
|
|
|
#define MGC3130_GESTURE_GARBAGE 1
|
|
#define MGC3130_FLICK_WEST_EAST 2
|
|
#define MGC3130_FLICK_EAST_WEST 3
|
|
#define MGC3130_FLICK_SOUTH_NORTH 4
|
|
#define MGC3130_FLICK_NORTH_SOUTH 5
|
|
#define MGC3130_CIRCLE_CLOCKWISE 6
|
|
#define MGC3130_CIRCLE_CCLOCKWISE 7
|
|
|
|
#define MGC3130_MIN_ROTVALUE 0
|
|
#define MGC3130_MAX_ROTVALUE 1023
|
|
#define MGC3130_MIN_ZVALUE 32768
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_MGC_3130_SNS[] PROGMEM =
|
|
"{s}" "%s" "{m}%s{e}"
|
|
"{s}" "HwRev" "{m}%u.%u{e}"
|
|
"{s}" "loaderVer" "{m}%u.%u{e}"
|
|
"{s}" "platVer" "{m}%u{e}";
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma pack(1)
|
|
union MGC3130_Union{
|
|
uint8_t buffer[132];
|
|
struct
|
|
{
|
|
|
|
uint8_t msgSize;
|
|
uint8_t flag;
|
|
uint8_t counter;
|
|
uint8_t id;
|
|
|
|
struct {
|
|
uint8_t DSPStatus:1;
|
|
uint8_t gestureInfo:1;
|
|
uint8_t touchInfo:1;
|
|
uint8_t airWheelInfo:1;
|
|
uint8_t xyzPosition:1;
|
|
uint8_t noisePower:1;
|
|
uint8_t reserved:2;
|
|
uint8_t electrodeConfiguration:3;
|
|
uint8_t CICData:1;
|
|
uint8_t SDData:1;
|
|
uint16_t reserved2:3;
|
|
} outputConfigMask;
|
|
uint8_t timestamp;
|
|
struct {
|
|
uint8_t positionValid:1;
|
|
uint8_t airWheelValid:1;
|
|
uint8_t rawDataValid:1;
|
|
uint8_t noisePowerValid:1;
|
|
uint8_t environmentalNoise:1;
|
|
uint8_t clipping:1;
|
|
uint8_t reserved:1;
|
|
uint8_t DSPRunning:1;
|
|
} systemInfo;
|
|
uint16_t dspInfo;
|
|
struct {
|
|
uint8_t gestureCode:8;
|
|
uint8_t reserved:4;
|
|
uint8_t gestureType:4;
|
|
uint8_t edgeFlick:1;
|
|
uint16_t reserved2:14;
|
|
uint8_t gestureInProgress:1;
|
|
} gestureInfo;
|
|
struct {
|
|
uint8_t touchSouth:1;
|
|
uint8_t touchWest:1;
|
|
uint8_t touchNorth:1;
|
|
uint8_t touchEast:1;
|
|
uint8_t touchCentre:1;
|
|
uint8_t tapSouth:1;
|
|
uint8_t tapWest:1;
|
|
uint8_t tapNorth:1;
|
|
uint8_t tapEast :1;
|
|
uint8_t tapCentre:1;
|
|
uint8_t doubleTapSouth:1;
|
|
uint8_t doubleTapWest:1;
|
|
uint8_t doubleTapNorth:1;
|
|
uint8_t doubleTapEast:1;
|
|
uint8_t doubleTapCentre:1;
|
|
uint8_t reserved:1;
|
|
uint8_t touchCounter;
|
|
uint8_t reserved2;
|
|
} touchInfo;
|
|
int8_t airWheel;
|
|
uint8_t reserved;
|
|
uint16_t x;
|
|
uint16_t y;
|
|
uint16_t z;
|
|
float noisePower;
|
|
float CICData[4];
|
|
float SDData[4];
|
|
} out;
|
|
struct {
|
|
uint8_t header[3];
|
|
|
|
uint8_t valid;
|
|
uint8_t hwRev[2];
|
|
uint8_t parameterStartAddr;
|
|
uint8_t loaderVersion[2];
|
|
uint8_t loaderPlatform;
|
|
uint8_t fwStartAddr;
|
|
char fwVersion[120];
|
|
} fw;
|
|
struct{
|
|
uint8_t id;
|
|
uint8_t size;
|
|
uint16_t error;
|
|
uint32_t reserved;
|
|
uint32_t reserved1;
|
|
} status;
|
|
} MGC_data;
|
|
#pragma pack()
|
|
|
|
char MGC3130_currentGesture[12];
|
|
|
|
int8_t MGC3130_delta, MGC3130_lastrotation = 0;
|
|
int16_t MGC3130_rotValue, MGC3130_lastSentRotValue = 0;
|
|
|
|
uint16_t MGC3130_lastSentX, MGC3130_lastSentY, MGC3130_lastSentZ = 0;
|
|
|
|
uint8_t hwRev[2], loaderVersion[2], loaderPlatform = 0;
|
|
char MGC3130_firmwareInfo[20];
|
|
|
|
uint8_t MGC3130_touchTimeout = 0;
|
|
uint16_t MGC3130_touchCounter = 1;
|
|
uint32_t MGC3130_touchTimeStamp = millis();
|
|
bool MGC3130_triggeredByTouch = false;
|
|
|
|
uint8_t MGC3130_mode = 1;
|
|
|
|
|
|
|
|
uint8_t MGC3130autoCal[] = {0x10, 0x00, 0x00, 0xA2, 0x80, 0x00 , 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
uint8_t MGC3130disableAirwheel[] = {0x10, 0x00, 0x00, 0xA2, 0x90, 0x00 , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00};
|
|
uint8_t MGC3130enableAirwheel[] = {0x10, 0x00, 0x00, 0xA2, 0x90, 0x00 , 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00};
|
|
|
|
void MGC3130_handleSensorData(){
|
|
if ( MGC_data.out.outputConfigMask.touchInfo && MGC3130_touchTimeout == 0){
|
|
if (MGC3130_handleTouch()){
|
|
MGC3130_triggeredByTouch = true;
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
|
|
if(MGC3130_mode == 1){
|
|
if( MGC_data.out.outputConfigMask.gestureInfo && MGC_data.out.gestureInfo.gestureCode > 0){
|
|
MGC3130_handleGesture();
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
if(MGC3130_mode == 2){
|
|
if(MGC_data.out.outputConfigMask.airWheelInfo && MGC_data.out.systemInfo.airWheelValid){
|
|
MGC3130_handleAirWheel();
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
if(MGC3130_mode == 3){
|
|
if(MGC_data.out.systemInfo.positionValid && (MGC_data.out.z > MGC3130_MIN_ZVALUE)){
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MGC3130_sendMessage(uint8_t data[], uint8_t length){
|
|
Wire.beginTransmission(MGC3130_I2C_ADDR);
|
|
Wire.write(data,length);
|
|
Wire.endTransmission();
|
|
delay(2);
|
|
MGC3130_receiveMessage();
|
|
}
|
|
|
|
|
|
void MGC3130_handleGesture(){
|
|
|
|
char edge[5];
|
|
if (MGC_data.out.gestureInfo.edgeFlick){
|
|
snprintf_P(edge, sizeof(edge), PSTR("ED_"));
|
|
}
|
|
else{
|
|
snprintf_P(edge, sizeof(edge), PSTR(""));
|
|
}
|
|
switch(MGC_data.out.gestureInfo.gestureCode){
|
|
case MGC3130_GESTURE_GARBAGE:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("NONE"));
|
|
break;
|
|
case MGC3130_FLICK_WEST_EAST:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_WE"), edge);
|
|
break;
|
|
case MGC3130_FLICK_EAST_WEST:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_EW"), edge);
|
|
break;
|
|
case MGC3130_FLICK_SOUTH_NORTH:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_SN"), edge);
|
|
break;
|
|
case MGC3130_FLICK_NORTH_SOUTH:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("%sFL_NS"), edge);
|
|
break;
|
|
case MGC3130_CIRCLE_CLOCKWISE:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("CW"));
|
|
break;
|
|
case MGC3130_CIRCLE_CCLOCKWISE:
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("CCW"));
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
bool MGC3130_handleTouch(){
|
|
|
|
bool success = false;
|
|
if (MGC_data.out.touchInfo.doubleTapCentre && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_C"));
|
|
MGC3130_touchTimeout = 5;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.doubleTapEast && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_E"));
|
|
MGC3130_touchTimeout = 5;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.doubleTapNorth && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_N"));
|
|
MGC3130_touchTimeout = 5;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.doubleTapWest && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_W"));
|
|
MGC3130_touchTimeout = 5;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.doubleTapSouth && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("DT_S"));
|
|
MGC3130_touchTimeout = 5;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
if (MGC_data.out.touchInfo.tapCentre && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_C"));
|
|
MGC3130_touchTimeout = 2;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.tapEast && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_E"));
|
|
MGC3130_touchTimeout = 2;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.tapNorth && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_N"));
|
|
MGC3130_touchTimeout = 2;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.tapWest && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_W"));
|
|
MGC3130_touchTimeout = 2;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.tapSouth && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TP_S"));
|
|
MGC3130_touchTimeout = 2;
|
|
success = true;
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
else if (MGC_data.out.touchInfo.touchCentre && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_C"));
|
|
success = true;
|
|
MGC3130_touchCounter++;
|
|
}
|
|
else if (MGC_data.out.touchInfo.touchEast && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_E"));
|
|
success = true;
|
|
MGC3130_touchCounter++;
|
|
}
|
|
else if (MGC_data.out.touchInfo.touchNorth && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_N"));
|
|
success = true;
|
|
MGC3130_touchCounter++;
|
|
}
|
|
else if (MGC_data.out.touchInfo.touchWest && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_W"));
|
|
success = true;
|
|
MGC3130_touchCounter++;
|
|
}
|
|
else if (MGC_data.out.touchInfo.touchSouth && !success){
|
|
|
|
snprintf_P(MGC3130_currentGesture, sizeof(MGC3130_currentGesture), PSTR("TH_S"));
|
|
success = true;
|
|
MGC3130_touchCounter++;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void MGC3130_handleAirWheel(){
|
|
MGC3130_delta = MGC_data.out.airWheel - MGC3130_lastrotation;
|
|
MGC3130_lastrotation = MGC_data.out.airWheel;
|
|
|
|
MGC3130_rotValue = MGC3130_rotValue + MGC3130_delta;
|
|
if(MGC3130_rotValue < MGC3130_MIN_ROTVALUE){
|
|
MGC3130_rotValue = MGC3130_MIN_ROTVALUE;
|
|
}
|
|
if(MGC3130_rotValue > MGC3130_MAX_ROTVALUE){
|
|
MGC3130_rotValue = MGC3130_MAX_ROTVALUE;
|
|
}
|
|
}
|
|
|
|
void MGC3130_handleSystemStatus(){
|
|
|
|
}
|
|
|
|
bool MGC3130_receiveMessage(){
|
|
if(MGC3130_readData()){
|
|
switch(MGC_data.out.id){
|
|
case MGC3130_SENSOR_DATA:
|
|
MGC3130_handleSensorData();
|
|
break;
|
|
case MGC3130_SYSTEM_STATUS:
|
|
MGC3130_handleSystemStatus();
|
|
break;
|
|
case MGC3130_FW_VERSION:
|
|
hwRev[0] = MGC_data.fw.hwRev[1];
|
|
hwRev[1] = MGC_data.fw.hwRev[0];
|
|
loaderVersion[0] = MGC_data.fw.loaderVersion[0];
|
|
loaderVersion[1] = MGC_data.fw.loaderVersion[1];
|
|
loaderPlatform = MGC_data.fw.loaderPlatform;
|
|
snprintf_P(MGC3130_firmwareInfo, sizeof(MGC3130_firmwareInfo), PSTR("FW: %s"), MGC_data.fw.fwVersion);
|
|
MGC3130_firmwareInfo[20] = '\0';
|
|
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MGC3130_readData()
|
|
{
|
|
bool success = false;
|
|
if (!digitalRead(MGC3130_xfer)){
|
|
pinMode(MGC3130_xfer, OUTPUT);
|
|
digitalWrite(MGC3130_xfer, LOW);
|
|
Wire.requestFrom(MGC3130_I2C_ADDR, (uint16_t)32);
|
|
|
|
MGC_data.buffer[0] = 4;
|
|
unsigned char i = 0;
|
|
while(Wire.available() && (i < MGC_data.buffer[0])){
|
|
MGC_data.buffer[i] = Wire.read();
|
|
i++;
|
|
}
|
|
digitalWrite(MGC3130_xfer, HIGH);
|
|
pinMode(MGC3130_xfer, INPUT);
|
|
success = true;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
void MGC3130_nextMode(){
|
|
if (MGC3130_mode < 3){
|
|
MGC3130_mode++;
|
|
}
|
|
else{
|
|
MGC3130_mode = 1;
|
|
}
|
|
switch(MGC3130_mode){
|
|
case 1:
|
|
MGC3130_sendMessage(MGC3130disableAirwheel,16);
|
|
break;
|
|
case 2:
|
|
MGC3130_sendMessage(MGC3130enableAirwheel,16);
|
|
break;
|
|
case 3:
|
|
MGC3130_sendMessage(MGC3130disableAirwheel,16);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MGC3130_loop()
|
|
{
|
|
if(MGC3130_touchTimeout > 0){
|
|
MGC3130_touchTimeout--;
|
|
}
|
|
MGC3130_receiveMessage();
|
|
}
|
|
|
|
void MGC3130_detect(void)
|
|
{
|
|
if (MGC3130_type || I2cActive(MGC3130_I2C_ADDR)) { return; }
|
|
|
|
pinMode(MGC3130_xfer, INPUT_PULLUP);
|
|
pinMode(MGC3130_reset, OUTPUT);
|
|
digitalWrite(MGC3130_reset, LOW);
|
|
delay(10);
|
|
digitalWrite(MGC3130_reset, HIGH);
|
|
delay(50);
|
|
|
|
if (MGC3130_receiveMessage()) {
|
|
I2cSetActiveFound(MGC3130_I2C_ADDR, MGC3130stype);
|
|
MGC3130_currentGesture[0] = '\0';
|
|
MGC3130_type = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MGC3130_show(bool json)
|
|
{
|
|
if (!MGC3130_type) { return; }
|
|
|
|
char status_chr[2];
|
|
if (MGC_data.out.systemInfo.DSPRunning) {
|
|
sprintf (status_chr, "1");
|
|
}
|
|
else{
|
|
sprintf (status_chr, "0");
|
|
}
|
|
|
|
if (json) {
|
|
if (MGC3130_mode == 3 && !MGC3130_triggeredByTouch) {
|
|
if (MGC_data.out.systemInfo.positionValid && !(MGC_data.out.x == MGC3130_lastSentX && MGC_data.out.y == MGC3130_lastSentY && MGC_data.out.z == MGC3130_lastSentZ)) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"X\":%u,\"Y\":%u,\"Z\":%u}"),
|
|
MGC3130stype, MGC_data.out.x/64, MGC_data.out.y/64, (MGC_data.out.z-(uint16_t)MGC3130_MIN_ZVALUE)/64);
|
|
MGC3130_lastSentX = MGC_data.out.x;
|
|
MGC3130_lastSentY = MGC_data.out.y;
|
|
MGC3130_lastSentZ = MGC_data.out.z;
|
|
}
|
|
}
|
|
MGC3130_triggeredByTouch = false;
|
|
|
|
if (MGC3130_mode == 2) {
|
|
if (MGC_data.out.systemInfo.airWheelValid && (MGC3130_rotValue != MGC3130_lastSentRotValue)) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"AW\":%i}"), MGC3130stype, MGC3130_rotValue);
|
|
MGC3130_lastSentRotValue = MGC3130_rotValue;
|
|
}
|
|
}
|
|
|
|
if (MGC3130_currentGesture[0] != '\0') {
|
|
if (millis() - MGC3130_touchTimeStamp > 220 ) {
|
|
MGC3130_touchCounter = 1;
|
|
}
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%u}"), MGC3130stype, MGC3130_currentGesture, MGC3130_touchCounter);
|
|
MGC3130_currentGesture[0] = '\0';
|
|
MGC3130_touchTimeStamp = millis();
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_MGC_3130_SNS, MGC3130stype, status_chr, hwRev[0], hwRev[1], loaderVersion[0], loaderVersion[1], loaderPlatform );
|
|
#endif
|
|
}
|
|
}
|
|
# 557 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_36_mgc3130.ino"
|
|
bool MGC3130CommandSensor()
|
|
{
|
|
bool serviced = true;
|
|
|
|
switch (XdrvMailbox.payload) {
|
|
case 0:
|
|
MGC3130_nextMode();
|
|
break;
|
|
case 1:
|
|
MGC3130_mode = 1;
|
|
MGC3130_sendMessage(MGC3130disableAirwheel,16);
|
|
break;
|
|
case 2:
|
|
MGC3130_mode = 2;
|
|
MGC3130_sendMessage(MGC3130enableAirwheel,16);
|
|
break;
|
|
case 3:
|
|
MGC3130_mode = 3;
|
|
MGC3130_sendMessage(MGC3130disableAirwheel,16);
|
|
break;
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns36(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_27)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if ((FUNC_INIT == function) && (pin[GPIO_MGC3130_XFER] < 99) && (pin[GPIO_MGC3130_RESET] < 99)) {
|
|
MGC3130_detect();
|
|
}
|
|
else if (MGC3130_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_50_MSECOND:
|
|
MGC3130_loop();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_36 == XdrvMailbox.index) {
|
|
result = MGC3130CommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MGC3130_show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MGC3130_show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
|
|
#ifdef USE_RF_SENSOR
|
|
# 33 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
|
|
#define XSNS_37 37
|
|
|
|
|
|
|
|
|
|
#define RFSNS_VALID_WINDOW 1800
|
|
|
|
#define RFSNS_LOOPS_PER_MILLI 1900
|
|
#define RFSNS_RAW_BUFFER_SIZE 180
|
|
#define RFSNS_MIN_RAW_PULSES 112
|
|
|
|
#define RFSNS_MIN_PULSE_LENGTH 300
|
|
#define RFSNS_RAWSIGNAL_SAMPLE 50
|
|
#define RFSNS_SIGNAL_TIMEOUT 10
|
|
#define RFSNS_SIGNAL_REPEAT_TIME 500
|
|
|
|
typedef struct RawSignalStruct
|
|
{
|
|
int Number;
|
|
uint8_t Repeats;
|
|
uint8_t Multiply;
|
|
unsigned long Time;
|
|
uint8_t Pulses[RFSNS_RAW_BUFFER_SIZE+2];
|
|
|
|
} raw_signal_t;
|
|
|
|
raw_signal_t *rfsns_raw_signal = nullptr;
|
|
uint8_t rfsns_rf_bit;
|
|
uint8_t rfsns_rf_port;
|
|
uint8_t rfsns_any_sensor = 0;
|
|
|
|
|
|
|
|
|
|
|
|
bool RfSnsFetchSignal(uint8_t DataPin, bool StateSignal)
|
|
{
|
|
uint8_t Fbit = digitalPinToBitMask(DataPin);
|
|
uint8_t Fport = digitalPinToPort(DataPin);
|
|
uint8_t FstateMask = (StateSignal ? Fbit : 0);
|
|
|
|
if ((*portInputRegister(Fport) & Fbit) == FstateMask) {
|
|
const unsigned long LoopsPerMilli = RFSNS_LOOPS_PER_MILLI;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsigned long PulseLength = 0;
|
|
if (rfsns_raw_signal->Time) {
|
|
if (rfsns_raw_signal->Repeats && (rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) {
|
|
PulseLength = micros() + RFSNS_SIGNAL_TIMEOUT *1000;
|
|
while (((rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) && (PulseLength > micros())) {
|
|
if ((*portInputRegister(Fport) & Fbit) == FstateMask) {
|
|
PulseLength = micros() + RFSNS_SIGNAL_TIMEOUT *1000;
|
|
}
|
|
}
|
|
while (((rfsns_raw_signal->Time + RFSNS_SIGNAL_REPEAT_TIME) > millis()) && ((*portInputRegister(Fport) & Fbit) != FstateMask));
|
|
}
|
|
}
|
|
|
|
int RawCodeLength = 1;
|
|
bool Ftoggle = false;
|
|
unsigned long numloops = 0;
|
|
unsigned long maxloops = RFSNS_SIGNAL_TIMEOUT * LoopsPerMilli;
|
|
rfsns_raw_signal->Multiply = RFSNS_RAWSIGNAL_SAMPLE;
|
|
do {
|
|
numloops = 0;
|
|
while(((*portInputRegister(Fport) & Fbit) == FstateMask) ^ Ftoggle) {
|
|
if (numloops++ == maxloops) { break; }
|
|
}
|
|
PulseLength = (numloops *1000) / LoopsPerMilli;
|
|
if (PulseLength < RFSNS_MIN_PULSE_LENGTH) { break; }
|
|
Ftoggle = !Ftoggle;
|
|
rfsns_raw_signal->Pulses[RawCodeLength++] = PulseLength / (unsigned long)rfsns_raw_signal->Multiply;
|
|
}
|
|
while(RawCodeLength < RFSNS_RAW_BUFFER_SIZE && numloops <= maxloops);
|
|
|
|
if ((RawCodeLength >= RFSNS_MIN_RAW_PULSES) && (RawCodeLength < RFSNS_RAW_BUFFER_SIZE -1)) {
|
|
rfsns_raw_signal->Repeats = 0;
|
|
rfsns_raw_signal->Number = RawCodeLength -1;
|
|
rfsns_raw_signal->Pulses[rfsns_raw_signal->Number] = 0;
|
|
rfsns_raw_signal->Time = millis();
|
|
return true;
|
|
}
|
|
else
|
|
rfsns_raw_signal->Number = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifdef USE_THEO_V2
|
|
# 149 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
|
|
#define RFSNS_THEOV2_MAX_CHANNEL 2
|
|
|
|
#define RFSNS_THEOV2_PULSECOUNT 114
|
|
#define RFSNS_THEOV2_RF_PULSE_MID 1000
|
|
|
|
typedef struct {
|
|
uint32_t time;
|
|
int16_t temp;
|
|
uint16_t lux;
|
|
uint8_t volt;
|
|
} theo_v2_t1_t;
|
|
|
|
typedef struct {
|
|
uint32_t time;
|
|
int16_t temp;
|
|
uint16_t hum;
|
|
uint8_t volt;
|
|
} theo_v2_t2_t;
|
|
|
|
theo_v2_t1_t *rfsns_theo_v2_t1 = nullptr;
|
|
theo_v2_t2_t *rfsns_theo_v2_t2 = nullptr;
|
|
|
|
void RfSnsInitTheoV2(void)
|
|
{
|
|
rfsns_theo_v2_t1 = (theo_v2_t1_t*)malloc(RFSNS_THEOV2_MAX_CHANNEL * sizeof(theo_v2_t1_t));
|
|
rfsns_theo_v2_t2 = (theo_v2_t2_t*)malloc(RFSNS_THEOV2_MAX_CHANNEL * sizeof(theo_v2_t2_t));
|
|
rfsns_any_sensor++;
|
|
}
|
|
|
|
void RfSnsAnalyzeTheov2(void)
|
|
{
|
|
if (rfsns_raw_signal->Number != RFSNS_THEOV2_PULSECOUNT) { return; }
|
|
|
|
uint8_t Checksum;
|
|
uint8_t Channel;
|
|
uint8_t Type;
|
|
uint8_t Voltage;
|
|
int Payload1;
|
|
int Payload2;
|
|
|
|
uint8_t b, bytes, bits, id;
|
|
|
|
uint8_t idx = 3;
|
|
uint8_t chksum = 0;
|
|
for (bytes = 0; bytes < 7; bytes++) {
|
|
b = 0;
|
|
for (bits = 0; bits <= 7; bits++)
|
|
{
|
|
if ((rfsns_raw_signal->Pulses[idx] * rfsns_raw_signal->Multiply) > RFSNS_THEOV2_RF_PULSE_MID) {
|
|
b |= 1 << bits;
|
|
}
|
|
idx += 2;
|
|
}
|
|
if (bytes > 0) { chksum += b; }
|
|
|
|
switch (bytes) {
|
|
case 0:
|
|
Checksum = b;
|
|
break;
|
|
case 1:
|
|
id = b;
|
|
Channel = b & 0x7;
|
|
Type = (b >> 3) & 0x1f;
|
|
break;
|
|
case 2:
|
|
Voltage = b;
|
|
break;
|
|
case 3:
|
|
Payload1 = b;
|
|
break;
|
|
case 4:
|
|
Payload1 = (b << 8) | Payload1;
|
|
break;
|
|
case 5:
|
|
Payload2 = b;
|
|
break;
|
|
case 6:
|
|
Payload2 = (b << 8) | Payload2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Checksum != chksum) { return; }
|
|
if ((Channel == 0) || (Channel > RFSNS_THEOV2_MAX_CHANNEL)) { return; }
|
|
Channel--;
|
|
|
|
rfsns_raw_signal->Repeats = 1;
|
|
|
|
int Payload3 = Voltage & 0x3f;
|
|
|
|
switch (Type) {
|
|
case 1:
|
|
rfsns_theo_v2_t1[Channel].time = LocalTime();
|
|
rfsns_theo_v2_t1[Channel].volt = Payload3;
|
|
rfsns_theo_v2_t1[Channel].temp = Payload1;
|
|
rfsns_theo_v2_t1[Channel].lux = Payload2;
|
|
break;
|
|
case 2:
|
|
rfsns_theo_v2_t2[Channel].time = LocalTime();
|
|
rfsns_theo_v2_t2[Channel].volt = Payload3;
|
|
rfsns_theo_v2_t2[Channel].temp = Payload1;
|
|
rfsns_theo_v2_t2[Channel].hum = Payload2;
|
|
break;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFS: TheoV2, ChkCalc %d, Chksum %d, id %d, Type %d, Ch %d, Volt %d, BattLo %d, Pld1 %d, Pld2 %d"),
|
|
chksum, Checksum, id, Type, Channel +1, Payload3, (Voltage & 0x80) >> 7, Payload1, Payload2);
|
|
}
|
|
|
|
void RfSnsTheoV2Show(bool json)
|
|
{
|
|
bool sensor_once = false;
|
|
|
|
for (uint32_t i = 0; i < RFSNS_THEOV2_MAX_CHANNEL; i++) {
|
|
if (rfsns_theo_v2_t1[i].time) {
|
|
char sensor[10];
|
|
snprintf_P(sensor, sizeof(sensor), PSTR("TV2T1C%d"), i +1);
|
|
char voltage[33];
|
|
dtostrfd((float)rfsns_theo_v2_t1[i].volt / 10, 1, voltage);
|
|
|
|
if (rfsns_theo_v2_t1[i].time < LocalTime() - RFSNS_VALID_WINDOW) {
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_RFRECEIVED "\":\"%s\",\"" D_JSON_VOLTAGE "\":%s}"),
|
|
sensor, GetDT(rfsns_theo_v2_t1[i].time).c_str(), voltage);
|
|
}
|
|
} else {
|
|
char temperature[33];
|
|
dtostrfd(ConvertTemp((float)rfsns_theo_v2_t1[i].temp / 100), Settings.flag2.temperature_resolution, temperature);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_VOLTAGE "\":%s}"),
|
|
sensor, temperature, rfsns_theo_v2_t1[i].lux, voltage);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period) && !sensor_once) {
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
DomoticzSensor(DZ_ILLUMINANCE, rfsns_theo_v2_t1[i].lux);
|
|
sensor_once = true;
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, sensor, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, sensor, rfsns_theo_v2_t1[i].lux);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sensor_once = false;
|
|
for (uint32_t i = 0; i < RFSNS_THEOV2_MAX_CHANNEL; i++) {
|
|
if (rfsns_theo_v2_t2[i].time) {
|
|
char sensor[10];
|
|
snprintf_P(sensor, sizeof(sensor), PSTR("TV2T2C%d"), i +1);
|
|
char voltage[33];
|
|
dtostrfd((float)rfsns_theo_v2_t2[i].volt / 10, 1, voltage);
|
|
|
|
if (rfsns_theo_v2_t2[i].time < LocalTime() - RFSNS_VALID_WINDOW) {
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_RFRECEIVED" \":\"%s\",\"" D_JSON_VOLTAGE "\":%s}"),
|
|
sensor, GetDT(rfsns_theo_v2_t2[i].time).c_str(), voltage);
|
|
}
|
|
} else {
|
|
float temp = ConvertTemp((float)rfsns_theo_v2_t2[i].temp / 100);
|
|
float humi = ConvertHumidity((float)rfsns_theo_v2_t2[i].hum / 100);
|
|
char temperature[33];
|
|
dtostrfd(temp, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(humi, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_VOLTAGE "\":%s}"),
|
|
sensor, temperature, humidity, voltage);
|
|
if ((0 == tele_period) && !sensor_once) {
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
#endif
|
|
#ifdef USE_KNX
|
|
KnxSensor(KNX_TEMPERATURE, temp);
|
|
KnxSensor(KNX_HUMIDITY, humi);
|
|
#endif
|
|
sensor_once = true;
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, sensor, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, sensor, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef USE_ALECTO_V2
|
|
# 392 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_37_rfsensor.ino"
|
|
#define RFSNS_DKW2012_PULSECOUNT 176
|
|
#define RFSNS_ACH2010_MIN_PULSECOUNT 160
|
|
#define RFSNS_ACH2010_MAX_PULSECOUNT 160
|
|
|
|
#define D_ALECTOV2 "AlectoV2"
|
|
|
|
const char kAlectoV2Directions[] PROGMEM = D_TX20_NORTH "|"
|
|
D_TX20_NORTH D_TX20_NORTH D_TX20_EAST "|"
|
|
D_TX20_NORTH D_TX20_EAST "|"
|
|
D_TX20_EAST D_TX20_NORTH D_TX20_EAST "|"
|
|
D_TX20_EAST "|"
|
|
D_TX20_EAST D_TX20_SOUTH D_TX20_EAST "|"
|
|
D_TX20_SOUTH D_TX20_EAST "|"
|
|
D_TX20_SOUTH D_TX20_SOUTH D_TX20_EAST "|"
|
|
D_TX20_SOUTH "|"
|
|
D_TX20_SOUTH D_TX20_SOUTH D_TX20_WEST "|"
|
|
D_TX20_SOUTH D_TX20_WEST "|"
|
|
D_TX20_WEST D_TX20_SOUTH D_TX20_WEST "|"
|
|
D_TX20_WEST "|"
|
|
D_TX20_WEST D_TX20_NORTH D_TX20_WEST "|"
|
|
D_TX20_NORTH D_TX20_WEST "|"
|
|
D_TX20_NORTH D_TX20_NORTH D_TX20_WEST;
|
|
|
|
typedef struct {
|
|
uint32_t time;
|
|
float temp;
|
|
float rain;
|
|
float wind;
|
|
float gust;
|
|
uint8_t type;
|
|
uint8_t humi;
|
|
uint8_t wdir;
|
|
} alecto_v2_t;
|
|
|
|
alecto_v2_t *rfsns_alecto_v2 = nullptr;
|
|
uint16_t rfsns_alecto_rain_base = 0;
|
|
|
|
void RfSnsInitAlectoV2(void)
|
|
{
|
|
rfsns_alecto_v2 = (alecto_v2_t*)malloc(sizeof(alecto_v2_t));
|
|
rfsns_any_sensor++;
|
|
}
|
|
|
|
void RfSnsAnalyzeAlectov2()
|
|
{
|
|
if (!(((rfsns_raw_signal->Number >= RFSNS_ACH2010_MIN_PULSECOUNT) &&
|
|
(rfsns_raw_signal->Number <= RFSNS_ACH2010_MAX_PULSECOUNT)) || (rfsns_raw_signal->Number == RFSNS_DKW2012_PULSECOUNT))) { return; }
|
|
|
|
uint8_t c = 0;
|
|
uint8_t rfbit;
|
|
uint8_t data[9] = { 0 };
|
|
uint8_t msgtype = 0;
|
|
uint8_t rc = 0;
|
|
int temp;
|
|
uint8_t checksum = 0;
|
|
uint8_t checksumcalc = 0;
|
|
uint8_t maxidx = 8;
|
|
unsigned long atime;
|
|
float factor;
|
|
char buf1[16];
|
|
|
|
if (rfsns_raw_signal->Number > RFSNS_ACH2010_MAX_PULSECOUNT) { maxidx = 9; }
|
|
|
|
uint8_t idx = maxidx;
|
|
for (uint32_t x = rfsns_raw_signal->Number; x > 0; x = x-2) {
|
|
if (rfsns_raw_signal->Pulses[x-1] * rfsns_raw_signal->Multiply < 0x300) {
|
|
rfbit = 0x80;
|
|
} else {
|
|
rfbit = 0;
|
|
}
|
|
data[idx] = (data[idx] >> 1) | rfbit;
|
|
c++;
|
|
if (c == 8) {
|
|
if (idx == 0) { break; }
|
|
c = 0;
|
|
idx--;
|
|
}
|
|
}
|
|
|
|
checksum = data[maxidx];
|
|
checksumcalc = RfSnsAlectoCRC8(data, maxidx);
|
|
|
|
msgtype = (data[0] >> 4) & 0xf;
|
|
rc = (data[0] << 4) | (data[1] >> 4);
|
|
|
|
if (checksum != checksumcalc) { return; }
|
|
if ((msgtype != 10) && (msgtype != 5)) { return; }
|
|
|
|
rfsns_raw_signal->Repeats = 1;
|
|
|
|
|
|
|
|
|
|
|
|
factor = 1.22;
|
|
|
|
|
|
|
|
|
|
|
|
rfsns_alecto_v2->time = LocalTime();
|
|
rfsns_alecto_v2->type = (RFSNS_DKW2012_PULSECOUNT == rfsns_raw_signal->Number);
|
|
rfsns_alecto_v2->temp = (float)(((data[1] & 0x3) * 256 + data[2]) - 400) / 10;
|
|
rfsns_alecto_v2->humi = data[3];
|
|
uint16_t rain = (data[6] * 256) + data[7];
|
|
|
|
if (rain < rfsns_alecto_rain_base) { rfsns_alecto_rain_base = rain; }
|
|
if (rfsns_alecto_rain_base > 0) {
|
|
rfsns_alecto_v2->rain += ((float)rain - rfsns_alecto_rain_base) * 0.30;
|
|
}
|
|
rfsns_alecto_rain_base = rain;
|
|
rfsns_alecto_v2->wind = (float)data[4] * factor;
|
|
rfsns_alecto_v2->gust = (float)data[5] * factor;
|
|
if (rfsns_alecto_v2->type) {
|
|
rfsns_alecto_v2->wdir = data[8] & 0xf;
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFS: " D_ALECTOV2 ", ChkCalc %d, Chksum %d, rc %d, Temp %d, Hum %d, Rain %d, Wind %d, Gust %d, Dir %d, Factor %s"),
|
|
checksumcalc, checksum, rc, ((data[1] & 0x3) * 256 + data[2]) - 400, data[3], (data[6] * 256) + data[7], data[4], data[5], data[8] & 0xf, dtostrfd(factor, 3, buf1));
|
|
}
|
|
|
|
void RfSnsAlectoResetRain(void)
|
|
{
|
|
if ((RtcTime.hour == 0) && (RtcTime.minute == 0) && (RtcTime.second == 5)) {
|
|
rfsns_alecto_v2->rain = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t RfSnsAlectoCRC8(uint8_t *addr, uint8_t len)
|
|
{
|
|
uint8_t crc = 0;
|
|
while (len--) {
|
|
uint8_t inbyte = *addr++;
|
|
for (uint32_t i = 8; i; i--) {
|
|
uint8_t mix = (crc ^ inbyte) & 0x80;
|
|
crc <<= 1;
|
|
if (mix) { crc ^= 0x31; }
|
|
inbyte <<= 1;
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_ALECTOV2[] PROGMEM =
|
|
"{s}" D_ALECTOV2 " " D_RAIN "{m}%s " D_UNIT_MILLIMETER "{e}"
|
|
"{s}" D_ALECTOV2 " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}"
|
|
"{s}" D_ALECTOV2 " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}";
|
|
const char HTTP_SNS_ALECTOV2_WDIR[] PROGMEM =
|
|
"{s}" D_ALECTOV2 " " D_TX20_WIND_DIRECTION "{m}%s{e}";
|
|
#endif
|
|
|
|
void RfSnsAlectoV2Show(bool json)
|
|
{
|
|
if (rfsns_alecto_v2->time) {
|
|
if (rfsns_alecto_v2->time < LocalTime() - RFSNS_VALID_WINDOW) {
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"" D_ALECTOV2 "\":{\"" D_JSON_RFRECEIVED "\":\"%s\"}"), GetDT(rfsns_alecto_v2->time).c_str());
|
|
}
|
|
} else {
|
|
float temp = ConvertTemp(rfsns_alecto_v2->temp);
|
|
char temperature[33];
|
|
dtostrfd(temp, Settings.flag2.temperature_resolution, temperature);
|
|
float humi = ConvertHumidity((float)rfsns_alecto_v2->humi);
|
|
char humidity[33];
|
|
dtostrfd(humi, Settings.flag2.humidity_resolution, humidity);
|
|
char rain[33];
|
|
dtostrfd(rfsns_alecto_v2->rain, 2, rain);
|
|
char wind[33];
|
|
dtostrfd(rfsns_alecto_v2->wind, 2, wind);
|
|
char gust[33];
|
|
dtostrfd(rfsns_alecto_v2->gust, 2, gust);
|
|
char wdir[4];
|
|
char direction[20];
|
|
if (rfsns_alecto_v2->type) {
|
|
GetTextIndexed(wdir, sizeof(wdir), rfsns_alecto_v2->wdir, kAlectoV2Directions);
|
|
snprintf_P(direction, sizeof(direction), PSTR(",\"Direction\":\"%s\""), wdir);
|
|
}
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"" D_ALECTOV2 "\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"Rain\":%s,\"Wind\":%s,\"Gust\":%s%s}"),
|
|
temperature, humidity, rain, wind, gust, (rfsns_alecto_v2->type) ? direction : "");
|
|
if (0 == tele_period) {
|
|
#ifdef USE_DOMOTICZ
|
|
|
|
|
|
|
|
|
|
#endif
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, D_ALECTOV2, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, D_ALECTOV2, humidity);
|
|
WSContentSend_PD(HTTP_SNS_ALECTOV2, rain, wind, gust);
|
|
if (rfsns_alecto_v2->type) {
|
|
WSContentSend_PD(HTTP_SNS_ALECTOV2_WDIR, wdir);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void RfSnsInit(void)
|
|
{
|
|
rfsns_raw_signal = (raw_signal_t*)(malloc(sizeof(raw_signal_t)));
|
|
if (rfsns_raw_signal) {
|
|
memset(rfsns_raw_signal, 0, sizeof(raw_signal_t));
|
|
#ifdef USE_THEO_V2
|
|
RfSnsInitTheoV2();
|
|
#endif
|
|
#ifdef USE_ALECTO_V2
|
|
RfSnsInitAlectoV2();
|
|
#endif
|
|
if (rfsns_any_sensor) {
|
|
rfsns_rf_bit = digitalPinToBitMask(pin[GPIO_RF_SENSOR]);
|
|
rfsns_rf_port = digitalPinToPort(pin[GPIO_RF_SENSOR]);
|
|
pinMode(pin[GPIO_RF_SENSOR], INPUT);
|
|
} else {
|
|
free(rfsns_raw_signal);
|
|
rfsns_raw_signal = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RfSnsAnalyzeRawSignal(void)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RFS: Pulses %d"), (int)rfsns_raw_signal->Number);
|
|
|
|
#ifdef USE_THEO_V2
|
|
RfSnsAnalyzeTheov2();
|
|
#endif
|
|
#ifdef USE_ALECTO_V2
|
|
RfSnsAnalyzeAlectov2();
|
|
#endif
|
|
}
|
|
|
|
void RfSnsEverySecond(void)
|
|
{
|
|
#ifdef USE_ALECTO_V2
|
|
RfSnsAlectoResetRain();
|
|
#endif
|
|
}
|
|
|
|
void RfSnsShow(bool json)
|
|
{
|
|
#ifdef USE_THEO_V2
|
|
RfSnsTheoV2Show(json);
|
|
#endif
|
|
#ifdef USE_ALECTO_V2
|
|
RfSnsAlectoV2Show(json);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns37(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if ((pin[GPIO_RF_SENSOR] < 99) && (FUNC_INIT == function)) {
|
|
RfSnsInit();
|
|
}
|
|
else if (rfsns_raw_signal) {
|
|
switch (function) {
|
|
case FUNC_LOOP:
|
|
if ((*portInputRegister(rfsns_rf_port) &rfsns_rf_bit) == rfsns_rf_bit) {
|
|
if (RfSnsFetchSignal(pin[GPIO_RF_SENSOR], HIGH)) {
|
|
RfSnsAnalyzeRawSignal();
|
|
}
|
|
}
|
|
sleep = 0;
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
RfSnsEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
RfSnsShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
RfSnsShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_38_az7798.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_38_az7798.ino"
|
|
#ifdef USE_AZ7798
|
|
|
|
#define XSNS_38 38
|
|
# 112 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_38_az7798.ino"
|
|
#include <TasmotaSerial.h>
|
|
|
|
#ifndef CO2_LOW
|
|
#define CO2_LOW 800
|
|
#endif
|
|
#ifndef CO2_HIGH
|
|
#define CO2_HIGH 1200
|
|
#endif
|
|
|
|
#define AZ_READ_TIMEOUT 400
|
|
|
|
#define AZ_CLOCK_UPDATE_INTERVAL (24UL * 60 * 60)
|
|
#define AZ_EPOCH (946684800UL)
|
|
|
|
TasmotaSerial *AzSerial;
|
|
|
|
const char ktype[] = "AZ7798";
|
|
uint8_t az_type = 1;
|
|
uint16_t az_co2 = 0;
|
|
double az_temperature = 0;
|
|
double az_humidity = 0;
|
|
uint8_t az_received = 0;
|
|
uint8_t az_state = 0;
|
|
unsigned long az_clock_update = 10;
|
|
|
|
|
|
|
|
void AzEverySecond(void)
|
|
{
|
|
unsigned long start = millis();
|
|
|
|
az_state++;
|
|
if (5 == az_state) {
|
|
az_state = 0;
|
|
|
|
AzSerial->flush();
|
|
AzSerial->write(":\r", 2);
|
|
az_received = 0;
|
|
|
|
uint8_t az_response[32];
|
|
uint8_t counter = 0;
|
|
uint8_t i, j;
|
|
uint8_t response_substr[16];
|
|
|
|
do {
|
|
if (AzSerial->available() > 0) {
|
|
az_response[counter] = AzSerial->read();
|
|
if(az_response[counter] == 0x0d) { az_received = 1; }
|
|
counter++;
|
|
} else {
|
|
delay(5);
|
|
}
|
|
} while(((millis() - start) < AZ_READ_TIMEOUT) && (counter < sizeof(az_response)) && !az_received);
|
|
|
|
AddLogBuffer(LOG_LEVEL_DEBUG_MORE, az_response, counter);
|
|
|
|
if (!az_received) {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 comms timeout"));
|
|
return;
|
|
}
|
|
|
|
i = 0;
|
|
while((az_response[i] != 'T') && (i < counter)) {i++;}
|
|
if(az_response[i] != 'T') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find start of response"));
|
|
return;
|
|
}
|
|
i++;
|
|
j = 0;
|
|
|
|
while((az_response[i] != 'C') && (az_response[i] != 'F') && (i < counter)) {
|
|
response_substr[j++] = az_response[i++];
|
|
}
|
|
if((az_response[i] != 'C') && (az_response[i] != 'F')){
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find end of temperature"));
|
|
return;
|
|
}
|
|
response_substr[j] = 0;
|
|
az_temperature = CharToFloat((char*)response_substr);
|
|
if(az_response[i] == 'C') {
|
|
az_temperature = ConvertTemp((float)az_temperature);
|
|
} else {
|
|
az_temperature = ConvertTemp((az_temperature - 32) / 1.8);
|
|
}
|
|
i++;
|
|
if(az_response[i] != ':') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error first delimiter"));
|
|
return;
|
|
}
|
|
i++;
|
|
if(az_response[i] != 'C') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error start of CO2"));
|
|
return;
|
|
}
|
|
i++;
|
|
j = 0;
|
|
|
|
while((az_response[i] != 'p') && (i < counter)) {
|
|
response_substr[j++] = az_response[i++];
|
|
}
|
|
if(az_response[i] != 'p') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find end of CO2"));
|
|
return;
|
|
}
|
|
response_substr[j] = 0;
|
|
az_co2 = atoi((char*)response_substr);
|
|
LightSetSignal(CO2_LOW, CO2_HIGH, az_co2);
|
|
i += 3;
|
|
if(az_response[i] != ':') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error second delimiter"));
|
|
return;
|
|
}
|
|
i++;
|
|
if(az_response[i] != 'H') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 error start of humidity"));
|
|
return;
|
|
}
|
|
i++;
|
|
j = 0;
|
|
|
|
while((az_response[i] != '%') && (i < counter)) {
|
|
response_substr[j++] = az_response[i++];
|
|
}
|
|
if(az_response[i] != '%') {
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 failed to find end of humidity"));
|
|
return;
|
|
}
|
|
response_substr[j] = 0;
|
|
az_humidity = ConvertHumidity(CharToFloat((char*)response_substr));
|
|
}
|
|
|
|
|
|
if ((az_clock_update == 0) && (LocalTime() > AZ_EPOCH)) {
|
|
char tmpString[16];
|
|
sprintf(tmpString, "C %d\r", (int)(LocalTime() - AZ_EPOCH));
|
|
AzSerial->write(tmpString);
|
|
|
|
do {
|
|
if (AzSerial->available() > 0) {
|
|
if(AzSerial->read() == 0x0d) { break; }
|
|
} else {
|
|
delay(5);
|
|
}
|
|
} while(((millis() - start) < AZ_READ_TIMEOUT));
|
|
az_clock_update = AZ_CLOCK_UPDATE_INTERVAL;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "AZ7798 clock updated"));
|
|
} else {
|
|
az_clock_update--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void AzInit(void)
|
|
{
|
|
az_type = 0;
|
|
if ((pin[GPIO_AZ_RXD] < 99) && (pin[GPIO_AZ_TXD] < 99)) {
|
|
AzSerial = new TasmotaSerial(pin[GPIO_AZ_RXD], pin[GPIO_AZ_TXD], 1);
|
|
if (AzSerial->begin(9600)) {
|
|
if (AzSerial->hardwareSerial()) { ClaimSerial(); }
|
|
az_type = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AzShow(bool json)
|
|
{
|
|
char temperature[33];
|
|
dtostrfd(az_temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(az_humidity, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"), ktype, az_co2, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, az_co2);
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_CO2, ktype, az_co2);
|
|
WSContentSend_PD(HTTP_SNS_TEMP, ktype, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, ktype, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns38(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if(az_type){
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
AzInit();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
AzEverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
AzShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
AzShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_39_max31855.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_39_max31855.ino"
|
|
#ifdef USE_MAX31855
|
|
|
|
#define XSNS_39 39
|
|
|
|
bool initialized = false;
|
|
|
|
struct MAX31855_ResultStruct{
|
|
uint8_t ErrorCode;
|
|
float ProbeTemperature;
|
|
float ReferenceTemperature;
|
|
} MAX31855_Result;
|
|
|
|
void MAX31855_Init(void){
|
|
if(initialized)
|
|
return;
|
|
|
|
|
|
pinMode(pin[GPIO_MAX31855CS], OUTPUT);
|
|
pinMode(pin[GPIO_MAX31855CLK], OUTPUT);
|
|
pinMode(pin[GPIO_MAX31855DO], INPUT);
|
|
|
|
|
|
digitalWrite(pin[GPIO_MAX31855CS], HIGH);
|
|
digitalWrite(pin[GPIO_MAX31855CLK], LOW);
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MAX31855_GetResult(void){
|
|
int32_t RawData = MAX31855_ShiftIn(32);
|
|
uint8_t probeerror = RawData & 0x7;
|
|
|
|
MAX31855_Result.ErrorCode = probeerror;
|
|
MAX31855_Result.ReferenceTemperature = MAX31855_GetReferenceTemperature(RawData);
|
|
if(probeerror)
|
|
MAX31855_Result.ProbeTemperature = NAN;
|
|
else
|
|
MAX31855_Result.ProbeTemperature = MAX31855_GetProbeTemperature(RawData);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float MAX31855_GetProbeTemperature(int32_t RawData){
|
|
if(RawData & 0x80000000)
|
|
RawData = (RawData >> 18) | 0xFFFFC000;
|
|
else
|
|
RawData >>= 18;
|
|
|
|
float result = (RawData * 0.25);
|
|
|
|
return ConvertTemp(result);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float MAX31855_GetReferenceTemperature(int32_t RawData){
|
|
if(RawData & 0x8000)
|
|
RawData = (RawData >> 4) | 0xFFFFF000;
|
|
else
|
|
RawData = (RawData >> 4) & 0x00000FFF;
|
|
|
|
float result = (RawData * 0.0625);
|
|
|
|
return ConvertTemp(result);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int32_t MAX31855_ShiftIn(uint8_t Length){
|
|
int32_t dataIn = 0;
|
|
|
|
digitalWrite(pin[GPIO_MAX31855CS], LOW);
|
|
delayMicroseconds(1);
|
|
|
|
for (uint32_t i = 0; i < Length; i++)
|
|
{
|
|
digitalWrite(pin[GPIO_MAX31855CLK], LOW);
|
|
delayMicroseconds(1);
|
|
dataIn <<= 1;
|
|
if(digitalRead(pin[GPIO_MAX31855DO]))
|
|
dataIn |= 1;
|
|
digitalWrite(pin[GPIO_MAX31855CLK], HIGH);
|
|
delayMicroseconds(1);
|
|
}
|
|
|
|
digitalWrite(pin[GPIO_MAX31855CS], HIGH);
|
|
digitalWrite(pin[GPIO_MAX31855CLK], LOW);
|
|
return dataIn;
|
|
}
|
|
|
|
void MAX31855_Show(bool Json){
|
|
char probetemp[33];
|
|
char referencetemp[33];
|
|
dtostrfd(MAX31855_Result.ProbeTemperature, Settings.flag2.temperature_resolution, probetemp);
|
|
dtostrfd(MAX31855_Result.ReferenceTemperature, Settings.flag2.temperature_resolution, referencetemp);
|
|
|
|
if(Json){
|
|
ResponseAppend_P(PSTR(",\"MAX31855\":{\"" D_JSON_PROBETEMPERATURE "\":%s,\"" D_JSON_REFERENCETEMPERATURE "\":%s,\"" D_JSON_ERROR "\":%d}"), \
|
|
probetemp, referencetemp, MAX31855_Result.ErrorCode);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_TEMP, probetemp);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, MAX31855_Result.ProbeTemperature);
|
|
}
|
|
#endif
|
|
} else {
|
|
#ifdef USE_WEBSERVER
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "MAX31855", probetemp, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns39(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
if((pin[GPIO_MAX31855CS] < 99) && (pin[GPIO_MAX31855CLK] < 99) && (pin[GPIO_MAX31855DO] < 99)){
|
|
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
MAX31855_Init();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
MAX31855_GetResult();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MAX31855_Show(true);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MAX31855_Show(false);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_40_pn532.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_40_pn532.ino"
|
|
#ifdef USE_PN532_HSU
|
|
|
|
#define XSNS_40 40
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *PN532_Serial;
|
|
|
|
#define PN532_INVALID_ACK -1
|
|
#define PN532_TIMEOUT -2
|
|
#define PN532_INVALID_FRAME -3
|
|
#define PN532_NO_SPACE -4
|
|
|
|
#define PN532_PREAMBLE 0x00
|
|
#define PN532_STARTCODE1 0x00
|
|
#define PN532_STARTCODE2 0xFF
|
|
#define PN532_POSTAMBLE 0x00
|
|
|
|
#define PN532_HOSTTOPN532 0xD4
|
|
#define PN532_PN532TOHOST 0xD5
|
|
|
|
#define PN532_ACK_WAIT_TIME 0x0A
|
|
|
|
#define PN532_COMMAND_GETFIRMWAREVERSION 0x02
|
|
#define PN532_COMMAND_SAMCONFIGURATION 0x14
|
|
#define PN532_COMMAND_RFCONFIGURATION 0x32
|
|
#define PN532_COMMAND_INDATAEXCHANGE 0x40
|
|
#define PN532_COMMAND_INLISTPASSIVETARGET 0x4A
|
|
|
|
#define PN532_MIFARE_ISO14443A 0x00
|
|
#define MIFARE_CMD_READ 0x30
|
|
#define MIFARE_CMD_AUTH_A 0x60
|
|
#define MIFARE_CMD_AUTH_B 0x61
|
|
#define MIFARE_CMD_WRITE 0xA0
|
|
|
|
uint8_t pn532_model = 0;
|
|
uint8_t pn532_command = 0;
|
|
uint8_t pn532_scantimer = 0;
|
|
|
|
uint8_t pn532_packetbuffer[64];
|
|
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
uint8_t pn532_function = 0;
|
|
uint8_t pn532_newdata[16];
|
|
uint8_t pn532_newdata_len = 0;
|
|
#endif
|
|
|
|
void PN532_Init(void)
|
|
{
|
|
if ((pin[GPIO_PN532_RXD] < 99) && (pin[GPIO_PN532_TXD] < 99)) {
|
|
PN532_Serial = new TasmotaSerial(pin[GPIO_PN532_RXD], pin[GPIO_PN532_TXD], 1);
|
|
if (PN532_Serial->begin(115200)) {
|
|
if (PN532_Serial->hardwareSerial()) { ClaimSerial(); }
|
|
PN532_wakeup();
|
|
uint32_t ver = PN532_getFirmwareVersion();
|
|
if (ver) {
|
|
PN532_setPassiveActivationRetries(0xFF);
|
|
PN532_SAMConfig();
|
|
pn532_model = 1;
|
|
AddLog_P2(LOG_LEVEL_INFO,"NFC: PN532 NFC Reader detected (V%u.%u)",(ver>>16) & 0xFF, (ver>>8) & 0xFF);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int8_t PN532_receive(uint8_t *buf, int len, uint16_t timeout)
|
|
{
|
|
int read_bytes = 0;
|
|
int ret;
|
|
unsigned long start_millis;
|
|
while (read_bytes < len) {
|
|
start_millis = millis();
|
|
do {
|
|
ret = PN532_Serial->read();
|
|
if (ret >= 0) {
|
|
break;
|
|
}
|
|
} while((timeout == 0) || ((millis()- start_millis ) < timeout));
|
|
|
|
if (ret < 0) {
|
|
if (read_bytes) {
|
|
return read_bytes;
|
|
} else {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
}
|
|
buf[read_bytes] = (uint8_t)ret;
|
|
read_bytes++;
|
|
}
|
|
return read_bytes;
|
|
}
|
|
|
|
int8_t PN532_readAckFrame(void)
|
|
{
|
|
const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0};
|
|
uint8_t ackBuf[sizeof(PN532_ACK)];
|
|
|
|
if (PN532_receive(ackBuf, sizeof(PN532_ACK), PN532_ACK_WAIT_TIME) <= 0) {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
|
|
if (memcmp(&ackBuf, &PN532_ACK, sizeof(PN532_ACK))) {
|
|
return PN532_INVALID_ACK;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int8_t PN532_writeCommand(const uint8_t *header, uint8_t hlen, const uint8_t *body = 0, uint8_t blen = 0)
|
|
{
|
|
|
|
PN532_Serial->flush();
|
|
|
|
pn532_command = header[0];
|
|
PN532_Serial->write((uint8_t)PN532_PREAMBLE);
|
|
PN532_Serial->write((uint8_t)PN532_STARTCODE1);
|
|
PN532_Serial->write(PN532_STARTCODE2);
|
|
|
|
uint8_t length = hlen + blen + 1;
|
|
PN532_Serial->write(length);
|
|
PN532_Serial->write(~length + 1);
|
|
|
|
PN532_Serial->write(PN532_HOSTTOPN532);
|
|
uint8_t sum = PN532_HOSTTOPN532;
|
|
|
|
PN532_Serial->write(header, hlen);
|
|
for (uint32_t i = 0; i < hlen; i++) {
|
|
sum += header[i];
|
|
}
|
|
|
|
PN532_Serial->write(body, blen);
|
|
for (uint32_t i = 0; i < blen; i++) {
|
|
sum += body[i];
|
|
}
|
|
|
|
uint8_t checksum = ~sum + 1;
|
|
PN532_Serial->write(checksum);
|
|
PN532_Serial->write((uint8_t)PN532_POSTAMBLE);
|
|
|
|
return PN532_readAckFrame();
|
|
}
|
|
|
|
int16_t PN532_readResponse(uint8_t buf[], uint8_t len, uint16_t timeout = 50)
|
|
{
|
|
uint8_t tmp[3];
|
|
|
|
|
|
if (PN532_receive(tmp, 3, timeout)<=0) {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
if (0 != tmp[0] || 0!= tmp[1] || 0xFF != tmp[2]) {
|
|
return PN532_INVALID_FRAME;
|
|
}
|
|
|
|
|
|
uint8_t length[2];
|
|
if (PN532_receive(length, 2, timeout) <= 0) {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
|
|
if (0 != (uint8_t)(length[0] + length[1])) {
|
|
return PN532_INVALID_FRAME;
|
|
}
|
|
length[0] -= 2;
|
|
if (length[0] > len) {
|
|
return PN532_NO_SPACE;
|
|
}
|
|
|
|
|
|
uint8_t cmd = pn532_command + 1;
|
|
if (PN532_receive(tmp, 2, timeout) <= 0) {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
if (PN532_PN532TOHOST != tmp[0] || cmd != tmp[1]) {
|
|
return PN532_INVALID_FRAME;
|
|
}
|
|
|
|
if (PN532_receive(buf, length[0], timeout) != length[0]) {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
|
|
uint8_t sum = PN532_PN532TOHOST + cmd;
|
|
for (uint32_t i=0; i<length[0]; i++) {
|
|
sum += buf[i];
|
|
}
|
|
|
|
|
|
if (PN532_receive(tmp, 2, timeout) <= 0) {
|
|
return PN532_TIMEOUT;
|
|
}
|
|
if (0 != (uint8_t)(sum + tmp[0]) || 0 != tmp[1]) {
|
|
return PN532_INVALID_FRAME;
|
|
}
|
|
|
|
return length[0];
|
|
}
|
|
|
|
uint32_t PN532_getFirmwareVersion(void)
|
|
{
|
|
uint32_t response;
|
|
|
|
pn532_packetbuffer[0] = PN532_COMMAND_GETFIRMWAREVERSION;
|
|
|
|
if (PN532_writeCommand(pn532_packetbuffer, 1)) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
int16_t status = PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer));
|
|
if (0 > status) {
|
|
return 0;
|
|
}
|
|
|
|
response = pn532_packetbuffer[0];
|
|
response <<= 8;
|
|
response |= pn532_packetbuffer[1];
|
|
response <<= 8;
|
|
response |= pn532_packetbuffer[2];
|
|
response <<= 8;
|
|
response |= pn532_packetbuffer[3];
|
|
|
|
return response;
|
|
}
|
|
|
|
void PN532_wakeup(void)
|
|
{
|
|
uint8_t wakeup[5] = {0x55, 0x55, 0, 0, 0 };
|
|
PN532_Serial->write(wakeup,sizeof(wakeup));
|
|
|
|
|
|
PN532_Serial->flush();
|
|
}
|
|
|
|
bool PN532_readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength, uint16_t timeout = 50)
|
|
{
|
|
pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET;
|
|
pn532_packetbuffer[1] = 1;
|
|
pn532_packetbuffer[2] = cardbaudrate;
|
|
if (PN532_writeCommand(pn532_packetbuffer, 3)) {
|
|
return 0x0;
|
|
}
|
|
|
|
if (PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer), timeout) < 0) {
|
|
return 0x0;
|
|
}
|
|
# 274 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_40_pn532.ino"
|
|
if (pn532_packetbuffer[0] != 1) {
|
|
return 0;
|
|
}
|
|
|
|
uint16_t sens_res = pn532_packetbuffer[2];
|
|
sens_res <<= 8;
|
|
sens_res |= pn532_packetbuffer[3];
|
|
|
|
|
|
*uidLength = pn532_packetbuffer[5];
|
|
|
|
for (uint32_t i = 0; i < pn532_packetbuffer[5]; i++) {
|
|
uid[i] = pn532_packetbuffer[6 + i];
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
bool PN532_setPassiveActivationRetries(uint8_t maxRetries)
|
|
{
|
|
pn532_packetbuffer[0] = PN532_COMMAND_RFCONFIGURATION;
|
|
pn532_packetbuffer[1] = 5;
|
|
pn532_packetbuffer[2] = 0xFF;
|
|
pn532_packetbuffer[3] = 0x01;
|
|
pn532_packetbuffer[4] = maxRetries;
|
|
if (PN532_writeCommand(pn532_packetbuffer, 5)) {
|
|
return 0;
|
|
}
|
|
return (0 < PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer)));
|
|
}
|
|
|
|
bool PN532_SAMConfig(void)
|
|
{
|
|
pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION;
|
|
pn532_packetbuffer[1] = 0x01;
|
|
pn532_packetbuffer[2] = 0x14;
|
|
pn532_packetbuffer[3] = 0x00;
|
|
if (PN532_writeCommand(pn532_packetbuffer, 4)) {
|
|
return false;
|
|
}
|
|
return (0 < PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer)));
|
|
}
|
|
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
|
|
uint8_t mifareclassic_AuthenticateBlock (uint8_t *uid, uint8_t uidLen, uint32_t blockNumber, uint8_t keyNumber, uint8_t *keyData)
|
|
{
|
|
uint8_t i;
|
|
uint8_t _key[6];
|
|
uint8_t _uid[7];
|
|
uint8_t _uidLen;
|
|
|
|
|
|
memcpy(&_key, keyData, 6);
|
|
memcpy(&_uid, uid, uidLen);
|
|
_uidLen = uidLen;
|
|
|
|
|
|
pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
|
pn532_packetbuffer[1] = 1;
|
|
pn532_packetbuffer[2] = (keyNumber) ? MIFARE_CMD_AUTH_B : MIFARE_CMD_AUTH_A;
|
|
pn532_packetbuffer[3] = blockNumber;
|
|
memcpy(&pn532_packetbuffer[4], &_key, 6);
|
|
for (i = 0; i < _uidLen; i++) {
|
|
pn532_packetbuffer[10 + i] = _uid[i];
|
|
}
|
|
|
|
if (PN532_writeCommand(pn532_packetbuffer, 10 + _uidLen)) { return 0; }
|
|
|
|
|
|
PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer));
|
|
|
|
|
|
|
|
|
|
if (pn532_packetbuffer[0] != 0x00) {
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint8_t mifareclassic_ReadDataBlock (uint8_t blockNumber, uint8_t *data)
|
|
{
|
|
|
|
pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
|
pn532_packetbuffer[1] = 1;
|
|
pn532_packetbuffer[2] = MIFARE_CMD_READ;
|
|
pn532_packetbuffer[3] = blockNumber;
|
|
|
|
|
|
if (PN532_writeCommand(pn532_packetbuffer, 4)) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer));
|
|
|
|
|
|
if (pn532_packetbuffer[0] != 0x00) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
memcpy (data, &pn532_packetbuffer[1], 16);
|
|
|
|
return 1;
|
|
}
|
|
|
|
uint8_t mifareclassic_WriteDataBlock (uint8_t blockNumber, uint8_t *data)
|
|
{
|
|
|
|
pn532_packetbuffer[0] = PN532_COMMAND_INDATAEXCHANGE;
|
|
pn532_packetbuffer[1] = 1;
|
|
pn532_packetbuffer[2] = MIFARE_CMD_WRITE;
|
|
pn532_packetbuffer[3] = blockNumber;
|
|
memcpy(&pn532_packetbuffer[4], data, 16);
|
|
|
|
|
|
if (PN532_writeCommand(pn532_packetbuffer, 20)) {
|
|
return 0;
|
|
}
|
|
|
|
|
|
return (0 < PN532_readResponse(pn532_packetbuffer, sizeof(pn532_packetbuffer)));
|
|
}
|
|
|
|
#endif
|
|
|
|
void PN532_ScanForTag(void)
|
|
{
|
|
if (!pn532_model) { return; }
|
|
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
|
|
uint8_t uid_len = 0;
|
|
uint8_t card_data[16];
|
|
bool erase_success = false;
|
|
bool set_success = false;
|
|
if (PN532_readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uid_len)) {
|
|
char uids[15];
|
|
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
char card_datas[34];
|
|
#endif
|
|
|
|
ToHex_P((unsigned char*)uid, uid_len, uids, sizeof(uids));
|
|
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
if (uid_len == 4) {
|
|
uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
|
if (mifareclassic_AuthenticateBlock (uid, uid_len, 1, 1, keyuniversal)) {
|
|
if (mifareclassic_ReadDataBlock(1, card_data)) {
|
|
#ifdef USE_PN532_DATA_RAW
|
|
memcpy(&card_datas,&card_data,sizeof(card_data));
|
|
#else
|
|
for (uint32_t i = 0;i < sizeof(card_data);i++) {
|
|
if ((isalpha(card_data[i])) || ((isdigit(card_data[i])))) {
|
|
card_datas[i] = char(card_data[i]);
|
|
} else {
|
|
card_datas[i] = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
if (pn532_function == 1) {
|
|
for (uint32_t i = 0;i<16;i++) {
|
|
card_data[i] = 0x00;
|
|
}
|
|
if (mifareclassic_WriteDataBlock(1, card_data)) {
|
|
erase_success = true;
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Erase success"));
|
|
memcpy(&card_datas,&card_data,sizeof(card_data));
|
|
}
|
|
}
|
|
if (pn532_function == 2) {
|
|
#ifdef USE_PN532_DATA_RAW
|
|
memcpy(&card_data,&pn532_newdata,sizeof(card_data));
|
|
if (mifareclassic_WriteDataBlock(1, card_data)) {
|
|
set_success = true;
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Data write successful"));
|
|
memcpy(&card_datas,&card_data,sizeof(card_data));
|
|
}
|
|
#else
|
|
bool IsAlphaNumeric = true;
|
|
for (uint32_t i = 0;i < pn532_newdata_len;i++) {
|
|
if ((!isalpha(pn532_newdata[i])) && (!isdigit(pn532_newdata[i]))) {
|
|
IsAlphaNumeric = false;
|
|
}
|
|
}
|
|
if (IsAlphaNumeric) {
|
|
memcpy(&card_data,&pn532_newdata,pn532_newdata_len);
|
|
card_data[pn532_newdata_len] = '\0';
|
|
if (mifareclassic_WriteDataBlock(1, card_data)) {
|
|
set_success = true;
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Data write successful"));
|
|
memcpy(&card_datas,&card_data,sizeof(card_data));
|
|
}
|
|
} else {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Data must be alphanumeric"));
|
|
}
|
|
#endif
|
|
}
|
|
} else {
|
|
sprintf(card_datas,"AUTHFAIL");
|
|
}
|
|
}
|
|
switch (pn532_function) {
|
|
case 0x01:
|
|
if (!erase_success) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Erase fail - exiting erase mode"));
|
|
}
|
|
break;
|
|
case 0x02:
|
|
if (!set_success) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Write failed - exiting set mode"));
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
pn532_function = 0;
|
|
#endif
|
|
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
ResponseTime_P(PSTR(",\"PN532\":{\"UID\":\"%s\", \"DATA\":\"%s\"}}"), uids, card_datas);
|
|
#else
|
|
ResponseTime_P(PSTR(",\"PN532\":{\"UID\":\"%s\"}}"), uids);
|
|
#endif
|
|
|
|
MqttPublishTeleSensor();
|
|
|
|
#ifdef USE_PN532_CAUSE_EVENTS
|
|
|
|
char command[71];
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
sprintf(command,"backlog event PN532_UID=%s;event PN532_DATA=%s",uids,card_datas);
|
|
#else
|
|
sprintf(command,"event PN532_UID=%s",uids);
|
|
#endif
|
|
ExecuteCommand(command, SRC_RULE);
|
|
#endif
|
|
|
|
pn532_scantimer = 7;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
|
|
bool PN532_Command(void)
|
|
{
|
|
bool serviced = true;
|
|
uint8_t paramcount = 0;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
paramcount=1;
|
|
} else {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
char sub_string[XdrvMailbox.data_len];
|
|
char sub_string_tmp[XdrvMailbox.data_len];
|
|
for (uint32_t ca=0;ca<XdrvMailbox.data_len;ca++) {
|
|
if ((' ' == XdrvMailbox.data[ca]) || ('=' == XdrvMailbox.data[ca])) { XdrvMailbox.data[ca] = ','; }
|
|
if (',' == XdrvMailbox.data[ca]) { paramcount++; }
|
|
}
|
|
UpperCase(XdrvMailbox.data,XdrvMailbox.data);
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"E")) {
|
|
pn532_function = 1;
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Next scanned tag data block 1 will be erased"));
|
|
ResponseTime_P(PSTR(",\"PN532\":{\"COMMAND\":\"E\"}}"));
|
|
return serviced;
|
|
}
|
|
if (!strcmp(subStr(sub_string, XdrvMailbox.data, ",", 1),"S")) {
|
|
if (paramcount > 1) {
|
|
if (XdrvMailbox.data[XdrvMailbox.data_len-1] == ',') {
|
|
serviced = false;
|
|
return serviced;
|
|
}
|
|
sprintf(sub_string_tmp,subStr(sub_string, XdrvMailbox.data, ",", 2));
|
|
pn532_newdata_len = strlen(sub_string_tmp);
|
|
if (pn532_newdata_len > 15) { pn532_newdata_len = 15; }
|
|
memcpy(&pn532_newdata,&sub_string_tmp,pn532_newdata_len);
|
|
pn532_newdata[pn532_newdata_len] = 0x00;
|
|
pn532_function = 2;
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("NFC: PN532 NFC - Next scanned tag data block 1 will be set to '%s'"), pn532_newdata);
|
|
ResponseTime_P(PSTR(",\"PN532\":{\"COMMAND\":\"S\"}}"));
|
|
return serviced;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
bool Xsns40(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
PN532_Init();
|
|
result = true;
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
break;
|
|
case FUNC_EVERY_250_MSECOND:
|
|
if (pn532_scantimer > 0) {
|
|
pn532_scantimer--;
|
|
} else {
|
|
PN532_ScanForTag();
|
|
}
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
break;
|
|
#ifdef USE_PN532_DATA_FUNCTION
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_40 == XdrvMailbox.index) {
|
|
result = PN532_Command();
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_41_max44009.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_41_max44009.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MAX44009
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_41 41
|
|
#define XI2C_28 28
|
|
|
|
#define MAX44009_ADDR1 0x4A
|
|
#define MAX44009_ADDR2 0x4B
|
|
#define MAX44009_NO_REGISTERS 8
|
|
#define REG_CONFIG 0x02
|
|
#define REG_LUMINANCE 0x03
|
|
#define REG_LOWER_THRESHOLD 0x06
|
|
#define REG_THRESHOLD_TIMER 0x07
|
|
|
|
#define MAX44009_CONTINUOUS_AUTO_MODE 0x80
|
|
|
|
uint8_t max44009_address;
|
|
uint8_t max44009_addresses[] = { MAX44009_ADDR1, MAX44009_ADDR2, 0 };
|
|
uint8_t max44009_found = 0;
|
|
uint8_t max44009_valid = 0;
|
|
float max44009_illuminance = 0;
|
|
char max44009_types[] = "MAX44009";
|
|
|
|
bool Max4409Read_lum(void)
|
|
{
|
|
max44009_valid = 0;
|
|
uint8_t regdata[2];
|
|
|
|
|
|
if (I2cValidRead16((uint16_t *)®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 <FrogmoreScd30.h>
|
|
|
|
#define D_CMND_SCD30 "SCD30"
|
|
|
|
const char S_JSON_SCD30_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_SCD30 "%s\":%d}";
|
|
const char S_JSON_SCD30_COMMAND_NFW_VALUE[] PROGMEM = "{\"" D_CMND_SCD30 "%s\":%d.%d}";
|
|
const char S_JSON_SCD30_COMMAND[] PROGMEM = "{\"" D_CMND_SCD30 "%s\"}";
|
|
const char kSCD30_Commands[] PROGMEM = "Alt|Auto|Cal|FW|Int|Pres|TOff";
|
|
|
|
|
|
|
|
|
|
|
|
enum SCD30_Commands {
|
|
CMND_SCD30_ALTITUDE,
|
|
CMND_SCD30_AUTOMODE,
|
|
CMND_SCD30_CALIBRATE,
|
|
CMND_SCD30_FW,
|
|
CMND_SCD30_INTERVAL,
|
|
CMND_SCD30_PRESSURE,
|
|
CMND_SCD30_TEMPOFFSET
|
|
};
|
|
|
|
FrogmoreScd30 scd30;
|
|
|
|
bool scd30Found = false;
|
|
bool scd30IsDataValid = false;
|
|
int scd30ErrorState = SCD30_STATE_NO_ERROR;
|
|
uint16_t scd30Interval_sec;
|
|
int scd30Loop_count = 0;
|
|
int scd30DataNotAvailable_count = 0;
|
|
int scd30GoodMeas_count = 0;
|
|
int scd30Reset_count = 0;
|
|
int scd30CrcError_count = 0;
|
|
int scd30Co2Zero_count = 0;
|
|
int i2cReset_count = 0;
|
|
uint16_t scd30_CO2 = 0;
|
|
uint16_t scd30_CO2EAvg = 0;
|
|
float scd30_Humid = 0.0;
|
|
float scd30_Temp = 0.0;
|
|
|
|
void Scd30Detect(void)
|
|
{
|
|
if (I2cActive(SCD30_ADDRESS)) { return; }
|
|
|
|
scd30.begin();
|
|
|
|
uint8_t major = 0;
|
|
uint8_t minor = 0;
|
|
if (scd30.getFirmwareVersion(&major, &minor)) { return; }
|
|
uint16_t interval_sec;
|
|
if (scd30.getMeasurementInterval(&scd30Interval_sec)) { return; }
|
|
if (scd30.beginMeasuring()) { return; }
|
|
|
|
I2cSetActiveFound(SCD30_ADDRESS, "SCD30");
|
|
scd30Found = true;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SCD: FW v%d.%d"), major, minor);
|
|
}
|
|
|
|
|
|
void Scd30Update(void)
|
|
{
|
|
scd30Loop_count++;
|
|
if (scd30Loop_count > (scd30Interval_sec - 1)) {
|
|
int error = 0;
|
|
switch (scd30ErrorState) {
|
|
case SCD30_STATE_NO_ERROR: {
|
|
error = scd30.readMeasurement(&scd30_CO2, &scd30_CO2EAvg, &scd30_Temp, &scd30_Humid);
|
|
switch (error) {
|
|
case ERROR_SCD30_NO_ERROR:
|
|
scd30Loop_count = 0;
|
|
scd30IsDataValid = true;
|
|
scd30GoodMeas_count++;
|
|
break;
|
|
|
|
case ERROR_SCD30_NO_DATA:
|
|
scd30DataNotAvailable_count++;
|
|
break;
|
|
|
|
case ERROR_SCD30_CRC_ERROR:
|
|
scd30ErrorState = SCD30_STATE_ERROR_DATA_CRC;
|
|
scd30CrcError_count++;
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"),
|
|
scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
|
|
#endif
|
|
break;
|
|
|
|
case ERROR_SCD30_CO2_ZERO:
|
|
scd30Co2Zero_count++;
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"),
|
|
scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
|
|
#endif
|
|
break;
|
|
|
|
default: {
|
|
scd30ErrorState = SCD30_STATE_ERROR_READ_MEAS;
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld"), error, scd30Loop_count);
|
|
#endif
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SCD30_STATE_ERROR_DATA_CRC: {
|
|
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
|
|
scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: got CRC error, try again, counter: %ld"), scd30Loop_count);
|
|
#endif
|
|
scd30ErrorState = ERROR_SCD30_NO_ERROR;
|
|
}
|
|
break;
|
|
|
|
case SCD30_STATE_ERROR_READ_MEAS: {
|
|
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
|
|
scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: not answering, sending soft reset, counter: %ld"), scd30Loop_count);
|
|
#endif
|
|
scd30Reset_count++;
|
|
error = scd30.softReset();
|
|
if (error) {
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: resetting got error: 0x%lX"), error);
|
|
#endif
|
|
error >>= 8;
|
|
if (error == 4) {
|
|
scd30ErrorState = SCD30_STATE_ERROR_SOFT_RESET;
|
|
} else {
|
|
scd30ErrorState = SCD30_STATE_ERROR_UNKNOWN;
|
|
}
|
|
} else {
|
|
scd30ErrorState = ERROR_SCD30_NO_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SCD30_STATE_ERROR_SOFT_RESET: {
|
|
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
|
|
scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
|
|
AddLog_P(LOG_LEVEL_ERROR, PSTR("SCD30: clearing i2c bus"));
|
|
#endif
|
|
i2cReset_count++;
|
|
error = scd30.clearI2CBus();
|
|
if (error) {
|
|
scd30ErrorState = SCD30_STATE_ERROR_I2C_RESET;
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error clearing i2c bus: 0x%lX"), error);
|
|
#endif
|
|
} else {
|
|
scd30ErrorState = ERROR_SCD30_NO_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: {
|
|
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: unknown error state: 0x%lX"), scd30ErrorState);
|
|
#endif
|
|
scd30ErrorState = SCD30_STATE_ERROR_SOFT_RESET;
|
|
}
|
|
}
|
|
|
|
if (scd30Loop_count > (SCD30_MAX_MISSED_READS * scd30Interval_sec)) {
|
|
scd30IsDataValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int Scd30GetCommand(int command_code, uint16_t *pvalue)
|
|
{
|
|
switch (command_code)
|
|
{
|
|
case CMND_SCD30_ALTITUDE:
|
|
return scd30.getAltitudeCompensation(pvalue);
|
|
break;
|
|
|
|
case CMND_SCD30_AUTOMODE:
|
|
return scd30.getCalibrationType(pvalue);
|
|
break;
|
|
|
|
case CMND_SCD30_CALIBRATE:
|
|
return scd30.getForcedRecalibrationFactor(pvalue);
|
|
break;
|
|
|
|
case CMND_SCD30_INTERVAL:
|
|
return scd30.getMeasurementInterval(pvalue);
|
|
break;
|
|
|
|
case CMND_SCD30_PRESSURE:
|
|
return scd30.getAmbientPressure(pvalue);
|
|
break;
|
|
|
|
case CMND_SCD30_TEMPOFFSET:
|
|
return scd30.getTemperatureOffset(pvalue);
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
int Scd30SetCommand(int command_code, uint16_t value)
|
|
{
|
|
switch (command_code)
|
|
{
|
|
case CMND_SCD30_ALTITUDE:
|
|
return scd30.setAltitudeCompensation(value);
|
|
break;
|
|
|
|
case CMND_SCD30_AUTOMODE:
|
|
return scd30.setCalibrationType(value);
|
|
break;
|
|
|
|
case CMND_SCD30_CALIBRATE:
|
|
return scd30.setForcedRecalibrationFactor(value);
|
|
break;
|
|
|
|
case CMND_SCD30_INTERVAL:
|
|
{
|
|
int error = scd30.setMeasurementInterval(value);
|
|
if (!error)
|
|
{
|
|
scd30Interval_sec = value;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
break;
|
|
|
|
case CMND_SCD30_PRESSURE:
|
|
return scd30.setAmbientPressure(value);
|
|
break;
|
|
|
|
case CMND_SCD30_TEMPOFFSET:
|
|
return scd30.setTemperatureOffset(value);
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Scd30CommandSensor()
|
|
{
|
|
char command[CMDSZ];
|
|
bool serviced = true;
|
|
uint8_t prefix_len = strlen(D_CMND_SCD30);
|
|
|
|
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_SCD30), prefix_len)) {
|
|
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + prefix_len, kSCD30_Commands);
|
|
|
|
switch (command_code) {
|
|
case CMND_SCD30_ALTITUDE:
|
|
case CMND_SCD30_AUTOMODE:
|
|
case CMND_SCD30_CALIBRATE:
|
|
case CMND_SCD30_INTERVAL:
|
|
case CMND_SCD30_PRESSURE:
|
|
case CMND_SCD30_TEMPOFFSET:
|
|
{
|
|
uint16_t value = 0;
|
|
if (XdrvMailbox.data_len > 0)
|
|
{
|
|
value = XdrvMailbox.payload;
|
|
Scd30SetCommand(command_code, value);
|
|
}
|
|
else
|
|
{
|
|
Scd30GetCommand(command_code, &value);
|
|
}
|
|
|
|
Response_P(S_JSON_SCD30_COMMAND_NVALUE, command, value);
|
|
}
|
|
break;
|
|
|
|
case CMND_SCD30_FW:
|
|
{
|
|
uint8_t major = 0;
|
|
uint8_t minor = 0;
|
|
int error;
|
|
error = scd30.getFirmwareVersion(&major, &minor);
|
|
if (error)
|
|
{
|
|
#ifdef SCD30_DEBUG
|
|
AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error getting FW version: 0x%lX"), error);
|
|
#endif
|
|
serviced = false;
|
|
}
|
|
else
|
|
{
|
|
Response_P(S_JSON_SCD30_COMMAND_NFW_VALUE, command, major, minor);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
serviced = false;
|
|
break;
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
void Scd30Show(bool json)
|
|
{
|
|
if (scd30IsDataValid)
|
|
{
|
|
char humidity[10];
|
|
dtostrfd(ConvertHumidity(scd30_Humid), Settings.flag2.humidity_resolution, humidity);
|
|
char temperature[10];
|
|
dtostrfd(ConvertTemp(scd30_Temp), Settings.flag2.temperature_resolution, temperature);
|
|
|
|
if (json) {
|
|
|
|
ResponseAppend_P(PSTR(",\"SCD30\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"),
|
|
scd30_CO2, scd30_CO2EAvg, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period)
|
|
{
|
|
DomoticzSensor(DZ_AIRQUALITY, scd30_CO2);
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_CO2EAVG, "SCD30", scd30_CO2EAvg);
|
|
WSContentSend_PD(HTTP_SNS_CO2, "SCD30", scd30_CO2);
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "SCD30", temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, "SCD30", humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns42(byte function)
|
|
{
|
|
if (!I2cEnabled(XI2C_29)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Scd30Detect();
|
|
}
|
|
else if (scd30Found) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Scd30Update();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = Scd30CommandSensor();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Scd30Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Scd30Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_43_hre.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_43_hre.ino"
|
|
#ifdef USE_HRE
|
|
# 49 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_43_hre.ino"
|
|
#define XSNS_43 43
|
|
|
|
enum hre_states {
|
|
hre_idle,
|
|
hre_sync,
|
|
hre_syncing,
|
|
hre_read,
|
|
hre_reading,
|
|
hre_sleep,
|
|
hre_sleeping
|
|
};
|
|
|
|
hre_states hre_state = hre_idle;
|
|
|
|
float hre_usage = 0;
|
|
float hre_rate = 0;
|
|
uint32_t hre_usage_time = 0;
|
|
|
|
int hre_read_errors = 0;
|
|
bool hre_good = false;
|
|
|
|
|
|
|
|
int hreReadBit()
|
|
{
|
|
digitalWrite(pin[GPIO_HRE_CLOCK], HIGH);
|
|
delay(1);
|
|
int bit = digitalRead(pin[GPIO_HRE_DATA]);
|
|
digitalWrite(pin[GPIO_HRE_CLOCK], LOW);
|
|
delay(1);
|
|
return bit;
|
|
}
|
|
|
|
|
|
|
|
char hreReadChar(int &parity_errors)
|
|
{
|
|
|
|
hreReadBit();
|
|
|
|
unsigned ch=0;
|
|
int sum=0;
|
|
for (uint32_t i=0; i<7; i++)
|
|
{
|
|
int b = hreReadBit();
|
|
ch |= b << i;
|
|
sum += b;
|
|
}
|
|
|
|
|
|
if ( (sum & 0x1) != hreReadBit())
|
|
parity_errors++;
|
|
|
|
|
|
hreReadBit();
|
|
|
|
return ch;
|
|
}
|
|
|
|
void hreInit(void)
|
|
{
|
|
hre_read_errors = 0;
|
|
hre_good = false;
|
|
|
|
pinMode(pin[GPIO_HRE_CLOCK], OUTPUT);
|
|
pinMode(pin[GPIO_HRE_DATA], INPUT);
|
|
|
|
|
|
|
|
digitalWrite(pin[GPIO_HRE_CLOCK], LOW);
|
|
|
|
hre_state = hre_sync;
|
|
}
|
|
|
|
|
|
void hreEvery50ms(void)
|
|
{
|
|
static int sync_counter = 0;
|
|
static int sync_run = 0;
|
|
|
|
static uint32_t curr_start = 0;
|
|
static int read_counter = 0;
|
|
static int parity_errors = 0;
|
|
static char buff[46];
|
|
|
|
static char ch;
|
|
static size_t i;
|
|
|
|
switch (hre_state)
|
|
{
|
|
case hre_sync:
|
|
if (uptime < 10)
|
|
break;
|
|
sync_run = 0;
|
|
sync_counter = 0;
|
|
hre_state = hre_syncing;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "hre_state:hre_syncing"));
|
|
break;
|
|
|
|
case hre_syncing:
|
|
|
|
|
|
for (uint32_t i=0; i<20; i++)
|
|
{
|
|
if (hreReadBit())
|
|
sync_run++;
|
|
else
|
|
sync_run = 0;
|
|
if (sync_run == 62)
|
|
{
|
|
hre_state = hre_read;
|
|
break;
|
|
}
|
|
sync_counter++;
|
|
}
|
|
|
|
if (sync_counter > 1000)
|
|
{
|
|
hre_state = hre_sleep;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE D_ERROR));
|
|
}
|
|
break;
|
|
|
|
|
|
case hre_read:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "sync_run:%d, sync_counter:%d"), sync_run, sync_counter);
|
|
read_counter = 0;
|
|
parity_errors = 0;
|
|
curr_start = uptime;
|
|
memset(buff, 0, sizeof(buff));
|
|
hre_state = hre_reading;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "hre_state:hre_reading"));
|
|
|
|
|
|
|
|
|
|
|
|
case hre_reading:
|
|
|
|
buff[read_counter++] = hreReadChar(parity_errors);
|
|
buff[read_counter++] = hreReadChar(parity_errors);
|
|
|
|
if (read_counter == 46)
|
|
{
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "pe:%d, re:%d, buff:%s"),
|
|
parity_errors, hre_read_errors, buff);
|
|
if (parity_errors == 0)
|
|
{
|
|
float curr_usage;
|
|
curr_usage = 0.01 * atol(buff+24);
|
|
if (hre_usage_time)
|
|
{
|
|
double dt = 1.666e-2 * (curr_start - hre_usage_time);
|
|
hre_rate = (curr_usage - hre_usage)/dt;
|
|
}
|
|
hre_usage = curr_usage;
|
|
hre_usage_time = curr_start;
|
|
hre_good = true;
|
|
|
|
hre_state = hre_sleep;
|
|
}
|
|
else
|
|
{
|
|
hre_read_errors++;
|
|
hre_state = hre_sleep;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case hre_sleep:
|
|
hre_usage_time = curr_start;
|
|
hre_state = hre_sleeping;
|
|
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HRE "hre_state:hre_sleeping"));
|
|
|
|
case hre_sleeping:
|
|
|
|
|
|
if (uptime - hre_usage_time >= 27)
|
|
hre_state = hre_sync;
|
|
}
|
|
}
|
|
|
|
void hreShow(boolean json)
|
|
{
|
|
if (!hre_good)
|
|
return;
|
|
|
|
const char *id = "HRE";
|
|
|
|
char usage[16];
|
|
char rate[16];
|
|
dtostrfd(hre_usage, 2, usage);
|
|
dtostrfd(hre_rate, 3, rate);
|
|
|
|
if (json)
|
|
{
|
|
ResponseAppend_P(JSON_SNS_GNGPM, id, usage, rate);
|
|
#ifdef USE_WEBSERVER
|
|
}
|
|
else
|
|
{
|
|
WSContentSend_PD(HTTP_SNS_GALLONS, id, usage);
|
|
WSContentSend_PD(HTTP_SNS_GPM, id, rate);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns43(byte function)
|
|
{
|
|
|
|
if (pin[GPIO_HRE_CLOCK] >= 99 || pin[GPIO_HRE_DATA] >= 99)
|
|
return false;
|
|
|
|
switch (function)
|
|
{
|
|
case FUNC_INIT:
|
|
hreInit();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
hreEvery50ms();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
hreShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
hreShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_44_sps30.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_44_sps30.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_SPS30
|
|
|
|
#define XSNS_44 44
|
|
#define XI2C_30 30
|
|
|
|
#define SPS30_ADDR 0x69
|
|
|
|
#include <Wire.h>
|
|
#include <twi.h>
|
|
|
|
uint8_t sps30_ready = 0;
|
|
uint8_t sps30_running;
|
|
|
|
struct SPS30 {
|
|
float PM1_0;
|
|
float PM2_5;
|
|
float PM4_0;
|
|
float PM10;
|
|
float NCPM0_5;
|
|
float NCPM1_0;
|
|
float NCPM2_5;
|
|
float NCPM4_0;
|
|
float NCPM10;
|
|
float TYPSIZ;
|
|
} sps30_result;
|
|
|
|
#define SPS_CMD_START_MEASUREMENT 0x0010
|
|
#define SPS_CMD_START_MEASUREMENT_ARG 0x0300
|
|
#define SPS_CMD_STOP_MEASUREMENT 0x0104
|
|
#define SPS_CMD_READ_MEASUREMENT 0x0300
|
|
#define SPS_CMD_GET_DATA_READY 0x0202
|
|
#define SPS_CMD_AUTOCLEAN_INTERVAL 0x8004
|
|
#define SPS_CMD_CLEAN 0x5607
|
|
#define SPS_CMD_GET_ACODE 0xd025
|
|
#define SPS_CMD_GET_SERIAL 0xd033
|
|
#define SPS_CMD_RESET 0xd304
|
|
#define SPS_WRITE_DELAY_US 20000
|
|
#define SPS_MAX_SERIAL_LEN 32
|
|
|
|
uint8_t sps30_calc_CRC(uint8_t *data) {
|
|
uint8_t crc = 0xFF;
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
crc ^= data[i];
|
|
for (uint32_t bit = 8; bit > 0; --bit) {
|
|
if(crc & 0x80) {
|
|
crc = (crc << 1) ^ 0x31u;
|
|
} else {
|
|
crc = (crc << 1);
|
|
}
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
void CmdClean(void);
|
|
|
|
unsigned char twi_readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop);
|
|
|
|
void sps30_get_data(uint16_t cmd, uint8_t *data, uint8_t dlen) {
|
|
unsigned char cmdb[2];
|
|
uint8_t tmp[3];
|
|
uint8_t index=0;
|
|
memset(data,0,dlen);
|
|
uint8_t twi_buff[64];
|
|
|
|
Wire.beginTransmission(SPS30_ADDR);
|
|
cmdb[0]=cmd>>8;
|
|
cmdb[1]=cmd;
|
|
Wire.write(cmdb,2);
|
|
Wire.endTransmission();
|
|
|
|
|
|
dlen/=2;
|
|
dlen*=3;
|
|
|
|
twi_readFrom(SPS30_ADDR,twi_buff,dlen,1);
|
|
|
|
uint8_t bind=0;
|
|
while (bind<dlen) {
|
|
tmp[0] = twi_buff[bind++];
|
|
tmp[1] = twi_buff[bind++];
|
|
tmp[2] = twi_buff[bind++];
|
|
if (sps30_calc_CRC(tmp)!=tmp[2]) {
|
|
|
|
index+=2;
|
|
} else {
|
|
data[index++]=tmp[0];
|
|
data[index++]=tmp[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
void sps30_cmd(uint16_t cmd) {
|
|
unsigned char cmdb[6];
|
|
Wire.beginTransmission(SPS30_ADDR);
|
|
cmdb[0]=cmd>>8;
|
|
cmdb[1]=cmd;
|
|
|
|
if (cmd==SPS_CMD_START_MEASUREMENT) {
|
|
cmdb[2]=SPS_CMD_START_MEASUREMENT_ARG>>8;
|
|
cmdb[3]=SPS_CMD_START_MEASUREMENT_ARG&0xff;
|
|
cmdb[4]=sps30_calc_CRC(&cmdb[2]);
|
|
Wire.write(cmdb,5);
|
|
} else {
|
|
Wire.write(cmdb,2);
|
|
}
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
void SPS30_Detect(void)
|
|
{
|
|
if (!I2cSetDevice(SPS30_ADDR)) { return; }
|
|
I2cSetActiveFound(SPS30_ADDR, "SPS30");
|
|
|
|
uint8_t dcode[32];
|
|
sps30_get_data(SPS_CMD_GET_SERIAL,dcode,sizeof(dcode));
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("sps30 found with serial: %s"),dcode);
|
|
sps30_cmd(SPS_CMD_START_MEASUREMENT);
|
|
sps30_running = 1;
|
|
sps30_ready = 1;
|
|
}
|
|
|
|
#define D_UNIT_PM "ug/m3"
|
|
#define D_UNIT_NCPM "#/m3"
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_SPS30_a[] PROGMEM ="{s}SPS30 " "%s" "{m}%s " D_UNIT_PM "{e}";
|
|
const char HTTP_SNS_SPS30_b[] PROGMEM ="{s}SPS30 " "%s" "{m}%s " D_UNIT_NCPM "{e}";
|
|
const char HTTP_SNS_SPS30_c[] PROGMEM ="{s}SPS30 " "TYPSIZ" "{m}%s " "um" "{e}";
|
|
#endif
|
|
|
|
#define PMDP 2
|
|
|
|
#define SPS30_HOURS Settings.sps30_inuse_hours
|
|
|
|
|
|
|
|
void SPS30_Every_Second() {
|
|
if (!sps30_running) return;
|
|
|
|
if (uptime%10==0) {
|
|
uint8_t vars[sizeof(float)*10];
|
|
sps30_get_data(SPS_CMD_READ_MEASUREMENT,vars,sizeof(vars));
|
|
float *fp=&sps30_result.PM1_0;
|
|
|
|
typedef union {
|
|
uint8_t array[4];
|
|
float value;
|
|
} ByteToFloat;
|
|
|
|
ByteToFloat conv;
|
|
|
|
for (uint32_t count=0; count<10; count++) {
|
|
for (uint32_t i = 0; i < 4; i++){
|
|
conv.array[3-i] = vars[count*sizeof(float)+i];
|
|
}
|
|
*fp++=conv.value;
|
|
}
|
|
}
|
|
|
|
if (uptime%3600==0 && uptime>60) {
|
|
|
|
|
|
SPS30_HOURS++;
|
|
if (SPS30_HOURS>(7*24)) {
|
|
CmdClean();
|
|
SPS30_HOURS=0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SPS30_Show(bool json)
|
|
{
|
|
if (!sps30_running) { return; }
|
|
|
|
char str[64];
|
|
if (json) {
|
|
dtostrfd(sps30_result.PM1_0,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"SPS30\":{\"" "PM1_0" "\":%s"), str);
|
|
dtostrfd(sps30_result.PM2_5,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "PM2_5" "\":%s"), str);
|
|
dtostrfd(sps30_result.PM4_0,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "PM4_0" "\":%s"), str);
|
|
dtostrfd(sps30_result.PM10,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "PM10" "\":%s"), str);
|
|
dtostrfd(sps30_result.NCPM0_5,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "NCPM0_5" "\":%s"), str);
|
|
dtostrfd(sps30_result.NCPM1_0,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "NCPM1_0" "\":%s"), str);
|
|
dtostrfd(sps30_result.NCPM2_5,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "NCPM2_5" "\":%s"), str);
|
|
dtostrfd(sps30_result.NCPM4_0,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "NCPM4_0" "\":%s"), str);
|
|
dtostrfd(sps30_result.NCPM10,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "NCPM10" "\":%s"), str);
|
|
dtostrfd(sps30_result.TYPSIZ,PMDP,str);
|
|
ResponseAppend_P(PSTR(",\"" "TYPSIZ" "\":%s}"), str);
|
|
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
dtostrfd(sps30_result.PM1_0,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 1.0",str);
|
|
dtostrfd(sps30_result.PM2_5,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 2.5",str);
|
|
dtostrfd(sps30_result.PM4_0,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 4.0",str);
|
|
dtostrfd(sps30_result.PM10,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_a,"PM 10",str);
|
|
dtostrfd(sps30_result.NCPM0_5,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 0.5",str);
|
|
dtostrfd(sps30_result.NCPM1_0,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 1.0",str);
|
|
dtostrfd(sps30_result.NCPM2_5,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 2.5",str);
|
|
dtostrfd(sps30_result.NCPM4_0,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 4.0",str);
|
|
dtostrfd(sps30_result.NCPM10,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_b,"NCPM 10",str);
|
|
dtostrfd(sps30_result.TYPSIZ,PMDP,str);
|
|
WSContentSend_PD(HTTP_SNS_SPS30_c,str);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void CmdClean(void)
|
|
{
|
|
sps30_cmd(SPS_CMD_CLEAN);
|
|
ResponseTime_P(PSTR(",\"SPS30\":{\"CFAN\":\"true\"}}"));
|
|
MqttPublishTeleSensor();
|
|
}
|
|
|
|
bool SPS30_cmd(void)
|
|
{
|
|
bool serviced = true;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char *cp=XdrvMailbox.data;
|
|
if (*cp=='c') {
|
|
|
|
CmdClean();
|
|
} else if (*cp=='0' || *cp=='1') {
|
|
sps30_running=*cp&1;
|
|
sps30_cmd(sps30_running?SPS_CMD_START_MEASUREMENT:SPS_CMD_STOP_MEASUREMENT);
|
|
} else {
|
|
serviced=false;
|
|
}
|
|
}
|
|
Response_P(PSTR("{\"SPS30\":\"%s\"}"), sps30_running?"running":"stopped");
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns44(byte function)
|
|
{
|
|
if (!I2cEnabled(XI2C_30)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
SPS30_Detect();
|
|
}
|
|
else if (sps30_ready) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
SPS30_Every_Second();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
SPS30_Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
SPS30_Show(0);
|
|
break;
|
|
#endif
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_44 == XdrvMailbox.index) {
|
|
result = SPS30_cmd();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_45_vl53l0x.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_45_vl53l0x.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_VL53L0X
|
|
|
|
#define XSNS_45 45
|
|
#define XI2C_31 31
|
|
|
|
#include <Wire.h>
|
|
#include "VL53L0X.h"
|
|
VL53L0X sensor;
|
|
|
|
uint8_t vl53l0x_ready = 0;
|
|
uint16_t vl53l0x_distance;
|
|
uint16_t Vl53l0_buffer[5];
|
|
uint8_t Vl53l0_index;
|
|
|
|
|
|
|
|
void Vl53l0Detect(void)
|
|
{
|
|
if (!I2cSetDevice(0x29)) { return; }
|
|
|
|
if (!sensor.init()) { return; }
|
|
|
|
I2cSetActiveFound(sensor.getAddress(), "VL53L0X");
|
|
|
|
sensor.setTimeout(500);
|
|
|
|
|
|
|
|
|
|
|
|
sensor.startContinuous();
|
|
vl53l0x_ready = 1;
|
|
|
|
Vl53l0_index=0;
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_VL53L0X[] PROGMEM =
|
|
"{s}VL53L0X " D_DISTANCE "{m}%d" D_UNIT_MILLIMETER "{e}";
|
|
#endif
|
|
|
|
#define USE_VL_MEDIAN
|
|
|
|
void Vl53l0Every_250MSecond(void)
|
|
{
|
|
uint16_t tbuff[5],tmp;
|
|
uint8_t flag;
|
|
|
|
|
|
uint16_t dist = sensor.readRangeContinuousMillimeters();
|
|
if (dist==0 || dist>2000) {
|
|
dist=9999;
|
|
}
|
|
|
|
#ifdef USE_VL_MEDIAN
|
|
|
|
Vl53l0_buffer[Vl53l0_index]=dist;
|
|
Vl53l0_index++;
|
|
if (Vl53l0_index>=5) Vl53l0_index=0;
|
|
|
|
|
|
memmove(tbuff,Vl53l0_buffer,sizeof(tbuff));
|
|
for (byte ocnt=0; ocnt<5; ocnt++) {
|
|
flag=0;
|
|
for (byte count=0; count<4; count++) {
|
|
if (tbuff[count]>tbuff[count+1]) {
|
|
tmp=tbuff[count];
|
|
tbuff[count]=tbuff[count+1];
|
|
tbuff[count+1]=tmp;
|
|
flag=1;
|
|
}
|
|
}
|
|
if (!flag) break;
|
|
}
|
|
vl53l0x_distance=tbuff[2];
|
|
#else
|
|
vl53l0x_distance=dist;
|
|
#endif
|
|
}
|
|
|
|
void Vl53l0Show(boolean json)
|
|
{
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"VL53L0X\":{\"" D_JSON_DISTANCE "\":%d}"), vl53l0x_distance);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_VL53L0X, vl53l0x_distance);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns45(byte function)
|
|
{
|
|
if (!I2cEnabled(XI2C_31)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Vl53l0Detect();
|
|
}
|
|
else if (vl53l0x_ready) {
|
|
switch (function) {
|
|
case FUNC_EVERY_250_MSECOND:
|
|
Vl53l0Every_250MSecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Vl53l0Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Vl53l0Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_46_MLX90614.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_46_MLX90614.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_MLX90614
|
|
|
|
#define XSNS_46 46
|
|
#define XI2C_32 32
|
|
|
|
#define I2_ADR_IRT 0x5a
|
|
|
|
#define MLX90614_RAWIR1 0x04
|
|
#define MLX90614_RAWIR2 0x05
|
|
#define MLX90614_TA 0x06
|
|
#define MLX90614_TOBJ1 0x07
|
|
#define MLX90614_TOBJ2 0x08
|
|
|
|
struct {
|
|
union {
|
|
uint16_t value;
|
|
uint32_t i2c_buf;
|
|
};
|
|
float obj_temp;
|
|
float amb_temp;
|
|
bool ready = false;
|
|
} mlx90614;
|
|
|
|
void MLX90614_Init(void)
|
|
{
|
|
if (!I2cSetDevice(I2_ADR_IRT)) { return; }
|
|
I2cSetActiveFound(I2_ADR_IRT, "MLX90614");
|
|
mlx90614.ready = true;
|
|
}
|
|
|
|
void MLX90614_Every_Second(void)
|
|
{
|
|
mlx90614.i2c_buf = I2cRead24(I2_ADR_IRT, MLX90614_TOBJ1);
|
|
if (mlx90614.value & 0x8000) {
|
|
mlx90614.obj_temp = -999;
|
|
} else {
|
|
mlx90614.obj_temp = ((float)mlx90614.value * 0.02) - 273.15;
|
|
}
|
|
mlx90614.i2c_buf = I2cRead24(I2_ADR_IRT,MLX90614_TA);
|
|
if (mlx90614.value & 0x8000) {
|
|
mlx90614.amb_temp = -999;
|
|
} else {
|
|
mlx90614.amb_temp = ((float)mlx90614.value * 0.02) - 273.15;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_IRTMP[] PROGMEM =
|
|
"{s}MXL90614 " "OBJ-" D_TEMPERATURE "{m}%s C" "{e}"
|
|
"{s}MXL90614 " "AMB-" D_TEMPERATURE "{m}%s C" "{e}";
|
|
#endif
|
|
|
|
void MLX90614_Show(uint8_t json)
|
|
{
|
|
char obj_tstr[16];
|
|
dtostrfd(mlx90614.obj_temp, Settings.flag2.temperature_resolution, obj_tstr);
|
|
char amb_tstr[16];
|
|
dtostrfd(mlx90614.amb_temp, Settings.flag2.temperature_resolution, amb_tstr);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"MLX90614\":{\"OBJTMP\":%s,\"AMBTMP\":%s}"), obj_tstr, amb_tstr);
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_IRTMP, obj_tstr, amb_tstr);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns46(byte function)
|
|
{
|
|
if (!I2cEnabled(XI2C_32)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
MLX90614_Init();
|
|
}
|
|
else if (mlx90614.ready) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
MLX90614_Every_Second();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MLX90614_Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MLX90614_Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_47_max31865.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_47_max31865.ino"
|
|
#ifdef USE_MAX31865
|
|
|
|
#ifndef USE_SPI
|
|
#error "MAX31865 requires USE_SPI enabled"
|
|
#endif
|
|
|
|
#include "Adafruit_MAX31865.h"
|
|
|
|
#define XSNS_47 47
|
|
|
|
#if MAX31865_PTD_WIRES == 4
|
|
#define PTD_WIRES MAX31865_4WIRE
|
|
#elif MAX31865_PTD_WIRES == 3
|
|
#define PTD_WIRES MAX31865_3WIRE
|
|
#else
|
|
#define PTD_WIRES MAX31865_2WIRE
|
|
#endif
|
|
|
|
int8_t init_status = 0;
|
|
|
|
Adafruit_MAX31865 max31865;
|
|
|
|
struct MAX31865_Result_Struct {
|
|
uint8_t ErrorCode;
|
|
uint16_t Rtd;
|
|
float PtdResistance;
|
|
float PtdTemp;
|
|
} MAX31865_Result;
|
|
|
|
void MAX31865_Init(void){
|
|
if(init_status)
|
|
return;
|
|
|
|
max31865.setPins(
|
|
pin[GPIO_SSPI_CS],
|
|
pin[GPIO_SSPI_MOSI],
|
|
pin[GPIO_SSPI_MISO],
|
|
pin[GPIO_SSPI_SCLK]
|
|
);
|
|
|
|
if(max31865.begin(PTD_WIRES))
|
|
init_status = 1;
|
|
else
|
|
init_status = -1;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MAX31865_GetResult(void){
|
|
uint16_t rtd;
|
|
|
|
rtd = max31865.readRTD();
|
|
MAX31865_Result.Rtd = rtd;
|
|
MAX31865_Result.PtdResistance = max31865.rtd_to_resistance(rtd, MAX31865_REF_RES);
|
|
MAX31865_Result.PtdTemp = max31865.rtd_to_temperature(rtd, MAX31865_PTD_RES, MAX31865_REF_RES) + MAX31865_PTD_BIAS;
|
|
}
|
|
|
|
void MAX31865_Show(bool Json){
|
|
char temperature[33];
|
|
char resistance[33];
|
|
|
|
dtostrfd(MAX31865_Result.PtdResistance, Settings.flag2.temperature_resolution, resistance);
|
|
dtostrfd(MAX31865_Result.PtdTemp, Settings.flag2.temperature_resolution, temperature);
|
|
|
|
if(Json){
|
|
ResponseAppend_P(PSTR(",\"MAX31865\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RESISTANCE "\":%s,\"" D_JSON_ERROR "\":%d}"), \
|
|
temperature, resistance, MAX31865_Result.ErrorCode);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, MAX31865_Result.PtdTemp);
|
|
}
|
|
#endif
|
|
} else {
|
|
#ifdef USE_WEBSERVER
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "MAX31865", temperature, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns47(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
if((pin[GPIO_SSPI_MISO] < 99) && (pin[GPIO_SSPI_MOSI] < 99) &&
|
|
(pin[GPIO_SSPI_SCLK] < 99) && (pin[GPIO_SSPI_CS] < 99)) {
|
|
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
MAX31865_Init();
|
|
break;
|
|
|
|
case FUNC_EVERY_SECOND:
|
|
MAX31865_GetResult();
|
|
break;
|
|
|
|
case FUNC_JSON_APPEND:
|
|
MAX31865_Show(true);
|
|
break;
|
|
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MAX31865_Show(false);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_48_chirp.ino"
|
|
# 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_48_chirp.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_CHIRP
|
|
# 47 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_48_chirp.ino"
|
|
#define XSNS_48 48
|
|
#define XI2C_33 33
|
|
|
|
#define CHIRP_MAX_SENSOR_COUNT 3
|
|
|
|
#define CHIRP_ADDR_STANDARD 0x20
|
|
|
|
|
|
|
|
|
|
|
|
#define D_CMND_CHIRP "CHIRP"
|
|
|
|
const char S_JSON_CHIRP_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_CHIRP "%s\":%d}";
|
|
const char S_JSON_CHIRP_COMMAND[] PROGMEM = "{\"" D_CMND_CHIRP "%s\"}";
|
|
const char kCHIRP_Commands[] PROGMEM = "Select|Set|Scan|Reset|Sleep|Wake";
|
|
|
|
const char kChirpTypes[] PROGMEM = "CHIRP";
|
|
|
|
|
|
|
|
|
|
|
|
enum CHIRP_Commands {
|
|
CMND_CHIRP_SELECT,
|
|
CMND_CHIRP_SET,
|
|
CMND_CHIRP_SCAN,
|
|
CMND_CHIRP_RESET,
|
|
CMND_CHIRP_SLEEP,
|
|
CMND_CHIRP_WAKE };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define CHIRP_GET_CAPACITANCE 0x00
|
|
#define CHIRP_SET_ADDRESS 0x01
|
|
#define CHIRP_GET_ADDRESS 0x02
|
|
#define CHIRP_MEASURE_LIGHT 0x03
|
|
#define CHIRP_GET_LIGHT 0x04
|
|
#define CHIRP_GET_TEMPERATURE 0x05
|
|
#define CHIRP_RESET 0x06
|
|
#define CHIRP_GET_VERSION 0x07
|
|
#define CHIRP_SLEEP 0x08
|
|
#define CHIRP_GET_BUSY 0x09
|
|
|
|
|
|
|
|
|
|
|
|
void ChirpWriteI2CRegister(uint8_t addr, uint8_t reg) {
|
|
Wire.beginTransmission(addr);
|
|
Wire.write(reg);
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
uint16_t ChirpFinishReadI2CRegister16bit(uint8_t addr) {
|
|
Wire.requestFrom(addr,(uint8_t)2);
|
|
uint16_t t = Wire.read() << 8;
|
|
t = t | Wire.read();
|
|
return t;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t chirp_current = 0;
|
|
uint8_t chirp_found_sensors = 0;
|
|
|
|
char chirp_name[7];
|
|
uint8_t chirp_next_job = 0;
|
|
uint32_t chirp_timeout_count = 0;
|
|
|
|
#pragma pack(1)
|
|
struct ChirpSensor_t{
|
|
uint16_t moisture = 0;
|
|
uint16_t light = 0;
|
|
int16_t temperature = 0;
|
|
uint8_t version = 0;
|
|
uint8_t address:7;
|
|
uint8_t explicitSleep:1;
|
|
};
|
|
#pragma pack()
|
|
|
|
ChirpSensor_t chirp_sensor[CHIRP_MAX_SENSOR_COUNT];
|
|
|
|
|
|
|
|
void ChirpReset(uint8_t addr) {
|
|
ChirpWriteI2CRegister(addr, CHIRP_RESET);
|
|
}
|
|
|
|
|
|
|
|
void ChirpResetAll(void) {
|
|
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
|
if (chirp_sensor[i].version) {
|
|
ChirpReset(chirp_sensor[i].address);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ChirpClockSet() {
|
|
Wire.setClockStretchLimit(4000);
|
|
Wire.setClock(50000);
|
|
}
|
|
|
|
|
|
|
|
void ChirpSleep(uint8_t addr) {
|
|
ChirpWriteI2CRegister(addr, CHIRP_SLEEP);
|
|
}
|
|
# 185 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_48_chirp.ino"
|
|
void ChirpSelect(uint8_t sensor) {
|
|
if(sensor < chirp_found_sensors) {
|
|
chirp_current = sensor;
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: Sensor %u now active."), chirp_current);
|
|
}
|
|
if (sensor == 255) {
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: Sensor %u active at address 0x%x."), chirp_current, chirp_sensor[chirp_current].address);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
uint8_t ChirpReadVersion(uint8_t addr) {
|
|
return (I2cRead8(addr, CHIRP_GET_VERSION));
|
|
}
|
|
|
|
|
|
|
|
bool ChirpSet(uint8_t addr) {
|
|
if(addr < 128){
|
|
if (I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr)){
|
|
if(chirp_sensor[chirp_current].version>0x25 && chirp_sensor[chirp_current].version != 255){
|
|
delay(5);
|
|
I2cWrite8(chirp_sensor[chirp_current].address, CHIRP_SET_ADDRESS, addr);
|
|
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: Wrote adress %u "), addr);
|
|
ChirpReset(chirp_sensor[chirp_current].address);
|
|
chirp_sensor[chirp_current].address = addr;
|
|
chirp_timeout_count = 10;
|
|
chirp_next_job = 0;
|
|
if(chirp_sensor[chirp_current].version == 255){
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: wrote new address %u, please power off device"), addr);
|
|
chirp_sensor[chirp_current].version == 0;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: address %u incorrect and not used"), addr);
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
bool ChirpScan()
|
|
{
|
|
ChirpClockSet();
|
|
chirp_found_sensors = 0;
|
|
for (uint8_t address = 1; address <= 127; address++) {
|
|
chirp_sensor[chirp_found_sensors].version = 0;
|
|
chirp_sensor[chirp_found_sensors].version = ChirpReadVersion(address);
|
|
delay(2);
|
|
chirp_sensor[chirp_found_sensors].version = ChirpReadVersion(address);
|
|
if (chirp_sensor[chirp_found_sensors].version > 0) {
|
|
I2cSetActiveFound(address, "CHIRP");
|
|
if (chirp_found_sensors<CHIRP_MAX_SENSOR_COUNT) {
|
|
chirp_sensor[chirp_found_sensors].address = address;
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CHIRP: fw %x"), chirp_sensor[chirp_found_sensors].version);
|
|
}
|
|
chirp_found_sensors++;
|
|
}
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Found %u CHIRP sensor(s)."), chirp_found_sensors);
|
|
return (chirp_found_sensors > 0);
|
|
}
|
|
|
|
|
|
|
|
void ChirpDetect(void)
|
|
{
|
|
if (chirp_next_job > 0) { return; }
|
|
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: scan will start ..."));
|
|
if (ChirpScan()) {
|
|
uint8_t chirp_model = 0;
|
|
GetTextIndexed(chirp_name, sizeof(chirp_name), chirp_model, kChirpTypes);
|
|
}
|
|
}
|
|
|
|
|
|
void ChirpServiceAllSensors(uint8_t job){
|
|
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
|
if (chirp_sensor[i].version && !chirp_sensor[i].explicitSleep) {
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare for sensor at address 0x%x"), chirp_sensor[i].address);
|
|
switch(job){
|
|
case 0:
|
|
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_CAPACITANCE);
|
|
break;
|
|
case 1:
|
|
chirp_sensor[i].moisture = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address);
|
|
break;
|
|
case 2:
|
|
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_TEMPERATURE);
|
|
break;
|
|
case 3:
|
|
chirp_sensor[i].temperature = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address);
|
|
break;
|
|
case 4:
|
|
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_MEASURE_LIGHT);
|
|
break;
|
|
case 5:
|
|
ChirpWriteI2CRegister(chirp_sensor[i].address, CHIRP_GET_LIGHT);
|
|
break;
|
|
case 6:
|
|
chirp_sensor[i].light = ChirpFinishReadI2CRegister16bit(chirp_sensor[i].address);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ChirpEvery100MSecond(void)
|
|
{
|
|
|
|
if(chirp_timeout_count == 0) {
|
|
switch(chirp_next_job) {
|
|
case 0:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: reset all"));
|
|
ChirpResetAll();
|
|
chirp_timeout_count = 10;
|
|
chirp_next_job++;
|
|
break;
|
|
case 1:
|
|
|
|
|
|
chirp_next_job++;
|
|
break;
|
|
case 2:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare moisture read"));
|
|
ChirpServiceAllSensors(0);
|
|
chirp_timeout_count = 11;
|
|
chirp_next_job++;
|
|
break;
|
|
case 3:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish moisture read"));
|
|
ChirpServiceAllSensors(1);
|
|
chirp_next_job++;
|
|
break;
|
|
case 4:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare moisture read - 2nd"));
|
|
ChirpServiceAllSensors(0);
|
|
chirp_timeout_count = 11;
|
|
chirp_next_job++;
|
|
break;
|
|
case 5:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish moisture read - 2nd"));
|
|
ChirpServiceAllSensors(1);
|
|
chirp_next_job++;
|
|
break;
|
|
case 6:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare temperature read"));
|
|
ChirpServiceAllSensors(2);
|
|
chirp_timeout_count = 11;
|
|
chirp_next_job++;
|
|
break;
|
|
case 7:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish temperature read"));
|
|
ChirpServiceAllSensors(3);
|
|
chirp_next_job++;
|
|
break;
|
|
case 8:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare temperature read - 2nd"));
|
|
ChirpServiceAllSensors(2);
|
|
chirp_timeout_count = 11;
|
|
chirp_next_job++;
|
|
break;
|
|
case 9:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish temperature read - 2nd"));
|
|
ChirpServiceAllSensors(3);
|
|
chirp_next_job++;
|
|
break;
|
|
case 10:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: start light measure process"));
|
|
ChirpServiceAllSensors(4);
|
|
chirp_timeout_count = 90;
|
|
chirp_next_job++;
|
|
break;
|
|
case 11:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: prepare light read"));
|
|
ChirpServiceAllSensors(5);
|
|
chirp_timeout_count = 11;
|
|
chirp_next_job++;
|
|
break;
|
|
case 12:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: finish light read"));
|
|
ChirpServiceAllSensors(6);
|
|
chirp_next_job++;
|
|
break;
|
|
case 13:
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: paused, waiting for TELE"));
|
|
break;
|
|
case 14:
|
|
if (Settings.tele_period > 16){
|
|
chirp_timeout_count = (Settings.tele_period - 17) * 10;
|
|
DEBUG_SENSOR_LOG(PSTR("CHIRP: timeout 1/10 sec: %u, tele: %u"), chirp_timeout_count, Settings.tele_period);
|
|
}
|
|
else{
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("CHIRP: TELEPERIOD must be > 16 seconds !"));
|
|
|
|
}
|
|
chirp_next_job = 1;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
chirp_timeout_count--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
|
|
const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %%{e}";
|
|
const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}"
|
|
"{s} FW-version{m}%s {e}"; ;
|
|
const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}";
|
|
#endif
|
|
|
|
|
|
|
|
void ChirpShow(bool json)
|
|
{
|
|
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
|
|
if (chirp_sensor[i].version) {
|
|
|
|
char str_temperature[33];
|
|
double t_temperature = ((double) chirp_sensor[i].temperature )/10.0;
|
|
dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature);
|
|
char str_light[33];
|
|
dtostrfd(chirp_sensor[i].light, 0, str_light);
|
|
char str_version[7];
|
|
if(chirp_sensor[i].version == 0xff){
|
|
strncpy_P(str_version, PSTR("Chirp!"), sizeof(str_version));
|
|
}
|
|
else{
|
|
sprintf(str_version, "%x", chirp_sensor[i].version);
|
|
}
|
|
|
|
if (json) {
|
|
if(!chirp_sensor[i].explicitSleep) {
|
|
ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%d"), chirp_name, i, chirp_sensor[i].moisture);
|
|
if(chirp_sensor[i].temperature!=-1){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature);
|
|
}
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_DARKNESS "\":%s}"),str_light);
|
|
}
|
|
else {
|
|
ResponseAppend_P(PSTR(",\"%s%u\":{\"sleeping\"}"),chirp_name, i);
|
|
}
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
char str_moisture[33];
|
|
dtostrfd(chirp_sensor[i].moisture, 0, str_moisture);
|
|
DomoticzTempHumSensor(str_temperature, str_moisture);
|
|
DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_CHIRPVER, i, chirp_sensor[i].address, str_version);
|
|
if (chirp_sensor[i].explicitSleep){
|
|
WSContentSend_PD(HTTP_SNS_CHIRPSLEEP);
|
|
}
|
|
else {
|
|
WSContentSend_PD(HTTP_SNS_MOISTURE, "", chirp_sensor[i].moisture);
|
|
WSContentSend_PD(HTTP_SNS_DARKNESS, str_light);
|
|
if (chirp_sensor[i].temperature!=-1) {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, "", str_temperature, TempUnit());
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ChirpCmd(void) {
|
|
char command[CMDSZ];
|
|
bool serviced = true;
|
|
uint8_t disp_len = strlen(D_CMND_CHIRP);
|
|
|
|
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_CHIRP), disp_len)) {
|
|
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kCHIRP_Commands);
|
|
|
|
switch (command_code) {
|
|
case CMND_CHIRP_SELECT:
|
|
case CMND_CHIRP_SET:
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (command_code == CMND_CHIRP_SELECT) { ChirpSelect(XdrvMailbox.payload); }
|
|
if (command_code == CMND_CHIRP_SET) { ChirpSet((uint8_t)XdrvMailbox.payload); }
|
|
Response_P(S_JSON_CHIRP_COMMAND_NVALUE, command, XdrvMailbox.payload);
|
|
}
|
|
else {
|
|
if (command_code == CMND_CHIRP_SELECT) { ChirpSelect(255); }
|
|
Response_P(S_JSON_CHIRP_COMMAND, command, XdrvMailbox.payload);
|
|
}
|
|
break;
|
|
case CMND_CHIRP_SCAN:
|
|
case CMND_CHIRP_SLEEP:
|
|
case CMND_CHIRP_WAKE:
|
|
case CMND_CHIRP_RESET:
|
|
if (command_code == CMND_CHIRP_SCAN) { chirp_next_job = 0;
|
|
ChirpDetect(); }
|
|
if (command_code == CMND_CHIRP_SLEEP) { chirp_sensor[chirp_current].explicitSleep = true;
|
|
ChirpSleep(chirp_sensor[chirp_current].address); }
|
|
if (command_code == CMND_CHIRP_WAKE) { chirp_sensor[chirp_current].explicitSleep = false;
|
|
ChirpReadVersion(chirp_sensor[chirp_current].address); }
|
|
if (command_code == CMND_CHIRP_RESET) { ChirpReset(chirp_sensor[chirp_current].address); }
|
|
Response_P(S_JSON_CHIRP_COMMAND, command, XdrvMailbox.payload);
|
|
break;
|
|
default:
|
|
|
|
serviced = false;
|
|
break;
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns48(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_33)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_EVERY_100_MSECOND:
|
|
if(chirp_found_sensors > 0){
|
|
ChirpEvery100MSecond();
|
|
}
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = ChirpCmd();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
ChirpShow(1);
|
|
chirp_next_job = 14;
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
ChirpShow(0);
|
|
break;
|
|
#endif
|
|
case FUNC_INIT:
|
|
ChirpDetect();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_50_paj7620.ino"
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_50_paj7620.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_PAJ7620
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_50 50
|
|
#define XI2C_34 34
|
|
|
|
#define PAJ7620_ADDR 0x73
|
|
|
|
#define PAJ7620_BANK_SEL 0xEF
|
|
|
|
|
|
|
|
#define PAJ7620_GET_GESTURE 0x43
|
|
#define PAJ7620_PROXIMITY_AVG_Y 0x6c
|
|
|
|
#define PAJ7620_OBJECT_CENTER_X 0xad
|
|
#define PAJ7620_OBJECT_CENTER_Y 0xaf
|
|
|
|
#define PAJ7620_DOWN 1
|
|
#define PAJ7620_UP 2
|
|
#define PAJ7620_RIGHT 4
|
|
#define PAJ7620_LEFT 8
|
|
#define PAJ7620_NEAR 16
|
|
#define PAJ7620_FAR 32
|
|
#define PAJ7620_CW 64
|
|
#define PAJ7620_CCW 128
|
|
|
|
|
|
|
|
|
|
const uint8_t PAJ7620initRegisterArray[][2] PROGMEM = {
|
|
{0xEF,0x00},
|
|
{0x32,0x29}, {0x33,0x01}, {0x34,0x00}, {0x35,0x01}, {0x36,0x00}, {0x37,0x07}, {0x38,0x17}, {0x39,0x06},
|
|
{0x3A,0x12}, {0x3F,0x00}, {0x40,0x02}, {0x41,0xFF}, {0x42,0x01}, {0x46,0x2D}, {0x47,0x0F}, {0x48,0x3C},
|
|
{0x49,0x00}, {0x4A,0x1E}, {0x4B,0x00}, {0x4C,0x20}, {0x4D,0x00}, {0x4E,0x1A}, {0x4F,0x14}, {0x50,0x00},
|
|
{0x51,0x10}, {0x52,0x00}, {0x5C,0x02}, {0x5D,0x00}, {0x5E,0x10}, {0x5F,0x3F}, {0x60,0x27}, {0x61,0x28},
|
|
{0x62,0x00}, {0x63,0x03}, {0x64,0xF7}, {0x65,0x03}, {0x66,0xD9}, {0x67,0x03}, {0x68,0x01}, {0x69,0xC8},
|
|
{0x6A,0x40}, {0x6D,0x04}, {0x6E,0x00}, {0x6F,0x00}, {0x70,0x80}, {0x71,0x00}, {0x72,0x00}, {0x73,0x00},
|
|
{0x74,0xF0}, {0x75,0x00}, {0x80,0x42}, {0x81,0x44}, {0x82,0x04}, {0x83,0x20}, {0x84,0x20}, {0x85,0x00},
|
|
{0x86,0x10}, {0x87,0x00}, {0x88,0x05}, {0x89,0x18}, {0x8A,0x10}, {0x8B,0x01}, {0x8C,0x37}, {0x8D,0x00},
|
|
{0x8E,0xF0}, {0x8F,0x81}, {0x90,0x06}, {0x91,0x06}, {0x92,0x1E}, {0x93,0x0D}, {0x94,0x0A}, {0x95,0x0A},
|
|
{0x96,0x0C}, {0x97,0x05}, {0x98,0x0A}, {0x99,0x41}, {0x9A,0x14}, {0x9B,0x0A}, {0x9C,0x3F}, {0x9D,0x33},
|
|
{0x9E,0xAE}, {0x9F,0xF9}, {0xA0,0x48}, {0xA1,0x13}, {0xA2,0x10}, {0xA3,0x08}, {0xA4,0x30}, {0xA5,0x19},
|
|
{0xA6,0x10}, {0xA7,0x08}, {0xA8,0x24}, {0xA9,0x04}, {0xAA,0x1E}, {0xAB,0x1E}, {0xCC,0x19}, {0xCD,0x0B},
|
|
{0xCE,0x13}, {0xCF,0x64}, {0xD0,0x21}, {0xD1,0x0F}, {0xD2,0x88}, {0xE0,0x01}, {0xE1,0x04}, {0xE2,0x41},
|
|
{0xE3,0xD6}, {0xE4,0x00}, {0xE5,0x0C}, {0xE6,0x0A}, {0xE7,0x00}, {0xE8,0x00}, {0xE9,0x00}, {0xEE,0x07},
|
|
{0xEF,0x01},
|
|
{0x00,0x1E}, {0x01,0x1E}, {0x02,0x0F}, {0x03,0x10}, {0x04,0x02}, {0x05,0x00}, {0x06,0xB0}, {0x07,0x04},
|
|
{0x08,0x0D}, {0x09,0x0E}, {0x0A,0x9C}, {0x0B,0x04}, {0x0C,0x05}, {0x0D,0x0F}, {0x0E,0x02}, {0x0F,0x12},
|
|
{0x10,0x02}, {0x11,0x02}, {0x12,0x00}, {0x13,0x01}, {0x14,0x05}, {0x15,0x07}, {0x16,0x05}, {0x17,0x07},
|
|
{0x18,0x01}, {0x19,0x04}, {0x1A,0x05}, {0x1B,0x0C}, {0x1C,0x2A}, {0x1D,0x01}, {0x1E,0x00}, {0x21,0x00},
|
|
{0x22,0x00}, {0x23,0x00}, {0x25,0x01}, {0x26,0x00}, {0x27,0x39}, {0x28,0x7F}, {0x29,0x08}, {0x30,0x03},
|
|
{0x31,0x00}, {0x32,0x1A}, {0x33,0x1A}, {0x34,0x07}, {0x35,0x07}, {0x36,0x01}, {0x37,0xFF}, {0x38,0x36},
|
|
{0x39,0x07}, {0x3A,0x00}, {0x3E,0xFF}, {0x3F,0x00}, {0x40,0x77}, {0x41,0x40}, {0x42,0x00}, {0x43,0x30},
|
|
{0x44,0xA0}, {0x45,0x5C}, {0x46,0x00}, {0x47,0x00}, {0x48,0x58}, {0x4A,0x1E}, {0x4B,0x1E}, {0x4C,0x00},
|
|
{0x4D,0x00}, {0x4E,0xA0}, {0x4F,0x80}, {0x50,0x00}, {0x51,0x00}, {0x52,0x00}, {0x53,0x00}, {0x54,0x00},
|
|
{0x57,0x80}, {0x59,0x10}, {0x5A,0x08}, {0x5B,0x94}, {0x5C,0xE8}, {0x5D,0x08}, {0x5E,0x3D}, {0x5F,0x99},
|
|
{0x60,0x45}, {0x61,0x40}, {0x63,0x2D}, {0x64,0x02}, {0x65,0x96}, {0x66,0x00}, {0x67,0x97}, {0x68,0x01},
|
|
{0x69,0xCD}, {0x6A,0x01}, {0x6B,0xB0}, {0x6C,0x04}, {0x6D,0x2C}, {0x6E,0x01}, {0x6F,0x32}, {0x71,0x00},
|
|
{0x72,0x01}, {0x73,0x35}, {0x74,0x00}, {0x75,0x33}, {0x76,0x31}, {0x77,0x01}, {0x7C,0x84}, {0x7D,0x03},
|
|
{0x7E,0x01},
|
|
{0xEF,0x00}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const char kPaj7620Directions[] PROGMEM = "Down|Up|Right|Left|Near|Far|CW|CCW";
|
|
|
|
const uint8_t PAJ7620_PIN[]= {1,2,3,4};
|
|
|
|
|
|
|
|
|
|
|
|
char PAJ7620_name[] = "PAJ7620";
|
|
|
|
uint32_t PAJ7620_timeout_counter = 10;
|
|
uint32_t PAJ7620_next_job = 0;
|
|
uint32_t PAJ7620_mode = 1;
|
|
|
|
struct {
|
|
uint8_t current;
|
|
uint8_t last;
|
|
uint8_t same;
|
|
uint8_t unfinished;
|
|
} PAJ7620_gesture;
|
|
|
|
bool PAJ7620_finished_gesture = false;
|
|
char PAJ7620_currentGestureName[6];
|
|
|
|
struct{
|
|
uint8_t x;
|
|
uint8_t y;
|
|
uint8_t last_x;
|
|
uint8_t last_y;
|
|
uint8_t proximity;
|
|
uint8_t last_proximity;
|
|
uint8_t corner;
|
|
struct {
|
|
uint8_t step:3;
|
|
uint8_t countdown:3;
|
|
uint8_t valid:1;
|
|
} PIN;
|
|
} PAJ7620_state;
|
|
|
|
|
|
|
|
|
|
|
|
void PAJ7620SelectBank(uint8_t bank)
|
|
{
|
|
I2cWrite(PAJ7620_ADDR, PAJ7620_BANK_SEL, bank &1, 1);
|
|
}
|
|
|
|
|
|
|
|
void PAJ7620DecodeGesture(void)
|
|
{
|
|
uint32_t index = 0;
|
|
switch (PAJ7620_gesture.current) {
|
|
case PAJ7620_LEFT:
|
|
index++;
|
|
case PAJ7620_RIGHT:
|
|
index++;
|
|
case PAJ7620_UP:
|
|
index++;
|
|
case PAJ7620_DOWN:
|
|
if (PAJ7620_gesture.unfinished) {
|
|
PAJ7620_finished_gesture = true;
|
|
break;
|
|
}
|
|
PAJ7620_gesture.unfinished = PAJ7620_gesture.current;
|
|
PAJ7620_timeout_counter = 5;
|
|
break;
|
|
case PAJ7620_NEAR:
|
|
index = 4;
|
|
PAJ7620_finished_gesture = true;
|
|
PAJ7620_timeout_counter = 25;
|
|
break;
|
|
case PAJ7620_FAR:
|
|
index = 5;
|
|
PAJ7620_finished_gesture = true;
|
|
PAJ7620_timeout_counter = 25;
|
|
break;
|
|
case PAJ7620_CW:
|
|
index = 6;
|
|
PAJ7620_finished_gesture = true;
|
|
break;
|
|
case PAJ7620_CCW:
|
|
index = 7;
|
|
PAJ7620_finished_gesture = true;
|
|
break;
|
|
default:
|
|
index = 8;
|
|
if (PAJ7620_gesture.unfinished) {
|
|
PAJ7620_finished_gesture = true;
|
|
}
|
|
break;
|
|
}
|
|
if (index < 8) {
|
|
GetTextIndexed(PAJ7620_currentGestureName, sizeof(PAJ7620_currentGestureName), index, kPaj7620Directions);
|
|
}
|
|
|
|
if (PAJ7620_finished_gesture) {
|
|
if (PAJ7620_gesture.unfinished) {
|
|
if ((PAJ7620_gesture.current != PAJ7620_NEAR) && (PAJ7620_gesture.current != PAJ7620_FAR)) {
|
|
PAJ7620_gesture.current = PAJ7620_gesture.unfinished;
|
|
}
|
|
}
|
|
if (PAJ7620_gesture.current == PAJ7620_gesture.last) {
|
|
PAJ7620_gesture.same++;
|
|
} else {
|
|
PAJ7620_gesture.same = 1;
|
|
}
|
|
PAJ7620_gesture.last = PAJ7620_gesture.current;
|
|
PAJ7620_finished_gesture = false;
|
|
PAJ7620_gesture.unfinished = 0;
|
|
PAJ7620_timeout_counter += 3;
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void PAJ7620ReadGesture(void)
|
|
{
|
|
switch (PAJ7620_mode) {
|
|
case 1:
|
|
PAJ7620_gesture.current = I2cRead8(PAJ7620_ADDR,PAJ7620_GET_GESTURE);
|
|
if ((PAJ7620_gesture.current > 0) || PAJ7620_gesture.unfinished) {
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: gesture: %u"), PAJ7620_gesture.current);
|
|
PAJ7620DecodeGesture();
|
|
}
|
|
break;
|
|
case 2:
|
|
PAJ7620_state.proximity = I2cRead8(PAJ7620_ADDR, PAJ7620_PROXIMITY_AVG_Y);
|
|
if ((PAJ7620_state.proximity > 0) || (PAJ7620_state.last_proximity > 0)) {
|
|
if (PAJ7620_state.proximity != PAJ7620_state.last_proximity) {
|
|
PAJ7620_state.last_proximity = PAJ7620_state.proximity;
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: Proximity: %u"), PAJ7620_state.proximity);
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
PAJ7620_state.x = I2cRead8(PAJ7620_ADDR, PAJ7620_OBJECT_CENTER_X);
|
|
PAJ7620_state.y = I2cRead8(PAJ7620_ADDR, PAJ7620_OBJECT_CENTER_Y);
|
|
if ((PAJ7620_state.y > 0) && (PAJ7620_state.x > 0)) {
|
|
if ((PAJ7620_state.y != PAJ7620_state.last_y) || (PAJ7620_state.x != PAJ7620_state.last_x)) {
|
|
PAJ7620_state.last_y = PAJ7620_state.y;
|
|
PAJ7620_state.last_x = PAJ7620_state.x;
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: x: %u y: %u"), PAJ7620_state.x, PAJ7620_state.y);
|
|
|
|
PAJ7620_state.corner = 0;
|
|
|
|
|
|
|
|
switch (PAJ7620_state.y) {
|
|
case 0: case 1: case 2: case 3: case 4: case 5:
|
|
PAJ7620_state.corner = 3;
|
|
break;
|
|
case 9: case 10: case 11: case 12: case 13: case 14:
|
|
PAJ7620_state.corner = 1;
|
|
break;
|
|
}
|
|
if (PAJ7620_state.corner != 0) {
|
|
switch (PAJ7620_state.x) {
|
|
case 0: case 1: case 2: case 3: case 4: case 5:
|
|
break;
|
|
case 9: case 10: case 11: case 12: case 13: case 14:
|
|
PAJ7620_state.corner++;
|
|
break;
|
|
default:
|
|
PAJ7620_state.corner = 0;
|
|
break;
|
|
}
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: corner: %u"), PAJ7620_state.corner);
|
|
|
|
if (PAJ7620_state.PIN.countdown == 0) {
|
|
PAJ7620_state.PIN.step = 0;
|
|
PAJ7620_state.PIN.valid = 0;
|
|
}
|
|
if (!PAJ7620_state.PIN.step) {
|
|
if (PAJ7620_state.corner == PAJ7620_PIN[PAJ7620_state.PIN.step]) {
|
|
PAJ7620_state.PIN.step = 1;
|
|
PAJ7620_state.PIN.countdown = 7;
|
|
}
|
|
} else {
|
|
if (PAJ7620_state.corner == PAJ7620_PIN[PAJ7620_state.PIN.step]) {
|
|
PAJ7620_state.PIN.step += 1;
|
|
PAJ7620_state.PIN.countdown = 7;
|
|
} else {
|
|
PAJ7620_state.PIN.countdown -= 1;
|
|
}
|
|
}
|
|
if (PAJ7620_state.PIN.step == 4) {
|
|
PAJ7620_state.PIN.valid = 1;
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: PIN valid!!"));
|
|
PAJ7620_state.PIN.countdown = 0;
|
|
}
|
|
MqttPublishSensor();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void PAJ7620Detect(void)
|
|
{
|
|
if (I2cActive(PAJ7620_ADDR)) { return; }
|
|
|
|
PAJ7620SelectBank(0);
|
|
PAJ7620SelectBank(0);
|
|
uint16_t PAJ7620_id = I2cRead16LE(PAJ7620_ADDR,0);
|
|
uint8_t PAJ7620_ver = I2cRead8(PAJ7620_ADDR,2);
|
|
if (0x7620 == PAJ7620_id) {
|
|
I2cSetActiveFound(PAJ7620_ADDR, PAJ7620_name);
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PAJ: ID: 0x%x and VER: %u"), PAJ7620_id, PAJ7620_ver);
|
|
PAJ7620_next_job = 1;
|
|
}
|
|
else {
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: sensor not found, false ID 0x%x"), PAJ7620_id);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void PAJ7620Init(void)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: init sensor start %u"),millis());
|
|
union{
|
|
uint32_t raw;
|
|
uint8_t reg_val[4];
|
|
} buf;
|
|
|
|
for (uint32_t i = 0; i < (sizeof(PAJ7620initRegisterArray) / 2); i += 2)
|
|
{
|
|
buf.raw = pgm_read_dword(PAJ7620initRegisterArray + i);
|
|
DEBUG_SENSOR_LOG("PAJ: %x %x %x %x",buf.reg_val[0],buf.reg_val[1],buf.reg_val[2],buf.reg_val[3]);
|
|
I2cWrite(PAJ7620_ADDR, buf.reg_val[0], buf.reg_val[1], 1);
|
|
I2cWrite(PAJ7620_ADDR, buf.reg_val[2], buf.reg_val[3], 1);
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("PAJ: init sensor done %u"),millis());
|
|
PAJ7620_next_job = 2;
|
|
}
|
|
|
|
|
|
|
|
void PAJ7620Loop(void)
|
|
{
|
|
if (0 == PAJ7620_timeout_counter) {
|
|
switch (PAJ7620_next_job) {
|
|
case 1:
|
|
PAJ7620Init();
|
|
break;
|
|
case 2:
|
|
if (PAJ7620_mode != 0) {
|
|
PAJ7620ReadGesture();
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
PAJ7620_timeout_counter--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void PAJ7620Show(bool json)
|
|
{
|
|
if (json) {
|
|
if (PAJ7620_currentGestureName[0] != '\0' ) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%u}"), PAJ7620_name, PAJ7620_currentGestureName, PAJ7620_gesture.same);
|
|
PAJ7620_currentGestureName[0] = '\0';
|
|
return;
|
|
}
|
|
switch (PAJ7620_mode) {
|
|
case 2:
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"Proximity\":%u}"), PAJ7620_name, PAJ7620_state.proximity);
|
|
break;
|
|
case 3:
|
|
if (PAJ7620_state.corner > 0) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"Corner\":%u}"), PAJ7620_name, PAJ7620_state.corner);
|
|
}
|
|
break;
|
|
case 4:
|
|
if (PAJ7620_state.PIN.valid) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"PIN\":%u}"), PAJ7620_name, 1);
|
|
PAJ7620_state.PIN.valid = 0;
|
|
}
|
|
break;
|
|
case 5:
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"x\":%u,\"y\":%u}"), PAJ7620_name, PAJ7620_state.x, PAJ7620_state.y);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
# 411 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_50_paj7620.ino"
|
|
bool PAJ7620CommandSensor(void)
|
|
{
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 5)) {
|
|
PAJ7620_mode = XdrvMailbox.payload;
|
|
}
|
|
Response_P(S_JSON_SENSOR_INDEX_NVALUE, XSNS_50, PAJ7620_mode);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns50(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_34)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
PAJ7620Detect();
|
|
}
|
|
else if (PAJ7620_next_job) {
|
|
switch (function) {
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_50 == XdrvMailbox.index){
|
|
result = PAJ7620CommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
PAJ7620Loop();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
PAJ7620Show(1);
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_51_rdm6300.ino"
|
|
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_51_rdm6300.ino"
|
|
#ifdef USE_RDM6300
|
|
|
|
#define XSNS_51 51
|
|
|
|
#define RDM6300_BAUDRATE 9600
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
#define RDM_TIMEOUT 100
|
|
char rdm_uid_str[10];
|
|
|
|
|
|
#define RDM6300_BLOCK 2*10
|
|
|
|
uint8_t rdm_blcnt;
|
|
TasmotaSerial *RDM6300_Serial = nullptr;
|
|
|
|
void RDM6300_Init() {
|
|
if (pin[GPIO_RDM6300_RX] < 99) {
|
|
RDM6300_Serial = new TasmotaSerial(pin[GPIO_RDM6300_RX],-1,1);
|
|
if (RDM6300_Serial->begin(RDM6300_BAUDRATE)) {
|
|
if (RDM6300_Serial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
}
|
|
}
|
|
rdm_blcnt=0;
|
|
}
|
|
|
|
|
|
void RDM6300_ScanForTag() {
|
|
char rdm_buffer[14];
|
|
uint8_t rdm_index;
|
|
uint8_t rdm_array[6];
|
|
|
|
if (!RDM6300_Serial) return;
|
|
|
|
if (rdm_blcnt>0) {
|
|
rdm_blcnt--;
|
|
while (RDM6300_Serial->available()) RDM6300_Serial->read();
|
|
return;
|
|
}
|
|
|
|
if (RDM6300_Serial->available()) {
|
|
|
|
char c=RDM6300_Serial->read();
|
|
if (c!=2) return;
|
|
|
|
|
|
rdm_index=0;
|
|
uint32_t cmillis=millis();
|
|
while (1) {
|
|
if (RDM6300_Serial->available()) {
|
|
char c=RDM6300_Serial->read();
|
|
if (c==3) {
|
|
|
|
break;
|
|
}
|
|
rdm_buffer[rdm_index++]=c;
|
|
if (rdm_index>13) {
|
|
|
|
return;
|
|
}
|
|
}
|
|
if ((millis()-cmillis)>RDM_TIMEOUT) {
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
rdm_blcnt=RDM6300_BLOCK;
|
|
|
|
|
|
rm6300_hstring_to_array(rdm_array,sizeof(rdm_array),rdm_buffer);
|
|
uint8_t accu=0;
|
|
for (uint8_t count=0;count<5;count++) {
|
|
accu^=rdm_array[count];
|
|
}
|
|
if (accu!=rdm_array[5]) {
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
memcpy(rdm_uid_str,&rdm_buffer[2],8);
|
|
rdm_uid_str[9]=0;
|
|
|
|
ResponseTime_P(PSTR(",\"RDM6300\":{\"UID\":\"%s\"}}"), rdm_uid_str);
|
|
MqttPublishTeleSensor();
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
uint8_t rm6300_hexnibble(char chr) {
|
|
uint8_t rVal = 0;
|
|
if (isdigit(chr)) {
|
|
rVal = chr - '0';
|
|
} else {
|
|
if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A';
|
|
if (chr >= 'a' && chr <= 'f') rVal = chr + 10 - 'a';
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
|
|
void rm6300_hstring_to_array(uint8_t array[], uint8_t len, char buffer[])
|
|
{
|
|
char *cp=buffer;
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
uint8_t val = rm6300_hexnibble(*cp++) << 4;
|
|
array[i]= val | rm6300_hexnibble(*cp++);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_RDM6300[] PROGMEM =
|
|
"{s}RDM6300 " "UID" "{m}%s" "{e}";
|
|
|
|
void RDM6300_Show(void) {
|
|
if (!RDM6300_Serial) return;
|
|
if (!rdm_uid_str[0]) strcpy(rdm_uid_str,"????");
|
|
WSContentSend_PD(HTTP_RDM6300,rdm_uid_str);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns51(byte function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
RDM6300_Init();
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
RDM6300_ScanForTag();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
RDM6300_Show();
|
|
break;
|
|
#endif
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_52_ibeacon.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_52_ibeacon.ino"
|
|
#ifdef USE_IBEACON
|
|
|
|
|
|
|
|
#define XSNS_52 52
|
|
|
|
#include <TasmotaSerial.h>
|
|
|
|
#define HM17_BAUDRATE 9600
|
|
|
|
#define IBEACON_DEBUG
|
|
|
|
|
|
#define HM17_V110
|
|
|
|
|
|
|
|
#define IB_TIMEOUT_INTERVAL 30
|
|
|
|
#define IB_UPDATE_TIME_INTERVAL 10
|
|
|
|
TasmotaSerial *IBEACON_Serial = nullptr;
|
|
|
|
|
|
uint8_t hm17_found,hm17_cmd,hm17_flag;
|
|
|
|
#ifdef IBEACON_DEBUG
|
|
uint8_t hm17_debug=0;
|
|
#endif
|
|
|
|
|
|
|
|
#define HM17_BSIZ 128
|
|
char hm17_sbuffer[HM17_BSIZ];
|
|
uint8_t hm17_sindex,hm17_result,hm17_scanning,hm17_connecting;
|
|
uint32_t hm17_lastms;
|
|
char ib_mac[14];
|
|
|
|
|
|
#if 1
|
|
uint8_t ib_upd_interval,ib_tout_interval;
|
|
#define IB_UPDATE_TIME ib_upd_interval
|
|
#define IB_TIMEOUT_TIME ib_tout_interval
|
|
#else
|
|
#undef IB_UPDATE_TIME
|
|
#undef IB_TIMEOUT_TIME
|
|
#define IB_UPDATE_TIME Settings.ib_upd_interval
|
|
#define IB_TIMEOUT_TIME Settings.ib_tout_interval
|
|
#endif
|
|
|
|
enum {HM17_TEST,HM17_ROLE,HM17_IMME,HM17_DISI,HM17_IBEA,HM17_SCAN,HM17_DISC,HM17_RESET,HM17_RENEW,HM17_CON};
|
|
#define HM17_SUCESS 99
|
|
|
|
struct IBEACON {
|
|
char FACID[8];
|
|
char UID[32];
|
|
char MAJOR[4];
|
|
char MINOR[4];
|
|
char PWR[2];
|
|
char MAC[12];
|
|
char RSSI[4];
|
|
};
|
|
|
|
#define MAX_IBEACONS 16
|
|
|
|
struct IBEACON_UID {
|
|
char MAC[12];
|
|
char RSSI[4];
|
|
uint8_t FLAGS;
|
|
uint8_t TIME;
|
|
} ibeacons[MAX_IBEACONS];
|
|
|
|
|
|
void IBEACON_Init() {
|
|
|
|
hm17_found=0;
|
|
|
|
|
|
if ((pin[GPIO_IBEACON_RX] < 99) && (pin[GPIO_IBEACON_TX] < 99)) {
|
|
IBEACON_Serial = new TasmotaSerial(pin[GPIO_IBEACON_RX], pin[GPIO_IBEACON_TX],1);
|
|
if (IBEACON_Serial->begin(HM17_BAUDRATE)) {
|
|
if (IBEACON_Serial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
hm17_sendcmd(HM17_TEST);
|
|
hm17_lastms=millis();
|
|
|
|
IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL;
|
|
IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hm17_every_second(void) {
|
|
if (!IBEACON_Serial) return;
|
|
|
|
if (hm17_found) {
|
|
if (IB_UPDATE_TIME && (uptime%IB_UPDATE_TIME==0)) {
|
|
if (hm17_cmd!=99) {
|
|
if (hm17_flag&2) {
|
|
ib_sendbeep();
|
|
} else {
|
|
if (!hm17_connecting) {
|
|
hm17_sendcmd(HM17_DISI);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
|
|
if (ibeacons[cnt].FLAGS) {
|
|
ibeacons[cnt].TIME++;
|
|
if (ibeacons[cnt].TIME>IB_TIMEOUT_TIME) {
|
|
ibeacons[cnt].FLAGS=0;
|
|
ibeacon_mqtt(ibeacons[cnt].MAC,"0000");
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (uptime%20==0) {
|
|
hm17_sendcmd(HM17_TEST);
|
|
}
|
|
}
|
|
}
|
|
|
|
void hm17_sbclr(void) {
|
|
memset(hm17_sbuffer,0,HM17_BSIZ);
|
|
hm17_sindex=0;
|
|
IBEACON_Serial->flush();
|
|
}
|
|
|
|
void hm17_sendcmd(uint8_t cmd) {
|
|
hm17_sbclr();
|
|
hm17_cmd=cmd;
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("hm17cmd %d"),cmd);
|
|
#endif
|
|
switch (cmd) {
|
|
case HM17_TEST:
|
|
IBEACON_Serial->write("AT");
|
|
break;
|
|
case HM17_ROLE:
|
|
IBEACON_Serial->write("AT+ROLE1");
|
|
break;
|
|
case HM17_IMME:
|
|
IBEACON_Serial->write("AT+IMME1");
|
|
break;
|
|
case HM17_DISI:
|
|
IBEACON_Serial->write("AT+DISI?");
|
|
hm17_scanning=1;
|
|
break;
|
|
case HM17_IBEA:
|
|
IBEACON_Serial->write("AT+IBEA1");
|
|
break;
|
|
case HM17_RESET:
|
|
IBEACON_Serial->write("AT+RESET");
|
|
break;
|
|
case HM17_RENEW:
|
|
IBEACON_Serial->write("AT+RENEW");
|
|
break;
|
|
case HM17_SCAN:
|
|
IBEACON_Serial->write("AT+SCAN5");
|
|
break;
|
|
case HM17_DISC:
|
|
IBEACON_Serial->write("AT+DISC?");
|
|
hm17_scanning=1;
|
|
break;
|
|
case HM17_CON:
|
|
IBEACON_Serial->write((const uint8_t*)"AT+CON",6);
|
|
IBEACON_Serial->write((const uint8_t*)ib_mac,12);
|
|
hm17_connecting=1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t ibeacon_add(struct IBEACON *ib) {
|
|
|
|
if (!strncmp(ib->MAC,"FFFF",4) || strncmp(ib->FACID,"00000000",8)) {
|
|
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
|
|
if (ibeacons[cnt].FLAGS) {
|
|
if (!strncmp(ibeacons[cnt].MAC,ib->MAC,12)) {
|
|
|
|
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
|
|
ibeacons[cnt].TIME=0;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
|
|
if (!ibeacons[cnt].FLAGS) {
|
|
memcpy(ibeacons[cnt].MAC,ib->MAC,12);
|
|
memcpy(ibeacons[cnt].RSSI,ib->RSSI,4);
|
|
ibeacons[cnt].FLAGS=1;
|
|
ibeacons[cnt].TIME=0;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void hm17_decode(void) {
|
|
struct IBEACON ib;
|
|
switch (hm17_cmd) {
|
|
case HM17_TEST:
|
|
if (!strncmp(hm17_sbuffer,"OK",2)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("AT OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
hm17_found=1;
|
|
}
|
|
break;
|
|
case HM17_ROLE:
|
|
if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("ROLE OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
}
|
|
break;
|
|
case HM17_IMME:
|
|
if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("IMME OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
}
|
|
break;
|
|
case HM17_IBEA:
|
|
if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("IBEA OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
}
|
|
break;
|
|
case HM17_SCAN:
|
|
if (!strncmp(hm17_sbuffer,"OK+Set:5",8)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("SCAN OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
}
|
|
break;
|
|
case HM17_RESET:
|
|
if (!strncmp(hm17_sbuffer,"OK+RESET",8)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("RESET OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
}
|
|
break;
|
|
case HM17_RENEW:
|
|
if (!strncmp(hm17_sbuffer,"OK+RENEW",8)) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("RENEW OK"));
|
|
#endif
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
}
|
|
break;
|
|
case HM17_CON:
|
|
if (!strncmp(hm17_sbuffer,"OK+CONNA",8)) {
|
|
hm17_sbclr();
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNA OK"));
|
|
#endif
|
|
hm17_connecting=2;
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+CONNE",8)) {
|
|
hm17_sbclr();
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNE ERROR"));
|
|
#endif
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+CONNF",8)) {
|
|
hm17_sbclr();
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONNF ERROR"));
|
|
#endif
|
|
break;
|
|
}
|
|
if (hm17_connecting==2 && !strncmp(hm17_sbuffer,"OK+CONN",7)) {
|
|
hm17_sbclr();
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("CONN OK"));
|
|
#endif
|
|
hm17_connecting=3;
|
|
hm17_sendcmd(HM17_TEST);
|
|
hm17_connecting=0;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case HM17_DISI:
|
|
case HM17_DISC:
|
|
if (!strncmp(hm17_sbuffer,"OK+DISCS",8)) {
|
|
hm17_sbclr();
|
|
hm17_result=1;
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISCS OK"));
|
|
#endif
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+DISIS",8)) {
|
|
hm17_sbclr();
|
|
hm17_result=1;
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISIS OK"));
|
|
#endif
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+DISCE",8)) {
|
|
hm17_sbclr();
|
|
hm17_result=HM17_SUCESS;
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR("DISCE OK"));
|
|
#endif
|
|
hm17_scanning=0;
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+NAME:",8)) {
|
|
if (hm17_sbuffer[hm17_sindex-1]=='\n') {
|
|
hm17_result=HM17_SUCESS;
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("NAME OK"));
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
|
|
}
|
|
#endif
|
|
hm17_sbclr();
|
|
}
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+DIS0:",8)) {
|
|
if (hm17_cmd==HM17_DISI) {
|
|
#ifdef HM17_V110
|
|
goto hm17_v110;
|
|
#endif
|
|
} else {
|
|
if (hm17_sindex==20) {
|
|
hm17_result=HM17_SUCESS;
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("DIS0 OK"));
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
|
|
}
|
|
#endif
|
|
hm17_sbclr();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (!strncmp(hm17_sbuffer,"OK+DISC:",8)) {
|
|
hm17_v110:
|
|
if (hm17_cmd==HM17_DISI) {
|
|
if (hm17_sindex==78) {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("DISC: OK"));
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
|
|
}
|
|
#endif
|
|
memcpy(ib.FACID,&hm17_sbuffer[8],8);
|
|
memcpy(ib.UID,&hm17_sbuffer[8+8+1],32);
|
|
memcpy(ib.MAJOR,&hm17_sbuffer[8+8+1+32+1],4);
|
|
memcpy(ib.MINOR,&hm17_sbuffer[8+8+1+32+1+4],4);
|
|
memcpy(ib.PWR,&hm17_sbuffer[8+8+1+32+1+4+4],2);
|
|
memcpy(ib.MAC,&hm17_sbuffer[8+8+1+32+1+4+4+2+1],12);
|
|
memcpy(ib.RSSI,&hm17_sbuffer[8+8+1+32+1+4+4+2+1+12+1],4);
|
|
|
|
if (ibeacon_add(&ib)) {
|
|
ibeacon_mqtt(ib.MAC,ib.RSSI);
|
|
}
|
|
hm17_sbclr();
|
|
hm17_result=1;
|
|
}
|
|
} else {
|
|
#ifdef IBEACON_DEBUG
|
|
if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IBEACON_loop() {
|
|
|
|
if (!IBEACON_Serial) return;
|
|
|
|
uint32_t difftime=millis()-hm17_lastms;
|
|
|
|
while (IBEACON_Serial->available()) {
|
|
hm17_lastms=millis();
|
|
|
|
if (hm17_sindex<HM17_BSIZ) {
|
|
hm17_sbuffer[hm17_sindex]=IBEACON_Serial->read();
|
|
hm17_sindex++;
|
|
hm17_decode();
|
|
} else {
|
|
hm17_sindex=0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hm17_cmd==99) {
|
|
if (hm17_sindex>=HM17_BSIZ-2 || (hm17_sindex && (difftime>100))) {
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("%s"),hm17_sbuffer);
|
|
hm17_sbclr();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_IBEACON[] PROGMEM =
|
|
"{s}IBEACON-UID : %s" " - RSSI : %s" "{m}{e}";
|
|
|
|
void IBEACON_Show(void) {
|
|
char mac[14];
|
|
char rssi[6];
|
|
|
|
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) {
|
|
if (ibeacons[cnt].FLAGS) {
|
|
memcpy(mac,ibeacons[cnt].MAC,12);
|
|
mac[12]=0;
|
|
memcpy(rssi,ibeacons[cnt].RSSI,4);
|
|
rssi[4]=0;
|
|
WSContentSend_PD(HTTP_IBEACON,mac,rssi);
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
# 485 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_52_ibeacon.ino"
|
|
bool xsns52_cmd(void) {
|
|
bool serviced = true;
|
|
const char S_JSON_IBEACON[] = "{\"" D_CMND_SENSOR "%d\":%s:%d}";
|
|
const char S_JSON_IBEACON1[] = "{\"" D_CMND_SENSOR "%d\":%s:%s}";
|
|
uint16_t len=XdrvMailbox.data_len;
|
|
if (len > 0) {
|
|
char *cp=XdrvMailbox.data;
|
|
if (*cp>='0' && *cp<='8') {
|
|
hm17_sendcmd(*cp&7);
|
|
Response_P(S_JSON_IBEACON, XSNS_52,"hm17cmd",*cp&7);
|
|
} else if (*cp=='s') {
|
|
cp++;
|
|
len--;
|
|
while (*cp==' ') {
|
|
len--;
|
|
cp++;
|
|
}
|
|
IBEACON_Serial->write((uint8_t*)cp,len);
|
|
hm17_cmd=99;
|
|
Response_P(S_JSON_IBEACON1, XSNS_52,"hm17cmd",cp);
|
|
} else if (*cp=='u') {
|
|
cp++;
|
|
if (*cp) IB_UPDATE_TIME=atoi(cp);
|
|
Response_P(S_JSON_IBEACON, XSNS_52,"uintv",IB_UPDATE_TIME);
|
|
} else if (*cp=='t') {
|
|
cp++;
|
|
if (*cp) IB_TIMEOUT_TIME=atoi(cp);
|
|
Response_P(S_JSON_IBEACON, XSNS_52,"lintv",IB_TIMEOUT_TIME);
|
|
} else if (*cp=='c') {
|
|
for (uint32_t cnt=0;cnt<MAX_IBEACONS;cnt++) ibeacons[cnt].FLAGS=0;
|
|
Response_P(S_JSON_IBEACON1, XSNS_52,"clr list","");
|
|
}
|
|
#ifdef IBEACON_DEBUG
|
|
else if (*cp=='d') {
|
|
cp++;
|
|
if (*cp) hm17_debug=atoi(cp);
|
|
Response_P(S_JSON_IBEACON, XSNS_52,"debug",hm17_debug);
|
|
}
|
|
#endif
|
|
} else {
|
|
serviced=false;
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
#define D_CMND_IBEACON "IBEACON"
|
|
|
|
bool ibeacon_cmd(void) {
|
|
ib_mac[0]=0;
|
|
int16_t rssi=0;
|
|
const char S_JSON_IBEACON[] = "{\"" D_CMND_IBEACON "_%s_RSSI\":%d}";
|
|
uint8_t cmd_len = strlen(D_CMND_IBEACON);
|
|
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_IBEACON), cmd_len)) {
|
|
|
|
rssi = XdrvMailbox.payload;
|
|
if (rssi==99) {
|
|
memcpy(ib_mac,XdrvMailbox.topic+cmd_len+1,12);
|
|
ib_mac[12]=0;
|
|
if (hm17_scanning) {
|
|
|
|
hm17_flag|=2;
|
|
} else {
|
|
ib_sendbeep();
|
|
}
|
|
}
|
|
Response_P(S_JSON_IBEACON,ib_mac,rssi);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ib_sendbeep(void) {
|
|
hm17_flag=0;
|
|
hm17_sendcmd(HM17_CON);
|
|
}
|
|
|
|
void ibeacon_mqtt(const char *mac,const char *rssi) {
|
|
char s_mac[14];
|
|
char s_rssi[6];
|
|
memcpy(s_mac,mac,12);
|
|
s_mac[12]=0;
|
|
memcpy(s_rssi,rssi,4);
|
|
s_rssi[4]=0;
|
|
int16_t n_rssi=atoi(s_rssi);
|
|
ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "_%s\":{\"RSSI\":%d}}"),s_mac,n_rssi);
|
|
MqttPublishTeleSensor();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns52(byte function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
IBEACON_Init();
|
|
break;
|
|
case FUNC_LOOP:
|
|
IBEACON_loop();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
hm17_every_second();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_52 == XdrvMailbox.index) {
|
|
result = xsns52_cmd();
|
|
}
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result=ibeacon_cmd();
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
if (hm17_found) IBEACON_Show();
|
|
break;
|
|
#endif
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
# 24 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
#ifdef USE_SML_M
|
|
|
|
#define XSNS_53 53
|
|
|
|
|
|
#define SML_BAUDRATE 9600
|
|
# 45 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
#include <TasmotaSerial.h>
|
|
|
|
|
|
#define SPECIAL_SS
|
|
|
|
|
|
|
|
|
|
|
|
#if MY_LANGUAGE==de-DE
|
|
|
|
#define D_TPWRIN "Verbrauch"
|
|
#define D_TPWROUT "Einspeisung"
|
|
#define D_TPWRCURR "Aktueller Verbrauch"
|
|
#define D_TPWRCURR1 "Verbrauch P1"
|
|
#define D_TPWRCURR2 "Verbrauch P2"
|
|
#define D_TPWRCURR3 "Verbrauch P3"
|
|
#define D_Strom_L1 "Strom L1"
|
|
#define D_Strom_L2 "Strom L2"
|
|
#define D_Strom_L3 "Strom L3"
|
|
#define D_Spannung_L1 "Spannung L1"
|
|
#define D_Spannung_L2 "Spannung L2"
|
|
#define D_Spannung_L3 "Spannung L3"
|
|
#define D_METERNR "Zähler Nr"
|
|
#define D_METERSID "Service ID"
|
|
#define D_GasIN "Zählerstand"
|
|
#define D_H2oIN "Zählerstand"
|
|
#define D_StL1L2L3 "Ströme L1+L2+L3"
|
|
#define D_SpL1L2L3 "Spannung L1+L2+L3/3"
|
|
|
|
#else
|
|
|
|
#undef D_TPWRIN
|
|
#undef D_TPWROUT
|
|
#undef D_TPWRCURR
|
|
#undef D_TPWRCURR1
|
|
#undef D_TPWRCURR2
|
|
#undef D_TPWRCURR3
|
|
#undef D_Strom_L1
|
|
#undef D_Strom_L2
|
|
#undef D_Strom_L3
|
|
#undef D_Spannung_L1
|
|
#undef D_Spannung_L2
|
|
#undef D_Spannung_L3
|
|
#undef D_METERNR
|
|
#undef D_METERSID
|
|
#undef D_GasIN
|
|
#undef D_H2oIN
|
|
#undef D_StL1L2L3
|
|
#undef D_SpL1L2L3
|
|
|
|
#define D_TPWRIN "Total-In"
|
|
#define D_TPWROUT "Total-Out"
|
|
#define D_TPWRCURR "Current-In/Out"
|
|
#define D_TPWRCURR1 "Current-In p1"
|
|
#define D_TPWRCURR2 "Current-In p2"
|
|
#define D_TPWRCURR3 "Current-In p3"
|
|
#define D_Strom_L1 "Current L1"
|
|
#define D_Strom_L2 "Current L2"
|
|
#define D_Strom_L3 "Current L3"
|
|
#define D_Spannung_L1 "Voltage L1"
|
|
#define D_Spannung_L2 "Voltage L2"
|
|
#define D_Spannung_L3 "Voltage L3"
|
|
#define D_METERNR "Meter_number"
|
|
#define D_METERSID "Service ID"
|
|
#define D_GasIN "Counter"
|
|
#define D_H2oIN "Counter"
|
|
#define D_StL1L2L3 "Current L1+L2+L3"
|
|
#define D_SpL1L2L3 "Voltage L1+L2+L3/3"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define DJ_TPWRIN "Total_in"
|
|
#define DJ_TPWROUT "Total_out"
|
|
#define DJ_TPWRCURR "Power_curr"
|
|
#define DJ_TPWRCURR1 "Power_p1"
|
|
#define DJ_TPWRCURR2 "Power_p2"
|
|
#define DJ_TPWRCURR3 "Power_p3"
|
|
#define DJ_CURR1 "Curr_p1"
|
|
#define DJ_CURR2 "Curr_p2"
|
|
#define DJ_CURR3 "Curr_p3"
|
|
#define DJ_VOLT1 "Volt_p1"
|
|
#define DJ_VOLT2 "Volt_p2"
|
|
#define DJ_VOLT3 "Volt_p3"
|
|
#define DJ_METERNR "Meter_number"
|
|
#define DJ_METERSID "Meter_id"
|
|
#define DJ_CSUM "Curr_summ"
|
|
#define DJ_VAVG "Volt_avg"
|
|
#define DJ_COUNTER "Count"
|
|
|
|
struct METER_DESC {
|
|
uint8_t srcpin;
|
|
uint8_t type;
|
|
uint16_t flag;
|
|
int32_t params;
|
|
char prefix[8];
|
|
int8_t trxpin;
|
|
uint8_t tsecs;
|
|
char *txmem;
|
|
uint8_t index;
|
|
uint8_t max_index;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define EHZ161_0 1
|
|
#define EHZ161_1 2
|
|
#define EHZ363 3
|
|
#define EHZH 4
|
|
#define EDL300 5
|
|
#define Q3B 6
|
|
#define COMBO3 7
|
|
#define COMBO2 8
|
|
#define COMBO3a 9
|
|
#define Q3B_V1 10
|
|
#define EHZ363_2 11
|
|
#define COMBO3b 12
|
|
#define WGS_COMBO 13
|
|
#define EBZD_G 14
|
|
|
|
|
|
#define METER EHZ161_1
|
|
|
|
|
|
#if METER==EHZ161_0
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}};
|
|
const uint8_t meter[]=
|
|
"1,1-0:1.8.0*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,1-0:2.8.0*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"1,1-0:21.7.0*255(@1," D_TPWRCURR1 ",W," DJ_TPWRCURR1 ",0|"
|
|
"1,1-0:41.7.0*255(@1," D_TPWRCURR2 ",W," DJ_TPWRCURR2 ",0|"
|
|
"1,1-0:61.7.0*255(@1," D_TPWRCURR3 ",W," DJ_TPWRCURR3 ",0|"
|
|
"1,=m 3+4+5 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if METER==EHZ161_1
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}};
|
|
const uint8_t meter[]=
|
|
"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0";
|
|
#endif
|
|
|
|
|
|
|
|
#if METER==EHZ363
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}};
|
|
|
|
const uint8_t meter[]=
|
|
|
|
"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
|
|
"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
|
|
"1,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
|
|
"1,77070100000009ff@#," D_METERNR ",," DJ_METERNR ",0";
|
|
#endif
|
|
|
|
|
|
|
|
#if METER==EHZH
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}};
|
|
|
|
|
|
const uint8_t meter[]=
|
|
|
|
"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
|
|
"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
|
|
"1,770701000f0700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0";
|
|
#endif
|
|
|
|
|
|
|
|
#if METER==EDL300
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}};
|
|
|
|
|
|
const uint8_t meter[]=
|
|
|
|
"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
|
|
"1,77070100020801ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
|
|
"1,770701000f0700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0";
|
|
#endif
|
|
|
|
#if METER==EBZD_G
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'s',0,SML_BAUDRATE,"strom",-1,1,0}};
|
|
const uint8_t meter[]=
|
|
|
|
"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
|
|
"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
|
|
"1,77070100010801ff@1000," D_TPWRCURR1 ",KWh," DJ_TPWRCURR1 ",4|"
|
|
|
|
"1,77070100010802ff@1000," D_TPWRCURR2 ",KWh," DJ_TPWRCURR2 ",4|"
|
|
|
|
"1,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
|
|
"1,77070100600100ff@#," D_METERNR ",," DJ_METERNR ",0";
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if METER==Q3B
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}};
|
|
const uint8_t meter[]=
|
|
|
|
"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
|
|
"1,77070100020801ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
|
|
"1,77070100010700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0";
|
|
#endif
|
|
|
|
#if METER==COMBO3
|
|
|
|
#undef METERS_USED
|
|
#define METERS_USED 3
|
|
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0},
|
|
[1]={14,'s',0,SML_BAUDRATE,"SML",-1,1,0},
|
|
[2]={4,'o',0,SML_BAUDRATE,"OBIS2",-1,1,0}};
|
|
|
|
|
|
const uint8_t meter[]=
|
|
"1,1-0:1.8.0*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,1-0:2.8.0*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"1,1-0:21.7.0*255(@1," D_TPWRCURR1 ",W," DJ_TPWRCURR1 ",0|"
|
|
"1,1-0:41.7.0*255(@1," D_TPWRCURR2 ",W," DJ_TPWRCURR2 ",0|"
|
|
"1,1-0:61.7.0*255(@1," D_TPWRCURR3 ",W," DJ_TPWRCURR3 ",0|"
|
|
"1,=m 3+4+5 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|"
|
|
"2,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"2,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"2,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"3,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"3,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"3,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"3,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0";
|
|
|
|
#endif
|
|
|
|
#if METER==COMBO2
|
|
|
|
#undef METERS_USED
|
|
#define METERS_USED 2
|
|
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS1",-1,1,0},
|
|
[1]={14,'o',0,SML_BAUDRATE,"OBIS2",-1,1,0}};
|
|
|
|
|
|
const uint8_t meter[]=
|
|
"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|"
|
|
|
|
"2,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"2,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"2,=d 6 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"2,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0";
|
|
|
|
#endif
|
|
|
|
#if METER==COMBO3a
|
|
#undef METERS_USED
|
|
#define METERS_USED 3
|
|
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS1",-1,1,0},
|
|
[1]={14,'o',0,SML_BAUDRATE,"OBIS2",-1,1,0},
|
|
[2]={1,'o',0,SML_BAUDRATE,"OBIS3",-1,1,0}};
|
|
|
|
|
|
const uint8_t meter[]=
|
|
"1,=h --- Zähler Nr 1 ---|"
|
|
"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|"
|
|
"2,=h --- Zähler Nr 2 ---|"
|
|
"2,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"2,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"2,=d 6 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"2,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|"
|
|
"3,=h --- Zähler Nr 3 ---|"
|
|
"3,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"3,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"3,=d 10 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"3,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0";
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if METER==Q3B_V1
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0}};
|
|
const uint8_t meter[]=
|
|
"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,=d 1 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0";
|
|
#endif
|
|
|
|
|
|
|
|
#if METER==EHZ363_2
|
|
#undef METERS_USED
|
|
#define METERS_USED 1
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}};
|
|
|
|
const uint8_t meter[]=
|
|
|
|
"1,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
|
|
"1,77070100020800ff@1000," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
|
|
"1,77070100010801ff@1000," D_TPWRCURR1 ",KWh," DJ_TPWRCURR1 ",4|"
|
|
|
|
"1,77070100010802ff@1000," D_TPWRCURR2 ",KWh," DJ_TPWRCURR2 ",4|"
|
|
|
|
"1,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
|
|
"1,77070100000009ff@#," D_METERNR ",," DJ_METERNR ",0";
|
|
#endif
|
|
|
|
|
|
#if METER==COMBO3b
|
|
#undef METERS_USED
|
|
#define METERS_USED 3
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={3,'o',0,SML_BAUDRATE,"OBIS",-1,1,0},
|
|
[1]={14,'c',0,50,"Gas"},
|
|
[2]={1,'c',0,10,"Wasser"}};
|
|
|
|
|
|
const uint8_t meter[]=
|
|
"1,1-0:1.8.1*255(@1," D_TPWRIN ",KWh," DJ_TPWRIN ",4|"
|
|
"1,1-0:2.8.1*255(@1," D_TPWROUT ",KWh," DJ_TPWROUT ",4|"
|
|
"1,=d 2 10 @1," D_TPWRCURR ",W," DJ_TPWRCURR ",0|"
|
|
"1,1-0:0.0.0*255(@#)," D_METERNR ",," DJ_METERNR ",0|"
|
|
|
|
|
|
"2,1-0:1.8.0*255(@100," D_GasIN ",cbm," DJ_COUNTER ",2|"
|
|
|
|
"3,1-0:1.8.0*255(@100," D_H2oIN ",cbm," DJ_COUNTER ",2";
|
|
#endif
|
|
|
|
|
|
#if METER==WGS_COMBO
|
|
#undef METERS_USED
|
|
#define METERS_USED 3
|
|
|
|
struct METER_DESC const meter_desc[METERS_USED]={
|
|
[0]={1,'c',0,10,"H20",-1,1,0},
|
|
[1]={4,'c',0,50,"GAS",-1,1,0},
|
|
[2]={3,'s',0,SML_BAUDRATE,"SML",-1,1,0}};
|
|
|
|
const uint8_t meter[]=
|
|
|
|
|
|
"1,1-0:1.8.0*255(@10000," D_H2oIN ",cbm," DJ_COUNTER ",4|"
|
|
|
|
|
|
"2,=h==================|"
|
|
"2,1-0:1.8.0*255(@100," D_GasIN ",cbm," DJ_COUNTER ",3|"
|
|
|
|
"3,=h==================|"
|
|
|
|
"3,77070100010800ff@1000," D_TPWRIN ",KWh," DJ_TPWRIN ",3|"
|
|
"3,=h==================|"
|
|
|
|
"3,77070100100700ff@1," D_TPWRCURR ",W," DJ_TPWRCURR ",2|"
|
|
"3,=h -------------------------------|"
|
|
"3,=m 10+11+12 @100," D_StL1L2L3 ",A," DJ_CSUM ",2|"
|
|
|
|
"3,=m 13+14+15/#3 @100," D_SpL1L2L3 ",V," DJ_VAVG ",2|"
|
|
"3,=h==================|"
|
|
|
|
"3,77070100240700ff@1," D_TPWRCURR1 ",W," DJ_TPWRCURR1 ",2|"
|
|
|
|
"3,77070100380700ff@1," D_TPWRCURR2 ",W," DJ_TPWRCURR2 ",2|"
|
|
|
|
"3,770701004c0700ff@1," D_TPWRCURR3 ",W," DJ_TPWRCURR3 ",2|"
|
|
"3,=h -------------------------------|"
|
|
|
|
"3,770701001f0700ff@100," D_Strom_L1 ",A," DJ_CURR1 ",2|"
|
|
|
|
"3,77070100330700ff@100," D_Strom_L2 ",A," DJ_CURR2 ",2|"
|
|
|
|
"3,77070100470700ff@100," D_Strom_L3 ",A," DJ_CURR3 ",2|"
|
|
"3,=h -------------------------------|"
|
|
|
|
"3,77070100200700ff@100," D_Spannung_L1 ",V," DJ_VOLT1 ",2|"
|
|
|
|
"3,77070100340700ff@100," D_Spannung_L2 ",V," DJ_VOLT2 ",2|"
|
|
|
|
"3,77070100480700ff@100," D_Spannung_L3 ",V," DJ_VOLT3 ",2|"
|
|
"3,=h==================|"
|
|
|
|
"3,77070100000009ff@#," D_METERSID ",," DJ_METERSID ",0|"
|
|
"3,=h--------------------------------";
|
|
#endif
|
|
# 499 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
#define USE_SML_MEDIAN_FILTER
|
|
|
|
|
|
#ifndef SML_MAX_VARS
|
|
#define SML_MAX_VARS 20
|
|
#endif
|
|
|
|
|
|
#define MAX_METERS 5
|
|
double meter_vars[SML_MAX_VARS];
|
|
|
|
#define MAX_DVARS MAX_METERS*2
|
|
double dvalues[MAX_DVARS];
|
|
uint32_t dtimes[MAX_DVARS];
|
|
uint8_t meters_used;
|
|
|
|
struct METER_DESC const *meter_desc_p;
|
|
const uint8_t *meter_p;
|
|
uint8_t meter_spos[MAX_METERS];
|
|
|
|
|
|
TasmotaSerial *meter_ss[MAX_METERS];
|
|
|
|
|
|
#define SML_BSIZ 48
|
|
uint8_t smltbuf[MAX_METERS][SML_BSIZ];
|
|
|
|
|
|
#define METER_ID_SIZE 24
|
|
char meter_id[MAX_METERS][METER_ID_SIZE];
|
|
|
|
#define EBUS_SYNC 0xaa
|
|
#define EBUS_ESC 0xa9
|
|
|
|
uint8_t sml_send_blocks;
|
|
uint8_t sml_100ms_cnt;
|
|
uint8_t sml_desc_cnt;
|
|
|
|
#ifdef USE_SML_MEDIAN_FILTER
|
|
|
|
#define MEDIAN_SIZE 5
|
|
struct SML_MEDIAN_FILTER {
|
|
double buffer[MEDIAN_SIZE];
|
|
int8_t index;
|
|
} sml_mf[SML_MAX_VARS];
|
|
|
|
#ifndef FLT_MAX
|
|
#define FLT_MAX 99999999
|
|
#endif
|
|
|
|
double sml_median_array(double *array,uint8_t len) {
|
|
uint8_t ind[len];
|
|
uint8_t mind=0,index=0,flg;
|
|
double min=FLT_MAX;
|
|
|
|
for (uint8_t hcnt=0; hcnt<len/2+1; hcnt++) {
|
|
for (uint8_t mcnt=0; mcnt<len; mcnt++) {
|
|
flg=0;
|
|
for (uint8_t icnt=0; icnt<index; icnt++) {
|
|
if (ind[icnt]==mcnt) {
|
|
flg=1;
|
|
}
|
|
}
|
|
if (!flg) {
|
|
if (array[mcnt]<min) {
|
|
min=array[mcnt];
|
|
mind=mcnt;
|
|
}
|
|
}
|
|
}
|
|
ind[index]=mind;
|
|
index++;
|
|
min=FLT_MAX;
|
|
}
|
|
return array[ind[len/2]];
|
|
}
|
|
|
|
|
|
|
|
double sml_median(struct SML_MEDIAN_FILTER* mf, double in) {
|
|
|
|
|
|
mf->buffer[mf->index]=in;
|
|
mf->index++;
|
|
if (mf->index>=MEDIAN_SIZE) mf->index=0;
|
|
|
|
return sml_median_array(mf->buffer,MEDIAN_SIZE);
|
|
# 603 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
}
|
|
#endif
|
|
|
|
#ifdef ANALOG_OPTO_SENSOR
|
|
|
|
uint8_t ads1115_up;
|
|
|
|
|
|
#define SAMPLE_BIT (0x8000)
|
|
|
|
#define ADS1115_COMP_QUEUE_SHIFT 0
|
|
#define ADS1115_COMP_LATCH_SHIFT 2
|
|
#define ADS1115_COMP_POLARITY_SHIFT 3
|
|
#define ADS1115_COMP_MODE_SHIFT 4
|
|
#define ADS1115_DATA_RATE_SHIFT 5
|
|
#define ADS1115_MODE_SHIFT 8
|
|
#define ADS1115_PGA_SHIFT 9
|
|
#define ADS1115_MUX_SHIFT 12
|
|
|
|
enum ads1115_comp_queue {
|
|
ADS1115_COMP_QUEUE_AFTER_ONE = 0,
|
|
ADS1115_COMP_QUEUE_AFTER_TWO = 0x1 << ADS1115_COMP_QUEUE_SHIFT,
|
|
ADS1115_COMP_QUEUE_AFTER_FOUR = 0x2 << ADS1115_COMP_QUEUE_SHIFT,
|
|
ADS1115_COMP_QUEUE_DISABLE = 0x3 << ADS1115_COMP_QUEUE_SHIFT,
|
|
ADS1115_COMP_QUEUE_MASK = 0x3 << ADS1115_COMP_QUEUE_SHIFT,
|
|
};
|
|
|
|
enum ads1115_comp_latch {
|
|
ADS1115_COMP_LATCH_NO = 0,
|
|
ADS1115_COMP_LATCH_YES = 1 << ADS1115_COMP_LATCH_SHIFT,
|
|
ADS1115_COMP_LATCH_MASK = 1 << ADS1115_COMP_LATCH_SHIFT,
|
|
};
|
|
|
|
enum ads1115_comp_polarity {
|
|
ADS1115_COMP_POLARITY_ACTIVE_LOW = 0,
|
|
ADS1115_COMP_POLARITY_ACTIVE_HIGH = 1 << ADS1115_COMP_POLARITY_SHIFT,
|
|
ADS1115_COMP_POLARITY_MASK = 1 << ADS1115_COMP_POLARITY_SHIFT,
|
|
};
|
|
|
|
enum ads1115_comp_mode {
|
|
ADS1115_COMP_MODE_WINDOW = 0,
|
|
ADS1115_COMP_MODE_HYSTERESIS = 1 << ADS1115_COMP_MODE_SHIFT,
|
|
ADS1115_COMP_MODE_MASK = 1 << ADS1115_COMP_MODE_SHIFT,
|
|
};
|
|
|
|
enum ads1115_data_rate {
|
|
ADS1115_DATA_RATE_8_SPS = 0,
|
|
ADS1115_DATA_RATE_16_SPS = 0x1 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_32_SPS = 0x2 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_64_SPS = 0x3 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_128_SPS = 0x4 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_250_SPS = 0x5 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_475_SPS = 0x6 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_860_SPS = 0x7 << ADS1115_DATA_RATE_SHIFT,
|
|
ADS1115_DATA_RATE_MASK = 0x7 << ADS1115_DATA_RATE_SHIFT,
|
|
};
|
|
|
|
enum ads1115_mode {
|
|
ADS1115_MODE_CONTINUOUS = 0,
|
|
ADS1115_MODE_SINGLE_SHOT = 1 << ADS1115_MODE_SHIFT,
|
|
ADS1115_MODE_MASK = 1 << ADS1115_MODE_SHIFT,
|
|
};
|
|
|
|
enum ads1115_pga {
|
|
ADS1115_PGA_TWO_THIRDS = 0,
|
|
ADS1115_PGA_ONE = 0x1 << ADS1115_PGA_SHIFT,
|
|
ADS1115_PGA_TWO = 0x2 << ADS1115_PGA_SHIFT,
|
|
ADS1115_PGA_FOUR = 0x3 << ADS1115_PGA_SHIFT,
|
|
ADS1115_PGA_EIGHT = 0x4 << ADS1115_PGA_SHIFT,
|
|
ADS1115_PGA_SIXTEEN = 0x5 << ADS1115_PGA_SHIFT,
|
|
ADS1115_PGA_MASK = 0x7 << ADS1115_PGA_SHIFT,
|
|
};
|
|
|
|
|
|
enum ads1115_mux {
|
|
ADS1115_MUX_DIFF_AIN0_AIN1 = 0,
|
|
ADS1115_MUX_DIFF_AIN0_AIN3 = 0x1 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_DIFF_AIN1_AIN3 = 0x2 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_DIFF_AIN2_AIN3 = 0x3 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_GND_AIN0 = 0x4 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_GND_AIN1 = 0x5 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_GND_AIN2 = 0x6 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_GND_AIN3 = 0x7 << ADS1115_MUX_SHIFT,
|
|
ADS1115_MUX_MASK = 0x7 << ADS1115_MUX_SHIFT,
|
|
};
|
|
|
|
class ADS1115 {
|
|
public:
|
|
ADS1115(uint8_t address = 0x48);
|
|
|
|
void begin();
|
|
uint8_t trigger_sample();
|
|
uint8_t reset();
|
|
bool is_sample_in_progress();
|
|
int16_t read_sample();
|
|
float sample_to_float(int16_t val);
|
|
float read_sample_float();
|
|
|
|
void set_comp_queue(enum ads1115_comp_queue val) { set_config(val, ADS1115_COMP_QUEUE_MASK); }
|
|
void set_comp_latching(enum ads1115_comp_latch val) { set_config(val, ADS1115_COMP_LATCH_MASK); }
|
|
void set_comp_polarity(enum ads1115_comp_polarity val) { set_config(val, ADS1115_COMP_POLARITY_MASK); }
|
|
void set_comp_mode(enum ads1115_comp_mode val) { set_config(val, ADS1115_COMP_MODE_MASK); }
|
|
void set_data_rate(enum ads1115_data_rate val) { set_config(val, ADS1115_DATA_RATE_MASK); }
|
|
void set_mode(enum ads1115_mode val) { set_config(val, ADS1115_MODE_MASK); }
|
|
void set_pga(enum ads1115_pga val) { set_config(val, ADS1115_PGA_MASK); m_voltage_range = val >> ADS1115_PGA_SHIFT; }
|
|
void set_mux(enum ads1115_mux val) { set_config(val, ADS1115_MUX_MASK); }
|
|
|
|
private:
|
|
void set_config(uint16_t val, uint16_t mask) {
|
|
m_config = (m_config & ~mask) | val;
|
|
}
|
|
|
|
uint8_t write_register(uint8_t reg, uint16_t val);
|
|
uint16_t read_register(uint8_t reg);
|
|
|
|
uint8_t m_address;
|
|
uint16_t m_config;
|
|
int m_voltage_range;
|
|
};
|
|
|
|
|
|
enum ads1115_register {
|
|
ADS1115_REGISTER_CONVERSION = 0,
|
|
ADS1115_REGISTER_CONFIG = 1,
|
|
ADS1115_REGISTER_LOW_THRESH = 2,
|
|
ADS1115_REGISTER_HIGH_THRESH = 3,
|
|
};
|
|
|
|
#define FACTOR 32768.0
|
|
static float ranges[] = { 6.144 / FACTOR, 4.096 / FACTOR, 2.048 / FACTOR, 1.024 / FACTOR, 0.512 / FACTOR, 0.256 / FACTOR};
|
|
|
|
ADS1115::ADS1115(uint8_t address)
|
|
{
|
|
m_address = address;
|
|
m_config = ADS1115_COMP_QUEUE_AFTER_ONE |
|
|
ADS1115_COMP_LATCH_NO |
|
|
ADS1115_COMP_POLARITY_ACTIVE_LOW |
|
|
ADS1115_COMP_MODE_WINDOW |
|
|
ADS1115_DATA_RATE_128_SPS |
|
|
ADS1115_MODE_SINGLE_SHOT |
|
|
ADS1115_MUX_GND_AIN0;
|
|
set_pga(ADS1115_PGA_ONE);
|
|
}
|
|
|
|
uint8_t ADS1115::write_register(uint8_t reg, uint16_t val)
|
|
{
|
|
Wire.beginTransmission(m_address);
|
|
Wire.write(reg);
|
|
Wire.write(val>>8);
|
|
Wire.write(val & 0xFF);
|
|
return Wire.endTransmission();
|
|
}
|
|
|
|
uint16_t ADS1115::read_register(uint8_t reg)
|
|
{
|
|
Wire.beginTransmission(m_address);
|
|
Wire.write(reg);
|
|
Wire.endTransmission();
|
|
|
|
uint8_t result = Wire.requestFrom((int)m_address, 2, 1);
|
|
if (result != 2) {
|
|
return 0;
|
|
}
|
|
|
|
uint16_t val;
|
|
|
|
val = Wire.read() << 8;
|
|
val |= Wire.read();
|
|
return val;
|
|
}
|
|
|
|
void ADS1115::begin()
|
|
{
|
|
Wire.begin();
|
|
}
|
|
|
|
uint8_t ADS1115::trigger_sample()
|
|
{
|
|
return write_register(ADS1115_REGISTER_CONFIG, m_config | SAMPLE_BIT);
|
|
}
|
|
|
|
uint8_t ADS1115::reset()
|
|
{
|
|
Wire.beginTransmission(0);
|
|
Wire.write(0x6);
|
|
return Wire.endTransmission();
|
|
}
|
|
|
|
bool ADS1115::is_sample_in_progress()
|
|
{
|
|
uint16_t val = read_register(ADS1115_REGISTER_CONFIG);
|
|
return (val & SAMPLE_BIT) == 0;
|
|
}
|
|
|
|
int16_t ADS1115::read_sample()
|
|
{
|
|
return read_register(ADS1115_REGISTER_CONVERSION);
|
|
}
|
|
|
|
float ADS1115::sample_to_float(int16_t val)
|
|
{
|
|
return val * ranges[m_voltage_range];
|
|
}
|
|
|
|
float ADS1115::read_sample_float()
|
|
{
|
|
return sample_to_float(read_sample());
|
|
}
|
|
|
|
ADS1115 adc;
|
|
|
|
void ADS1115_init(void) {
|
|
|
|
ads1115_up=0;
|
|
if (!i2c_flg) return;
|
|
|
|
adc.begin();
|
|
adc.set_data_rate(ADS1115_DATA_RATE_128_SPS);
|
|
adc.set_mode(ADS1115_MODE_CONTINUOUS);
|
|
adc.set_mux(ADS1115_MUX_DIFF_AIN0_AIN3);
|
|
adc.set_pga(ADS1115_PGA_TWO);
|
|
|
|
int16_t val = adc.read_sample();
|
|
ads1115_up=1;
|
|
}
|
|
|
|
#endif
|
|
|
|
char sml_start;
|
|
uint8_t dump2log=0;
|
|
|
|
#define SML_SAVAILABLE Serial_available()
|
|
#define SML_SREAD Serial_read()
|
|
#define SML_SPEAK Serial_peek()
|
|
|
|
bool Serial_available() {
|
|
uint8_t num=dump2log&7;
|
|
if (num<1 || num>meters_used) num=1;
|
|
return meter_ss[num-1]->available();
|
|
}
|
|
|
|
uint8_t Serial_read() {
|
|
uint8_t num=dump2log&7;
|
|
if (num<1 || num>meters_used) num=1;
|
|
return meter_ss[num-1]->read();
|
|
}
|
|
|
|
uint8_t Serial_peek() {
|
|
uint8_t num=dump2log&7;
|
|
if (num<1 || num>meters_used) num=1;
|
|
return meter_ss[num-1]->peek();
|
|
}
|
|
|
|
uint8_t sml_logindex;
|
|
|
|
void Dump2log(void) {
|
|
|
|
int16_t index=0,hcnt=0;
|
|
uint32_t d_lastms;
|
|
uint8_t dchars[16];
|
|
|
|
|
|
|
|
if (dump2log&8) {
|
|
|
|
while (SML_SAVAILABLE) {
|
|
log_data[index]=':';
|
|
index++;
|
|
log_data[index]=' ';
|
|
index++;
|
|
d_lastms=millis();
|
|
while ((millis()-d_lastms)<40) {
|
|
if (SML_SAVAILABLE) {
|
|
uint8_t c=SML_SREAD;
|
|
sprintf(&log_data[index],"%02x ",c);
|
|
dchars[hcnt]=c;
|
|
index+=3;
|
|
hcnt++;
|
|
if (hcnt>15) {
|
|
|
|
log_data[index]='=';
|
|
index++;
|
|
log_data[index]='>';
|
|
index++;
|
|
log_data[index]=' ';
|
|
index++;
|
|
for (uint8_t ccnt=0; ccnt<16; ccnt++) {
|
|
if (isprint(dchars[ccnt])) {
|
|
log_data[index]=dchars[ccnt];
|
|
} else {
|
|
log_data[index]=' ';
|
|
}
|
|
index++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (index>0) {
|
|
log_data[index]=0;
|
|
AddLog(LOG_LEVEL_INFO);
|
|
index=0;
|
|
hcnt=0;
|
|
}
|
|
}
|
|
} else {
|
|
if (meter_desc_p[(dump2log&7)-1].type=='o') {
|
|
|
|
while (SML_SAVAILABLE) {
|
|
char c=SML_SREAD&0x7f;
|
|
if (c=='\n' || c=='\r') {
|
|
log_data[sml_logindex]=0;
|
|
AddLog(LOG_LEVEL_INFO);
|
|
sml_logindex=2;
|
|
log_data[0]=':';
|
|
log_data[1]=' ';
|
|
break;
|
|
}
|
|
log_data[sml_logindex]=c;
|
|
if (sml_logindex<sizeof(log_data)-2) {
|
|
sml_logindex++;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
index=0;
|
|
log_data[index]=':';
|
|
index++;
|
|
log_data[index]=' ';
|
|
index++;
|
|
d_lastms=millis();
|
|
while ((millis()-d_lastms)<40) {
|
|
if (SML_SAVAILABLE) {
|
|
unsigned char c;
|
|
if (meter_desc_p[(dump2log&7)-1].type=='e') {
|
|
|
|
c=SML_SREAD;
|
|
sprintf(&log_data[index],"%02x ",c);
|
|
index+=3;
|
|
if (c==EBUS_SYNC) break;
|
|
} else {
|
|
|
|
if (sml_start==0x77) {
|
|
sml_start=0;
|
|
} else {
|
|
c=SML_SPEAK;
|
|
if (c==0x77) {
|
|
sml_start=c;
|
|
break;
|
|
}
|
|
}
|
|
c=SML_SREAD;
|
|
sprintf(&log_data[index],"%02x ",c);
|
|
index+=3;
|
|
}
|
|
}
|
|
}
|
|
if (index>2) {
|
|
log_data[index]=0;
|
|
AddLog(LOG_LEVEL_INFO);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
uint8_t *skip_sml(uint8_t *cp,int16_t *res) {
|
|
uint8_t len,len1,type;
|
|
len=*cp&0xf;
|
|
type=*cp&0x70;
|
|
if (type==0x70) {
|
|
|
|
|
|
cp++;
|
|
while (len--) {
|
|
len1=*cp&0x0f;
|
|
cp+=len1;
|
|
}
|
|
*res=0;
|
|
} else {
|
|
|
|
*res=(signed char)*(cp+1);
|
|
cp+=len;
|
|
}
|
|
return cp;
|
|
}
|
|
|
|
|
|
|
|
double sml_getvalue(unsigned char *cp,uint8_t index) {
|
|
uint8_t len,unit,type;
|
|
int16_t scaler,result;
|
|
int64_t value;
|
|
double dval;
|
|
|
|
|
|
|
|
cp=skip_sml(cp,&result);
|
|
|
|
cp=skip_sml(cp,&result);
|
|
|
|
cp=skip_sml(cp,&result);
|
|
|
|
cp=skip_sml(cp,&result);
|
|
scaler=result;
|
|
|
|
type=*cp&0x70;
|
|
len=*cp&0x0f;
|
|
cp++;
|
|
if (type==0x50 || type==0x60) {
|
|
|
|
uint64_t uvalue=0;
|
|
uint8_t nlen=len;
|
|
while (--nlen) {
|
|
uvalue<<=8;
|
|
uvalue|=*cp++;
|
|
}
|
|
if (type==0x50) {
|
|
|
|
switch (len-1) {
|
|
case 1:
|
|
|
|
value=(signed char)uvalue;
|
|
break;
|
|
case 2:
|
|
|
|
#ifdef DWS74_BUG
|
|
if (scaler==-2) {
|
|
value=(uint32_t)uvalue;
|
|
} else {
|
|
value=(int16_t)uvalue;
|
|
}
|
|
#else
|
|
value=(int16_t)uvalue;
|
|
#endif
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
|
|
value=(int32_t)uvalue;
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
|
|
value=(int64_t)uvalue;
|
|
break;
|
|
}
|
|
} else {
|
|
|
|
value=uvalue;
|
|
}
|
|
|
|
} else {
|
|
if (!(type&0xf0)) {
|
|
|
|
|
|
|
|
if (len==9) {
|
|
|
|
cp++;
|
|
uint32_t s1,s2;
|
|
s1=*cp<<16|*(cp+1)<<8|*(cp+2);
|
|
cp+=4;
|
|
s2=*cp<<16|*(cp+1)<<8|*(cp+2);
|
|
sprintf(&meter_id[index][0],"%u-%u",s1,s2);
|
|
} else {
|
|
|
|
char *str=&meter_id[index][0];
|
|
for (type=0; type<len-1; type++) {
|
|
sprintf(str,"%02x",*cp++);
|
|
str+=2;
|
|
}
|
|
}
|
|
value=0;
|
|
} else {
|
|
value=999999;
|
|
scaler=0;
|
|
}
|
|
}
|
|
dval=value;
|
|
if (scaler==-1) {
|
|
dval/=10;
|
|
} else if (scaler==-2) {
|
|
dval/=100;
|
|
} else if (scaler==-3) {
|
|
dval/=1000;
|
|
} else if (scaler==-4) {
|
|
dval/=10000;
|
|
} else if (scaler==1) {
|
|
dval*=10;
|
|
} else if (scaler==2) {
|
|
dval*=100;
|
|
} else if (scaler==3) {
|
|
dval*=1000;
|
|
}
|
|
return dval;
|
|
}
|
|
|
|
uint8_t hexnibble(char chr) {
|
|
uint8_t rVal = 0;
|
|
if (isdigit(chr)) {
|
|
rVal = chr - '0';
|
|
} else {
|
|
chr=toupper(chr);
|
|
if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A';
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
uint8_t sb_counter;
|
|
|
|
|
|
double CharToDouble(const char *str)
|
|
{
|
|
|
|
char strbuf[24];
|
|
|
|
strlcpy(strbuf, str, sizeof(strbuf));
|
|
char *pt = strbuf;
|
|
while ((*pt != '\0') && isblank(*pt)) { pt++; }
|
|
|
|
signed char sign = 1;
|
|
if (*pt == '-') { sign = -1; }
|
|
if (*pt == '-' || *pt=='+') { pt++; }
|
|
|
|
double left = 0;
|
|
if (*pt != '.') {
|
|
left = atoi(pt);
|
|
while (isdigit(*pt)) { pt++; }
|
|
}
|
|
|
|
double right = 0;
|
|
if (*pt == '.') {
|
|
pt++;
|
|
right = atoi(pt);
|
|
while (isdigit(*pt)) {
|
|
pt++;
|
|
right /= 10.0;
|
|
}
|
|
}
|
|
|
|
double result = left + right;
|
|
if (sign < 0) {
|
|
return -result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
void ebus_esc(uint8_t *ebus_buffer, unsigned char len) {
|
|
short count,count1;
|
|
for (count=0; count<len; count++) {
|
|
if (ebus_buffer[count]==EBUS_ESC) {
|
|
|
|
ebus_buffer[count]+=ebus_buffer[count+1];
|
|
|
|
count++;
|
|
for (count1=count; count1<len; count1++) {
|
|
ebus_buffer[count1]=ebus_buffer[count1+1];
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
uint8_t ebus_crc8(uint8_t data, uint8_t crc_init) {
|
|
uint8_t crc;
|
|
uint8_t polynom;
|
|
int i;
|
|
|
|
crc = crc_init;
|
|
for (i = 0; i < 8; i++) {
|
|
if (crc & 0x80) {
|
|
polynom = (uint8_t) 0x9B;
|
|
}
|
|
else {
|
|
polynom = (uint8_t) 0;
|
|
}
|
|
crc = (uint8_t)((crc & ~0x80) << 1);
|
|
if (data & 0x80) {
|
|
crc = (uint8_t)(crc | 1) ;
|
|
}
|
|
crc = (uint8_t)(crc ^ polynom);
|
|
data = (uint8_t)(data << 1);
|
|
}
|
|
return (crc);
|
|
}
|
|
|
|
|
|
uint8_t ebus_CalculateCRC( uint8_t *Data, uint16_t DataLen ) {
|
|
uint16_t i;
|
|
uint8_t Crc = 0;
|
|
for(i = 0 ; i < DataLen ; ++i, ++Data ) {
|
|
Crc = ebus_crc8( *Data, Crc );
|
|
}
|
|
return Crc;
|
|
}
|
|
|
|
void sml_empty_receiver(uint32_t meters) {
|
|
while (meter_ss[meters]->available()) {
|
|
meter_ss[meters]->read();
|
|
}
|
|
}
|
|
|
|
|
|
void sml_shift_in(uint32_t meters,uint32_t shard) {
|
|
uint32_t count;
|
|
if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m' && meter_desc_p[meters].type!='M' && meter_desc_p[meters].type!='p') {
|
|
|
|
for (count=0; count<SML_BSIZ-1; count++) {
|
|
smltbuf[meters][count]=smltbuf[meters][count+1];
|
|
}
|
|
}
|
|
uint8_t iob=(uint8_t)meter_ss[meters]->read();
|
|
|
|
if (meter_desc_p[meters].type=='o') {
|
|
smltbuf[meters][SML_BSIZ-1]=iob&0x7f;
|
|
} else if (meter_desc_p[meters].type=='s') {
|
|
smltbuf[meters][SML_BSIZ-1]=iob;
|
|
} else if (meter_desc_p[meters].type=='r') {
|
|
smltbuf[meters][SML_BSIZ-1]=iob;
|
|
} else if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M') {
|
|
smltbuf[meters][meter_spos[meters]] = iob;
|
|
meter_spos[meters]++;
|
|
if (meter_spos[meters]>=9) {
|
|
SML_Decode(meters);
|
|
sml_empty_receiver(meters);
|
|
meter_spos[meters]=0;
|
|
}
|
|
} else if (meter_desc_p[meters].type=='p') {
|
|
smltbuf[meters][meter_spos[meters]] = iob;
|
|
meter_spos[meters]++;
|
|
if (meter_spos[meters]>=7) {
|
|
SML_Decode(meters);
|
|
sml_empty_receiver(meters);
|
|
meter_spos[meters]=0;
|
|
}
|
|
} else {
|
|
if (iob==EBUS_SYNC) {
|
|
|
|
|
|
if (meter_spos[meters]>4+5) {
|
|
|
|
uint8_t tlen=smltbuf[meters][4]+5;
|
|
|
|
if (smltbuf[meters][tlen]=ebus_CalculateCRC(smltbuf[meters],tlen)) {
|
|
ebus_esc(smltbuf[meters],tlen);
|
|
SML_Decode(meters);
|
|
} else {
|
|
|
|
|
|
}
|
|
}
|
|
meter_spos[meters]=0;
|
|
return;
|
|
}
|
|
smltbuf[meters][meter_spos[meters]] = iob;
|
|
meter_spos[meters]++;
|
|
if (meter_spos[meters]>=SML_BSIZ) {
|
|
meter_spos[meters]=0;
|
|
}
|
|
}
|
|
sb_counter++;
|
|
if (meter_desc_p[meters].type!='e' && meter_desc_p[meters].type!='m' && meter_desc_p[meters].type!='M' && meter_desc_p[meters].type!='p') SML_Decode(meters);
|
|
}
|
|
|
|
|
|
|
|
void SML_Poll(void) {
|
|
uint32_t meters;
|
|
|
|
for (meters=0; meters<meters_used; meters++) {
|
|
if (meter_desc_p[meters].type!='c') {
|
|
|
|
while (meter_ss[meters]->available()) {
|
|
sml_shift_in(meters,0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SML_Decode(uint8_t index) {
|
|
const char *mp=(const char*)meter_p;
|
|
int8_t mindex;
|
|
uint8_t *cp;
|
|
uint8_t dindex=0,vindex=0;
|
|
delay(0);
|
|
while (mp != NULL) {
|
|
|
|
|
|
|
|
mindex=((*mp)&7)-1;
|
|
|
|
if (mindex<0 || mindex>=meters_used) mindex=0;
|
|
mp+=2;
|
|
if (*mp=='=' && *(mp+1)=='h') {
|
|
mp = strchr(mp, '|');
|
|
if (mp) mp++;
|
|
continue;
|
|
}
|
|
|
|
if (index!=mindex) goto nextsect;
|
|
|
|
|
|
cp=&smltbuf[mindex][0];
|
|
|
|
|
|
if (*mp=='=') {
|
|
|
|
mp++;
|
|
|
|
if (*mp=='m' && !sb_counter) {
|
|
|
|
|
|
mp++;
|
|
while (*mp==' ') mp++;
|
|
|
|
double dvar;
|
|
uint8_t opr;
|
|
uint32_t ind;
|
|
ind=atoi(mp);
|
|
while (*mp>='0' && *mp<='9') mp++;
|
|
if (ind<1 || ind>SML_MAX_VARS) ind=1;
|
|
dvar=meter_vars[ind-1];
|
|
for (uint8_t p=0;p<5;p++) {
|
|
if (*mp=='@') {
|
|
|
|
meter_vars[vindex]=dvar;
|
|
mp++;
|
|
SML_Immediate_MQTT((const char*)mp,vindex,mindex);
|
|
break;
|
|
}
|
|
opr=*mp;
|
|
mp++;
|
|
uint8_t iflg=0;
|
|
if (*mp=='#') {
|
|
iflg=1;
|
|
mp++;
|
|
}
|
|
ind=atoi(mp);
|
|
while (*mp>='0' && *mp<='9') mp++;
|
|
if (ind<1 || ind>SML_MAX_VARS) ind=1;
|
|
switch (opr) {
|
|
case '+':
|
|
if (iflg) dvar+=ind;
|
|
else dvar+=meter_vars[ind-1];
|
|
break;
|
|
case '-':
|
|
if (iflg) dvar-=ind;
|
|
else dvar-=meter_vars[ind-1];
|
|
break;
|
|
case '*':
|
|
if (iflg) dvar*=ind;
|
|
else dvar*=meter_vars[ind-1];
|
|
break;
|
|
case '/':
|
|
if (iflg) dvar/=ind;
|
|
else dvar/=meter_vars[ind-1];
|
|
break;
|
|
}
|
|
while (*mp==' ') mp++;
|
|
if (*mp=='@') {
|
|
|
|
meter_vars[vindex]=dvar;
|
|
mp++;
|
|
SML_Immediate_MQTT((const char*)mp,vindex,mindex);
|
|
break;
|
|
}
|
|
}
|
|
} else if (*mp=='d') {
|
|
|
|
if (dindex<MAX_DVARS) {
|
|
|
|
mp++;
|
|
while (*mp==' ') mp++;
|
|
uint8_t ind=atoi(mp);
|
|
while (*mp>='0' && *mp<='9') mp++;
|
|
if (ind<1 || ind>SML_MAX_VARS) ind=1;
|
|
uint32_t delay=atoi(mp)*1000;
|
|
uint32_t dtime=millis()-dtimes[dindex];
|
|
if (dtime>delay) {
|
|
|
|
dtimes[dindex]=millis();
|
|
double vdiff = meter_vars[ind-1]-dvalues[dindex];
|
|
dvalues[dindex]=meter_vars[ind-1];
|
|
meter_vars[vindex]=(double)360000.0*vdiff/((double)dtime/10000.0);
|
|
|
|
mp=strchr(mp,'@');
|
|
if (mp) {
|
|
mp++;
|
|
SML_Immediate_MQTT((const char*)mp,vindex,mindex);
|
|
}
|
|
}
|
|
dindex++;
|
|
}
|
|
} else if (*mp=='h') {
|
|
|
|
mp = strchr(mp, '|');
|
|
if (mp) mp++;
|
|
continue;
|
|
}
|
|
} else {
|
|
|
|
uint8_t found=1;
|
|
uint32_t ebus_dval=99;
|
|
float mbus_dval=99;
|
|
while (*mp!='@') {
|
|
if (meter_desc_p[mindex].type=='o' || meter_desc_p[mindex].type=='c') {
|
|
if (*mp++!=*cp++) {
|
|
found=0;
|
|
}
|
|
} else {
|
|
if (meter_desc_p[mindex].type=='s') {
|
|
|
|
uint8_t val = hexnibble(*mp++) << 4;
|
|
val |= hexnibble(*mp++);
|
|
if (val!=*cp++) {
|
|
found=0;
|
|
}
|
|
} else {
|
|
|
|
|
|
if (*mp=='x' && *(mp+1)=='x') {
|
|
|
|
mp+=2;
|
|
cp++;
|
|
} else if (!strncmp(mp,"UUuuUUuu",8)) {
|
|
uint32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0);
|
|
ebus_dval=val;
|
|
mbus_dval=val;
|
|
mp+=8;
|
|
cp+=4;
|
|
} else if (*mp=='U' && *(mp+1)=='U' && *(mp+2)=='u' && *(mp+3)=='u'){
|
|
uint16_t val = cp[1]|(cp[0]<<8);
|
|
mbus_dval=val;
|
|
ebus_dval=val;
|
|
mp+=4;
|
|
cp+=2;
|
|
} else if (!strncmp(mp,"SSssSSss",8)) {
|
|
int32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0);
|
|
ebus_dval=val;
|
|
mbus_dval=val;
|
|
mp+=8;
|
|
cp+=4;
|
|
} else if (*mp=='u' && *(mp+1)=='u' && *(mp+2)=='U' && *(mp+3)=='U'){
|
|
uint16_t val = cp[0]|(cp[1]<<8);
|
|
mbus_dval=val;
|
|
ebus_dval=val;
|
|
mp+=4;
|
|
cp+=2;
|
|
} else if (*mp=='u' && *(mp+1)=='u') {
|
|
uint8_t val = *cp++;
|
|
mbus_dval=val;
|
|
ebus_dval=val;
|
|
mp+=2;
|
|
} else if (*mp=='s' && *(mp+1)=='s' && *(mp+2)=='S' && *(mp+3)=='S') {
|
|
int16_t val = *cp|(*(cp+1)<<8);
|
|
mbus_dval=val;
|
|
ebus_dval=val;
|
|
mp+=4;
|
|
cp+=2;
|
|
} else if (*mp=='S' && *(mp+1)=='S' && *(mp+2)=='s' && *(mp+3)=='s') {
|
|
int16_t val = cp[1]|(cp[0]<<8);
|
|
mbus_dval=val;
|
|
ebus_dval=val;
|
|
mp+=4;
|
|
cp+=2;
|
|
}
|
|
else if (*mp=='s' && *(mp+1)=='s') {
|
|
int8_t val = *cp++;
|
|
mbus_dval=val;
|
|
ebus_dval=val;
|
|
mp+=2;
|
|
}
|
|
else if (!strncmp(mp,"ffffffff",8)) {
|
|
uint32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0);
|
|
float *fp=(float*)&val;
|
|
ebus_dval=*fp;
|
|
mbus_dval=*fp;
|
|
mp+=8;
|
|
cp+=4;
|
|
}
|
|
else if (!strncmp(mp,"FFffFFff",8)) {
|
|
|
|
uint32_t val= (cp[1]<<0)|(cp[0]<<8)|(cp[3]<<16)|(cp[2]<<24);
|
|
float *fp=(float*)&val;
|
|
ebus_dval=*fp;
|
|
mbus_dval=*fp;
|
|
mp+=8;
|
|
cp+=4;
|
|
}
|
|
else if (!strncmp(mp,"eeeeee",6)) {
|
|
uint32_t val=(cp[0]<<16)|(cp[1]<<8)|(cp[2]<<0);
|
|
mbus_dval=val;
|
|
mp+=6;
|
|
cp+=3;
|
|
}
|
|
else if (!strncmp(mp,"vvvvvv",6)) {
|
|
mbus_dval=(float)((cp[0]<<8)|(cp[1])) + ((float)cp[2]/10.0);
|
|
mp+=6;
|
|
cp+=3;
|
|
}
|
|
else if (!strncmp(mp,"cccccc",6)) {
|
|
mbus_dval=(float)((cp[0]<<8)|(cp[1])) + ((float)cp[2]/100.0);
|
|
mp+=6;
|
|
cp+=3;
|
|
}
|
|
else if (!strncmp(mp,"pppp",4)) {
|
|
mbus_dval=(float)((cp[0]<<8)|cp[1]);
|
|
mp+=4;
|
|
cp+=2;
|
|
}
|
|
else {
|
|
uint8_t val = hexnibble(*mp++) << 4;
|
|
val |= hexnibble(*mp++);
|
|
if (val!=*cp++) {
|
|
found=0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
|
|
mp++;
|
|
if (*mp=='#') {
|
|
|
|
mp++;
|
|
if (meter_desc_p[mindex].type=='o') {
|
|
for (uint8_t p=0;p<METER_ID_SIZE;p++) {
|
|
if (*cp==*mp) {
|
|
meter_id[mindex][p]=0;
|
|
break;
|
|
}
|
|
meter_id[mindex][p]=*cp++;
|
|
}
|
|
} else {
|
|
sml_getvalue(cp,mindex);
|
|
}
|
|
} else {
|
|
double dval;
|
|
if (meter_desc_p[mindex].type!='e' && meter_desc_p[mindex].type!='r' && meter_desc_p[mindex].type!='m' && meter_desc_p[mindex].type!='M' && meter_desc_p[mindex].type!='p') {
|
|
|
|
if (meter_desc_p[mindex].type=='o' || meter_desc_p[mindex].type=='c') {
|
|
dval=CharToDouble((char*)cp);
|
|
} else {
|
|
dval=sml_getvalue(cp,mindex);
|
|
}
|
|
} else {
|
|
|
|
if (*mp=='b') {
|
|
mp++;
|
|
uint8_t shift=*mp&7;
|
|
ebus_dval>>=shift;
|
|
ebus_dval&=1;
|
|
mp+=2;
|
|
}
|
|
if (*mp=='i') {
|
|
|
|
mp++;
|
|
uint8_t mb_index=strtol((char*)mp,(char**)&mp,10);
|
|
if (mb_index!=meter_desc_p[mindex].index) {
|
|
goto nextsect;
|
|
}
|
|
uint16_t crc = MBUS_calculateCRC(&smltbuf[mindex][0],7);
|
|
if (lowByte(crc)!=smltbuf[mindex][7]) goto nextsect;
|
|
if (highByte(crc)!=smltbuf[mindex][8]) goto nextsect;
|
|
dval=mbus_dval;
|
|
|
|
mp++;
|
|
} else {
|
|
if (meter_desc_p[mindex].type=='p') {
|
|
uint8_t crc = SML_PzemCrc(&smltbuf[mindex][0],6);
|
|
if (crc!=smltbuf[mindex][6]) goto nextsect;
|
|
dval=mbus_dval;
|
|
} else {
|
|
dval=ebus_dval;
|
|
}
|
|
}
|
|
|
|
}
|
|
#ifdef USE_SML_MEDIAN_FILTER
|
|
if (meter_desc_p[mindex].flag&16) {
|
|
meter_vars[vindex]=sml_median(&sml_mf[vindex],dval);
|
|
} else {
|
|
meter_vars[vindex]=dval;
|
|
}
|
|
#else
|
|
meter_vars[vindex]=dval;
|
|
#endif
|
|
|
|
|
|
double fac=CharToDouble((char*)mp);
|
|
meter_vars[vindex]/=fac;
|
|
SML_Immediate_MQTT((const char*)mp,vindex,mindex);
|
|
}
|
|
}
|
|
}
|
|
nextsect:
|
|
|
|
if (vindex<SML_MAX_VARS-1) {
|
|
vindex++;
|
|
}
|
|
mp = strchr(mp, '|');
|
|
if (mp) mp++;
|
|
}
|
|
}
|
|
|
|
|
|
void SML_Immediate_MQTT(const char *mp,uint8_t index,uint8_t mindex) {
|
|
char tpowstr[32];
|
|
char jname[24];
|
|
|
|
|
|
char *cp=strchr(mp,',');
|
|
if (cp) {
|
|
cp++;
|
|
|
|
cp=strchr(cp,',');
|
|
if (cp) {
|
|
cp++;
|
|
|
|
cp=strchr(cp,',');
|
|
if (cp) {
|
|
cp++;
|
|
|
|
for (uint8_t count=0;count<sizeof(jname);count++) {
|
|
if (*cp==',') {
|
|
jname[count]=0;
|
|
break;
|
|
}
|
|
jname[count]=*cp++;
|
|
}
|
|
cp++;
|
|
uint8_t dp=atoi(cp);
|
|
if (dp&0x10) {
|
|
|
|
dtostrfd(meter_vars[index],dp&0xf,tpowstr);
|
|
ResponseTime_P(PSTR(",\"%s\":{\"%s\":%s}}"),meter_desc_p[mindex].prefix,jname,tpowstr);
|
|
MqttPublishTeleSensor();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SML_Show(boolean json) {
|
|
int8_t count,mindex,cindex=0;
|
|
char tpowstr[32];
|
|
char name[24];
|
|
char unit[8];
|
|
char jname[24];
|
|
int8_t index=0,mid=0;
|
|
char *mp=(char*)meter_p;
|
|
char *cp;
|
|
|
|
|
|
|
|
int8_t lastmind=((*mp)&7)-1;
|
|
if (lastmind<0 || lastmind>=meters_used) lastmind=0;
|
|
while (mp != NULL) {
|
|
|
|
mindex=((*mp)&7)-1;
|
|
if (mindex<0 || mindex>=meters_used) mindex=0;
|
|
mp+=2;
|
|
if (*mp=='=' && *(mp+1)=='h') {
|
|
mp+=2;
|
|
|
|
if (json) {
|
|
mp = strchr(mp, '|');
|
|
if (mp) mp++;
|
|
continue;
|
|
}
|
|
|
|
uint8_t i;
|
|
for (i=0;i<sizeof(tpowstr)-2;i++) {
|
|
if (*mp=='|' || *mp==0) break;
|
|
tpowstr[i]=*mp++;
|
|
}
|
|
tpowstr[i]=0;
|
|
|
|
|
|
WSContentSend_PD(PSTR("{s}%s{e}"),tpowstr);
|
|
|
|
mp--;
|
|
mp = strchr(mp, '|');
|
|
if (mp) mp++;
|
|
continue;
|
|
}
|
|
|
|
cp=strchr(mp,'@');
|
|
if (cp) {
|
|
cp++;
|
|
if (*cp=='#') {
|
|
|
|
sprintf(tpowstr,"\"%s\"",&meter_id[mindex][0]);
|
|
mid=1;
|
|
} else {
|
|
mid=0;
|
|
}
|
|
|
|
cp=strchr(cp,',');
|
|
if (cp) {
|
|
|
|
cp++;
|
|
for (count=0;count<sizeof(name);count++) {
|
|
if (*cp==',') {
|
|
name[count]=0;
|
|
break;
|
|
}
|
|
name[count]=*cp++;
|
|
}
|
|
cp++;
|
|
|
|
for (count=0;count<sizeof(unit);count++) {
|
|
if (*cp==',') {
|
|
unit[count]=0;
|
|
break;
|
|
}
|
|
unit[count]=*cp++;
|
|
}
|
|
cp++;
|
|
|
|
for (count=0;count<sizeof(jname);count++) {
|
|
if (*cp==',') {
|
|
jname[count]=0;
|
|
break;
|
|
}
|
|
jname[count]=*cp++;
|
|
}
|
|
|
|
cp++;
|
|
|
|
if (!mid) {
|
|
uint8_t dp=atoi(cp)&0xf;
|
|
dtostrfd(meter_vars[index],dp,tpowstr);
|
|
}
|
|
|
|
if (json) {
|
|
|
|
if (index==0) {
|
|
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"),meter_desc_p[mindex].prefix,jname,tpowstr);
|
|
}
|
|
else {
|
|
if (lastmind!=mindex) {
|
|
|
|
|
|
ResponseAppend_P(PSTR("}"));
|
|
|
|
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"%s\":%s"),meter_desc_p[mindex].prefix,jname,tpowstr);
|
|
lastmind=mindex;
|
|
} else {
|
|
|
|
ResponseAppend_P(PSTR(",\"%s\":%s"),jname,tpowstr);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
|
|
WSContentSend_PD(PSTR("{s}%s %s: {m}%s %s{e}"),meter_desc_p[mindex].prefix,name,tpowstr,unit);
|
|
}
|
|
}
|
|
}
|
|
if (index<SML_MAX_VARS-1) {
|
|
index++;
|
|
}
|
|
|
|
mp = strchr(cp, '|');
|
|
if (mp) mp++;
|
|
}
|
|
if (json) {
|
|
|
|
|
|
ResponseAppend_P(PSTR("}"));
|
|
} else {
|
|
|
|
}
|
|
# 1802 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
}
|
|
|
|
struct SML_COUNTER {
|
|
uint8_t sml_cnt_debounce;
|
|
uint8_t sml_cnt_old_state;
|
|
uint32_t sml_cnt_last_ts;
|
|
uint32_t sml_counter_ltime;
|
|
uint16_t sml_debounce;
|
|
|
|
#ifdef ANALOG_OPTO_SENSOR
|
|
int16_t ana_curr;
|
|
int16_t ana_max;
|
|
int16_t ana_min;
|
|
int16_t ana_cmpl;
|
|
int16_t ana_cmph;
|
|
#endif
|
|
} sml_counters[MAX_COUNTERS];
|
|
|
|
|
|
#ifndef ARDUINO_ESP8266_RELEASE_2_3_0
|
|
void SML_CounterUpd(uint8_t index) ICACHE_RAM_ATTR;
|
|
void SML_CounterUpd1(void) ICACHE_RAM_ATTR;
|
|
void SML_CounterUpd2(void) ICACHE_RAM_ATTR;
|
|
void SML_CounterUpd3(void) ICACHE_RAM_ATTR;
|
|
void SML_CounterUpd4(void) ICACHE_RAM_ATTR;
|
|
#endif
|
|
|
|
void SML_CounterUpd(uint8_t index) {
|
|
|
|
uint8_t level=digitalRead(meter_desc_p[sml_counters[index].sml_cnt_old_state].srcpin);
|
|
if (!level) {
|
|
|
|
uint32_t ltime=millis()-sml_counters[index].sml_counter_ltime;
|
|
sml_counters[index].sml_counter_ltime=millis();
|
|
if (ltime>sml_counters[index].sml_debounce) {
|
|
RtcSettings.pulse_counter[index]++;
|
|
InjektCounterValue(sml_counters[index].sml_cnt_old_state,RtcSettings.pulse_counter[index]);
|
|
}
|
|
} else {
|
|
|
|
sml_counters[index].sml_counter_ltime=millis();
|
|
}
|
|
}
|
|
|
|
void SML_CounterUpd1(void) {
|
|
SML_CounterUpd(0);
|
|
}
|
|
|
|
void SML_CounterUpd2(void) {
|
|
SML_CounterUpd(1);
|
|
}
|
|
|
|
void SML_CounterUpd3(void) {
|
|
SML_CounterUpd(2);
|
|
}
|
|
|
|
void SML_CounterUpd4(void) {
|
|
SML_CounterUpd(3);
|
|
}
|
|
|
|
#ifdef USE_SCRIPT
|
|
struct METER_DESC script_meter_desc[MAX_METERS];
|
|
uint8_t *script_meter;
|
|
#endif
|
|
|
|
#ifndef METER_DEF_SIZE
|
|
#define METER_DEF_SIZE 3000
|
|
#endif
|
|
|
|
bool Gpio_used(uint8_t gpiopin) {
|
|
for (uint16_t i=0;i<GPIO_SENSOR_END;i++) {
|
|
if (pin[i]==gpiopin) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SML_Init(void) {
|
|
meters_used=METERS_USED;
|
|
meter_desc_p=meter_desc;
|
|
meter_p=meter;
|
|
|
|
sml_desc_cnt=0;
|
|
|
|
for (uint32_t cnt=0;cnt<SML_MAX_VARS;cnt++) {
|
|
meter_vars[cnt]=0;
|
|
}
|
|
|
|
for (uint32_t cnt=0;cnt<MAX_METERS;cnt++) {
|
|
meter_spos[cnt]=0;
|
|
}
|
|
|
|
#ifdef USE_SCRIPT
|
|
|
|
for (uint32_t cnt=0;cnt<MAX_METERS;cnt++) {
|
|
if (script_meter_desc[cnt].txmem) {
|
|
free(script_meter_desc[cnt].txmem);
|
|
script_meter_desc[cnt].txmem=0;
|
|
}
|
|
}
|
|
|
|
uint8_t meter_script=Run_Scripter(">M",-2,0);
|
|
if (meter_script==99) {
|
|
|
|
if (script_meter) free(script_meter);
|
|
script_meter=0;
|
|
uint8_t *tp=0;
|
|
uint16_t index=0;
|
|
uint8_t section=0;
|
|
uint8_t srcpin=0;
|
|
char *lp=glob_script_mem.scriptptr;
|
|
sml_send_blocks=0;
|
|
while (lp) {
|
|
if (!section) {
|
|
if (*lp=='>' && *(lp+1)=='M') {
|
|
lp+=2;
|
|
meters_used=strtol(lp,0,10);
|
|
section=1;
|
|
uint32_t mlen=0;
|
|
for (uint32_t cnt=0;cnt<METER_DEF_SIZE-1;cnt++) {
|
|
if (lp[cnt]=='\n' && lp[cnt+1]=='#') {
|
|
mlen=cnt+3;
|
|
break;
|
|
}
|
|
}
|
|
if (mlen==0) return;
|
|
script_meter=(uint8_t*)calloc(mlen,1);
|
|
if (!script_meter) {
|
|
goto dddef_exit;
|
|
}
|
|
tp=script_meter;
|
|
goto next_line;
|
|
}
|
|
}
|
|
else {
|
|
if (!*lp || *lp=='#' || *lp=='>') {
|
|
if (*(tp-1)=='|') *(tp-1)=0;
|
|
break;
|
|
}
|
|
if (*lp=='+') {
|
|
|
|
|
|
lp++;
|
|
index=*lp&7;
|
|
lp+=2;
|
|
if (index<1 || index>meters_used) goto next_line;
|
|
index--;
|
|
srcpin=strtol(lp,&lp,10);
|
|
if (Gpio_used(srcpin)) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("gpio rx double define!"));
|
|
dddef_exit:
|
|
if (script_meter) free(script_meter);
|
|
script_meter=0;
|
|
meters_used=METERS_USED;
|
|
goto init10;
|
|
}
|
|
script_meter_desc[index].srcpin=srcpin;
|
|
if (*lp!=',') goto next_line;
|
|
lp++;
|
|
script_meter_desc[index].type=*lp;
|
|
lp+=2;
|
|
script_meter_desc[index].flag=strtol(lp,&lp,10);
|
|
if (*lp!=',') goto next_line;
|
|
lp++;
|
|
script_meter_desc[index].params=strtol(lp,&lp,10);
|
|
if (*lp!=',') goto next_line;
|
|
lp++;
|
|
script_meter_desc[index].prefix[7]=0;
|
|
for (uint32_t cnt=0; cnt<8; cnt++) {
|
|
if (*lp==SCRIPT_EOL || *lp==',') {
|
|
script_meter_desc[index].prefix[cnt]=0;
|
|
break;
|
|
}
|
|
script_meter_desc[index].prefix[cnt]=*lp++;
|
|
}
|
|
if (*lp==',') {
|
|
lp++;
|
|
script_meter_desc[index].trxpin=strtol(lp,&lp,10);
|
|
if (Gpio_used(script_meter_desc[index].trxpin)) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("gpio tx double define!"));
|
|
goto dddef_exit;
|
|
}
|
|
if (*lp!=',') goto next_line;
|
|
lp++;
|
|
script_meter_desc[index].tsecs=strtol(lp,&lp,10);
|
|
if (*lp==',') {
|
|
lp++;
|
|
char txbuff[256];
|
|
uint32_t txlen=0,tx_entries=1;
|
|
for (uint32_t cnt=0; cnt<sizeof(txbuff); cnt++) {
|
|
if (*lp==SCRIPT_EOL) {
|
|
txbuff[cnt]=0;
|
|
txlen=cnt;
|
|
break;
|
|
}
|
|
if (*lp==',') tx_entries++;
|
|
txbuff[cnt]=*lp++;
|
|
}
|
|
if (txlen) {
|
|
script_meter_desc[index].txmem=(char*)calloc(txlen+2,1);
|
|
if (script_meter_desc[index].txmem) {
|
|
strcpy(script_meter_desc[index].txmem,txbuff);
|
|
}
|
|
script_meter_desc[index].index=0;
|
|
script_meter_desc[index].max_index=tx_entries;
|
|
sml_send_blocks++;
|
|
}
|
|
}
|
|
}
|
|
if (*lp==SCRIPT_EOL) lp--;
|
|
goto next_line;
|
|
}
|
|
|
|
if (*lp=='-' || isdigit(*lp)) {
|
|
|
|
|
|
if (*lp=='-') lp++;
|
|
uint8_t mnum=strtol(lp,0,10);
|
|
if (mnum<1 || mnum>meters_used) goto next_line;
|
|
while (1) {
|
|
if (*lp==SCRIPT_EOL) {
|
|
if (*(tp-1)!='|') *tp++='|';
|
|
goto next_line;
|
|
}
|
|
*tp++=*lp++;
|
|
index++;
|
|
if (index>=METER_DEF_SIZE) break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
next_line:
|
|
if (*lp==SCRIPT_EOL) {
|
|
lp++;
|
|
} else {
|
|
lp = strchr(lp, SCRIPT_EOL);
|
|
if (!lp) break;
|
|
lp++;
|
|
}
|
|
}
|
|
*tp=0;
|
|
meter_desc_p=script_meter_desc;
|
|
meter_p=script_meter;
|
|
}
|
|
#endif
|
|
|
|
init10:
|
|
typedef void (*function)();
|
|
function counter_callbacks[] = {SML_CounterUpd1,SML_CounterUpd2,SML_CounterUpd3,SML_CounterUpd4};
|
|
uint8_t cindex=0;
|
|
|
|
for (byte i = 0; i < MAX_COUNTERS; i++) {
|
|
RtcSettings.pulse_counter[i]=Settings.pulse_counter[i];
|
|
sml_counters[i].sml_cnt_last_ts=millis();
|
|
}
|
|
for (uint8_t meters=0; meters<meters_used; meters++) {
|
|
if (meter_desc_p[meters].type=='c') {
|
|
if (meter_desc_p[meters].flag&2) {
|
|
|
|
#ifdef ANALOG_OPTO_SENSOR
|
|
ADS1115_init();
|
|
sml_counters[cindex].ana_max=-32768;
|
|
sml_counters[cindex].ana_min=+32767;
|
|
#endif
|
|
} else {
|
|
|
|
if (meter_desc_p[meters].flag&1) {
|
|
pinMode(meter_desc_p[meters].srcpin,INPUT_PULLUP);
|
|
} else {
|
|
pinMode(meter_desc_p[meters].srcpin,INPUT);
|
|
}
|
|
|
|
if (meter_desc_p[meters].params<=0) {
|
|
|
|
attachInterrupt(meter_desc_p[meters].srcpin, counter_callbacks[cindex], CHANGE);
|
|
sml_counters[cindex].sml_cnt_old_state=meters;
|
|
sml_counters[cindex].sml_debounce=-meter_desc_p[meters].params;
|
|
}
|
|
InjektCounterValue(meters,RtcSettings.pulse_counter[cindex]);
|
|
cindex++;
|
|
}
|
|
} else {
|
|
|
|
#ifdef SPECIAL_SS
|
|
if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M' || meter_desc_p[meters].type=='p') {
|
|
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1);
|
|
} else {
|
|
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,1);
|
|
}
|
|
#else
|
|
meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1);
|
|
#endif
|
|
if (meter_ss[meters]->begin(meter_desc_p[meters].params)) {
|
|
meter_ss[meters]->flush();
|
|
}
|
|
if (meter_ss[meters]->hardwareSerial()) {
|
|
if (meter_desc_p[meters].type=='M') {
|
|
Serial.begin(meter_desc_p[meters].params, SERIAL_8E1);
|
|
}
|
|
ClaimSerial();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#ifdef USE_SML_SCRIPT_CMD
|
|
uint32_t SML_SetBaud(uint32_t meter, uint32_t br) {
|
|
if (meter<1 || meter>meters_used) return 0;
|
|
meter--;
|
|
if (!meter_ss[meter]) return 0;
|
|
if (meter_ss[meter]->begin(br)) {
|
|
meter_ss[meter]->flush();
|
|
}
|
|
if (meter_ss[meter]->hardwareSerial()) {
|
|
if (meter_desc_p[meter].type=='M') {
|
|
Serial.begin(br, SERIAL_8E1);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
uint32_t SML_Write(uint32_t meter,char *hstr) {
|
|
if (meter<1 || meter>meters_used) return 0;
|
|
meter--;
|
|
if (!meter_ss[meter]) return 0;
|
|
SML_Send_Seq(meter,hstr);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
|
|
void SetDBGLed(uint8_t srcpin, uint8_t ledpin) {
|
|
pinMode(ledpin, OUTPUT);
|
|
if (digitalRead(srcpin)) {
|
|
digitalWrite(ledpin,LOW);
|
|
} else {
|
|
digitalWrite(ledpin,HIGH);
|
|
}
|
|
}
|
|
|
|
|
|
void SML_Counter_Poll(void) {
|
|
uint16_t meters,cindex=0;
|
|
uint32_t ctime=millis();
|
|
|
|
for (meters=0; meters<meters_used; meters++) {
|
|
if (meter_desc_p[meters].type=='c') {
|
|
|
|
if (meter_desc_p[meters].params>0) {
|
|
if (ctime-sml_counters[cindex].sml_cnt_last_ts>meter_desc_p[meters].params) {
|
|
sml_counters[cindex].sml_cnt_last_ts=ctime;
|
|
|
|
if (meter_desc_p[meters].flag&2) {
|
|
|
|
#ifdef ANALOG_OPTO_SENSOR
|
|
if (ads1115_up) {
|
|
int16_t val = adc.read_sample();
|
|
if (val>sml_counters[cindex].ana_max) sml_counters[cindex].ana_max=val;
|
|
if (val<sml_counters[cindex].ana_min) sml_counters[cindex].ana_min=val;
|
|
sml_counters[cindex].ana_curr=val;
|
|
int16_t range=sml_counters[cindex].ana_max-sml_counters[cindex].ana_min;
|
|
}
|
|
#endif
|
|
} else {
|
|
|
|
uint8_t state;
|
|
sml_counters[cindex].sml_cnt_debounce<<=1;
|
|
sml_counters[cindex].sml_cnt_debounce|=(digitalRead(meter_desc_p[meters].srcpin)&1)|0x80;
|
|
if (sml_counters[cindex].sml_cnt_debounce==0xc0) {
|
|
|
|
state=1;
|
|
} else {
|
|
|
|
state=0;
|
|
}
|
|
if (sml_counters[cindex].sml_cnt_old_state!=state) {
|
|
|
|
sml_counters[cindex].sml_cnt_old_state=state;
|
|
if (state==0) {
|
|
|
|
RtcSettings.pulse_counter[cindex]++;
|
|
InjektCounterValue(meters,RtcSettings.pulse_counter[cindex]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef DEBUG_CNT_LED1
|
|
if (cindex==0) SetDBGLed(meter_desc_p[meters].srcpin,DEBUG_CNT_LED1);
|
|
#endif
|
|
#ifdef DEBUG_CNT_LED2
|
|
if (cindex==1) SetDBGLed(meter_desc_p[meters].srcpin,DEBUG_CNT_LED2);
|
|
#endif
|
|
} else {
|
|
if (ctime-sml_counters[cindex].sml_cnt_last_ts>10) {
|
|
sml_counters[cindex].sml_cnt_last_ts=ctime;
|
|
#ifdef DEBUG_CNT_LED1
|
|
if (cindex==0) SetDBGLed(meter_desc_p[meters].srcpin,DEBUG_CNT_LED1);
|
|
#endif
|
|
#ifdef DEBUG_CNT_LED2
|
|
if (cindex==1) SetDBGLed(meter_desc_p[meters].srcpin,DEBUG_CNT_LED2);
|
|
#endif
|
|
}
|
|
}
|
|
cindex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_SCRIPT
|
|
char *SML_Get_Sequence(char *cp,uint32_t index) {
|
|
if (!index) return cp;
|
|
uint32_t cindex=0;
|
|
while (cp) {
|
|
cp=strchr(cp,',');
|
|
if (cp) {
|
|
cp++;
|
|
cindex++;
|
|
if (cindex==index) {
|
|
return cp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SML_Check_Send(void) {
|
|
sml_100ms_cnt++;
|
|
char *cp;
|
|
for (uint32_t cnt=sml_desc_cnt; cnt<meters_used; cnt++) {
|
|
if (script_meter_desc[cnt].trxpin>=0 && script_meter_desc[cnt].txmem) {
|
|
if ((sml_100ms_cnt%script_meter_desc[cnt].tsecs)==0) {
|
|
if (script_meter_desc[cnt].max_index>1) {
|
|
script_meter_desc[cnt].index++;
|
|
if (script_meter_desc[cnt].index>=script_meter_desc[cnt].max_index) {
|
|
script_meter_desc[cnt].index=0;
|
|
sml_desc_cnt++;
|
|
}
|
|
cp=SML_Get_Sequence(script_meter_desc[cnt].txmem,script_meter_desc[cnt].index);
|
|
|
|
} else {
|
|
cp=script_meter_desc[cnt].txmem;
|
|
|
|
sml_desc_cnt++;
|
|
}
|
|
|
|
SML_Send_Seq(cnt,cp);
|
|
if (sml_desc_cnt>=meters_used) {
|
|
sml_desc_cnt=0;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
sml_desc_cnt++;
|
|
}
|
|
|
|
if (sml_desc_cnt>=meters_used) {
|
|
sml_desc_cnt=0;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t sml_hexnibble(char chr) {
|
|
uint8_t rVal = 0;
|
|
if (isdigit(chr)) {
|
|
rVal = chr - '0';
|
|
} else {
|
|
if (chr >= 'A' && chr <= 'F') rVal = chr + 10 - 'A';
|
|
if (chr >= 'a' && chr <= 'f') rVal = chr + 10 - 'a';
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
|
|
void SML_Send_Seq(uint32_t meter,char *seq) {
|
|
uint8_t sbuff[32];
|
|
uint8_t *ucp=sbuff,slen=0;
|
|
char *cp=seq;
|
|
while (*cp) {
|
|
if (!*cp || !*(cp+1)) break;
|
|
if (*cp==',') break;
|
|
uint8_t iob=(sml_hexnibble(*cp) << 4) | sml_hexnibble(*(cp+1));
|
|
cp+=2;
|
|
*ucp++=iob;
|
|
slen++;
|
|
if (slen>=sizeof(sbuff)) break;
|
|
}
|
|
if (script_meter_desc[meter].type=='m' || script_meter_desc[meter].type=='M') {
|
|
*ucp++=0;
|
|
*ucp++=2;
|
|
|
|
uint16_t crc = MBUS_calculateCRC(sbuff,6);
|
|
*ucp++=lowByte(crc);
|
|
*ucp++=highByte(crc);
|
|
slen+=4;
|
|
}
|
|
if (script_meter_desc[meter].type=='o') {
|
|
for (uint32_t cnt=0;cnt<slen;cnt++) {
|
|
sbuff[cnt]|=(CalcEvenParity(sbuff[cnt])<<7);
|
|
}
|
|
}
|
|
if (script_meter_desc[meter].type=='p') {
|
|
*ucp++=0xc0;
|
|
*ucp++=0xa8;
|
|
*ucp++=1;
|
|
*ucp++=1;
|
|
*ucp++=0;
|
|
*ucp++=SML_PzemCrc(sbuff,6);
|
|
slen+=6;
|
|
}
|
|
meter_ss[meter]->write(sbuff,slen);
|
|
}
|
|
#endif
|
|
|
|
uint16_t MBUS_calculateCRC(uint8_t *frame, uint8_t num) {
|
|
uint16_t crc, flag;
|
|
crc = 0xFFFF;
|
|
for (uint32_t i = 0; i < num; i++) {
|
|
crc ^= frame[i];
|
|
for (uint32_t j = 8; j; j--) {
|
|
if ((crc & 0x0001) != 0) {
|
|
crc >>= 1;
|
|
crc ^= 0xA001;
|
|
} else {
|
|
crc >>= 1;
|
|
}
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
uint8_t SML_PzemCrc(uint8_t *data, uint8_t len) {
|
|
uint16_t crc = 0;
|
|
for (uint32_t i = 0; i < len; i++) crc += *data++;
|
|
return (uint8_t)(crc & 0xFF);
|
|
}
|
|
|
|
|
|
uint8_t CalcEvenParity(uint8_t data) {
|
|
uint8_t parity=0;
|
|
|
|
while(data) {
|
|
parity^=(data &1);
|
|
data>>=1;
|
|
}
|
|
return parity;
|
|
}
|
|
# 2361 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
bool XSNS_53_cmd(void) {
|
|
bool serviced = true;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
char *cp=XdrvMailbox.data;
|
|
if (*cp=='d') {
|
|
|
|
cp++;
|
|
uint8_t index=atoi(cp);
|
|
if ((index&7)>meters_used) index=1;
|
|
if (index>0 && meter_desc_p[(index&7)-1].type=='c') {
|
|
index=0;
|
|
}
|
|
dump2log=index;
|
|
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"dump: %d\"}}"),dump2log);
|
|
} else if (*cp=='c') {
|
|
|
|
cp++;
|
|
uint8_t index=*cp&7;
|
|
if (index<1 || index>MAX_COUNTERS) index=1;
|
|
cp++;
|
|
while (*cp==' ') cp++;
|
|
if (isdigit(*cp)) {
|
|
uint32_t cval=atoi(cp);
|
|
while (isdigit(*cp)) cp++;
|
|
RtcSettings.pulse_counter[index-1]=cval;
|
|
uint8_t cindex=0;
|
|
for (uint8_t meters=0; meters<meters_used; meters++) {
|
|
if (meter_desc_p[meters].type=='c') {
|
|
InjektCounterValue(meters,RtcSettings.pulse_counter[cindex]);
|
|
cindex++;
|
|
}
|
|
}
|
|
}
|
|
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"counter%d: %d\"}}"),index,RtcSettings.pulse_counter[index-1]);
|
|
} else if (*cp=='r') {
|
|
|
|
ResponseTime_P(PSTR(",\"SML\":{\"CMD\":\"restart\"}}"));
|
|
SML_CounterSaveState();
|
|
SML_Init();
|
|
} else {
|
|
serviced=false;
|
|
}
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
void InjektCounterValue(uint8_t meter,uint32_t counter) {
|
|
sprintf((char*)&smltbuf[meter][0],"1-0:1.8.0*255(%d)",counter);
|
|
SML_Decode(meter);
|
|
}
|
|
|
|
void SML_CounterSaveState(void) {
|
|
for (byte i = 0; i < MAX_COUNTERS; i++) {
|
|
Settings.pulse_counter[i] = RtcSettings.pulse_counter[i];
|
|
}
|
|
}
|
|
# 2425 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_53_sml.ino"
|
|
bool Xsns53(byte function) {
|
|
bool result = false;
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
SML_Init();
|
|
break;
|
|
case FUNC_LOOP:
|
|
SML_Counter_Poll();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
if (dump2log) Dump2log();
|
|
else SML_Poll();
|
|
break;
|
|
#ifdef USE_SCRIPT
|
|
case FUNC_EVERY_100_MSECOND:
|
|
SML_Check_Send();
|
|
break;
|
|
#endif
|
|
case FUNC_JSON_APPEND:
|
|
SML_Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
SML_Show(0);
|
|
break;
|
|
#endif
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_53 == XdrvMailbox.index) {
|
|
result = XSNS_53_cmd();
|
|
}
|
|
break;
|
|
case FUNC_SAVE_BEFORE_RESTART:
|
|
case FUNC_SAVE_AT_MIDNIGHT:
|
|
SML_CounterSaveState();
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_INA226
|
|
# 70 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
|
|
#define XSNS_54 54
|
|
#define XI2C_35 35
|
|
|
|
#define INA226_MAX_ADDRESSES 4
|
|
#define INA226_ADDRESS1 (0x40)
|
|
#define INA226_ADDRESS2 (0x41)
|
|
#define INA226_ADDRESS3 (0x44)
|
|
#define INA226_ADDRESS4 (0x45)
|
|
|
|
#define INA226_REG_CONFIG (0x00)
|
|
#define INA226_RES_CONFIG (0x4127)
|
|
#define INA226_DEF_CONFIG (0x42FF)
|
|
#define INA226_CONFIG_RESET (0x8000)
|
|
|
|
#define INA226_REG_SHUNTVOLTAGE (0x01)
|
|
#define INA226_REG_BUSVOLTAGE (0x02)
|
|
#define INA226_REG_POWER (0x03)
|
|
#define INA226_REG_CURRENT (0x04)
|
|
#define INA226_REG_CALIBRATION (0x05)
|
|
|
|
|
|
typedef struct Ina226SlaveInfo_tag {
|
|
uint8_t address;
|
|
uint16_t calibrationValue;
|
|
uint16_t config;
|
|
uint8_t present : 1;
|
|
float i_lsb;
|
|
} Ina226SlaveInfo_t;
|
|
|
|
|
|
|
|
|
|
|
|
static const uint8_t PROGMEM probeAddresses[INA226_MAX_ADDRESSES] = {INA226_ADDRESS1, INA226_ADDRESS2, INA226_ADDRESS3, INA226_ADDRESS4};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char Ina226Str[] = "INA226";
|
|
static uint8_t slavesFound = 0;
|
|
static uint8_t schedule_reinit = 0;
|
|
static Ina226SlaveInfo_t slaveInfo[4] = {0};
|
|
|
|
static float voltages[4];
|
|
static float currents[4];
|
|
static float powers[4];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void _debug_fval(const char *str, float fval, uint8_t prec = 4 )
|
|
{
|
|
char fstr[32];
|
|
dtostrfd(fval, prec, fstr);
|
|
AddLog_P2( LOG_LEVEL_DEBUG, PSTR("%s: %s"), str, fstr );
|
|
}
|
|
# 138 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
|
|
static uint32_t _expand_r_shunt(uint16_t compact_r_shunt)
|
|
{
|
|
|
|
uint32_t r_shunt_uohms = (compact_r_shunt & 0x8000) ?
|
|
(((uint32_t)(compact_r_shunt & 0x7FFF)) * 1000ul) :
|
|
(compact_r_shunt & 0x7FFF);
|
|
return r_shunt_uohms;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Ina226SetCalibration(uint8_t slaveIndex)
|
|
{
|
|
|
|
Ina226SlaveInfo_t *si = slaveInfo + slaveIndex;
|
|
|
|
I2cWrite16( si->address, INA226_REG_CALIBRATION, si->calibrationValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Ina226TestPresence(uint8_t device)
|
|
{
|
|
|
|
|
|
|
|
uint16_t config = I2cRead16( slaveInfo[device].address, INA226_REG_CONFIG );
|
|
|
|
|
|
if (config != slaveInfo[device].config)
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
void Ina226ResetActive(void)
|
|
{
|
|
Ina226SlaveInfo_t *p = slaveInfo;
|
|
|
|
for (uint32_t i = 0; i < INA226_MAX_ADDRESSES; i++) {
|
|
p = &slaveInfo[i];
|
|
|
|
uint8_t addr = p->address;
|
|
if (addr) {
|
|
I2cResetActive(addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Ina226Init()
|
|
{
|
|
uint32_t i;
|
|
|
|
slavesFound = 0;
|
|
|
|
Ina226SlaveInfo_t *p = slaveInfo;
|
|
# 215 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
|
|
for (i = 0; i < 4; i++){
|
|
*p = {0};
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < INA226_MAX_ADDRESSES; i++){
|
|
uint8_t addr = pgm_read_byte(probeAddresses + i);
|
|
|
|
if (I2cActive(addr)) { continue; }
|
|
|
|
|
|
|
|
|
|
if (!Settings.ina226_i_fs[i])
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!I2cWrite16( addr, INA226_REG_CONFIG, INA226_CONFIG_RESET)){
|
|
|
|
AddLog_P2( LOG_LEVEL_DEBUG, "No INA226 at address: %02X", addr);
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
uint16_t config = I2cRead16( addr, INA226_REG_CONFIG );
|
|
|
|
|
|
if (INA226_RES_CONFIG != config)
|
|
continue;
|
|
|
|
|
|
config = INA226_DEF_CONFIG;
|
|
|
|
|
|
if (!I2cWrite16( addr, INA226_REG_CONFIG, config))
|
|
continue;
|
|
|
|
|
|
p = &slaveInfo[i];
|
|
|
|
p->address = addr;
|
|
|
|
p->config = config;
|
|
|
|
|
|
p->i_lsb = (((float) Settings.ina226_i_fs[i])/10.0f)/32768.0f;
|
|
|
|
|
|
|
|
uint32_t r_shunt_uohms = _expand_r_shunt(Settings.ina226_r_shunt[i]);
|
|
|
|
|
|
|
|
p->calibrationValue = ((uint16_t) (0.00512/(p->i_lsb * r_shunt_uohms/1000000.0f)));
|
|
|
|
p->present = true;
|
|
|
|
|
|
Ina226SetCalibration(i);
|
|
|
|
I2cSetActiveFound(addr, Ina226Str);
|
|
|
|
slavesFound++;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float Ina226ReadBus_v(uint8_t device)
|
|
{
|
|
uint8_t addr = slaveInfo[device].address;
|
|
int16_t reg_bus_v = I2cReadS16( addr, INA226_REG_BUSVOLTAGE);
|
|
|
|
float result = ((float) reg_bus_v) * 0.00125f;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float Ina226ReadShunt_i(uint8_t device)
|
|
{
|
|
uint8_t addr = slaveInfo[device].address;
|
|
int16_t reg_shunt_i = I2cReadS16( addr, INA226_REG_CURRENT);
|
|
|
|
float result = ((float) reg_shunt_i) * slaveInfo[device].i_lsb;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float Ina226ReadPower_w(uint8_t device)
|
|
{
|
|
uint8_t addr = slaveInfo[device].address;
|
|
int16_t reg_shunt_i = I2cReadS16( addr, INA226_REG_POWER);
|
|
|
|
float result = ((float) reg_shunt_i) * (slaveInfo[device].i_lsb * 25.0);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Ina226Read(uint8_t device)
|
|
{
|
|
|
|
voltages[device] = Ina226ReadBus_v(device);
|
|
currents[device] = Ina226ReadShunt_i(device);
|
|
powers[device] = Ina226ReadPower_w(device);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Ina226EverySecond()
|
|
{
|
|
|
|
for (uint8_t device = 0; device < INA226_MAX_ADDRESSES; device++){
|
|
|
|
if (slavesFound && slaveInfo[device].present && Ina226TestPresence(device)){
|
|
Ina226Read(device);
|
|
}
|
|
else {
|
|
powers[device] = currents[device] = voltages[device] = 0.0f;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
slaveInfo[device].present = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Ina226CommandSensor()
|
|
{
|
|
bool serviced = true;
|
|
bool show_config = false;
|
|
char param_str[64];
|
|
char *cp, *params[4];
|
|
uint8_t i, param_count, device, p1 = XdrvMailbox.payload;
|
|
uint32_t r_shunt_uohms;
|
|
uint16_t compact_r_shunt_uohms;
|
|
|
|
|
|
|
|
|
|
|
|
if (XdrvMailbox.data_len > 62){
|
|
return false;
|
|
}
|
|
|
|
strncpy(param_str, XdrvMailbox.data, XdrvMailbox.data_len + 1);
|
|
param_str[XdrvMailbox.data_len] = 0;
|
|
|
|
|
|
for (cp = param_str, i = 0, param_count = 0; *cp && (i < XdrvMailbox.data_len + 1) && (param_count <= 3); i++)
|
|
if (param_str[i] == ' ' || param_str[i] == ',' || param_str[i] == 0){
|
|
param_str[i] = 0;
|
|
params[param_count] = cp;
|
|
|
|
param_count++;
|
|
cp = param_str + i + 1;
|
|
}
|
|
|
|
|
|
if (p1 < 10 || p1 >= 50){
|
|
|
|
switch (p1){
|
|
case 1:
|
|
Ina226ResetActive();
|
|
Ina226Init();
|
|
Response_P(PSTR("{\"Sensor54-Command-Result\":{\"SlavesFound\":%d}}"),slavesFound);
|
|
break;
|
|
|
|
case 2:
|
|
restart_flag = 2;
|
|
Response_P(PSTR("{\"Sensor54-Command-Result\":{\"Restart_flag\":%d}}"),restart_flag);
|
|
break;
|
|
|
|
default:
|
|
serviced = false;
|
|
}
|
|
}
|
|
else if (p1 < 50){
|
|
|
|
device = (p1 / 10) - 1;
|
|
switch (p1 % 10){
|
|
case 0:
|
|
show_config = true;
|
|
break;
|
|
|
|
case 1:
|
|
r_shunt_uohms = (uint32_t) ((CharToFloat(params[1])) * 1000000.0f);
|
|
|
|
|
|
|
|
if (r_shunt_uohms > 32767){
|
|
uint32_t r_shunt_mohms = r_shunt_uohms/1000UL;
|
|
Settings.ina226_r_shunt[device] = (uint16_t) (r_shunt_mohms | 0x8000);
|
|
}
|
|
else
|
|
Settings.ina226_r_shunt[device] = (uint16_t) r_shunt_uohms;
|
|
|
|
|
|
show_config = true;
|
|
break;
|
|
|
|
case 2:
|
|
Settings.ina226_i_fs[device] = (uint16_t) ((CharToFloat(params[1])) * 10.0f);
|
|
|
|
show_config = true;
|
|
break;
|
|
|
|
|
|
default:
|
|
serviced = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
serviced = false;
|
|
|
|
if (show_config) {
|
|
char shunt_r_str[16];
|
|
char fs_i_str[16];
|
|
|
|
|
|
r_shunt_uohms = _expand_r_shunt(Settings.ina226_r_shunt[device]);
|
|
dtostrfd(((float)r_shunt_uohms)/1000000.0f, 6, shunt_r_str);
|
|
|
|
dtostrfd(((float)Settings.ina226_i_fs[device])/10.0f, 1, fs_i_str);
|
|
|
|
Response_P(PSTR("{\"Sensor54-device-settings-%d\":{\"SHUNT_R\":%s,\"FS_I\":%s}}"),
|
|
device + 1, shunt_r_str, fs_i_str);
|
|
}
|
|
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_INA226_DATA[] PROGMEM =
|
|
"{s}%s " D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"
|
|
"{s}%s " D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"
|
|
"{s}%s " D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}";
|
|
#endif
|
|
|
|
void Ina226Show(bool json)
|
|
{
|
|
int i, num_found;
|
|
for (num_found = 0, i = 0; i < INA226_MAX_ADDRESSES; i++) {
|
|
|
|
if (!slaveInfo[i].present)
|
|
continue;
|
|
|
|
num_found++;
|
|
|
|
char voltage[16];
|
|
dtostrfd(voltages[i], Settings.flag2.voltage_resolution, voltage);
|
|
char current[16];
|
|
dtostrfd(currents[i], Settings.flag2.current_resolution, current);
|
|
char power[16];
|
|
dtostrfd(powers[i], Settings.flag2.wattage_resolution, power);
|
|
char name[16];
|
|
snprintf_P(name, sizeof(name), PSTR("INA226%c%d"),IndexSeparator(), i + 1);
|
|
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"Id\":%d,\"" D_JSON_VOLTAGE "\":%s,\"" D_JSON_CURRENT "\":%s,\"" D_JSON_POWERUSAGE "\":%s}"),
|
|
name, i, voltage, current, power);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_VOLTAGE, voltage);
|
|
DomoticzSensor(DZ_CURRENT, current);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_INA226_DATA, name, voltage, name, current, name, power);
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
# 546 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_54_ina226.ino"
|
|
bool Xsns54(byte callback_id)
|
|
{
|
|
if (!I2cEnabled(XI2C_35)) { return false; }
|
|
|
|
|
|
bool result = false;
|
|
|
|
|
|
switch (callback_id) {
|
|
case FUNC_EVERY_SECOND:
|
|
Ina226EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Ina226Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Ina226Show(0);
|
|
break;
|
|
#endif
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_54 == XdrvMailbox.index) {
|
|
result = Ina226CommandSensor();
|
|
}
|
|
break;
|
|
case FUNC_INIT:
|
|
Ina226Init();
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_55_hih_series.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_55_hih_series.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_HIH6
|
|
# 33 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_55_hih_series.ino"
|
|
#define XSNS_55 55
|
|
#define XI2C_36 36
|
|
|
|
#define HIH6_ADDR 0x27
|
|
|
|
struct HIH6 {
|
|
float temperature = 0;
|
|
float humidity = 0;
|
|
uint8_t valid = 0;
|
|
uint8_t type = 0;
|
|
char types[4] = "HIH";
|
|
} Hih6;
|
|
|
|
bool Hih6Read(void)
|
|
{
|
|
Wire.beginTransmission(HIH6_ADDR);
|
|
if (Wire.endTransmission() != 0) { return false; }
|
|
|
|
delay(40);
|
|
|
|
uint8_t data[4];
|
|
Wire.requestFrom(HIH6_ADDR, 4);
|
|
if (4 == Wire.available()) {
|
|
data[0] = Wire.read();
|
|
data[1] = Wire.read();
|
|
data[2] = Wire.read();
|
|
data[3] = Wire.read();
|
|
} else { return false; }
|
|
|
|
|
|
|
|
Hih6.humidity = ConvertHumidity(((float)(((data[0] & 0x3F) << 8) | data[1]) * 100.0) / 16383.0);
|
|
|
|
int temp = ((data[2] << 8) | (data[3] & 0xFC)) / 4;
|
|
Hih6.temperature = ConvertTemp(((float)temp / 16384.0) * 165.0 - 40.0);
|
|
|
|
Hih6.valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void Hih6Detect(void)
|
|
{
|
|
if (I2cActive(HIH6_ADDR)) { return; }
|
|
|
|
if (uptime < 2) { delay(20); }
|
|
Hih6.type = Hih6Read();
|
|
if (Hih6.type) {
|
|
I2cSetActiveFound(HIH6_ADDR, Hih6.types);
|
|
}
|
|
}
|
|
|
|
void Hih6EverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
if (!Hih6Read()) {
|
|
AddLogMissed(Hih6.types, Hih6.valid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Hih6Show(bool json)
|
|
{
|
|
if (Hih6.valid) {
|
|
char temperature[33];
|
|
dtostrfd(Hih6.temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Hih6.humidity, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Hih6.types, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, Hih6.temperature);
|
|
KnxSensor(KNX_HUMIDITY, Hih6.humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Hih6.types, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Hih6.types, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns55(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_36)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Hih6Detect();
|
|
}
|
|
else if (Hih6.type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Hih6EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Hih6Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Hih6Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_56_hpma.ino"
|
|
# 21 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_56_hpma.ino"
|
|
#ifdef USE_HPMA
|
|
# 31 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_56_hpma.ino"
|
|
#define XSNS_56 56
|
|
|
|
#include <hpma115S0.h>
|
|
#include <TasmotaSerial.h>
|
|
|
|
TasmotaSerial *HpmaSerial;
|
|
HPMA115S0 *hpma115S0;
|
|
|
|
uint8_t hpma_type = 1;
|
|
uint8_t hpma_valid = 0;
|
|
|
|
struct hpmadata {
|
|
unsigned int pm10;
|
|
unsigned int pm2_5;
|
|
} hpma_data;
|
|
|
|
|
|
|
|
void HpmaSecond(void)
|
|
{
|
|
unsigned int pm2_5, pm10;
|
|
|
|
|
|
|
|
|
|
if (hpma115S0->ReadParticleMeasurement(&pm2_5, &pm10)) {
|
|
hpma_data.pm2_5 = pm2_5;
|
|
hpma_data.pm10 = pm10;
|
|
hpma_valid = 1;
|
|
}
|
|
|
|
}
|
|
|
|
void HpmaInit(void)
|
|
{
|
|
hpma_type = 0;
|
|
if (pin[GPIO_HPMA_RX] < 99 && pin[GPIO_HPMA_TX] < 99) {
|
|
HpmaSerial = new TasmotaSerial(pin[GPIO_HPMA_RX], pin[GPIO_HPMA_TX], 1);
|
|
hpma115S0 = new HPMA115S0(*HpmaSerial);
|
|
|
|
if (HpmaSerial->begin(9600)) {
|
|
if (HpmaSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
}
|
|
hpma_type = 1;
|
|
hpma115S0->Init();
|
|
hpma115S0->StartParticleMeasurement();
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_HPMA_SNS[] PROGMEM =
|
|
"{s}HPMA " D_ENVIRONMENTAL_CONCENTRATION "2.5 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
|
|
"{s}HPMA " D_ENVIRONMENTAL_CONCENTRATION "10 " D_UNIT_MICROMETER "{m}%s " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}";
|
|
#endif
|
|
|
|
void HpmaShow(bool json)
|
|
{
|
|
if (hpma_valid) {
|
|
char pm10[33];
|
|
snprintf_P(pm10, 33, PSTR("%d"), hpma_data.pm10);
|
|
char pm2_5[33];
|
|
snprintf_P(pm2_5, 33, PSTR("%d"), hpma_data.pm2_5);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"HPMA\":{\"PM2.5\":%d,\"PM10\":%d}"), hpma_data.pm2_5, hpma_data.pm10);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) {
|
|
DomoticzSensor(DZ_VOLTAGE, pm2_5);
|
|
DomoticzSensor(DZ_CURRENT, pm10);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_HPMA_SNS, pm2_5, pm10);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns56(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (hpma_type) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
HpmaInit();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
HpmaSecond();
|
|
break;
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_56 == XdrvMailbox.index) {
|
|
return true;
|
|
}
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
HpmaShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
HpmaShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_57_tsl2591.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_57_tsl2591.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_TSL2591
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_57 57
|
|
#define XI2C_40 40
|
|
|
|
#define TSL2591_ADDRESS 0x29
|
|
|
|
#include <Adafruit_TSL2591.h>
|
|
|
|
Adafruit_TSL2591 tsl = Adafruit_TSL2591();
|
|
|
|
uint8_t tsl2591_type = 0;
|
|
uint8_t tsl2591_valid = 0;
|
|
float tsl2591_lux = 0;
|
|
|
|
void Tsl2591Init(void)
|
|
{
|
|
|
|
if (I2cSetDevice(0x29)) {
|
|
if (tsl.begin()) {
|
|
tsl.setGain(TSL2591_GAIN_MED);
|
|
tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
|
|
tsl2591_type = 1;
|
|
I2cSetActiveFound(TSL2591_ADDRESS, "TSL2591");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Tsl2591Read(void)
|
|
{
|
|
uint32_t lum = tsl.getFullLuminosity();
|
|
uint16_t ir, full;
|
|
ir = lum >> 16;
|
|
full = lum & 0xFFFF;
|
|
tsl2591_lux = tsl.calculateLux(full, ir);
|
|
tsl2591_valid = 1;
|
|
}
|
|
|
|
void Tsl2591EverySecond(void)
|
|
{
|
|
Tsl2591Read();
|
|
}
|
|
|
|
#ifdef USE_WEBSERVER
|
|
const char HTTP_SNS_TSL2591[] PROGMEM =
|
|
"{s}TSL2591 " D_ILLUMINANCE "{m}%s " D_UNIT_LUX "{e}";
|
|
#endif
|
|
|
|
void Tsl2591Show(bool json)
|
|
{
|
|
if (tsl2591_valid) {
|
|
char lux_str[10];
|
|
dtostrf(tsl2591_lux, sizeof(lux_str)-1, 3, lux_str);
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"TSL2591\":{\"" D_JSON_ILLUMINANCE "\":%s}"), lux_str);
|
|
#ifdef USE_DOMOTICZ
|
|
if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, tsl2591_lux); }
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TSL2591, lux_str);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns57(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_40)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Tsl2591Init();
|
|
}
|
|
else if (tsl2591_type) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Tsl2591EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Tsl2591Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Tsl2591Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_58_dht12.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_58_dht12.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DHT12
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_58 58
|
|
#define XI2C_41 41
|
|
|
|
#define DHT12_ADDR 0x5C
|
|
|
|
struct DHT12 {
|
|
float temperature = NAN;
|
|
float humidity = NAN;
|
|
uint8_t valid = 0;
|
|
uint8_t count = 0;
|
|
char name[6] = "DHT12";
|
|
} Dht12;
|
|
|
|
bool Dht12Read(void)
|
|
{
|
|
if (Dht12.valid) { Dht12.valid--; }
|
|
|
|
Wire.beginTransmission(DHT12_ADDR);
|
|
Wire.write(0);
|
|
if (Wire.endTransmission() != 0) { return false; }
|
|
|
|
delay(50);
|
|
|
|
Wire.requestFrom(DHT12_ADDR, 5);
|
|
delay(5);
|
|
uint8_t humidity = Wire.read();
|
|
uint8_t humidityTenth = Wire.read();
|
|
uint8_t temp = Wire.read();
|
|
uint8_t tempTenth = Wire.read();
|
|
uint8_t checksum = Wire.read();
|
|
|
|
Dht12.humidity = ConvertHumidity( (float) humidity + (float) humidityTenth/(float) 10.0 );
|
|
Dht12.temperature = ConvertTemp( (float) temp + (float) tempTenth/(float) 10.0 );
|
|
|
|
if (isnan(Dht12.temperature) || isnan(Dht12.humidity)) { return false; }
|
|
|
|
Dht12.valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void Dht12Detect(void)
|
|
{
|
|
if (I2cActive(DHT12_ADDR)) { return; }
|
|
|
|
if (Dht12Read()) {
|
|
I2cSetActiveFound(DHT12_ADDR, Dht12.name);
|
|
Dht12.count = 1;
|
|
}
|
|
}
|
|
|
|
void Dht12EverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
if (!Dht12Read()) {
|
|
AddLogMissed(Dht12.name, Dht12.valid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Dht12Show(bool json)
|
|
{
|
|
if (Dht12.valid) {
|
|
char temperature[33];
|
|
dtostrfd(Dht12.temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(Dht12.humidity, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, Dht12.name, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, Dht12.temperature);
|
|
KnxSensor(KNX_HUMIDITY, Dht12.humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, Dht12.name, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, Dht12.name, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns58(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_41)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
Dht12Detect();
|
|
}
|
|
else if (Dht12.count) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
Dht12EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
Dht12Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
Dht12Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_59_ds1624.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_59_ds1624.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_DS1624
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_59 59
|
|
#define XI2C_42 42
|
|
|
|
#define DS1624_MEM_REGISTER 0x17
|
|
#define DS1624_CONF_REGISTER 0xAC
|
|
#define DS1624_TEMP_REGISTER 0xAA
|
|
#define DS1624_START_REGISTER 0xEE
|
|
#define DS1624_STOP_REGISTER 0x22
|
|
|
|
#define DS1621_COUNTER_REGISTER 0xA8
|
|
#define DS1621_SLOPE_REGISTER 0xA9
|
|
|
|
#define DS1621_CFG_1SHOT (1<<0)
|
|
#define DS1621_CFG_DONE (1<<7)
|
|
|
|
enum {
|
|
DS1624_TYPE_DS1624,
|
|
DS1624_TYPE_DS1621
|
|
};
|
|
|
|
#define DS1624_MAX_SENSORS 8
|
|
|
|
bool ds1624_init = false;
|
|
|
|
struct {
|
|
float value;
|
|
uint8_t type;
|
|
int errcnt;
|
|
int misscnt;
|
|
bool valid;
|
|
char name[9];
|
|
} ds1624_sns[DS1624_MAX_SENSORS];
|
|
|
|
uint32_t DS1624_Idx2Addr(uint32_t idx) {
|
|
return 0x48 + idx;
|
|
}
|
|
|
|
int DS1624_Restart(uint8_t config, uint32_t idx) {
|
|
uint32_t addr = DS1624_Idx2Addr(idx);
|
|
if ((config & 1) == 1) {
|
|
config &= ~(DS1621_CFG_DONE|DS1621_CFG_1SHOT);
|
|
I2cWrite8(addr, DS1624_CONF_REGISTER, config);
|
|
delay(10);
|
|
AddLog_P2(LOG_LEVEL_ERROR, "%s addr %x is reset, reconfig: %x", ds1624_sns[idx].name, addr, config);
|
|
}
|
|
I2cValidRead(addr, DS1624_START_REGISTER, 1);
|
|
}
|
|
|
|
void DS1624_HotPlugUp(uint32_t idx)
|
|
{
|
|
uint32_t addr = DS1624_Idx2Addr(idx);
|
|
|
|
if (I2cActive(addr)) { return; }
|
|
if (!I2cSetDevice(addr)) { return; }
|
|
|
|
uint8_t config;
|
|
if (I2cValidRead8(&config, addr, DS1624_CONF_REGISTER)) {
|
|
uint8_t tmp;
|
|
ds1624_sns[idx].type = (I2cValidRead8(&tmp, addr, DS1624_MEM_REGISTER)) ? DS1624_TYPE_DS1624 : DS1624_TYPE_DS1621;
|
|
|
|
snprintf_P(ds1624_sns[idx].name, sizeof(ds1624_sns[idx].name), PSTR("DS162%c%c%d"),
|
|
(ds1624_sns[idx].type == DS1624_TYPE_DS1621) ? '1' : '4', IndexSeparator(), idx);
|
|
I2cSetActiveFound(addr, ds1624_sns[idx].name);
|
|
|
|
ds1624_sns[idx].valid = true;
|
|
ds1624_sns[idx].errcnt = 0;
|
|
ds1624_sns[idx].misscnt = 0;
|
|
DS1624_Restart(config,idx);
|
|
AddLog_P2(LOG_LEVEL_INFO, "Hot Plug %s addr %x config: %x", ds1624_sns[idx].name, addr, config);
|
|
}
|
|
}
|
|
|
|
void DS1624_HotPlugDown(int idx)
|
|
{
|
|
uint32_t addr = DS1624_Idx2Addr(idx);
|
|
if (!I2cActive(addr)) { return; }
|
|
I2cResetActive(addr);
|
|
ds1624_sns[idx].valid = false;
|
|
AddLog_P2(LOG_LEVEL_INFO, "Hot UnPlug %s", ds1624_sns[idx].name);
|
|
}
|
|
|
|
bool DS1624GetTemp(float *value, int idx)
|
|
{
|
|
uint32_t addr = DS1624_Idx2Addr(idx);
|
|
|
|
uint8_t config;
|
|
if (!I2cValidRead8(&config, addr, DS1624_CONF_REGISTER)) {
|
|
ds1624_sns[idx].misscnt++;
|
|
AddLog_P2(LOG_LEVEL_INFO, "%s device missing (errors: %i)", ds1624_sns[idx].name, ds1624_sns[idx].misscnt);
|
|
return false;
|
|
}
|
|
ds1624_sns[idx].misscnt=0;
|
|
if (config & (DS1621_CFG_1SHOT|DS1621_CFG_DONE)) {
|
|
ds1624_sns[idx].errcnt++;
|
|
AddLog_P2(LOG_LEVEL_INFO, "%s config error, restart... (errors: %i)", ds1624_sns[idx].name, ds1624_sns[idx].errcnt);
|
|
DS1624_Restart(config, idx);
|
|
return false;
|
|
}
|
|
|
|
uint16_t t;
|
|
if (!I2cValidRead16(&t, DS1624_Idx2Addr(idx), DS1624_TEMP_REGISTER)) { return false; }
|
|
if (ds1624_sns[idx].type == DS1624_TYPE_DS1624) {
|
|
*value = ((float)(int8_t)(t>>8)) + ((t>>4)&0xf)*0.0625;
|
|
} else {
|
|
|
|
*value = ((float)(int8_t)(t>>8));
|
|
uint8_t remain;
|
|
if (!I2cValidRead8(&remain, addr, DS1621_COUNTER_REGISTER)) { return true; }
|
|
uint8_t perc;
|
|
if (!I2cValidRead8(&perc, addr, DS1621_SLOPE_REGISTER)) { return true; }
|
|
float fix=(float)(perc - remain)/(float)perc;
|
|
*value+=fix;
|
|
}
|
|
ds1624_sns[idx].errcnt=0;
|
|
config &= ~(DS1621_CFG_DONE);
|
|
I2cWrite8(addr, DS1624_CONF_REGISTER, config);
|
|
return true;
|
|
}
|
|
|
|
void DS1624HotPlugScan(void)
|
|
{
|
|
uint16_t t;
|
|
|
|
for (uint32_t idx = 0; idx < DS1624_MAX_SENSORS; idx++) {
|
|
uint32_t addr = DS1624_Idx2Addr(idx);
|
|
if (I2cActive(addr) && !ds1624_sns[idx].valid) {
|
|
continue;
|
|
}
|
|
if (ds1624_sns[idx].valid) {
|
|
if ((ds1624_sns[idx].misscnt>2)||(ds1624_sns[idx].errcnt>2)) {
|
|
DS1624_HotPlugDown(idx);
|
|
continue;
|
|
}
|
|
}
|
|
DS1624_HotPlugUp(idx);
|
|
}
|
|
}
|
|
|
|
void DS1624EverySecond(void)
|
|
{
|
|
float t;
|
|
for (uint32_t i = 0; i < DS1624_MAX_SENSORS; i++) {
|
|
if (!ds1624_sns[i].valid) { continue; }
|
|
if (!DS1624GetTemp(&t, i)) { continue; }
|
|
ds1624_sns[i].value = ConvertTemp(t);
|
|
}
|
|
}
|
|
|
|
void DS1624Show(bool json)
|
|
{
|
|
char temperature[33];
|
|
bool once = true;
|
|
|
|
for (uint32_t i = 0; i < DS1624_MAX_SENSORS; i++) {
|
|
if (!ds1624_sns[i].valid) { continue; }
|
|
|
|
dtostrfd(ds1624_sns[i].value, Settings.flag2.temperature_resolution, temperature);
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"), ds1624_sns[i].name, temperature);
|
|
if ((0 == tele_period) && once) {
|
|
#ifdef USE_DOMOTICZ
|
|
DomoticzSensor(DZ_TEMP, temperature);
|
|
#endif
|
|
#ifdef USE_KNX
|
|
KnxSensor(KNX_TEMPERATURE, temperature);
|
|
#endif
|
|
once = false;
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, ds1624_sns[i].name, temperature, TempUnit());
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns59(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_42)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
if (!ds1624_init) {
|
|
memset(ds1624_sns, 0, sizeof(ds1624_sns));
|
|
ds1624_init = true;
|
|
DS1624HotPlugScan();
|
|
}
|
|
}
|
|
switch (function) {
|
|
case FUNC_HOTPLUG_SCAN:
|
|
DS1624HotPlugScan();
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
DS1624EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
DS1624Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
DS1624Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_60_GPS.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_60_GPS.ino"
|
|
#ifdef USE_GPS
|
|
# 113 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_60_GPS.ino"
|
|
#define XSNS_60 60
|
|
|
|
#include "NTPServer.h"
|
|
#include "NTPPacket.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define D_CMND_UBX "UBX"
|
|
|
|
const char S_JSON_UBX_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_UBX "%s\":%d}";
|
|
|
|
const char kUBXTypes[] PROGMEM = "UBX";
|
|
|
|
#define UBX_LAT_LON_THRESHOLD 1000
|
|
|
|
#define UBX_SERIAL_BUFFER_SIZE 256
|
|
#define UBX_TCP_PORT 1234
|
|
|
|
|
|
|
|
|
|
|
|
const char UBLOX_INIT[] PROGMEM = {
|
|
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x24,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x2B,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x32,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x39,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x04,0x40,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01,0x05,0x47,
|
|
|
|
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xDC,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0xB9,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xC0,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0x92,
|
|
|
|
|
|
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x13,0xBE,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x14,0xC5,
|
|
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x32,0x97,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
char UBX_name[4];
|
|
|
|
struct UBX_t {
|
|
const char UBX_HEADER[2] = { 0xB5, 0x62 };
|
|
const char NAV_POSLLH_HEADER[2] = { 0x01, 0x02 };
|
|
const char NAV_STATUS_HEADER[2] = { 0x01, 0x03 };
|
|
const char NAV_TIME_HEADER[2] = { 0x01, 0x21 };
|
|
|
|
struct entry_t {
|
|
int32_t lat;
|
|
int32_t lon;
|
|
uint32_t time;
|
|
};
|
|
|
|
union {
|
|
entry_t values;
|
|
uint8_t bytes[sizeof(entry_t)];
|
|
} rec_buffer;
|
|
|
|
struct POLL_MSG {
|
|
uint8_t cls;
|
|
uint8_t id;
|
|
uint16_t zero;
|
|
};
|
|
|
|
struct NAV_POSLLH {
|
|
uint8_t cls;
|
|
uint8_t id;
|
|
uint16_t len;
|
|
uint32_t iTOW;
|
|
int32_t lon;
|
|
int32_t lat;
|
|
int32_t alt;
|
|
int32_t hMSL;
|
|
uint32_t hAcc;
|
|
uint32_t vAcc;
|
|
};
|
|
|
|
struct NAV_STATUS {
|
|
uint8_t cls;
|
|
uint8_t id;
|
|
uint16_t len;
|
|
uint32_t iTOW;
|
|
uint8_t gpsFix;
|
|
uint8_t flags;
|
|
uint8_t fixStat;
|
|
uint8_t flags2;
|
|
uint32_t ttff;
|
|
uint32_t msss;
|
|
};
|
|
|
|
struct NAV_TIME_UTC {
|
|
uint8_t cls;
|
|
uint8_t id;
|
|
uint16_t len;
|
|
uint32_t iTOW;
|
|
uint32_t tAcc;
|
|
int32_t nano;
|
|
uint16_t year;
|
|
uint8_t month;
|
|
uint8_t day;
|
|
uint8_t hour;
|
|
uint8_t min;
|
|
uint8_t sec;
|
|
struct {
|
|
uint8_t UTC:1;
|
|
uint8_t WKN:1;
|
|
uint8_t TOW:1;
|
|
uint8_t padding:5;
|
|
} valid;
|
|
};
|
|
|
|
struct CFG_RATE {
|
|
uint8_t cls;
|
|
uint8_t id;
|
|
uint16_t len;
|
|
uint16_t measRate;
|
|
uint16_t navRate;
|
|
uint16_t timeRef;
|
|
char CK[2];
|
|
};
|
|
|
|
struct {
|
|
uint32_t last_iTOW;
|
|
int32_t last_alt;
|
|
uint32_t last_hAcc;
|
|
uint32_t last_vAcc;
|
|
uint8_t gpsFix;
|
|
uint8_t non_empty_loops;
|
|
uint16_t log_interval;
|
|
} state;
|
|
|
|
struct {
|
|
uint32_t init:1;
|
|
uint32_t filter_noise:1;
|
|
uint32_t send_when_new:1;
|
|
uint32_t send_UI_only:1;
|
|
uint32_t runningNTP:1;
|
|
uint32_t forceUTCupdate:1;
|
|
uint32_t runningVPort:1;
|
|
|
|
} mode;
|
|
|
|
union {
|
|
NAV_POSLLH navPosllh;
|
|
NAV_STATUS navStatus;
|
|
NAV_TIME_UTC navTime;
|
|
POLL_MSG pollMsg;
|
|
CFG_RATE cfgRate;
|
|
} Message;
|
|
|
|
uint8_t TCPbuf[UBX_SERIAL_BUFFER_SIZE];
|
|
size_t TCPbufSize;
|
|
} UBX;
|
|
|
|
enum UBXMsgType {
|
|
MT_NONE,
|
|
MT_NAV_POSLLH,
|
|
MT_NAV_STATUS,
|
|
MT_NAV_TIME,
|
|
MT_POLL
|
|
};
|
|
|
|
#ifdef USE_FLOG
|
|
FLOG *Flog = nullptr;
|
|
#endif
|
|
TasmotaSerial *UBXSerial;
|
|
|
|
NtpServer timeServer(PortUdp);
|
|
|
|
WiFiServer vPortServer(UBX_TCP_PORT);
|
|
WiFiClient vPortClient;
|
|
|
|
|
|
|
|
|
|
|
|
void UBXcalcChecksum(char* CK, size_t msgSize)
|
|
{
|
|
memset(CK, 0, 2);
|
|
for (int i = 0; i < msgSize; i++) {
|
|
CK[0] += ((char*)(&UBX.Message))[i];
|
|
CK[1] += CK[0];
|
|
}
|
|
}
|
|
|
|
bool UBXcompareMsgHeader(const char* msgHeader)
|
|
{
|
|
char* ptr = (char*)(&UBX.Message);
|
|
return ptr[0] == msgHeader[0] && ptr[1] == msgHeader[1];
|
|
}
|
|
|
|
void UBXinitCFG(void)
|
|
{
|
|
for (uint32_t i = 0; i < sizeof(UBLOX_INIT); i++) {
|
|
UBXSerial->write( pgm_read_byte(UBLOX_INIT+i) );
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: turn off NMEA"));
|
|
}
|
|
|
|
void UBXTriggerTele(void)
|
|
{
|
|
mqtt_data[0] = '\0';
|
|
if (MqttShowSensor()) {
|
|
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
|
|
#ifdef USE_RULES
|
|
RulesTeleperiod();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UBXDetect(void)
|
|
{
|
|
UBX.mode.init = 0;
|
|
if ((pin[GPIO_GPS_RX] < 99) && (pin[GPIO_GPS_TX] < 99)) {
|
|
UBXSerial = new TasmotaSerial(pin[GPIO_GPS_RX], pin[GPIO_GPS_TX], 1, 0, UBX_SERIAL_BUFFER_SIZE);
|
|
if (UBXSerial->begin(9600)) {
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: started serial"));
|
|
if (UBXSerial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: claim HW"));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
UBXinitCFG();
|
|
UBX.mode.init = 1;
|
|
|
|
#ifdef USE_FLOG
|
|
if (!Flog) {
|
|
Flog = new FLOG;
|
|
Flog->init();
|
|
}
|
|
#endif
|
|
|
|
UBX.state.log_interval = 10;
|
|
UBX.mode.send_UI_only = true;
|
|
UBXTriggerTele();
|
|
}
|
|
|
|
uint32_t UBXprocessGPS()
|
|
{
|
|
static uint32_t fpos = 0;
|
|
static char checksum[2];
|
|
static uint8_t currentMsgType = MT_NONE;
|
|
static size_t payloadSize = sizeof(UBX.Message);
|
|
|
|
|
|
uint32_t data_bytes = 0;
|
|
while ( UBXSerial->available() ) {
|
|
data_bytes++;
|
|
byte c = UBXSerial->read();
|
|
if (UBX.mode.runningVPort){
|
|
UBX.TCPbuf[data_bytes-1] = c;
|
|
UBX.TCPbufSize = data_bytes;
|
|
}
|
|
if ( fpos < 2 ) {
|
|
|
|
if ( c == UBX.UBX_HEADER[fpos] ) {
|
|
fpos++;
|
|
} else {
|
|
fpos = 0;
|
|
}
|
|
} else {
|
|
|
|
|
|
|
|
|
|
|
|
if ( (fpos-2) < payloadSize ) {
|
|
((char*)(&UBX.Message))[fpos-2] = c;
|
|
}
|
|
fpos++;
|
|
|
|
if ( fpos == 4 ) {
|
|
|
|
|
|
if ( UBXcompareMsgHeader(UBX.NAV_POSLLH_HEADER) ) {
|
|
currentMsgType = MT_NAV_POSLLH;
|
|
payloadSize = sizeof(UBX_t::NAV_POSLLH);
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_POSLLH"));
|
|
}
|
|
else if ( UBXcompareMsgHeader(UBX.NAV_STATUS_HEADER) ) {
|
|
currentMsgType = MT_NAV_STATUS;
|
|
payloadSize = sizeof(UBX_t::NAV_STATUS);
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_STATUS"));
|
|
}
|
|
else if ( UBXcompareMsgHeader(UBX.NAV_TIME_HEADER) ) {
|
|
currentMsgType = MT_NAV_TIME;
|
|
payloadSize = sizeof(UBX_t::NAV_TIME_UTC);
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_TIME_UTC"));
|
|
}
|
|
else {
|
|
|
|
fpos = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( fpos == (payloadSize+2) ) {
|
|
|
|
|
|
UBXcalcChecksum(checksum, payloadSize);
|
|
}
|
|
else if ( fpos == (payloadSize+3) ) {
|
|
|
|
|
|
if ( c != checksum[0] ) {
|
|
|
|
fpos = 0;
|
|
}
|
|
}
|
|
else if ( fpos == (payloadSize+4) ) {
|
|
|
|
|
|
fpos = 0;
|
|
if ( c == checksum[1] ) {
|
|
|
|
return currentMsgType;
|
|
}
|
|
}
|
|
else if ( fpos > (payloadSize+4) ) {
|
|
|
|
|
|
fpos = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data_bytes!=0) {
|
|
UBX.state.non_empty_loops++;
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: got %u bytes, non-empty-loop: %u"), data_bytes, UBX.state.non_empty_loops);
|
|
} else {
|
|
UBX.state.non_empty_loops = 0;
|
|
}
|
|
return MT_NONE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_FLOG
|
|
void UBXsendHeader(void)
|
|
{
|
|
WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN);
|
|
WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=TASMOTA.gpx"));
|
|
WSSend(200, CT_STREAM, F(
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\r\n"
|
|
"<GPX version=\"1.1\" creator=\"TASMOTA\" xmlns=\"http://www.topografix.com/GPX/1/1\" \r\n"
|
|
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
|
|
"xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
|
|
"<trk>\r\n<trkseg>\r\n"));
|
|
}
|
|
|
|
void UBXsendRecord(uint8_t *buf)
|
|
{
|
|
char record[100];
|
|
char stime[32];
|
|
UBX_t::entry_t *entry = (UBX_t::entry_t*)buf;
|
|
snprintf_P(stime, sizeof(stime), GetDT(entry->time).c_str());
|
|
char lat[12];
|
|
char lon[12];
|
|
dtostrfd((double)entry->lat/10000000.0f,7,lat);
|
|
dtostrfd((double)entry->lon/10000000.0f,7,lon);
|
|
snprintf_P(record, sizeof(record),PSTR("<trkpt\n\t lat=\"%s\" lon=\"%s\">\n\t<time>%s</time>\n</trkpt>\n"),lat ,lon, stime);
|
|
|
|
WebServer->sendContent_P(record);
|
|
}
|
|
|
|
void UBXsendFooter(void)
|
|
{
|
|
WebServer->sendContent(F("</trkseg>\n</trk>\n</gpx>"));
|
|
WebServer->sendContent("");
|
|
Rtc.user_time_entry = false;
|
|
}
|
|
|
|
|
|
|
|
void UBXsendFile(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
Flog->startDownload(sizeof(UBX.rec_buffer),UBXsendHeader,UBXsendRecord,UBXsendFooter);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
void UBXSetRate(uint16_t interval)
|
|
{
|
|
UBX.Message.cfgRate.cls = 0x06;
|
|
UBX.Message.cfgRate.id = 0x08;
|
|
UBX.Message.cfgRate.len = 6;
|
|
uint32_t measRate = (1000*(uint32_t)interval);
|
|
if (measRate > 0xffff) {
|
|
measRate = 0xffff;
|
|
}
|
|
UBX.Message.cfgRate.measRate = (uint16_t)measRate;
|
|
UBX.Message.cfgRate.navRate = 1;
|
|
UBX.Message.cfgRate.timeRef = 1;
|
|
UBXcalcChecksum(UBX.Message.cfgRate.CK, sizeof(UBX.Message.cfgRate)-sizeof(UBX.Message.cfgRate.CK));
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: requested interval: %u seconds measRate: %u ms"), interval, UBX.Message.cfgRate.measRate);
|
|
UBXSerial->write(UBX.UBX_HEADER[0]);
|
|
UBXSerial->write(UBX.UBX_HEADER[1]);
|
|
for (uint32_t i =0; i<sizeof(UBX.Message.cfgRate); i++) {
|
|
UBXSerial->write(((uint8_t*)(&UBX.Message.cfgRate))[i]);
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: cfgRate byte %u: %x"), i, ((uint8_t*)(&UBX.Message.cfgRate))[i]);
|
|
}
|
|
UBX.state.log_interval = 10*interval;
|
|
}
|
|
|
|
void UBXSelectMode(uint16_t mode)
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: set mode to %u"),mode);
|
|
switch(mode){
|
|
#ifdef USE_FLOG
|
|
case 0:
|
|
Flog->mode = 0;
|
|
break;
|
|
case 1:
|
|
Flog->mode = 1;
|
|
break;
|
|
case 2:
|
|
UBX.mode.filter_noise = true;
|
|
break;
|
|
case 3:
|
|
UBX.mode.filter_noise = false;
|
|
break;
|
|
case 4:
|
|
Flog->startRecording(true);
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - appending"));
|
|
break;
|
|
case 5:
|
|
Flog->startRecording(false);
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - new log"));
|
|
break;
|
|
case 6:
|
|
if(Flog->recording == true){
|
|
Flog->stopRecording();
|
|
}
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: stop recording"));
|
|
break;
|
|
#endif
|
|
case 7:
|
|
UBX.mode.send_when_new = 1;
|
|
break;
|
|
case 8:
|
|
UBX.mode.send_when_new = 0;
|
|
break;
|
|
case 9:
|
|
if (timeServer.beginListening()) {
|
|
UBX.mode.runningNTP = true;
|
|
}
|
|
break;
|
|
case 10:
|
|
UBX.mode.runningNTP = false;
|
|
break;
|
|
case 11:
|
|
UBX.mode.forceUTCupdate = true;
|
|
break;
|
|
case 12:
|
|
UBX.mode.forceUTCupdate = false;
|
|
break;
|
|
case 13:
|
|
Settings.latitude = UBX.rec_buffer.values.lat/10;
|
|
Settings.longitude = UBX.rec_buffer.values.lon/10;
|
|
break;
|
|
case 14:
|
|
vPortServer.begin();
|
|
UBX.mode.runningVPort = 1;
|
|
break;
|
|
case 15:
|
|
|
|
UBX.mode.runningVPort = 0;
|
|
break;
|
|
default:
|
|
if (mode>1000 && mode <1066) {
|
|
|
|
UBXSetRate(mode-1000);
|
|
}
|
|
break;
|
|
}
|
|
UBX.mode.send_UI_only = true;
|
|
UBXTriggerTele();
|
|
}
|
|
|
|
|
|
|
|
bool UBXHandlePOSLLH()
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: iTOW: %u"),UBX.Message.navPosllh.iTOW);
|
|
if (UBX.state.gpsFix>1) {
|
|
if (UBX.mode.filter_noise) {
|
|
if ((UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat<abs(UBX_LAT_LON_THRESHOLD))||(UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon<abs(UBX_LAT_LON_THRESHOLD))) {
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: Diff lat: %u lon: %u "),UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat, UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon);
|
|
return false;
|
|
}
|
|
}
|
|
UBX.rec_buffer.values.lat = UBX.Message.navPosllh.lat;
|
|
UBX.rec_buffer.values.lon = UBX.Message.navPosllh.lon;
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: lat/lon: %i / %i"), UBX.rec_buffer.values.lat, UBX.rec_buffer.values.lon);
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: hAcc: %d"), UBX.Message.navPosllh.hAcc);
|
|
UBX.state.last_iTOW = UBX.Message.navPosllh.iTOW;
|
|
UBX.state.last_alt = UBX.Message.navPosllh.alt;
|
|
UBX.state.last_vAcc = UBX.Message.navPosllh.vAcc;
|
|
UBX.state.last_hAcc = UBX.Message.navPosllh.hAcc;
|
|
if (UBX.mode.send_when_new) {
|
|
UBXTriggerTele();
|
|
}
|
|
return true;
|
|
} else {
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: no valid position data"));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UBXHandleSTATUS()
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: gpsFix: %u, valid: %u"), UBX.Message.navStatus.gpsFix, (UBX.Message.navStatus.flags)&1);
|
|
if ((UBX.Message.navStatus.flags)&1) {
|
|
UBX.state.gpsFix = UBX.Message.navStatus.gpsFix;
|
|
} else {
|
|
UBX.state.gpsFix = 0;
|
|
}
|
|
}
|
|
|
|
void UBXHandleTIME()
|
|
{
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time: %u-%u-%u %u:%u:%u"), UBX.Message.navTime.year, UBX.Message.navTime.month ,UBX.Message.navTime.day,UBX.Message.navTime.hour,UBX.Message.navTime.min,UBX.Message.navTime.sec);
|
|
if (UBX.Message.navTime.valid.UTC == 1) {
|
|
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time is valid"));
|
|
if (Rtc.user_time_entry == false || UBX.mode.forceUTCupdate) {
|
|
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: UTC-Time is valid, set system time"));
|
|
TIME_T gpsTime;
|
|
gpsTime.year = UBX.Message.navTime.year - 1970;
|
|
gpsTime.month = UBX.Message.navTime.month;
|
|
gpsTime.day_of_month = UBX.Message.navTime.day;
|
|
gpsTime.hour = UBX.Message.navTime.hour;
|
|
gpsTime.minute = UBX.Message.navTime.min;
|
|
gpsTime.second = UBX.Message.navTime.sec;
|
|
Rtc.utc_time = MakeTime(gpsTime);
|
|
Rtc.user_time_entry = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UBXHandleOther(void)
|
|
{
|
|
if (UBX.state.non_empty_loops>6) {
|
|
if(UBX.mode.runningVPort) return;
|
|
UBXinitCFG();
|
|
AddLog_P(LOG_LEVEL_ERROR, PSTR("UBX: possible device-reset, will re-init"));
|
|
UBXSerial->flush();
|
|
UBX.state.non_empty_loops = 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UBXLoop50msec(void)
|
|
{
|
|
|
|
if (UBX.mode.runningVPort){
|
|
if(!vPortClient.connected()) {
|
|
vPortClient = vPortServer.available();
|
|
}
|
|
while(vPortClient.available()) {
|
|
byte _newByte = vPortClient.read();
|
|
UBXSerial->write(_newByte);
|
|
}
|
|
|
|
if (UBX.TCPbufSize!=0){
|
|
vPortClient.write((char*)UBX.TCPbuf, UBX.TCPbufSize);
|
|
UBX.TCPbufSize = 0;
|
|
}
|
|
}
|
|
|
|
if(UBX.mode.runningNTP){
|
|
timeServer.processOneRequest(Rtc.utc_time, UBX.state.last_iTOW%1000);
|
|
}
|
|
}
|
|
|
|
void UBXLoop(void)
|
|
{
|
|
static uint16_t counter;
|
|
static bool new_position;
|
|
|
|
uint32_t msgType = UBXprocessGPS();
|
|
|
|
switch(msgType){
|
|
case MT_NAV_POSLLH:
|
|
new_position = UBXHandlePOSLLH();
|
|
break;
|
|
case MT_NAV_STATUS:
|
|
UBXHandleSTATUS();
|
|
break;
|
|
case MT_NAV_TIME:
|
|
UBXHandleTIME();
|
|
break;
|
|
default:
|
|
UBXHandleOther();
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_FLOG
|
|
if (counter>UBX.state.log_interval) {
|
|
if (Flog->recording && new_position) {
|
|
UBX.rec_buffer.values.time = Rtc.local_time;
|
|
Flog->addToBuffer(UBX.rec_buffer.bytes, sizeof(UBX.rec_buffer.bytes));
|
|
counter = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
counter++;
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef USE_WEBSERVER
|
|
|
|
|
|
#ifdef USE_FLOG
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
const char HTTP_SNS_FLOGVER[] PROGMEM = "{s}<hr>{m}<hr>{e}{s} FLOG with %u sectors: {m}%u bytes{e}"
|
|
"{s} FLOG next sector for REC: {m} %u {e}"
|
|
"{s} %u sector(s) with data at sector: {m} %u {e}";
|
|
const char HTTP_SNS_FLOGREC[] PROGMEM = "{s} RECORDING (bytes in buffer) {m}%u{e}";
|
|
#endif
|
|
|
|
const char HTTP_SNS_FLOG[] PROGMEM = "{s}<hr>{m}<hr>{e}{s} Flash-Log {m} %s{e}";
|
|
const char kFLOG_STATE0[] PROGMEM = "ready";
|
|
const char kFLOG_STATE1[] PROGMEM = "recording";
|
|
const char * kFLOG_STATE[] ={kFLOG_STATE0, kFLOG_STATE1};
|
|
|
|
const char HTTP_BTN_FLOG_DL[] PROGMEM = "<button><a href='/UBX'>Download GPX-File</a></button>";
|
|
|
|
#endif
|
|
const char HTTP_SNS_NTPSERVER[] PROGMEM = "{s} NTP server {m}active{e}";
|
|
|
|
const char HTTP_SNS_GPS[] PROGMEM = "{s} GPS latitude {m}%s{e}"
|
|
"{s} GPS longitude {m}%s{e}"
|
|
"{s} GPS altitude {m}%s m{e}"
|
|
"{s} GPS hor. Accuracy {m}%s m{e}"
|
|
"{s} GPS vert. Accuracy {m}%s m{e}"
|
|
"{s} GPS sat-fix status {m}%s{e}";
|
|
|
|
const char kGPSFix0[] PROGMEM = "no fix";
|
|
const char kGPSFix1[] PROGMEM = "dead reckoning only";
|
|
const char kGPSFix2[] PROGMEM = "2D-fix";
|
|
const char kGPSFix3[] PROGMEM = "3D-fix";
|
|
const char kGPSFix4[] PROGMEM = "GPS + dead reckoning combined";
|
|
const char kGPSFix5[] PROGMEM = "Time only fix";
|
|
const char * kGPSFix[] PROGMEM ={kGPSFix0, kGPSFix1, kGPSFix2, kGPSFix3, kGPSFix4, kGPSFix5};
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void UBXShow(bool json)
|
|
{
|
|
char lat[12];
|
|
char lon[12];
|
|
char alt[12];
|
|
char hAcc[12];
|
|
char vAcc[12];
|
|
dtostrfd((double)UBX.rec_buffer.values.lat/10000000.0f,7,lat);
|
|
dtostrfd((double)UBX.rec_buffer.values.lon/10000000.0f,7,lon);
|
|
dtostrfd((double)UBX.state.last_alt/1000.0f,3,alt);
|
|
dtostrfd((double)UBX.state.last_vAcc/1000.0f,3,hAcc);
|
|
dtostrfd((double)UBX.state.last_hAcc/1000.0f,3,vAcc);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(PSTR(",\"GPS\":{"));
|
|
if (UBX.mode.send_UI_only) {
|
|
uint32_t i = UBX.state.log_interval / 10;
|
|
ResponseAppend_P(PSTR("\"fil\":%u,\"int\":%u}"), UBX.mode.filter_noise, i);
|
|
} else {
|
|
ResponseAppend_P(PSTR("\"lat\":%s,\"lon\":%s,\"alt\":%s,\"hAcc\":%s,\"vAcc\":%s}"), lat, lon, alt, hAcc, vAcc);
|
|
}
|
|
#ifdef USE_FLOG
|
|
ResponseAppend_P(PSTR(",\"FLOG\":{\"rec\":%u,\"mode\":%u,\"sec\":%u}"), Flog->recording, Flog->mode, Flog->sectors_left);
|
|
#endif
|
|
UBX.mode.send_UI_only = false;
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_GPS, lat, lon, alt, hAcc, vAcc, kGPSFix[UBX.state.gpsFix]);
|
|
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
#ifdef USE_FLOG
|
|
WSContentSend_PD(HTTP_SNS_FLOGVER, Flog->num_sectors, Flog->size, Flog->current_sector, Flog->sectors_left, Flog->sector.header.physical_start_sector);
|
|
if (Flog->recording) {
|
|
WSContentSend_PD(HTTP_SNS_FLOGREC, Flog->sector.header.buf_pointer - 8);
|
|
}
|
|
#endif
|
|
#endif
|
|
#ifdef USE_FLOG
|
|
if (Flog->ready) {
|
|
WSContentSend_P(HTTP_SNS_FLOG,kFLOG_STATE[Flog->recording]);
|
|
}
|
|
if (!Flog->recording && Flog->found_saved_data) {
|
|
WSContentSend_P(HTTP_BTN_FLOG_DL);
|
|
}
|
|
#endif
|
|
if (UBX.mode.runningNTP) {
|
|
WSContentSend_P(HTTP_SNS_NTPSERVER);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool UBXCmd(void)
|
|
{
|
|
bool serviced = true;
|
|
if (XdrvMailbox.data_len > 0) {
|
|
UBXSelectMode(XdrvMailbox.payload);
|
|
Response_P(S_JSON_UBX_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload);
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns60(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
UBXDetect();
|
|
}
|
|
|
|
if (UBX.mode.init) {
|
|
switch (function) {
|
|
case FUNC_COMMAND_SENSOR:
|
|
if (XSNS_60 == XdrvMailbox.index) {
|
|
result = UBXCmd();
|
|
}
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
UBXLoop50msec();
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
#ifdef USE_FLOG
|
|
if (!Flog->running_download)
|
|
#endif
|
|
{
|
|
UBXLoop();
|
|
}
|
|
break;
|
|
#ifdef USE_FLOG
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/UBX", UBXsendFile);
|
|
break;
|
|
#endif
|
|
case FUNC_JSON_APPEND:
|
|
UBXShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
#ifdef USE_FLOG
|
|
if (!Flog->running_download)
|
|
#endif
|
|
{
|
|
UBXShow(0);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
|
|
# 35 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
|
|
#ifdef USE_SPI
|
|
#ifdef USE_NRF24
|
|
#ifdef USE_MIBLE
|
|
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
#define MINRF_LOG_BUFFER(x) MINRFshowBuffer(x);
|
|
#else
|
|
#define MINRF_LOG_BUFFER(x)
|
|
#endif
|
|
# 53 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
|
|
#define XSNS_61 61
|
|
|
|
#include <vector>
|
|
|
|
#define FLORA 1
|
|
#define MJ_HT_V1 2
|
|
#define LYWSD02 3
|
|
#define LYWSD03 4
|
|
|
|
uint8_t kMINRFSlaveID[4][3] = { 0xC4,0x7C,0x8D,
|
|
0x58,0x2D,0x34,
|
|
0xE7,0x2E,0x00,
|
|
0xA4,0xC1,0x38,
|
|
};
|
|
|
|
const char kMINRFSlaveType1[] PROGMEM = "Flora";
|
|
const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1";
|
|
const char kMINRFSlaveType3[] PROGMEM = "LYWSD02";
|
|
const char kMINRFSlaveType4[] PROGMEM = "LYWSD03";
|
|
const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4};
|
|
|
|
|
|
const uint32_t kMINRFFloPDU[3] = {0x3eaa857d,0xef3b8730,0x71da7b46};
|
|
const uint32_t kMINRFMJPDU[3] = {0x4760cd66,0xdbcc0cd3,0x33048df5};
|
|
const uint32_t kMINRFL2PDU[3] = {0x3eaa057d,0xef3b0730,0x71da7646};
|
|
|
|
const uint32_t kMINRFL3PDU[3] = {0x4760cb78,0xdbcc0acd,0x33048beb};
|
|
|
|
|
|
const uint8_t kMINRFlsfrList_A[3] = {0x4b,0x17,0x23};
|
|
const uint8_t kMINRFlsfrList_B[3] = {0x21,0x72,0x43};
|
|
|
|
|
|
#pragma pack(1)
|
|
struct MJ_HT_V1Header_t {
|
|
uint8_t padding[3];
|
|
uint8_t mesSize;
|
|
uint8_t padding2;
|
|
uint16_t uuid;
|
|
uint16_t type;
|
|
uint8_t padding3[2];
|
|
uint8_t counter;
|
|
uint8_t serial[6];
|
|
uint8_t mode;
|
|
uint8_t padding5;
|
|
uint8_t effectiveDataLength;
|
|
};
|
|
|
|
struct FlowerHeader_t {
|
|
uint8_t padding[4];
|
|
uint8_t padding2;
|
|
uint16_t uuid;
|
|
uint8_t mesSize;
|
|
uint8_t padding22;
|
|
uint16_t uuid2;
|
|
uint16_t type;
|
|
uint8_t padding3[2];
|
|
uint8_t counter;
|
|
uint8_t serial[6];
|
|
uint8_t padding4;
|
|
uint8_t mode;
|
|
};
|
|
|
|
union floraPacket_t {
|
|
struct {
|
|
uint16_t idWord;
|
|
uint8_t padding;
|
|
uint8_t serial[6];
|
|
uint8_t padding4;
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint16_t data;
|
|
} T;
|
|
struct {
|
|
uint16_t idWord;
|
|
uint8_t padding;
|
|
uint8_t serial[6];
|
|
uint8_t padding4;
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint32_t data:24;
|
|
} L;
|
|
struct {
|
|
uint8_t padding[3];
|
|
uint8_t serial[6];
|
|
uint8_t padding4;
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint8_t data;
|
|
} M;
|
|
struct {
|
|
uint8_t padding[3];
|
|
uint8_t serial[6];
|
|
uint8_t padding4;
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint16_t data;
|
|
} F;
|
|
};
|
|
|
|
union MJ_HT_V1Packet_t {
|
|
struct {
|
|
uint16_t idWord;
|
|
uint8_t padding;
|
|
uint8_t serial[6];
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint16_t temp;
|
|
uint16_t hum;
|
|
} TH;
|
|
struct {
|
|
uint8_t padding[3];
|
|
uint8_t serial[6];
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint8_t battery;
|
|
} B;
|
|
|
|
};
|
|
|
|
union LYWSD02Packet_t {
|
|
struct {
|
|
uint16_t idWord;
|
|
uint8_t padding;
|
|
uint8_t serial[6];
|
|
uint8_t padding4;
|
|
uint8_t mode;
|
|
uint8_t valueTen;
|
|
uint8_t effectiveDataLength;
|
|
uint16_t data;
|
|
} TH;
|
|
};
|
|
|
|
struct bleAdvPacket_t {
|
|
uint8_t pduType;
|
|
uint8_t payloadSize;
|
|
uint8_t mac[6];
|
|
union {
|
|
uint8_t payload[24];
|
|
MJ_HT_V1Header_t header;
|
|
FlowerHeader_t flowerHeader;
|
|
struct {
|
|
uint8_t padding[21];
|
|
uint16_t temp;
|
|
uint8_t hum_lb;
|
|
} TH;
|
|
struct {
|
|
uint8_t padding[21];
|
|
uint16_t temp;
|
|
} T;
|
|
struct {
|
|
uint8_t padding[21];
|
|
uint16_t hum;
|
|
} H;
|
|
struct {
|
|
uint8_t padding[21];
|
|
uint8_t battery;
|
|
} B;
|
|
struct {
|
|
uint8_t padding[2];
|
|
uint8_t mode;
|
|
uint16_t size;
|
|
uint16_t data;
|
|
} F_T;
|
|
struct {
|
|
uint8_t padding[2];
|
|
uint8_t mode;
|
|
uint16_t size;
|
|
uint16_t data;
|
|
uint8_t data2;
|
|
} F_L;
|
|
struct {
|
|
uint8_t padding[2];
|
|
uint8_t mode;
|
|
uint16_t size;
|
|
uint8_t data;
|
|
} F_M;
|
|
struct {
|
|
uint8_t padding[2];
|
|
uint8_t mode;
|
|
uint16_t size;
|
|
uint16_t data;
|
|
} F_F;
|
|
};
|
|
};
|
|
|
|
union FIFO_t{
|
|
bleAdvPacket_t bleAdv;
|
|
floraPacket_t floraPacket;
|
|
MJ_HT_V1Packet_t MJ_HT_V1Packet;
|
|
LYWSD02Packet_t LYWSD02Packet;
|
|
uint8_t raw[32];
|
|
};
|
|
|
|
#pragma pack(0)
|
|
|
|
struct {
|
|
const uint8_t channel[3] = {37,38,39};
|
|
const uint8_t frequency[3] = { 2,26,80};
|
|
|
|
uint16_t timer;
|
|
uint8_t currentChan=0;
|
|
FIFO_t buffer;
|
|
uint8_t packetMode;
|
|
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
uint8_t streamBuffer[sizeof(buffer)];
|
|
uint8_t lsfrBuffer[sizeof(buffer)];
|
|
#endif
|
|
|
|
} MINRF;
|
|
|
|
struct mi_sensor_t{
|
|
uint8_t type;
|
|
uint8_t serial[6];
|
|
uint8_t showedUp;
|
|
float temp;
|
|
union {
|
|
struct {
|
|
float moisture;
|
|
float fertility;
|
|
uint32_t lux;
|
|
};
|
|
struct {
|
|
float hum;
|
|
uint8_t bat;
|
|
};
|
|
};
|
|
};
|
|
|
|
std::vector<mi_sensor_t> MIBLEsensors;
|
|
|
|
|
|
|
|
|
|
bool MINRFinitBLE(uint8_t _mode)
|
|
{
|
|
if (MINRF.timer%1000 == 0){
|
|
NRF24radio.begin(pin[GPIO_SPI_CS],pin[GPIO_SPI_DC]);
|
|
NRF24radio.setAutoAck(false);
|
|
NRF24radio.setDataRate(RF24_1MBPS);
|
|
NRF24radio.disableCRC();
|
|
NRF24radio.setChannel( MINRF.frequency[MINRF.currentChan] );
|
|
NRF24radio.setRetries(0,0);
|
|
NRF24radio.setPALevel(RF24_PA_MIN);
|
|
NRF24radio.setAddressWidth(4);
|
|
|
|
|
|
NRF24radio.powerUp();
|
|
}
|
|
if(NRF24radio.isChipConnected()){
|
|
|
|
MINRFchangePacketModeTo(_mode);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MINRFhopChannel()
|
|
{
|
|
MINRF.currentChan++;
|
|
if(MINRF.currentChan >= sizeof(MINRF.channel)) {
|
|
MINRF.currentChan = 0;
|
|
}
|
|
NRF24radio.setChannel( MINRF.frequency[MINRF.currentChan] );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool MINRFreceivePacket(void)
|
|
{
|
|
if(!NRF24radio.available()) {
|
|
return false;
|
|
}
|
|
while(NRF24radio.available()) {
|
|
|
|
|
|
NRF24radio.read( &MINRF.buffer, sizeof(MINRF.buffer) );
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
memcpy(&MINRF.streamBuffer, &MINRF.buffer, sizeof(MINRF.buffer));
|
|
#endif
|
|
MINRFswapbuf( sizeof(MINRF.buffer) );
|
|
|
|
|
|
|
|
switch (MINRF.packetMode) {
|
|
case 0:
|
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), MINRF.channel[MINRF.currentChan] | 0x40);
|
|
break;
|
|
case 1:
|
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_A[MINRF.currentChan]);
|
|
break;
|
|
case 2:
|
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]);
|
|
break;
|
|
case 3:
|
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_A[MINRF.currentChan]);
|
|
break;
|
|
case 4:
|
|
MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]);
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
void MINRFshowBuffer(uint8_t (&buf)[32]){
|
|
|
|
|
|
|
|
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Buffer: %02x %02x %02x %02x %02x %02x %02x %02x "
|
|
"%02x %02x %02x %02x %02x %02x %02x %02x "
|
|
"%02x %02x %02x %02x %02x %02x %02x %02x "
|
|
"%02x %02x %02x %02x %02x %02x %02x %02x ")
|
|
,buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7],buf[8],buf[9],buf[10],buf[11],
|
|
buf[12],buf[13],buf[14],buf[15],buf[16],buf[17],buf[18],buf[19],buf[20],buf[21],buf[22],buf[23],
|
|
buf[24],buf[25],buf[26],buf[27],buf[28],buf[29],buf[30],buf[31]
|
|
);
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MINRFswapbuf(uint8_t len)
|
|
{
|
|
uint8_t* buf = (uint8_t*)&MINRF.buffer;
|
|
while(len--) {
|
|
uint8_t a = *buf;
|
|
uint8_t v = 0;
|
|
if (a & 0x80) v |= 0x01;
|
|
if (a & 0x40) v |= 0x02;
|
|
if (a & 0x20) v |= 0x04;
|
|
if (a & 0x10) v |= 0x08;
|
|
if (a & 0x08) v |= 0x10;
|
|
if (a & 0x04) v |= 0x20;
|
|
if (a & 0x02) v |= 0x40;
|
|
if (a & 0x01) v |= 0x80;
|
|
*(buf++) = v;
|
|
}
|
|
}
|
|
# 420 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
|
|
void MINRFwhiten(uint8_t *buf, uint8_t len, uint8_t lfsr)
|
|
{
|
|
while(len--) {
|
|
uint8_t res = 0;
|
|
|
|
for (uint8_t i = 1; i; i <<= 1) {
|
|
if (lfsr & 0x01) {
|
|
lfsr ^= 0x88;
|
|
res |= i;
|
|
}
|
|
lfsr >>= 1;
|
|
}
|
|
*(buf++) ^= res;
|
|
#ifdef DEBUG_TASMOTA_SENSOR
|
|
MINRF.lsfrBuffer[31-len] = lfsr;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void MINRFreverseMAC(uint8_t _mac[]){
|
|
uint8_t _reversedMAC[6];
|
|
for (uint8_t i=0; i<6; i++){
|
|
_reversedMAC[5-i] = _mac[i];
|
|
}
|
|
memcpy(_mac,_reversedMAC, sizeof(_reversedMAC));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MINRFchangePacketModeTo(uint8_t _mode) {
|
|
uint32_t (_nextchannel) = MINRF.currentChan+1;
|
|
if (_nextchannel>2) _nextchannel=0;
|
|
|
|
switch(_mode){
|
|
case 0:
|
|
NRF24radio.openReadingPipe(0,0x6B7D9171);
|
|
break;
|
|
case 1:
|
|
NRF24radio.openReadingPipe(0,kMINRFFloPDU[_nextchannel]);
|
|
break;
|
|
case 2:
|
|
NRF24radio.openReadingPipe(0,kMINRFMJPDU[_nextchannel]);
|
|
break;
|
|
case 3:
|
|
NRF24radio.openReadingPipe(0,kMINRFL2PDU[_nextchannel]);
|
|
break;
|
|
case 4:
|
|
if(kMINRFL3PDU[_nextchannel]==0xffffffff) break;
|
|
NRF24radio.openReadingPipe(0,kMINRFL3PDU[_nextchannel]);
|
|
break;
|
|
}
|
|
|
|
MINRF.packetMode = _mode;
|
|
}
|
|
# 485 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_61_MI_NRF24.ino"
|
|
uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){
|
|
if(_type==0xff){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: will test MAC-type"));
|
|
for (uint32_t i=0;i<4;i++){
|
|
if(memcmp(_serial,kMINRFSlaveID+i,3)==0){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: MAC is type %u"), i);
|
|
_type = i+1;
|
|
}
|
|
else {
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: MAC-type is unknown"));
|
|
}
|
|
}
|
|
}
|
|
if(_type==0xff) return _type;
|
|
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: vector size %u"), MIBLEsensors.size());
|
|
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
|
|
if(memcmp(_serial,MIBLEsensors.at(i).serial,sizeof(_serial))==0){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: known sensor at slot: %u"), i);
|
|
if(MIBLEsensors.at(i).showedUp < 3){
|
|
MIBLEsensors.at(i).showedUp++;
|
|
}
|
|
return i;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF i: %x %x %x %x %x %x"), MIBLEsensors.at(i).serial[5], MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[0]);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF n: %x %x %x %x %x %x"), _serial[5], _serial[4], _serial[3],_serial[2],_serial[1],_serial[0]);
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: found new sensor"));
|
|
mi_sensor_t _newSensor;
|
|
memcpy(_newSensor.serial,_serial, sizeof(_serial));
|
|
_newSensor.type = _type;
|
|
_newSensor.showedUp = 1;
|
|
_newSensor.temp =-1000.0f;
|
|
switch (_type)
|
|
{
|
|
case 1:
|
|
_newSensor.moisture =-1000.0f;
|
|
_newSensor.fertility =-1000.0f;
|
|
_newSensor.lux = 0x00ffffff;
|
|
break;
|
|
case 2: case 3: case 4:
|
|
_newSensor.hum=-1.0f;
|
|
_newSensor.bat=0xff;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
MIBLEsensors.push_back(_newSensor);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: new sensor at slot: %u"), MIBLEsensors.size()-1);
|
|
return MIBLEsensors.size()-1;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MINRFpurgeFakeSensors(void){
|
|
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
|
|
if(MIBLEsensors.at(i).showedUp<3){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: remove FAKE sensor at slot: %u"), i);
|
|
MIBLEsensors.erase(MIBLEsensors.begin()+i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MINRFhandleFloraPacket(void){
|
|
if(MINRF.buffer.floraPacket.T.idWord!=0x9800 && MINRF.buffer.floraPacket.T.valueTen!=0x10){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected Flora packet"));
|
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
|
return;
|
|
}
|
|
MINRFreverseMAC(MINRF.buffer.floraPacket.T.serial);
|
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.floraPacket.T.serial, 0xff);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
|
if(_slot==0xff) return;
|
|
|
|
static float _tempFloat;
|
|
switch(MINRF.buffer.floraPacket.L.mode) {
|
|
case 4:
|
|
_tempFloat=(float)(MINRF.buffer.floraPacket.T.data)/10.0f;
|
|
if(_tempFloat<60){
|
|
MIBLEsensors.at(_slot).temp=_tempFloat;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("Flora: Mode 4: U16: %x Temp"), MINRF.buffer.floraPacket.T.data );
|
|
break;
|
|
case 7:
|
|
if(true){
|
|
MIBLEsensors.at(_slot).lux=MINRF.buffer.floraPacket.L.data;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("Flora: Mode 7: U24: %x Lux"), MINRF.buffer.floraPacket.L.data);
|
|
break;
|
|
case 8:
|
|
_tempFloat =(float)MINRF.buffer.floraPacket.M.data;
|
|
if(_tempFloat<100){
|
|
MIBLEsensors.at(_slot).moisture=_tempFloat;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("Flora: Mode 8: U8: %x Moisture"), MINRF.buffer.floraPacket.M.data);
|
|
break;
|
|
case 9:
|
|
_tempFloat=(float)(MINRF.buffer.floraPacket.F.data);
|
|
if(_tempFloat<65535){
|
|
MIBLEsensors.at(_slot).fertility=_tempFloat;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("Mode 9: U16: %x Fertility"), MINRF.buffer.floraPacket.F.data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MINRFhandleMJ_HT_V1Packet(void){
|
|
if(MINRF.buffer.MJ_HT_V1Packet.TH.idWord != 0xaa01 && MINRF.buffer.MJ_HT_V1Packet.TH.valueTen!=0x10){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected MJ_HT_V1-packet"));
|
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
|
return;
|
|
}
|
|
MINRFreverseMAC(MINRF.buffer.MJ_HT_V1Packet.TH.serial);
|
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.MJ_HT_V1Packet.TH.serial, 0xff);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
|
if(_slot==0xff) return;
|
|
|
|
static float _tempFloat;
|
|
switch(MINRF.buffer.MJ_HT_V1Packet.TH.mode) {
|
|
case 0x0d:
|
|
_tempFloat=(float)(MINRF.buffer.MJ_HT_V1Packet.TH.temp)/10.0f;
|
|
if(_tempFloat<60){
|
|
MIBLEsensors.at(_slot).temp = _tempFloat;
|
|
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: temp updated"));
|
|
}
|
|
_tempFloat=(float)(MINRF.buffer.MJ_HT_V1Packet.TH.hum)/10.0f;
|
|
if(_tempFloat<100){
|
|
MIBLEsensors.at(_slot).hum = _tempFloat;
|
|
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: hum updated"));
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1 mode:0x0d: U16: %x Temp U16: %x Hum"), MINRF.buffer.MJ_HT_V1Packet.TH.temp, MINRF.buffer.MJ_HT_V1Packet.TH.hum);
|
|
break;
|
|
case 0x0a:
|
|
if(MINRF.buffer.MJ_HT_V1Packet.B.battery<101){
|
|
MIBLEsensors.at(_slot).bat = MINRF.buffer.MJ_HT_V1Packet.B.battery;
|
|
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1: bat updated"));
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("MJ_HT_V1 mode:0x0a: U8: %x %%"), MINRF.buffer.MJ_HT_V1Packet.B.battery);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MINRFhandleLYWSD02Packet(void){
|
|
if(MINRF.buffer.LYWSD02Packet.TH.valueTen!=0x10){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unexpected LYWSD02-packet"));
|
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
|
return;
|
|
}
|
|
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial);
|
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, 0xff);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
|
if(_slot==0xff) return;
|
|
|
|
static float _tempFloat;
|
|
switch(MINRF.buffer.LYWSD02Packet.TH.mode) {
|
|
case 4:
|
|
_tempFloat=(float)(MINRF.buffer.LYWSD02Packet.TH.data)/10.0f;
|
|
if(_tempFloat<60){
|
|
MIBLEsensors.at(_slot).temp=_tempFloat;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("LYWSD02: Mode 4: U16: %x Temp"), MINRF.buffer.LYWSD02Packet.TH.data );
|
|
break;
|
|
case 6:
|
|
_tempFloat=(float)(MINRF.buffer.LYWSD02Packet.TH.data)/10.0f;
|
|
if(_tempFloat<101){
|
|
MIBLEsensors.at(_slot).hum=_tempFloat;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("LYWSD02: Mode 6: U16: %x Hum"), MINRF.buffer.LYWSD02Packet.TH.data );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MINRFhandleLYWSD03Packet(void){
|
|
|
|
MINRFreverseMAC(MINRF.buffer.LYWSD02Packet.TH.serial);
|
|
uint32_t _slot = MINRFgetSensorSlot(MINRF.buffer.LYWSD02Packet.TH.serial, 0xff);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Sensor slot: %u"), _slot);
|
|
if(_slot==0xff) return;
|
|
|
|
MINRF_LOG_BUFFER(MINRF.streamBuffer);
|
|
MINRF_LOG_BUFFER(MINRF.lsfrBuffer);
|
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
|
}
|
|
|
|
void MINRF_EVERY_50_MSECOND() {
|
|
|
|
if(MINRF.timer>6000){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: check for FAKE sensors"));
|
|
MINRFpurgeFakeSensors();
|
|
MINRF.timer=0;
|
|
}
|
|
MINRF.timer++;
|
|
|
|
if (!MINRFreceivePacket()){
|
|
|
|
}
|
|
|
|
else if(MINRF.buffer.bleAdv.header.uuid==0xfe95){
|
|
MINRF_LOG_BUFFER(MINRF.streamBuffer);
|
|
MINRF_LOG_BUFFER(MINRF.lsfrBuffer);
|
|
MINRF_LOG_BUFFER(MINRF.buffer.raw);
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Type: %x"), MINRF.buffer.bleAdv.header.type);
|
|
switch(MINRF.buffer.bleAdv.header.type){
|
|
case 0x2050:
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: MJ_HT_V1 Packet"));
|
|
break;
|
|
case 0x1613:case 0x1614:case 0x1615:
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: Flora Packet"));
|
|
break;
|
|
default:
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: unknown Packet"));
|
|
break;
|
|
}
|
|
}
|
|
else if (MINRF.packetMode == FLORA){
|
|
MINRFhandleFloraPacket();
|
|
}
|
|
else if (MINRF.packetMode == MJ_HT_V1){
|
|
MINRFhandleMJ_HT_V1Packet();
|
|
}
|
|
else if (MINRF.packetMode == LYWSD02){
|
|
MINRFhandleLYWSD02Packet();
|
|
}
|
|
else if (MINRF.packetMode == LYWSD03){
|
|
MINRFhandleLYWSD03Packet();
|
|
}
|
|
|
|
|
|
if (MINRF.packetMode == LYWSD03){
|
|
MINRFinitBLE(1);
|
|
}
|
|
else {
|
|
MINRFinitBLE(++MINRF.packetMode);
|
|
}
|
|
|
|
MINRFhopChannel();
|
|
NRF24radio.startListening();
|
|
}
|
|
|
|
|
|
|
|
|
|
const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u%%{e}";
|
|
const char HTTP_MINRF_MAC[] PROGMEM = "{s}%s %s{m}%02x:%02x:%02x:%02x:%02x:%02x%{e}";
|
|
const char HTTP_MINRF_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%sus/cm{e}";
|
|
const char HTTP_MINRF_HL[] PROGMEM = "{s}<hr>{m}<hr>{e}";
|
|
|
|
void MINRFShow(bool json)
|
|
{
|
|
if (json) {
|
|
for (uint32_t i = 0; i < MIBLEsensors.size(); i++) {
|
|
if(MIBLEsensors.at(i).showedUp < 3){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: sensor not fully registered yet"));
|
|
break;
|
|
}
|
|
char slave[33];
|
|
sprintf_P(slave,"%s-%02x%02x%02x",kMINRFSlaveType[MIBLEsensors.at(i).type-1],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]);
|
|
char temperature[33];
|
|
dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature);
|
|
|
|
ResponseAppend_P(PSTR(",\"%s\":{"),slave);
|
|
if(MIBLEsensors.at(i).temp!=-1000.0f){
|
|
ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), temperature);
|
|
}
|
|
if (MIBLEsensors.at(i).type==FLORA){
|
|
char lux[33];
|
|
char moisture[33];
|
|
char fertility[33];
|
|
dtostrfd((float)MIBLEsensors.at(i).lux, 0, lux);
|
|
dtostrfd(MIBLEsensors.at(i).moisture, 0, moisture);
|
|
dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility);
|
|
if(MIBLEsensors.at(i).lux!=0xffff){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%s"), lux);
|
|
}
|
|
if(MIBLEsensors.at(i).moisture!=-1000.0f){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%s"), moisture);
|
|
}
|
|
if(MIBLEsensors.at(i).fertility!=-1000.0f){
|
|
ResponseAppend_P(PSTR(",\"Fertility\":%s"), fertility);
|
|
}
|
|
}
|
|
if (MIBLEsensors.at(i).type>FLORA){
|
|
char humidity[33];
|
|
dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity);
|
|
if(MIBLEsensors.at(i).hum!=-1.0f){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity);
|
|
}
|
|
if(MIBLEsensors.at(i).bat!=0xff){
|
|
ResponseAppend_P(PSTR(",\"Battery\":%u"), MIBLEsensors.at(i).bat);
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("}"));
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_NRF24, NRF24type, NRF24.chipType);
|
|
for (uint32_t i = 0; i < MIBLEsensors.size(); i++) {
|
|
if(MIBLEsensors.at(i).showedUp < 3){
|
|
DEBUG_SENSOR_LOG(PSTR("MINRF: sensor not fully registered yet"));
|
|
break;
|
|
}
|
|
WSContentSend_PD(HTTP_MINRF_HL);
|
|
WSContentSend_PD(HTTP_MINRF_MAC, kMINRFSlaveType[MIBLEsensors.at(i).type-1], D_MAC_ADDRESS, MIBLEsensors.at(i).serial[0], MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]);
|
|
if(MIBLEsensors.at(i).temp!=-1000.0f){
|
|
char temperature[33];
|
|
dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature);
|
|
WSContentSend_PD(HTTP_SNS_TEMP, kMINRFSlaveType[MIBLEsensors.at(i).type-1], temperature, TempUnit());
|
|
}
|
|
if (MIBLEsensors.at(i).type==FLORA){
|
|
if(MIBLEsensors.at(i).lux!=0x00ffffff){
|
|
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMINRFSlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).lux);
|
|
}
|
|
if(MIBLEsensors.at(i).moisture!=-1000.0f){
|
|
WSContentSend_PD(HTTP_SNS_MOISTURE, kMINRFSlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).moisture);
|
|
}
|
|
if(MIBLEsensors.at(i).fertility!=-1000.0f){
|
|
char fertility[33];
|
|
dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility);
|
|
WSContentSend_PD(HTTP_MINRF_FLORA_DATA, kMINRFSlaveType[MIBLEsensors.at(i).type-1], fertility);
|
|
}
|
|
}
|
|
if (MIBLEsensors.at(i).type>FLORA){
|
|
if(MIBLEsensors.at(i).hum!=-1.0f){
|
|
char humidity[33];
|
|
dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity);
|
|
WSContentSend_PD(HTTP_SNS_HUM, kMINRFSlaveType[MIBLEsensors.at(i).type-1], humidity);
|
|
}
|
|
if(MIBLEsensors.at(i).bat!=0xff){
|
|
WSContentSend_PD(HTTP_BATTERY, kMINRFSlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).bat);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns61(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if (NRF24.chipType) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
MINRFinitBLE(1);
|
|
AddLog_P2(LOG_LEVEL_INFO,PSTR("MINRF: started"));
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
MINRF_EVERY_50_MSECOND();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
MINRFShow(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
MINRFShow(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_62_MI_HM10.ino"
|
|
# 30 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_62_MI_HM10.ino"
|
|
#ifdef USE_HM10
|
|
|
|
#define XSNS_62 62
|
|
|
|
#include <TasmotaSerial.h>
|
|
#include <vector>
|
|
|
|
TasmotaSerial *HM10Serial;
|
|
#define HM10_BAUDRATE 115200
|
|
|
|
#define HM10_MAX_TASK_NUMBER 12
|
|
uint8_t HM10_TASK_LIST[HM10_MAX_TASK_NUMBER+1][2];
|
|
|
|
#define HM10_MAX_RX_BUF 512
|
|
char HM10_RX_STRING[HM10_MAX_RX_BUF] = {0};
|
|
|
|
struct {
|
|
uint8_t current_task_delay;
|
|
uint8_t last_command;
|
|
uint16_t firmware;
|
|
uint32_t period;
|
|
uint32_t serialSpeed;
|
|
union {
|
|
uint32_t time;
|
|
uint8_t timebuf[4];
|
|
};
|
|
struct {
|
|
uint32_t init:1;
|
|
uint32_t pending_task:1;
|
|
uint32_t connected:1;
|
|
uint32_t subscribed:1;
|
|
uint32_t awaitingHT:1;
|
|
uint32_t awaitingB:1;
|
|
|
|
} mode;
|
|
struct {
|
|
uint8_t sensor;
|
|
|
|
} state;
|
|
} HM10;
|
|
|
|
#pragma pack(1)
|
|
struct {
|
|
uint16_t temp;
|
|
uint8_t hum;
|
|
} LYWSD0x_HT;
|
|
#pragma pack(0)
|
|
|
|
struct mi_sensor_t{
|
|
uint8_t type;
|
|
uint8_t serial[6];
|
|
uint8_t showedUp;
|
|
float temp;
|
|
union {
|
|
struct {
|
|
float moisture;
|
|
float fertility;
|
|
uint16_t lux;
|
|
};
|
|
struct {
|
|
float hum;
|
|
uint8_t bat;
|
|
};
|
|
};
|
|
};
|
|
|
|
std::vector<mi_sensor_t> MIBLEsensors;
|
|
|
|
|
|
|
|
|
|
|
|
#define D_CMND_HM10 "HM10"
|
|
|
|
const char S_JSON_HM10_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_HM10 "%s\":%d}";
|
|
const char S_JSON_HM10_COMMAND[] PROGMEM = "{\"" D_CMND_HM10 "%s%s\"}";
|
|
const char kHM10_Commands[] PROGMEM = "Scan|AT|Period|Baud|Time";
|
|
|
|
#define FLORA 1
|
|
#define MJ_HT_V1 2
|
|
#define LYWSD02 3
|
|
#define LYWSD03MMC 4
|
|
|
|
uint8_t kHM10SlaveID[4][3] = { 0xC4,0x7C,0x8D,
|
|
0x58,0x2D,0x34,
|
|
0xE7,0x2E,0x00,
|
|
0xA4,0xC1,0x38,
|
|
};
|
|
|
|
const char kHM10SlaveType1[] PROGMEM = "Flora";
|
|
const char kHM10SlaveType2[] PROGMEM = "MJ_HT_V1";
|
|
const char kHM10SlaveType3[] PROGMEM = "LYWSD02";
|
|
const char kHM10SlaveType4[] PROGMEM = "LYWSD03";
|
|
const char * kHM10SlaveType[] PROGMEM = {kHM10SlaveType1,kHM10SlaveType2,kHM10SlaveType3,kHM10SlaveType4};
|
|
|
|
|
|
|
|
|
|
|
|
enum HM10_Commands {
|
|
CMND_HM10_DISC_SCAN,
|
|
CMND_HM10_AT,
|
|
CMND_HM10_PERIOD,
|
|
CMND_HM10_BAUD,
|
|
CMND_HM10_TIME
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#define TASK_HM10_NOTASK 0
|
|
#define TASK_HM10_ROLE1 1
|
|
#define TASK_HM10_IMME1 2
|
|
#define TASK_HM10_RENEW 3
|
|
#define TASK_HM10_RESET 4
|
|
#define TASK_HM10_DISC 5
|
|
#define TASK_HM10_CONN 6
|
|
#define TASK_HM10_VERSION 7
|
|
#define TASK_HM10_NAME 8
|
|
#define TASK_HM10_FEEDBACK 9
|
|
#define TASK_HM10_DISCONN 10
|
|
#define TASK_HM10_SUB_L3 11
|
|
#define TASK_HM10_READ_HT 12
|
|
#define TASK_HM10_FINDALLCHARS 13
|
|
#define TASK_HM10_UN_L3 14
|
|
#define TASK_HM10_DELAY_SUB 15
|
|
#define TASK_HM10_READ_BT_L3 16
|
|
#define TASK_HM10_SUB_L2 17
|
|
#define TASK_HM10_UN_L2 18
|
|
#define TASK_HM10_READ_BT_L2 19
|
|
#define TASK_HM10_TIME_L2 20
|
|
|
|
#define TASK_HM10_DONE 99
|
|
|
|
|
|
|
|
|
|
|
|
void HM10_Launchtask(uint8_t task, uint8_t slot, uint8_t delay){
|
|
HM10_TASK_LIST[slot][0] = task;
|
|
HM10_TASK_LIST[slot][1] = delay;
|
|
HM10_TASK_LIST[slot+1][0] = TASK_HM10_NOTASK;
|
|
HM10.current_task_delay = HM10_TASK_LIST[0][1];
|
|
}
|
|
|
|
void HM10_TaskReplaceInSlot(uint8_t task, uint8_t slot){
|
|
HM10.last_command = HM10_TASK_LIST[slot][0];
|
|
HM10_TASK_LIST[slot][0] = task;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HM10_Reset(void) { HM10_Launchtask(TASK_HM10_DISCONN,0,1);
|
|
HM10_Launchtask(TASK_HM10_ROLE1,1,1);
|
|
HM10_Launchtask(TASK_HM10_IMME1,2,1);
|
|
HM10_Launchtask(TASK_HM10_RESET,3,1);
|
|
HM10_Launchtask(TASK_HM10_VERSION,4,10);
|
|
HM10_Launchtask(TASK_HM10_DISC,5,50);
|
|
}
|
|
|
|
void HM10_Discovery_Scan(void) {
|
|
HM10_Launchtask(TASK_HM10_DISCONN,0,1);
|
|
HM10_Launchtask(TASK_HM10_DISC,1,1);
|
|
}
|
|
|
|
void HM10_Read_LYWSD03(void) {
|
|
HM10_Launchtask(TASK_HM10_CONN,0,1);
|
|
HM10_Launchtask(TASK_HM10_FEEDBACK,1,35);
|
|
HM10_Launchtask(TASK_HM10_SUB_L3,2,20);
|
|
HM10_Launchtask(TASK_HM10_UN_L3,3,80);
|
|
HM10_Launchtask(TASK_HM10_READ_BT_L3,4,5);
|
|
HM10_Launchtask(TASK_HM10_DISCONN,5,5);
|
|
}
|
|
|
|
void HM10_Read_LYWSD02(void) {
|
|
HM10_Launchtask(TASK_HM10_CONN,0,1);
|
|
HM10_Launchtask(TASK_HM10_FEEDBACK,1,35);
|
|
HM10_Launchtask(TASK_HM10_SUB_L2,2,20);
|
|
HM10_Launchtask(TASK_HM10_UN_L2,3,80);
|
|
HM10_Launchtask(TASK_HM10_READ_BT_L2,4,5);
|
|
HM10_Launchtask(TASK_HM10_DISCONN,5,5);
|
|
}
|
|
|
|
void HM10_Time_LYWSD02(void) {
|
|
HM10_Launchtask(TASK_HM10_DISCONN,0,0);
|
|
HM10_Launchtask(TASK_HM10_CONN,1,5);
|
|
HM10_Launchtask(TASK_HM10_FEEDBACK,2,35);
|
|
HM10_Launchtask(TASK_HM10_TIME_L2,3,20);
|
|
HM10_Launchtask(TASK_HM10_DISCONN,4,5);
|
|
}
|
|
# 231 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_62_MI_HM10.ino"
|
|
uint32_t MIBLEgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){
|
|
if(_type==0xff){
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: will test MAC-type"));
|
|
for (uint32_t i=0;i<4;i++){
|
|
if(memcmp(_serial,kHM10SlaveID+i,3)==0){
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: MAC is type %u"), i);
|
|
_type = i+1;
|
|
}
|
|
else {
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: MAC-type is unknown"));
|
|
}
|
|
}
|
|
}
|
|
if(_type==0xff) return _type;
|
|
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: vector size %u"), MIBLEsensors.size());
|
|
for(uint32_t i=0; i<MIBLEsensors.size(); i++){
|
|
if(memcmp(_serial,MIBLEsensors.at(i).serial,sizeof(_serial))==0){
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: known sensor at slot: %u"), i);
|
|
if(MIBLEsensors.at(i).showedUp < 3){
|
|
MIBLEsensors.at(i).showedUp++;
|
|
}
|
|
return i;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE i: %x %x %x %x %x %x"), MIBLEsensors.at(i).serial[0], MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]);
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE n: %x %x %x %x %x %x"), _serial[0], _serial[1], _serial[2],_serial[3],_serial[4],_serial[5]);
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: found new sensor"));
|
|
mi_sensor_t _newSensor;
|
|
memcpy(_newSensor.serial,_serial, sizeof(_serial));
|
|
_newSensor.type = _type;
|
|
_newSensor.showedUp = 1;
|
|
_newSensor.temp =-1000.0f;
|
|
switch (_type)
|
|
{
|
|
case 1:
|
|
_newSensor.moisture =-1000.0f;
|
|
_newSensor.fertility =-1000.0f;
|
|
_newSensor.lux = 0xffff;
|
|
break;
|
|
case 2: case 3: case 4:
|
|
_newSensor.hum=-1.0f;
|
|
_newSensor.bat=0xff;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
MIBLEsensors.push_back(_newSensor);
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: new sensor at slot: %u"), MIBLEsensors.size()-1);
|
|
return MIBLEsensors.size()-1;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void HM10SerialInit(void) {
|
|
HM10.mode.init = false;
|
|
HM10.serialSpeed = HM10_BAUDRATE;
|
|
HM10Serial = new TasmotaSerial(pin[GPIO_HM10_RX], pin[GPIO_HM10_TX], 1, 0, HM10_MAX_RX_BUF);
|
|
if (HM10Serial->begin(HM10.serialSpeed)) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s start serial communication fixed to 115200 baud"),D_CMND_HM10);
|
|
if (HM10Serial->hardwareSerial()) {
|
|
ClaimSerial();
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: claim HW"));
|
|
}
|
|
HM10_Reset();
|
|
HM10.mode.pending_task = 1;
|
|
HM10.mode.init = 1;
|
|
HM10.period = Settings.tele_period;
|
|
DEBUG_SENSOR_LOG(PSTR("%s_TASK_LIST initialized, now return to main loop"),D_CMND_HM10);
|
|
}
|
|
return;
|
|
}
|
|
# 315 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_62_MI_HM10.ino"
|
|
void HM10MACStringToBytes(const char* string, uint8_t _mac[]) {
|
|
uint32_t index = 0;
|
|
while (index < 12) {
|
|
char c = string[index];
|
|
uint32_t value = 0;
|
|
if(c >= '0' && c <= '9')
|
|
value = (c - '0');
|
|
else if (c >= 'A' && c <= 'F')
|
|
value = (10 + (c - 'A'));
|
|
_mac[(index/2)] += value << (((index + 1) % 2) * 4);
|
|
|
|
index++;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: MAC-array: %x%x%x%x%x%x"),_mac[0],_mac[1],_mac[2],_mac[3],_mac[4],_mac[5]);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void HM10ParseResponse(char *buf) {
|
|
if (!strncmp(buf,"OK",2)) {
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: got OK"));
|
|
}
|
|
if (!strncmp(buf,"HMSoft",6)) {
|
|
const char* _fw = "000";
|
|
memcpy((void *)_fw,(void *)(buf+8),3);
|
|
HM10.firmware = atoi(_fw);
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: Firmware: %d"), HM10.firmware);
|
|
return;
|
|
}
|
|
char * _pos = strstr(buf, "IS0:");
|
|
if(_pos) {
|
|
const char* _mac = "000000000000";
|
|
memcpy((void *)_mac,(void *)(_pos+4),12);
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: found Mac: %s"), _mac);
|
|
uint8_t _newMacArray[6] = {0};
|
|
HM10MACStringToBytes(_mac, _newMacArray);
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: MAC-array: %x%x%x%x%x%x"),_newMacArray[0],_newMacArray[1],_newMacArray[2],_newMacArray[3],_newMacArray[4],_newMacArray[5]);
|
|
MIBLEgetSensorSlot(_newMacArray, 0xff);
|
|
}
|
|
if (strstr(buf, "LOST")){
|
|
HM10.mode.connected = false;
|
|
}
|
|
else {
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: empty response"));
|
|
}
|
|
}
|
|
|
|
void HM10readTempHum(char *_buf){
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: raw data: %x%x%x%x%x%x%x"),_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]);
|
|
if(_buf[0] != 0 && _buf[1] != 0){
|
|
memcpy(&LYWSD0x_HT,(void *)_buf,3);
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: Temperature * 100: %u, Humidity: %u"),LYWSD0x_HT.temp,LYWSD0x_HT.hum);
|
|
uint32_t _slot = HM10.state.sensor;
|
|
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot);
|
|
static float _tempFloat;
|
|
_tempFloat=(float)(LYWSD0x_HT.temp)/100.0f;
|
|
if(_tempFloat<60){
|
|
MIBLEsensors.at(_slot).temp=_tempFloat;
|
|
HM10.mode.awaitingHT = false;
|
|
HM10.current_task_delay = 0;
|
|
}
|
|
_tempFloat=(float)LYWSD0x_HT.hum;
|
|
if(_tempFloat<100){
|
|
MIBLEsensors.at(_slot).hum = _tempFloat;
|
|
DEBUG_SENSOR_LOG(PSTR("LYWSD03: hum updated"));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HM10readBat(char *_buf){
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: raw data: %x%x%x%x%x%x%x"),_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]);
|
|
if(_buf[0] != 0){
|
|
DEBUG_SENSOR_LOG(PSTR("HM10: Battery: %u"),_buf[0]);
|
|
uint32_t _slot = HM10.state.sensor;
|
|
DEBUG_SENSOR_LOG(PSTR("MIBLE: Sensor slot: %u"), _slot);
|
|
if(_buf[0]<101){
|
|
MIBLEsensors.at(_slot).bat=_buf[0];
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool HM10SerialHandleFeedback(){
|
|
bool success = false;
|
|
uint32_t i = 0;
|
|
char ret[HM10_MAX_RX_BUF] = {0};
|
|
|
|
|
|
while(HM10Serial->available()) {
|
|
|
|
if(i<HM10_MAX_RX_BUF){
|
|
ret[i] = HM10Serial->read();
|
|
}
|
|
i++;
|
|
success = true;
|
|
}
|
|
if(HM10.mode.awaitingHT) {
|
|
if (HM10.mode.connected) HM10readTempHum(ret);
|
|
}
|
|
else if(HM10.mode.awaitingB) {
|
|
if (HM10.mode.connected) {
|
|
if (HM10readBat(ret)){
|
|
HM10.mode.awaitingB = false;
|
|
HM10.current_task_delay = 0;
|
|
}
|
|
}
|
|
}
|
|
else if(success) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s response: %s"),D_CMND_HM10, (char *)ret);
|
|
HM10ParseResponse(ret);
|
|
}
|
|
else {
|
|
|
|
}
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HM10_TaskEvery100ms(){
|
|
if (HM10.current_task_delay == 0) {
|
|
uint8_t i = 0;
|
|
bool runningTaskLoop = true;
|
|
while (runningTaskLoop) {
|
|
switch(HM10_TASK_LIST[i][0]) {
|
|
case TASK_HM10_ROLE1:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s set role to 1"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+ROLE1");
|
|
break;
|
|
case TASK_HM10_IMME1:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s set imme to 1"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+IMME1");
|
|
break;
|
|
case TASK_HM10_DISC:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s start discovery"),D_CMND_HM10);
|
|
HM10.current_task_delay = 35;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+DISC?");
|
|
break;
|
|
case TASK_HM10_VERSION:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read version"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+VERR?");
|
|
break;
|
|
case TASK_HM10_NAME:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read name"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+NAME?");
|
|
break;
|
|
case TASK_HM10_CONN:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s connect"),D_CMND_HM10);
|
|
HM10.current_task_delay = 2;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
char _con[20];
|
|
sprintf_P(_con,"AT+CON%02x%02x%02x%02x%02x%02x",MIBLEsensors.at(HM10.state.sensor).serial[0],MIBLEsensors.at(HM10.state.sensor).serial[1],MIBLEsensors.at(HM10.state.sensor).serial[2],MIBLEsensors.at(HM10.state.sensor).serial[3],MIBLEsensors.at(HM10.state.sensor).serial[4],MIBLEsensors.at(HM10.state.sensor).serial[5]);
|
|
HM10Serial->write(_con);
|
|
HM10.mode.awaitingB = false;
|
|
HM10.mode.connected = true;
|
|
break;
|
|
case TASK_HM10_DISCONN:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s disconnect"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT");
|
|
break;
|
|
case TASK_HM10_RESET:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s Reset Device"),D_CMND_HM10);
|
|
HM10Serial->write("AT+RESET");
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
break;
|
|
case TASK_HM10_SUB_L3:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s subscribe"),D_CMND_HM10);
|
|
HM10.current_task_delay = 25;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_DELAY_SUB,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+NOTIFY_ON0037");
|
|
break;
|
|
case TASK_HM10_UN_L3:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s un-subscribe"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10.mode.awaitingHT = false;
|
|
HM10Serial->write("AT+NOTIFYOFF0037");
|
|
break;
|
|
case TASK_HM10_SUB_L2:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s subscribe"),D_CMND_HM10);
|
|
HM10.current_task_delay = 25;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_DELAY_SUB,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+NOTIFY_ON003C");
|
|
break;
|
|
case TASK_HM10_UN_L2:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s un-subscribe"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10.mode.awaitingHT = false;
|
|
HM10Serial->write("AT+NOTIFYOFF003C");
|
|
break;
|
|
case TASK_HM10_TIME_L2:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s set time"),D_CMND_HM10);
|
|
HM10.current_task_delay = 5;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10.time = Rtc.utc_time;
|
|
HM10Serial->write("AT+SEND_DATAWR002F");
|
|
HM10Serial->write(HM10.timebuf,4);
|
|
HM10Serial->write(Rtc.time_timezone / 60);
|
|
AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s Time-string: %x%x%x%x%x"),D_CMND_HM10, HM10.timebuf[0],HM10.timebuf[1],HM10.timebuf[2],HM10.timebuf[3],(Rtc.time_timezone /60));
|
|
break;
|
|
case TASK_HM10_READ_HT:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read handle 0036"),D_CMND_HM10);
|
|
HM10.current_task_delay = 0;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+READDATA0036?");
|
|
HM10.mode.awaitingHT = true;
|
|
break;
|
|
case TASK_HM10_READ_BT_L3:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read handle 003A"),D_CMND_HM10);
|
|
HM10.current_task_delay = 2;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+READDATA003A?");
|
|
HM10.mode.awaitingB = true;
|
|
break;
|
|
case TASK_HM10_READ_BT_L2:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s read handle 0043"),D_CMND_HM10);
|
|
HM10.current_task_delay = 2;
|
|
HM10_TaskReplaceInSlot(TASK_HM10_FEEDBACK,i);
|
|
runningTaskLoop = false;
|
|
HM10Serial->write("AT+READDATA0043?");
|
|
HM10.mode.awaitingB = true;
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case TASK_HM10_FEEDBACK:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s get response"),D_CMND_HM10);
|
|
HM10SerialHandleFeedback();
|
|
HM10.current_task_delay = HM10_TASK_LIST[i+1][1];;
|
|
HM10_TASK_LIST[i][0] = TASK_HM10_DONE;
|
|
runningTaskLoop = false;
|
|
break;
|
|
case TASK_HM10_DELAY_SUB:
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%s start reading"),D_CMND_HM10);
|
|
HM10SerialHandleFeedback();
|
|
HM10.current_task_delay = HM10_TASK_LIST[i+1][1];;
|
|
HM10_TASK_LIST[i][0] = TASK_HM10_DONE;
|
|
HM10.mode.awaitingHT = true;
|
|
runningTaskLoop = false;
|
|
break;
|
|
case TASK_HM10_DONE:
|
|
|
|
|
|
if(HM10_TASK_LIST[i+1][0] == TASK_HM10_NOTASK) {
|
|
DEBUG_SENSOR_LOG(PSTR("%sno Tasks left"),D_CMND_HM10);
|
|
DEBUG_SENSOR_LOG(PSTR("%sHM10_TASK_DONE current slot %u"),D_CMND_HM10, i);
|
|
for (uint8_t j = 0; j < HM10_MAX_TASK_NUMBER+1; j++) {
|
|
DEBUG_SENSOR_LOG(PSTR("%sHM10_TASK cleanup slot %u"),D_CMND_HM10, j);
|
|
HM10_TASK_LIST[j][0] = TASK_HM10_NOTASK;
|
|
HM10_TASK_LIST[j][1] = 0;
|
|
}
|
|
runningTaskLoop = false;
|
|
HM10.mode.pending_task = 0;
|
|
break;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
else {
|
|
HM10.current_task_delay--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void HM10EverySecond(){
|
|
if(HM10.firmware == 0) return;
|
|
if(HM10.mode.pending_task == 1) return;
|
|
if (MIBLEsensors.size()==0) return;
|
|
|
|
static uint32_t _counter = 0;
|
|
static uint32_t _nextSensorSlot = 0;
|
|
if(_counter==0) {
|
|
HM10.state.sensor = _nextSensorSlot;
|
|
_nextSensorSlot++;
|
|
if(MIBLEsensors.at(HM10.state.sensor).type==LYWSD03MMC) {
|
|
HM10.mode.pending_task = 1;
|
|
HM10_Read_LYWSD03();
|
|
}
|
|
if(MIBLEsensors.at(HM10.state.sensor).type==LYWSD02) {
|
|
HM10.mode.pending_task = 1;
|
|
HM10_Read_LYWSD02();
|
|
}
|
|
if (HM10.state.sensor==MIBLEsensors.size()-1) {
|
|
_nextSensorSlot= 0;
|
|
_counter++;
|
|
}
|
|
DEBUG_SENSOR_LOG(PSTR("%s active sensor now: %u"),D_CMND_HM10, HM10.state.sensor);
|
|
}
|
|
else _counter++;
|
|
if (_counter>HM10.period) _counter = 0;
|
|
}
|
|
|
|
bool HM10Cmd(void) {
|
|
char command[CMDSZ];
|
|
bool serviced = true;
|
|
uint8_t disp_len = strlen(D_CMND_HM10);
|
|
|
|
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_HM10), disp_len)) {
|
|
uint32_t command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kHM10_Commands);
|
|
switch (command_code) {
|
|
case CMND_HM10_PERIOD:
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (command_code == CMND_HM10_PERIOD) { HM10.period = XdrvMailbox.payload; }
|
|
}
|
|
else {
|
|
if (command_code == CMND_HM10_PERIOD) XdrvMailbox.payload = HM10.period;
|
|
}
|
|
Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload);
|
|
break;
|
|
case CMND_HM10_BAUD:
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if (command_code == CMND_HM10_BAUD) {
|
|
HM10.serialSpeed = XdrvMailbox.payload;
|
|
HM10Serial->begin(HM10.serialSpeed);
|
|
}
|
|
}
|
|
else {
|
|
if (command_code == CMND_HM10_BAUD) XdrvMailbox.payload = HM10.serialSpeed;
|
|
}
|
|
Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload);
|
|
break;
|
|
case CMND_HM10_TIME:
|
|
if (XdrvMailbox.data_len > 0) {
|
|
if(MIBLEsensors.size()>XdrvMailbox.payload){
|
|
if(MIBLEsensors.at(XdrvMailbox.payload).type == LYWSD02){
|
|
HM10.state.sensor = XdrvMailbox.payload;
|
|
HM10_Time_LYWSD02();
|
|
}
|
|
}
|
|
}
|
|
Response_P(S_JSON_HM10_COMMAND_NVALUE, command, XdrvMailbox.payload);
|
|
break;
|
|
case CMND_HM10_AT:
|
|
HM10Serial->write("AT");
|
|
if (strlen(XdrvMailbox.data)!=0) {
|
|
HM10Serial->write("+");
|
|
HM10Serial->write(XdrvMailbox.data);
|
|
Response_P(S_JSON_HM10_COMMAND, ":AT+",XdrvMailbox.data);
|
|
}
|
|
else Response_P(S_JSON_HM10_COMMAND, ":AT",XdrvMailbox.data);
|
|
break;
|
|
case CMND_HM10_DISC_SCAN:
|
|
if (command_code == CMND_HM10_DISC_SCAN) { HM10_Discovery_Scan(); }
|
|
Response_P(S_JSON_HM10_COMMAND, command, "");
|
|
break;
|
|
default:
|
|
|
|
serviced = false;
|
|
break;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return serviced;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const char HTTP_HM10[] PROGMEM = "{s}HM10" " Firmware " "{m}%u{e}";
|
|
const char HTTP_HM10_SERIAL[] PROGMEM = "{s}%s %s{m}%02x:%02x:%02x:%02x:%02x:%02x%{e}";
|
|
const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u%%{e}";
|
|
const char HTTP_HM10_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%sus/cm{e}";
|
|
const char HTTP_HM10_HL[] PROGMEM = "{s}<hr>{m}<hr>{e}";
|
|
|
|
void HM10Show(bool json)
|
|
{
|
|
if (json) {
|
|
for (uint32_t i = 0; i < MIBLEsensors.size(); i++) {
|
|
char slave[33];
|
|
sprintf_P(slave,"%s-%02x%02x%02x",kHM10SlaveType[MIBLEsensors.at(i).type-1],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]);
|
|
char temperature[33];
|
|
dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature);
|
|
|
|
ResponseAppend_P(PSTR(",\"%s\":{"),slave);
|
|
if(MIBLEsensors.at(i).temp!=-1000.0f){
|
|
ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s"), temperature);
|
|
}
|
|
if (MIBLEsensors.at(i).type==FLORA){
|
|
char lux[33];
|
|
char moisture[33];
|
|
char fertility[33];
|
|
dtostrfd((float)MIBLEsensors.at(i).lux, 0, lux);
|
|
dtostrfd(MIBLEsensors.at(i).moisture, 0, moisture);
|
|
dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility);
|
|
if(MIBLEsensors.at(i).lux!=0xffff){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%s"), lux);
|
|
}
|
|
if(MIBLEsensors.at(i).moisture!=-1000.0f){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%s"), moisture);
|
|
}
|
|
if(MIBLEsensors.at(i).fertility!=-1000.0f){
|
|
ResponseAppend_P(PSTR(",\"Fertility\":%s"), fertility);
|
|
}
|
|
}
|
|
if (MIBLEsensors.at(i).type>FLORA){
|
|
char humidity[33];
|
|
dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity);
|
|
if(MIBLEsensors.at(i).hum!=-1.0f){
|
|
ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), humidity);
|
|
}
|
|
if(MIBLEsensors.at(i).bat!=0xff){
|
|
ResponseAppend_P(PSTR(",\"Battery\":%u"), MIBLEsensors.at(i).bat);
|
|
}
|
|
}
|
|
ResponseAppend_P(PSTR("}"));
|
|
}
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_HM10, HM10.firmware);
|
|
for (uint32_t i = 0; i < MIBLEsensors.size(); i++) {
|
|
WSContentSend_PD(HTTP_HM10_HL);
|
|
WSContentSend_PD(HTTP_HM10_SERIAL, kHM10SlaveType[MIBLEsensors.at(i).type-1], D_MAC_ADDRESS, MIBLEsensors.at(i).serial[0], MIBLEsensors.at(i).serial[1],MIBLEsensors.at(i).serial[2],MIBLEsensors.at(i).serial[3],MIBLEsensors.at(i).serial[4],MIBLEsensors.at(i).serial[5]);
|
|
if(MIBLEsensors.at(i).temp!=-1000.0f){
|
|
char temperature[33];
|
|
dtostrfd(MIBLEsensors.at(i).temp, Settings.flag2.temperature_resolution, temperature);
|
|
WSContentSend_PD(HTTP_SNS_TEMP, kHM10SlaveType[MIBLEsensors.at(i).type-1], temperature, TempUnit());
|
|
}
|
|
if (MIBLEsensors.at(i).type==FLORA){
|
|
if(MIBLEsensors.at(i).lux!=0xffff){
|
|
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).lux);
|
|
}
|
|
if(MIBLEsensors.at(i).moisture!=-1000.0f){
|
|
WSContentSend_PD(HTTP_SNS_MOISTURE, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).moisture);
|
|
}
|
|
if(MIBLEsensors.at(i).fertility!=-1000.0f){
|
|
char fertility[33];
|
|
dtostrfd(MIBLEsensors.at(i).fertility, 0, fertility);
|
|
WSContentSend_PD(HTTP_HM10_FLORA_DATA, kHM10SlaveType[MIBLEsensors.at(i).type-1], fertility);
|
|
}
|
|
}
|
|
if (MIBLEsensors.at(i).type>FLORA){
|
|
if(MIBLEsensors.at(i).hum!=-1.0f){
|
|
char humidity[33];
|
|
dtostrfd(MIBLEsensors.at(i).hum, Settings.flag2.humidity_resolution, humidity);
|
|
WSContentSend_PD(HTTP_SNS_HUM, kHM10SlaveType[MIBLEsensors.at(i).type-1], humidity);
|
|
}
|
|
if(MIBLEsensors.at(i).bat!=0xff){
|
|
WSContentSend_PD(HTTP_BATTERY, kHM10SlaveType[MIBLEsensors.at(i).type-1], MIBLEsensors.at(i).bat);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns62(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
if ((pin[GPIO_HM10_RX] < 99) && (pin[GPIO_HM10_TX] < 99)) {
|
|
switch (function) {
|
|
case FUNC_INIT:
|
|
HM10SerialInit();
|
|
break;
|
|
case FUNC_EVERY_50_MSECOND:
|
|
HM10SerialHandleFeedback();
|
|
break;
|
|
case FUNC_EVERY_100_MSECOND:
|
|
if (HM10_TASK_LIST[0][0] != TASK_HM10_NOTASK) {
|
|
HM10_TaskEvery100ms();
|
|
}
|
|
break;
|
|
case FUNC_EVERY_SECOND:
|
|
HM10EverySecond();
|
|
break;
|
|
case FUNC_COMMAND:
|
|
result = HM10Cmd();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
HM10Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
HM10Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_64_aht10.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_64_aht10.ino"
|
|
#ifdef USE_I2C
|
|
#ifdef USE_AHT10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define XSNS_64 64
|
|
#define XI2C_43 43
|
|
|
|
#define AHT10_ADDR 0x38
|
|
|
|
unsigned char eSensorCalibrateCmd[3] = {0xE1, 0x08, 0x00};
|
|
unsigned char eSensorNormalCmd[3] = {0xA8, 0x00, 0x00};
|
|
unsigned char eSensorMeasureCmd[3] = {0xAC, 0x33, 0x00};
|
|
unsigned char eSensorResetCmd = 0xBA;
|
|
|
|
struct AHT10 {
|
|
float humidity = NAN;
|
|
float temperature = NAN;
|
|
uint8_t valid = 0;
|
|
uint8_t count = 0;
|
|
char name[6] = "AHT10";
|
|
} AHT10;
|
|
|
|
bool begin()
|
|
{
|
|
Wire.begin(AHT10_ADDR);
|
|
Wire.beginTransmission(AHT10_ADDR);
|
|
Wire.write(eSensorCalibrateCmd, 3);
|
|
Wire.endTransmission();
|
|
delay(500);
|
|
|
|
if((readStatus() & 0x68) == 0x08)
|
|
return true;
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool AHT10Read(void)
|
|
{
|
|
unsigned long result, temp[6];
|
|
|
|
if (AHT10.valid) { AHT10.valid--; }
|
|
|
|
Wire.beginTransmission(AHT10_ADDR);
|
|
Wire.write(eSensorMeasureCmd, 3);
|
|
Wire.endTransmission();
|
|
delay(100);
|
|
|
|
Wire.requestFrom(AHT10_ADDR, 6);
|
|
for(unsigned char i = 0; Wire.available() > 0; i++)
|
|
{
|
|
temp[i] = Wire.read();
|
|
}
|
|
|
|
AHT10.humidity = (((temp[1] << 16) | (temp[2] << 8) | temp[3]) >> 4)* 100 / 1048576;
|
|
AHT10.temperature = ((200 * (((temp[3] & 0x0F) << 16) | (temp[4] << 8) | temp[5])) / 1048576) - 50;
|
|
|
|
if (isnan(AHT10.temperature) || isnan(AHT10.humidity)) { return false; }
|
|
|
|
AHT10.valid = SENSOR_MAX_MISS;
|
|
return true;
|
|
}
|
|
|
|
|
|
unsigned char readStatus(void)
|
|
{
|
|
unsigned char result = 0;
|
|
|
|
Wire.requestFrom(AHT10_ADDR, 1);
|
|
result = Wire.read();
|
|
return result;
|
|
}
|
|
|
|
void AHT10Detect(void)
|
|
{
|
|
if (I2cActive(AHT10_ADDR))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (begin())
|
|
{
|
|
I2cSetActiveFound(AHT10_ADDR, AHT10.name);
|
|
AHT10.count = 1;
|
|
}
|
|
}
|
|
|
|
void AHT10EverySecond(void)
|
|
{
|
|
if (uptime &1) {
|
|
|
|
if (!AHT10Read()) {
|
|
AddLogMissed(AHT10.name, AHT10.valid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AHT10Show(bool json)
|
|
{
|
|
if (AHT10.valid) {
|
|
char temperature[33];
|
|
dtostrfd(AHT10.temperature, Settings.flag2.temperature_resolution, temperature);
|
|
char humidity[33];
|
|
dtostrfd(AHT10.humidity, Settings.flag2.humidity_resolution, humidity);
|
|
|
|
if (json) {
|
|
ResponseAppend_P(JSON_SNS_TEMPHUM, AHT10.name, temperature, humidity);
|
|
#ifdef USE_DOMOTICZ
|
|
if ((0 == tele_period)) {
|
|
DomoticzTempHumSensor(temperature, humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_KNX
|
|
if (0 == tele_period) {
|
|
KnxSensor(KNX_TEMPERATURE, AHT10.temperature);
|
|
KnxSensor(KNX_HUMIDITY, AHT10.humidity);
|
|
}
|
|
#endif
|
|
#ifdef USE_WEBSERVER
|
|
} else {
|
|
WSContentSend_PD(HTTP_SNS_TEMP, AHT10.name, temperature, TempUnit());
|
|
WSContentSend_PD(HTTP_SNS_HUM, AHT10.name, humidity);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns64(uint8_t function)
|
|
{
|
|
if (!I2cEnabled(XI2C_43)) { return false; }
|
|
|
|
bool result = false;
|
|
|
|
if (FUNC_INIT == function) {
|
|
AHT10Detect();
|
|
}
|
|
else if (AHT10.count) {
|
|
switch (function) {
|
|
case FUNC_EVERY_SECOND:
|
|
AHT10EverySecond();
|
|
break;
|
|
case FUNC_JSON_APPEND:
|
|
AHT10Show(1);
|
|
break;
|
|
#ifdef USE_WEBSERVER
|
|
case FUNC_WEB_SENSOR:
|
|
AHT10Show(0);
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_91_prometheus.ino"
|
|
# 22 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_91_prometheus.ino"
|
|
#ifdef USE_PROMETHEUS
|
|
|
|
|
|
|
|
|
|
#define XSNS_91 91
|
|
|
|
void HandleMetrics(void)
|
|
{
|
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
|
|
|
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR("Prometheus"));
|
|
|
|
WSContentBegin(200, CT_PLAIN);
|
|
|
|
|
|
char parameter[FLOATSZ];
|
|
|
|
if (global_temperature != 9999) {
|
|
dtostrfd(global_temperature, Settings.flag2.temperature_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE global_temperature gauge\nglobal_temperature %s\n"), parameter);
|
|
}
|
|
if (global_humidity != 0) {
|
|
dtostrfd(global_humidity, Settings.flag2.humidity_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE global_humidity gauge\nglobal_humidity %s\n"), parameter);
|
|
}
|
|
if (global_pressure != 0) {
|
|
dtostrfd(global_pressure, Settings.flag2.pressure_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE global_pressure gauge\nglobal_pressure %s\n"), parameter);
|
|
}
|
|
|
|
#ifdef USE_ENERGY_SENSOR
|
|
dtostrfd(Energy.voltage[0], Settings.flag2.voltage_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE voltage gauge\nvoltage %s\n"), parameter);
|
|
dtostrfd(Energy.current[0], Settings.flag2.current_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE current gauge\ncurrent %s\n"), parameter);
|
|
dtostrfd(Energy.active_power[0], Settings.flag2.wattage_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE active_power gauge\nactive_power %s\n"), parameter);
|
|
dtostrfd(Energy.daily, Settings.flag2.energy_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE energy_daily gauge\nenergy_daily %s\n"), parameter);
|
|
dtostrfd(Energy.total, Settings.flag2.energy_resolution, parameter);
|
|
WSContentSend_P(PSTR("# TYPE energy_total counter\nenergy_total %s\n"), parameter);
|
|
#endif
|
|
# 80 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_91_prometheus.ino"
|
|
WSContentEnd();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Xsns91(uint8_t function)
|
|
{
|
|
bool result = false;
|
|
|
|
switch (function) {
|
|
case FUNC_WEB_ADD_HANDLER:
|
|
WebServer->on("/metrics", HandleMetrics);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#endif
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_interface.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xsns_interface.ino"
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
bool (* const xsns_func_ptr[])(uint8_t) PROGMEM = {
|
|
#else
|
|
bool (* const xsns_func_ptr[])(uint8_t) = {
|
|
#endif
|
|
|
|
#ifdef XSNS_01
|
|
&Xsns01,
|
|
#endif
|
|
|
|
#ifdef XSNS_02
|
|
&Xsns02,
|
|
#endif
|
|
|
|
#ifdef XSNS_03
|
|
&Xsns03,
|
|
#endif
|
|
|
|
#ifdef XSNS_04
|
|
&Xsns04,
|
|
#endif
|
|
|
|
#ifdef XSNS_05
|
|
&Xsns05,
|
|
#endif
|
|
|
|
#ifdef XSNS_06
|
|
&Xsns06,
|
|
#endif
|
|
|
|
#ifdef XSNS_07
|
|
&Xsns07,
|
|
#endif
|
|
|
|
#ifdef XSNS_08
|
|
&Xsns08,
|
|
#endif
|
|
|
|
#ifdef XSNS_09
|
|
&Xsns09,
|
|
#endif
|
|
|
|
#ifdef XSNS_10
|
|
&Xsns10,
|
|
#endif
|
|
|
|
#ifdef XSNS_11
|
|
&Xsns11,
|
|
#endif
|
|
|
|
#ifdef XSNS_12
|
|
&Xsns12,
|
|
#endif
|
|
|
|
#ifdef XSNS_13
|
|
&Xsns13,
|
|
#endif
|
|
|
|
#ifdef XSNS_14
|
|
&Xsns14,
|
|
#endif
|
|
|
|
#ifdef XSNS_15
|
|
&Xsns15,
|
|
#endif
|
|
|
|
#ifdef XSNS_16
|
|
&Xsns16,
|
|
#endif
|
|
|
|
#ifdef XSNS_17
|
|
&Xsns17,
|
|
#endif
|
|
|
|
#ifdef XSNS_18
|
|
&Xsns18,
|
|
#endif
|
|
|
|
#ifdef XSNS_19
|
|
&Xsns19,
|
|
#endif
|
|
|
|
#ifdef XSNS_20
|
|
&Xsns20,
|
|
#endif
|
|
|
|
#ifdef XSNS_21
|
|
&Xsns21,
|
|
#endif
|
|
|
|
#ifdef XSNS_22
|
|
&Xsns22,
|
|
#endif
|
|
|
|
#ifdef XSNS_23
|
|
&Xsns23,
|
|
#endif
|
|
|
|
#ifdef XSNS_24
|
|
&Xsns24,
|
|
#endif
|
|
|
|
#ifdef XSNS_25
|
|
&Xsns25,
|
|
#endif
|
|
|
|
#ifdef XSNS_26
|
|
&Xsns26,
|
|
#endif
|
|
|
|
#ifdef XSNS_27
|
|
&Xsns27,
|
|
#endif
|
|
|
|
#ifdef XSNS_28
|
|
&Xsns28,
|
|
#endif
|
|
|
|
#ifdef XSNS_29
|
|
&Xsns29,
|
|
#endif
|
|
|
|
#ifdef XSNS_30
|
|
&Xsns30,
|
|
#endif
|
|
|
|
#ifdef XSNS_31
|
|
&Xsns31,
|
|
#endif
|
|
|
|
#ifdef XSNS_32
|
|
&Xsns32,
|
|
#endif
|
|
|
|
#ifdef XSNS_33
|
|
&Xsns33,
|
|
#endif
|
|
|
|
#ifdef XSNS_34
|
|
&Xsns34,
|
|
#endif
|
|
|
|
#ifdef XSNS_35
|
|
&Xsns35,
|
|
#endif
|
|
|
|
#ifdef XSNS_36
|
|
&Xsns36,
|
|
#endif
|
|
|
|
#ifdef XSNS_37
|
|
&Xsns37,
|
|
#endif
|
|
|
|
#ifdef XSNS_38
|
|
&Xsns38,
|
|
#endif
|
|
|
|
#ifdef XSNS_39
|
|
&Xsns39,
|
|
#endif
|
|
|
|
#ifdef XSNS_40
|
|
&Xsns40,
|
|
#endif
|
|
|
|
#ifdef XSNS_41
|
|
&Xsns41,
|
|
#endif
|
|
|
|
#ifdef XSNS_42
|
|
&Xsns42,
|
|
#endif
|
|
|
|
#ifdef XSNS_43
|
|
&Xsns43,
|
|
#endif
|
|
|
|
#ifdef XSNS_44
|
|
&Xsns44,
|
|
#endif
|
|
|
|
#ifdef XSNS_45
|
|
&Xsns45,
|
|
#endif
|
|
|
|
#ifdef XSNS_46
|
|
&Xsns46,
|
|
#endif
|
|
|
|
#ifdef XSNS_47
|
|
&Xsns47,
|
|
#endif
|
|
|
|
#ifdef XSNS_48
|
|
&Xsns48,
|
|
#endif
|
|
|
|
#ifdef XSNS_49
|
|
&Xsns49,
|
|
#endif
|
|
|
|
#ifdef XSNS_50
|
|
&Xsns50,
|
|
#endif
|
|
|
|
#ifdef XSNS_51
|
|
&Xsns51,
|
|
#endif
|
|
|
|
#ifdef XSNS_52
|
|
&Xsns52,
|
|
#endif
|
|
|
|
#ifdef XSNS_53
|
|
&Xsns53,
|
|
#endif
|
|
|
|
#ifdef XSNS_54
|
|
&Xsns54,
|
|
#endif
|
|
|
|
#ifdef XSNS_55
|
|
&Xsns55,
|
|
#endif
|
|
|
|
#ifdef XSNS_56
|
|
&Xsns56,
|
|
#endif
|
|
|
|
#ifdef XSNS_57
|
|
&Xsns57,
|
|
#endif
|
|
|
|
#ifdef XSNS_58
|
|
&Xsns58,
|
|
#endif
|
|
|
|
#ifdef XSNS_59
|
|
&Xsns59,
|
|
#endif
|
|
|
|
#ifdef XSNS_60
|
|
&Xsns60,
|
|
#endif
|
|
|
|
#ifdef XSNS_61
|
|
&Xsns61,
|
|
#endif
|
|
|
|
#ifdef XSNS_62
|
|
&Xsns62,
|
|
#endif
|
|
|
|
#ifdef XSNS_63
|
|
&Xsns63,
|
|
#endif
|
|
|
|
#ifdef XSNS_64
|
|
&Xsns64,
|
|
#endif
|
|
|
|
#ifdef XSNS_65
|
|
&Xsns65,
|
|
#endif
|
|
|
|
#ifdef XSNS_66
|
|
&Xsns66,
|
|
#endif
|
|
|
|
#ifdef XSNS_67
|
|
&Xsns67,
|
|
#endif
|
|
|
|
#ifdef XSNS_68
|
|
&Xsns68,
|
|
#endif
|
|
|
|
#ifdef XSNS_69
|
|
&Xsns69,
|
|
#endif
|
|
|
|
#ifdef XSNS_70
|
|
&Xsns70,
|
|
#endif
|
|
|
|
#ifdef XSNS_71
|
|
&Xsns71,
|
|
#endif
|
|
|
|
#ifdef XSNS_72
|
|
&Xsns72,
|
|
#endif
|
|
|
|
#ifdef XSNS_73
|
|
&Xsns73,
|
|
#endif
|
|
|
|
#ifdef XSNS_74
|
|
&Xsns74,
|
|
#endif
|
|
|
|
#ifdef XSNS_75
|
|
&Xsns75,
|
|
#endif
|
|
|
|
#ifdef XSNS_76
|
|
&Xsns76,
|
|
#endif
|
|
|
|
#ifdef XSNS_77
|
|
&Xsns77,
|
|
#endif
|
|
|
|
#ifdef XSNS_78
|
|
&Xsns78,
|
|
#endif
|
|
|
|
#ifdef XSNS_79
|
|
&Xsns79,
|
|
#endif
|
|
|
|
#ifdef XSNS_80
|
|
&Xsns80,
|
|
#endif
|
|
|
|
#ifdef XSNS_81
|
|
&Xsns81,
|
|
#endif
|
|
|
|
#ifdef XSNS_82
|
|
&Xsns82,
|
|
#endif
|
|
|
|
#ifdef XSNS_83
|
|
&Xsns83,
|
|
#endif
|
|
|
|
#ifdef XSNS_84
|
|
&Xsns84,
|
|
#endif
|
|
|
|
#ifdef XSNS_85
|
|
&Xsns85,
|
|
#endif
|
|
|
|
#ifdef XSNS_86
|
|
&Xsns86,
|
|
#endif
|
|
|
|
#ifdef XSNS_87
|
|
&Xsns87,
|
|
#endif
|
|
|
|
#ifdef XSNS_88
|
|
&Xsns88,
|
|
#endif
|
|
|
|
#ifdef XSNS_89
|
|
&Xsns89,
|
|
#endif
|
|
|
|
#ifdef XSNS_90
|
|
&Xsns90,
|
|
#endif
|
|
|
|
#ifdef XSNS_91
|
|
&Xsns91,
|
|
#endif
|
|
|
|
#ifdef XSNS_92
|
|
&Xsns92,
|
|
#endif
|
|
|
|
#ifdef XSNS_93
|
|
&Xsns93,
|
|
#endif
|
|
|
|
#ifdef XSNS_94
|
|
&Xsns94,
|
|
#endif
|
|
|
|
#ifdef XSNS_95
|
|
&Xsns95,
|
|
#endif
|
|
|
|
#ifdef XSNS_96
|
|
&Xsns96,
|
|
#endif
|
|
|
|
#ifdef XSNS_97
|
|
&Xsns97,
|
|
#endif
|
|
|
|
#ifdef XSNS_98
|
|
&Xsns98,
|
|
#endif
|
|
|
|
#ifdef XSNS_99
|
|
&Xsns99
|
|
#endif
|
|
};
|
|
|
|
const uint8_t xsns_present = sizeof(xsns_func_ptr) / sizeof(xsns_func_ptr[0]);
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
const uint8_t kXsnsList[] PROGMEM = {
|
|
#else
|
|
const uint8_t kXsnsList[] = {
|
|
#endif
|
|
|
|
#ifdef XSNS_01
|
|
XSNS_01,
|
|
#endif
|
|
|
|
#ifdef XSNS_02
|
|
XSNS_02,
|
|
#endif
|
|
|
|
#ifdef XSNS_03
|
|
XSNS_03,
|
|
#endif
|
|
|
|
#ifdef XSNS_04
|
|
XSNS_04,
|
|
#endif
|
|
|
|
#ifdef XSNS_05
|
|
XSNS_05,
|
|
#endif
|
|
|
|
#ifdef XSNS_06
|
|
XSNS_06,
|
|
#endif
|
|
|
|
#ifdef XSNS_07
|
|
XSNS_07,
|
|
#endif
|
|
|
|
#ifdef XSNS_08
|
|
XSNS_08,
|
|
#endif
|
|
|
|
#ifdef XSNS_09
|
|
XSNS_09,
|
|
#endif
|
|
|
|
#ifdef XSNS_10
|
|
XSNS_10,
|
|
#endif
|
|
|
|
#ifdef XSNS_11
|
|
XSNS_11,
|
|
#endif
|
|
|
|
#ifdef XSNS_12
|
|
XSNS_12,
|
|
#endif
|
|
|
|
#ifdef XSNS_13
|
|
XSNS_13,
|
|
#endif
|
|
|
|
#ifdef XSNS_14
|
|
XSNS_14,
|
|
#endif
|
|
|
|
#ifdef XSNS_15
|
|
XSNS_15,
|
|
#endif
|
|
|
|
#ifdef XSNS_16
|
|
XSNS_16,
|
|
#endif
|
|
|
|
#ifdef XSNS_17
|
|
XSNS_17,
|
|
#endif
|
|
|
|
#ifdef XSNS_18
|
|
XSNS_18,
|
|
#endif
|
|
|
|
#ifdef XSNS_19
|
|
XSNS_19,
|
|
#endif
|
|
|
|
#ifdef XSNS_20
|
|
XSNS_20,
|
|
#endif
|
|
|
|
#ifdef XSNS_21
|
|
XSNS_21,
|
|
#endif
|
|
|
|
#ifdef XSNS_22
|
|
XSNS_22,
|
|
#endif
|
|
|
|
#ifdef XSNS_23
|
|
XSNS_23,
|
|
#endif
|
|
|
|
#ifdef XSNS_24
|
|
XSNS_24,
|
|
#endif
|
|
|
|
#ifdef XSNS_25
|
|
XSNS_25,
|
|
#endif
|
|
|
|
#ifdef XSNS_26
|
|
XSNS_26,
|
|
#endif
|
|
|
|
#ifdef XSNS_27
|
|
XSNS_27,
|
|
#endif
|
|
|
|
#ifdef XSNS_28
|
|
XSNS_28,
|
|
#endif
|
|
|
|
#ifdef XSNS_29
|
|
XSNS_29,
|
|
#endif
|
|
|
|
#ifdef XSNS_30
|
|
XSNS_30,
|
|
#endif
|
|
|
|
#ifdef XSNS_31
|
|
XSNS_31,
|
|
#endif
|
|
|
|
#ifdef XSNS_32
|
|
XSNS_32,
|
|
#endif
|
|
|
|
#ifdef XSNS_33
|
|
XSNS_33,
|
|
#endif
|
|
|
|
#ifdef XSNS_34
|
|
XSNS_34,
|
|
#endif
|
|
|
|
#ifdef XSNS_35
|
|
XSNS_35,
|
|
#endif
|
|
|
|
#ifdef XSNS_36
|
|
XSNS_36,
|
|
#endif
|
|
|
|
#ifdef XSNS_37
|
|
XSNS_37,
|
|
#endif
|
|
|
|
#ifdef XSNS_38
|
|
XSNS_38,
|
|
#endif
|
|
|
|
#ifdef XSNS_39
|
|
XSNS_39,
|
|
#endif
|
|
|
|
#ifdef XSNS_40
|
|
XSNS_40,
|
|
#endif
|
|
|
|
#ifdef XSNS_41
|
|
XSNS_41,
|
|
#endif
|
|
|
|
#ifdef XSNS_42
|
|
XSNS_42,
|
|
#endif
|
|
|
|
#ifdef XSNS_43
|
|
XSNS_43,
|
|
#endif
|
|
|
|
#ifdef XSNS_44
|
|
XSNS_44,
|
|
#endif
|
|
|
|
#ifdef XSNS_45
|
|
XSNS_45,
|
|
#endif
|
|
|
|
#ifdef XSNS_46
|
|
XSNS_46,
|
|
#endif
|
|
|
|
#ifdef XSNS_47
|
|
XSNS_47,
|
|
#endif
|
|
|
|
#ifdef XSNS_48
|
|
XSNS_48,
|
|
#endif
|
|
|
|
#ifdef XSNS_49
|
|
XSNS_49,
|
|
#endif
|
|
|
|
#ifdef XSNS_50
|
|
XSNS_50,
|
|
#endif
|
|
|
|
#ifdef XSNS_51
|
|
XSNS_51,
|
|
#endif
|
|
|
|
#ifdef XSNS_52
|
|
XSNS_52,
|
|
#endif
|
|
|
|
#ifdef XSNS_53
|
|
XSNS_53,
|
|
#endif
|
|
|
|
#ifdef XSNS_54
|
|
XSNS_54,
|
|
#endif
|
|
|
|
#ifdef XSNS_55
|
|
XSNS_55,
|
|
#endif
|
|
|
|
#ifdef XSNS_56
|
|
XSNS_56,
|
|
#endif
|
|
|
|
#ifdef XSNS_57
|
|
XSNS_57,
|
|
#endif
|
|
|
|
#ifdef XSNS_58
|
|
XSNS_58,
|
|
#endif
|
|
|
|
#ifdef XSNS_59
|
|
XSNS_59,
|
|
#endif
|
|
|
|
#ifdef XSNS_60
|
|
XSNS_60,
|
|
#endif
|
|
|
|
#ifdef XSNS_61
|
|
XSNS_61,
|
|
#endif
|
|
|
|
#ifdef XSNS_62
|
|
XSNS_62,
|
|
#endif
|
|
|
|
#ifdef XSNS_63
|
|
XSNS_63,
|
|
#endif
|
|
|
|
#ifdef XSNS_64
|
|
XSNS_64,
|
|
#endif
|
|
|
|
#ifdef XSNS_65
|
|
XSNS_65,
|
|
#endif
|
|
|
|
#ifdef XSNS_66
|
|
XSNS_66,
|
|
#endif
|
|
|
|
#ifdef XSNS_67
|
|
XSNS_67,
|
|
#endif
|
|
|
|
#ifdef XSNS_68
|
|
XSNS_68,
|
|
#endif
|
|
|
|
#ifdef XSNS_69
|
|
XSNS_69,
|
|
#endif
|
|
|
|
#ifdef XSNS_70
|
|
XSNS_70,
|
|
#endif
|
|
|
|
#ifdef XSNS_71
|
|
XSNS_71,
|
|
#endif
|
|
|
|
#ifdef XSNS_72
|
|
XSNS_72,
|
|
#endif
|
|
|
|
#ifdef XSNS_73
|
|
XSNS_73,
|
|
#endif
|
|
|
|
#ifdef XSNS_74
|
|
XSNS_74,
|
|
#endif
|
|
|
|
#ifdef XSNS_75
|
|
XSNS_75,
|
|
#endif
|
|
|
|
#ifdef XSNS_76
|
|
XSNS_76,
|
|
#endif
|
|
|
|
#ifdef XSNS_77
|
|
XSNS_77,
|
|
#endif
|
|
|
|
#ifdef XSNS_78
|
|
XSNS_78,
|
|
#endif
|
|
|
|
#ifdef XSNS_79
|
|
XSNS_79,
|
|
#endif
|
|
|
|
#ifdef XSNS_80
|
|
XSNS_80,
|
|
#endif
|
|
|
|
#ifdef XSNS_81
|
|
XSNS_81,
|
|
#endif
|
|
|
|
#ifdef XSNS_82
|
|
XSNS_82,
|
|
#endif
|
|
|
|
#ifdef XSNS_83
|
|
XSNS_83,
|
|
#endif
|
|
|
|
#ifdef XSNS_84
|
|
XSNS_84,
|
|
#endif
|
|
|
|
#ifdef XSNS_85
|
|
XSNS_85,
|
|
#endif
|
|
|
|
#ifdef XSNS_86
|
|
XSNS_86,
|
|
#endif
|
|
|
|
#ifdef XSNS_87
|
|
XSNS_87,
|
|
#endif
|
|
|
|
#ifdef XSNS_88
|
|
XSNS_88,
|
|
#endif
|
|
|
|
#ifdef XSNS_89
|
|
XSNS_89,
|
|
#endif
|
|
|
|
#ifdef XSNS_90
|
|
XSNS_90,
|
|
#endif
|
|
|
|
#ifdef XSNS_91
|
|
XSNS_91,
|
|
#endif
|
|
|
|
#ifdef XSNS_92
|
|
XSNS_92,
|
|
#endif
|
|
|
|
#ifdef XSNS_93
|
|
XSNS_93,
|
|
#endif
|
|
|
|
#ifdef XSNS_94
|
|
XSNS_94,
|
|
#endif
|
|
|
|
#ifdef XSNS_95
|
|
XSNS_95,
|
|
#endif
|
|
|
|
#ifdef XSNS_96
|
|
XSNS_96,
|
|
#endif
|
|
|
|
#ifdef XSNS_97
|
|
XSNS_97,
|
|
#endif
|
|
|
|
#ifdef XSNS_98
|
|
XSNS_98,
|
|
#endif
|
|
|
|
#ifdef XSNS_99
|
|
XSNS_99
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
bool XsnsEnabled(uint32_t sns_index)
|
|
{
|
|
if (sns_index < sizeof(kXsnsList)) {
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
uint32_t index = pgm_read_byte(kXsnsList + sns_index);
|
|
#else
|
|
uint32_t index = kXsnsList[sns_index];
|
|
#endif
|
|
return bitRead(Settings.sensors[index / 32], index % 32);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void XsnsSensorState(void)
|
|
{
|
|
ResponseAppend_P(PSTR("\""));
|
|
for (uint32_t i = 0; i < sizeof(kXsnsList); i++) {
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
uint32_t sensorid = pgm_read_byte(kXsnsList + i);
|
|
#else
|
|
uint32_t sensorid = kXsnsList[i];
|
|
#endif
|
|
bool disabled = false;
|
|
if (sensorid < MAX_XSNS_DRIVERS) {
|
|
disabled = !bitRead(Settings.sensors[sensorid / 32], sensorid % 32);
|
|
}
|
|
ResponseAppend_P(PSTR("%s%s%d"), (i) ? "," : "", (disabled) ? "!" : "", sensorid);
|
|
}
|
|
ResponseAppend_P(PSTR("\""));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index)
|
|
{
|
|
xsns_index++;
|
|
if (xsns_index == xsns_present) { xsns_index = 0; }
|
|
|
|
#ifndef USE_DEBUG_DRIVER
|
|
if (FUNC_WEB_SENSOR == Function) {
|
|
#endif
|
|
uint32_t max_disabled = xsns_present;
|
|
while (!XsnsEnabled(xsns_index) && max_disabled--) {
|
|
xsns_index++;
|
|
if (xsns_index == xsns_present) { xsns_index = 0; }
|
|
}
|
|
#ifndef USE_DEBUG_DRIVER
|
|
}
|
|
#endif
|
|
|
|
return xsns_func_ptr[xsns_index](Function);
|
|
}
|
|
|
|
bool XsnsCall(uint8_t Function)
|
|
{
|
|
bool result = false;
|
|
|
|
DEBUG_TRACE_LOG(PSTR("SNS: %d"), Function);
|
|
|
|
#ifdef PROFILE_XSNS_EVERY_SECOND
|
|
uint32_t profile_start_millis = millis();
|
|
#endif
|
|
|
|
for (uint32_t x = 0; x < xsns_present; x++) {
|
|
#ifdef USE_DEBUG_DRIVER
|
|
if (XsnsEnabled(x)) {
|
|
#endif
|
|
|
|
if ((FUNC_WEB_SENSOR == Function) && !XsnsEnabled(x)) { continue; }
|
|
|
|
#ifdef PROFILE_XSNS_SENSOR_EVERY_SECOND
|
|
uint32_t profile_start_millis = millis();
|
|
#endif
|
|
result = xsns_func_ptr[x](Function);
|
|
|
|
#ifdef PROFILE_XSNS_SENSOR_EVERY_SECOND
|
|
uint32_t profile_millis = millis() - profile_start_millis;
|
|
if (profile_millis) {
|
|
if (FUNC_EVERY_SECOND == Function) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PRF: At %08u XsnsCall %d to Sensor %d took %u mS"), uptime, Function, x, profile_millis);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (result && ((FUNC_COMMAND == Function) ||
|
|
(FUNC_PIN_STATE == Function) ||
|
|
(FUNC_COMMAND_SENSOR == Function)
|
|
)) {
|
|
break;
|
|
}
|
|
#ifdef USE_DEBUG_DRIVER
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef PROFILE_XSNS_EVERY_SECOND
|
|
uint32_t profile_millis = millis() - profile_start_millis;
|
|
if (profile_millis) {
|
|
if (FUNC_EVERY_SECOND == Function) {
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PRF: At %08u XsnsCall %d took %u mS"), uptime, Function, profile_millis);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
# 1 "C:/shared/sonoff/Git/Tasmota/tasmota/xx2c_interface.ino"
|
|
# 20 "C:/shared/sonoff/Git/Tasmota/tasmota/xx2c_interface.ino"
|
|
#ifdef USE_I2C
|
|
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
const uint8_t kI2cList[] PROGMEM = {
|
|
#else
|
|
const uint8_t kI2cList[] = {
|
|
#endif
|
|
|
|
#ifdef XI2C_01
|
|
XI2C_01,
|
|
#endif
|
|
|
|
#ifdef XI2C_02
|
|
XI2C_02,
|
|
#endif
|
|
|
|
#ifdef XI2C_03
|
|
XI2C_03,
|
|
#endif
|
|
|
|
#ifdef XI2C_04
|
|
XI2C_04,
|
|
#endif
|
|
|
|
#ifdef XI2C_05
|
|
XI2C_05,
|
|
#endif
|
|
|
|
#ifdef XI2C_06
|
|
XI2C_06,
|
|
#endif
|
|
|
|
#ifdef XI2C_07
|
|
XI2C_07,
|
|
#endif
|
|
|
|
#ifdef XI2C_08
|
|
XI2C_08,
|
|
#endif
|
|
|
|
#ifdef XI2C_09
|
|
XI2C_09,
|
|
#endif
|
|
|
|
#ifdef XI2C_10
|
|
XI2C_10,
|
|
#endif
|
|
|
|
#ifdef XI2C_11
|
|
XI2C_11,
|
|
#endif
|
|
|
|
#ifdef XI2C_12
|
|
XI2C_12,
|
|
#endif
|
|
|
|
#ifdef XI2C_13
|
|
XI2C_13,
|
|
#endif
|
|
|
|
#ifdef XI2C_14
|
|
XI2C_14,
|
|
#endif
|
|
|
|
#ifdef XI2C_15
|
|
XI2C_15,
|
|
#endif
|
|
|
|
#ifdef XI2C_16
|
|
XI2C_16,
|
|
#endif
|
|
|
|
#ifdef XI2C_17
|
|
XI2C_17,
|
|
#endif
|
|
|
|
#ifdef XI2C_18
|
|
XI2C_18,
|
|
#endif
|
|
|
|
#ifdef XI2C_19
|
|
XI2C_19,
|
|
#endif
|
|
|
|
#ifdef XI2C_20
|
|
XI2C_20,
|
|
#endif
|
|
|
|
#ifdef XI2C_21
|
|
XI2C_21,
|
|
#endif
|
|
|
|
#ifdef XI2C_22
|
|
XI2C_22,
|
|
#endif
|
|
|
|
#ifdef XI2C_23
|
|
XI2C_23,
|
|
#endif
|
|
|
|
#ifdef XI2C_24
|
|
XI2C_24,
|
|
#endif
|
|
|
|
#ifdef XI2C_25
|
|
XI2C_25,
|
|
#endif
|
|
|
|
#ifdef XI2C_26
|
|
XI2C_26,
|
|
#endif
|
|
|
|
#ifdef XI2C_27
|
|
XI2C_27,
|
|
#endif
|
|
|
|
#ifdef XI2C_28
|
|
XI2C_28,
|
|
#endif
|
|
|
|
#ifdef XI2C_29
|
|
XI2C_29,
|
|
#endif
|
|
|
|
#ifdef XI2C_30
|
|
XI2C_30,
|
|
#endif
|
|
|
|
#ifdef XI2C_31
|
|
XI2C_31,
|
|
#endif
|
|
|
|
#ifdef XI2C_32
|
|
XI2C_32,
|
|
#endif
|
|
|
|
#ifdef XI2C_33
|
|
XI2C_33,
|
|
#endif
|
|
|
|
#ifdef XI2C_34
|
|
XI2C_34,
|
|
#endif
|
|
|
|
#ifdef XI2C_35
|
|
XI2C_35,
|
|
#endif
|
|
|
|
#ifdef XI2C_36
|
|
XI2C_36,
|
|
#endif
|
|
|
|
#ifdef XI2C_37
|
|
XI2C_37,
|
|
#endif
|
|
|
|
#ifdef XI2C_38
|
|
XI2C_38,
|
|
#endif
|
|
|
|
#ifdef XI2C_39
|
|
XI2C_39,
|
|
#endif
|
|
|
|
#ifdef XI2C_40
|
|
XI2C_40,
|
|
#endif
|
|
|
|
#ifdef XI2C_41
|
|
XI2C_41,
|
|
#endif
|
|
|
|
#ifdef XI2C_42
|
|
XI2C_42,
|
|
#endif
|
|
|
|
#ifdef XI2C_43
|
|
XI2C_43,
|
|
#endif
|
|
|
|
#ifdef XI2C_44
|
|
XI2C_44,
|
|
#endif
|
|
|
|
#ifdef XI2C_45
|
|
XI2C_45,
|
|
#endif
|
|
|
|
#ifdef XI2C_46
|
|
XI2C_46,
|
|
#endif
|
|
|
|
#ifdef XI2C_47
|
|
XI2C_47,
|
|
#endif
|
|
|
|
#ifdef XI2C_48
|
|
XI2C_48,
|
|
#endif
|
|
|
|
#ifdef XI2C_49
|
|
XI2C_49,
|
|
#endif
|
|
|
|
#ifdef XI2C_50
|
|
XI2C_50,
|
|
#endif
|
|
|
|
#ifdef XI2C_51
|
|
XI2C_51,
|
|
#endif
|
|
|
|
#ifdef XI2C_52
|
|
XI2C_52,
|
|
#endif
|
|
|
|
#ifdef XI2C_53
|
|
XI2C_53,
|
|
#endif
|
|
|
|
#ifdef XI2C_54
|
|
XI2C_54,
|
|
#endif
|
|
|
|
#ifdef XI2C_55
|
|
XI2C_55,
|
|
#endif
|
|
|
|
#ifdef XI2C_56
|
|
XI2C_56,
|
|
#endif
|
|
|
|
#ifdef XI2C_57
|
|
XI2C_57,
|
|
#endif
|
|
|
|
#ifdef XI2C_58
|
|
XI2C_58,
|
|
#endif
|
|
|
|
#ifdef XI2C_59
|
|
XI2C_59,
|
|
#endif
|
|
|
|
#ifdef XI2C_60
|
|
XI2C_60,
|
|
#endif
|
|
|
|
#ifdef XI2C_61
|
|
XI2C_61,
|
|
#endif
|
|
|
|
#ifdef XI2C_62
|
|
XI2C_62,
|
|
#endif
|
|
|
|
#ifdef XI2C_63
|
|
XI2C_63,
|
|
#endif
|
|
|
|
#ifdef XI2C_64
|
|
XI2C_64,
|
|
#endif
|
|
|
|
#ifdef XI2C_65
|
|
XI2C_65,
|
|
#endif
|
|
|
|
#ifdef XI2C_66
|
|
XI2C_66,
|
|
#endif
|
|
|
|
#ifdef XI2C_67
|
|
XI2C_67,
|
|
#endif
|
|
|
|
#ifdef XI2C_68
|
|
XI2C_68,
|
|
#endif
|
|
|
|
#ifdef XI2C_69
|
|
XI2C_69,
|
|
#endif
|
|
|
|
#ifdef XI2C_70
|
|
XI2C_70,
|
|
#endif
|
|
|
|
#ifdef XI2C_71
|
|
XI2C_71,
|
|
#endif
|
|
|
|
#ifdef XI2C_72
|
|
XI2C_72,
|
|
#endif
|
|
|
|
#ifdef XI2C_73
|
|
XI2C_73,
|
|
#endif
|
|
|
|
#ifdef XI2C_74
|
|
XI2C_74,
|
|
#endif
|
|
|
|
#ifdef XI2C_75
|
|
XI2C_75,
|
|
#endif
|
|
|
|
#ifdef XI2C_76
|
|
XI2C_76,
|
|
#endif
|
|
|
|
#ifdef XI2C_77
|
|
XI2C_77,
|
|
#endif
|
|
|
|
#ifdef XI2C_78
|
|
XI2C_78,
|
|
#endif
|
|
|
|
#ifdef XI2C_79
|
|
XI2C_79,
|
|
#endif
|
|
|
|
#ifdef XI2C_80
|
|
XI2C_80,
|
|
#endif
|
|
|
|
#ifdef XI2C_81
|
|
XI2C_81,
|
|
#endif
|
|
|
|
#ifdef XI2C_82
|
|
XI2C_82,
|
|
#endif
|
|
|
|
#ifdef XI2C_83
|
|
XI2C_83,
|
|
#endif
|
|
|
|
#ifdef XI2C_84
|
|
XI2C_84,
|
|
#endif
|
|
|
|
#ifdef XI2C_85
|
|
XI2C_85,
|
|
#endif
|
|
|
|
#ifdef XI2C_86
|
|
XI2C_86,
|
|
#endif
|
|
|
|
#ifdef XI2C_87
|
|
XI2C_87,
|
|
#endif
|
|
|
|
#ifdef XI2C_88
|
|
XI2C_88,
|
|
#endif
|
|
|
|
#ifdef XI2C_89
|
|
XI2C_89,
|
|
#endif
|
|
|
|
#ifdef XI2C_90
|
|
XI2C_90,
|
|
#endif
|
|
|
|
#ifdef XI2C_91
|
|
XI2C_91,
|
|
#endif
|
|
|
|
#ifdef XI2C_92
|
|
XI2C_92,
|
|
#endif
|
|
|
|
#ifdef XI2C_93
|
|
XI2C_93,
|
|
#endif
|
|
|
|
#ifdef XI2C_94
|
|
XI2C_94,
|
|
#endif
|
|
|
|
#ifdef XI2C_95
|
|
XI2C_95,
|
|
#endif
|
|
|
|
#ifdef XI2C_96
|
|
XI2C_96
|
|
#endif
|
|
};
|
|
|
|
|
|
|
|
bool I2cEnabled(uint32_t i2c_index)
|
|
{
|
|
return (i2c_flg && bitRead(Settings.i2c_drivers[i2c_index / 32], i2c_index % 32));
|
|
}
|
|
|
|
void I2cDriverState(void)
|
|
{
|
|
ResponseAppend_P(PSTR("\""));
|
|
for (uint32_t i = 0; i < sizeof(kI2cList); i++) {
|
|
#ifdef XFUNC_PTR_IN_ROM
|
|
uint32_t i2c_driver_id = pgm_read_byte(kI2cList + i);
|
|
#else
|
|
uint32_t i2c_driver_id = kI2cList[i];
|
|
#endif
|
|
bool disabled = false;
|
|
if (i2c_driver_id < MAX_I2C_DRIVERS) {
|
|
disabled = !bitRead(Settings.i2c_drivers[i2c_driver_id / 32], i2c_driver_id % 32);
|
|
}
|
|
ResponseAppend_P(PSTR("%s%s%d"), (i) ? "," : "", (disabled) ? "!" : "", i2c_driver_id);
|
|
}
|
|
ResponseAppend_P(PSTR("\""));
|
|
}
|
|
|
|
#endif |