mirror of https://github.com/arendst/Tasmota.git
Merge branch 'development' into release-7.1
This commit is contained in:
commit
d868c6d7fd
|
@ -127,7 +127,7 @@ boolean PubSubClient::connect(const char *id, const char *user, const char *pass
|
|||
if (_client->connected()) {
|
||||
result = 1;
|
||||
} else {
|
||||
if (domain != NULL) {
|
||||
if (domain.length() != 0) {
|
||||
result = _client->connect(this->domain.c_str(), this->port);
|
||||
} else {
|
||||
result = _client->connect(this->ip, this->port);
|
||||
|
@ -321,9 +321,10 @@ boolean PubSubClient::loop() {
|
|||
} else {
|
||||
buffer[0] = MQTTPINGREQ;
|
||||
buffer[1] = 0;
|
||||
_client->write(buffer,2);
|
||||
lastOutActivity = t;
|
||||
lastInActivity = t;
|
||||
if (_client->write(buffer,2) != 0) {
|
||||
lastOutActivity = t;
|
||||
lastInActivity = t;
|
||||
}
|
||||
pingOutstanding = true;
|
||||
}
|
||||
}
|
||||
|
@ -351,9 +352,9 @@ boolean PubSubClient::loop() {
|
|||
buffer[1] = 2;
|
||||
buffer[2] = (msgId >> 8);
|
||||
buffer[3] = (msgId & 0xFF);
|
||||
_client->write(buffer,4);
|
||||
lastOutActivity = t;
|
||||
|
||||
if (_client->write(buffer,4) != 0) {
|
||||
lastOutActivity = t;
|
||||
}
|
||||
} else {
|
||||
payload = buffer+llen+3+tl;
|
||||
callback(topic,payload,len-llen-3-tl);
|
||||
|
@ -456,11 +457,12 @@ boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsig
|
|||
for (i=0;i<plength;i++) {
|
||||
rc += _client->write((char)pgm_read_byte_near(payload + i));
|
||||
}
|
||||
|
||||
lastOutActivity = millis();
|
||||
if (rc > 0) {
|
||||
lastOutActivity = millis();
|
||||
}
|
||||
|
||||
// Header (1 byte) + llen + identifier (2 bytes) + topic len + payload len
|
||||
const int expectedLength = 1 + llen + 2 + tlen + plength;
|
||||
const unsigned int expectedLength = 1 + llen + 2 + tlen + plength;
|
||||
return (rc == expectedLength);
|
||||
}
|
||||
|
||||
|
@ -475,7 +477,9 @@ boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, bool
|
|||
}
|
||||
size_t hlen = buildHeader(header, buffer, plength+length-MQTT_MAX_HEADER_SIZE);
|
||||
uint16_t rc = _client->write(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
|
||||
lastOutActivity = millis();
|
||||
if (rc > 0) {
|
||||
lastOutActivity = millis();
|
||||
}
|
||||
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
|
||||
}
|
||||
return false;
|
||||
|
@ -486,19 +490,27 @@ int PubSubClient::endPublish() {
|
|||
}
|
||||
|
||||
size_t PubSubClient::write(uint8_t data) {
|
||||
lastOutActivity = millis();
|
||||
if (_client == nullptr) {
|
||||
return 0;
|
||||
lastOutActivity = millis();
|
||||
return 0;
|
||||
}
|
||||
return _client->write(data);
|
||||
size_t rc = _client->write(data);
|
||||
if (rc != 0) {
|
||||
lastOutActivity = millis();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
|
||||
lastOutActivity = millis();
|
||||
if (_client == nullptr) {
|
||||
return 0;
|
||||
lastOutActivity = millis();
|
||||
return 0;
|
||||
}
|
||||
return _client->write(buffer,size);
|
||||
size_t rc = _client->write(buffer,size);
|
||||
if (rc != 0) {
|
||||
lastOutActivity = millis();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
|
||||
|
@ -544,7 +556,9 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
|
|||
return result;
|
||||
#else
|
||||
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
|
||||
lastOutActivity = millis();
|
||||
if (rc != 0) {
|
||||
lastOutActivity = millis();
|
||||
}
|
||||
return (rc == hlen+length);
|
||||
#endif
|
||||
}
|
||||
|
@ -623,22 +637,19 @@ uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t po
|
|||
|
||||
|
||||
boolean PubSubClient::connected() {
|
||||
boolean rc;
|
||||
if (_client == NULL ) {
|
||||
rc = false;
|
||||
} else {
|
||||
rc = (int)_client->connected();
|
||||
if (!rc) {
|
||||
if (this->_state == MQTT_CONNECTED) {
|
||||
this->_state = MQTT_CONNECTION_LOST;
|
||||
_client->flush();
|
||||
_client->stop();
|
||||
}
|
||||
} else {
|
||||
return this->_state == MQTT_CONNECTED;
|
||||
}
|
||||
this->_state = MQTT_DISCONNECTED;
|
||||
return false;
|
||||
}
|
||||
return rc;
|
||||
if (_client->connected() == 0) {
|
||||
bool lastStateConnected = this->_state == MQTT_CONNECTED;
|
||||
this->disconnect();
|
||||
if (lastStateConnected) {
|
||||
this->_state = MQTT_CONNECTION_LOST;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this->_state == MQTT_CONNECTED;
|
||||
}
|
||||
|
||||
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
/*********************************************************************************************\
|
||||
* 7.0.0.5 20191118
|
||||
* Fix boot loop regression
|
||||
* Add command TempOffset -12.6 .. 12.6 to set global temperature sensor offset (#6958)
|
||||
*
|
||||
* 7.0.0.4 20191108
|
||||
* Add command WifiPower 0 .. 20.5 to set Wifi Output Power which will be default set to 17dBm
|
||||
* Change supported PCF8574 I2C address range to 0x20 - 0x26 allowing other I2C devices with address 0x27 to be used at the same time
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
#define D_JSON_GPIO "GPIO"
|
||||
#define D_JSON_FLAG "FLAG"
|
||||
#define D_JSON_BASE "BASE"
|
||||
#define D_CMND_TEMPOFFSET "TempOffset"
|
||||
|
||||
// Commands xdrv_01_mqtt.ino
|
||||
#define D_CMND_MQTTLOG "MqttLog"
|
||||
|
|
|
@ -434,8 +434,9 @@ struct SYSCFG {
|
|||
uint32_t deepsleep; // E94
|
||||
uint16_t energy_power_delta; // E98
|
||||
uint8_t shutter_motordelay[MAX_SHUTTERS]; // E9A
|
||||
int8_t temp_comp; // E9E
|
||||
|
||||
uint8_t free_e9e[2]; // E9E
|
||||
uint8_t free_e9f[1]; // E9F
|
||||
|
||||
uint8_t web_color2[2][3]; // EA0 - Needs to be on integer / 3 distance from web_color
|
||||
|
||||
|
|
|
@ -401,7 +401,7 @@ void UpdateQuickPowerCycle(bool update)
|
|||
if (update && ((pc_register & 0xFFFFFFF0) == 0xFFA55AB0)) {
|
||||
uint32_t counter = ((pc_register & 0xF) << 1) & 0xF;
|
||||
if (0 == counter) { // 4 power cycles in a row
|
||||
SettingsErase(2); // Quickly reset all settings including QuickPowerCycle flag
|
||||
SettingsErase(3); // Quickly reset all settings including QuickPowerCycle flag
|
||||
EspRestart(); // And restart
|
||||
} else {
|
||||
pc_register = 0xFFA55AB0 | counter;
|
||||
|
@ -571,29 +571,30 @@ void SettingsErase(uint8_t type)
|
|||
|
||||
0 = Erase from program end until end of flash as seen by SDK
|
||||
1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0xFCxxx - 0x0xFFFFF) solving possible wifi errors
|
||||
2 = Erase Tasmota settings (0x0xF3xxx - 0x0xFBFFF)
|
||||
2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF)
|
||||
3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
|
||||
*/
|
||||
|
||||
#ifndef FIRMWARE_MINIMAL
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SDK: Flash size 0x%08X"), flashchip->chip_size);
|
||||
|
||||
uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1;
|
||||
uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE;
|
||||
// uint32_t _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
|
||||
uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE; // Flash size as reported by hardware
|
||||
if (1 == type) {
|
||||
// source Esp.cpp and core_esp8266_phy.cpp
|
||||
_sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4;
|
||||
_sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; // SDK parameter area
|
||||
}
|
||||
else if (2 == type) {
|
||||
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota parameter area (0x0F3xxx - 0x0FBFFF)
|
||||
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota parameter area (0x0F3xxx - 0x0FBFFF)
|
||||
_sectorEnd = SETTINGS_LOCATION +1;
|
||||
}
|
||||
else if (3 == type) {
|
||||
_sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
|
||||
_sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK
|
||||
}
|
||||
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart);
|
||||
|
||||
// EspErase(_sectorStart, _sectorEnd); // Arduino core and SDK - erases flash as seen by SDK
|
||||
EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely
|
||||
|
||||
// EspErase(_sectorStart, _sectorEnd); // Arduino core and SDK - erases flash as seen by SDK
|
||||
EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely
|
||||
#endif // FIRMWARE_MINIMAL
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,26 @@
|
|||
IPAddress syslog_host_addr; // Syslog host IP address
|
||||
uint32_t syslog_host_hash = 0; // Syslog host name hash
|
||||
|
||||
extern "C" {
|
||||
extern struct rst_info resetInfo;
|
||||
}
|
||||
|
||||
uint32_t ResetReason(void)
|
||||
{
|
||||
/*
|
||||
user_interface.h
|
||||
REASON_DEFAULT_RST = 0, // normal startup by power on
|
||||
REASON_WDT_RST = 1, // hardware watch dog reset
|
||||
REASON_EXCEPTION_RST = 2, // exception reset, GPIO status won’t change
|
||||
REASON_SOFT_WDT_RST = 3, // software watch dog reset, GPIO status won’t change
|
||||
REASON_SOFT_RESTART = 4, // software restart ,system_restart , GPIO status won’t change
|
||||
REASON_DEEP_SLEEP_AWAKE = 5, // wake up from deep-sleep
|
||||
REASON_EXT_SYS_RST = 6 // external system reset
|
||||
*/
|
||||
|
||||
return resetInfo.reason;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Watchdog extension (https://github.com/esp8266/Arduino/issues/1532)
|
||||
\*********************************************************************************************/
|
||||
|
@ -520,6 +540,7 @@ float ConvertTemp(float c)
|
|||
if (!isnan(c) && Settings.flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
|
||||
result = c * 1.8 + 32; // Fahrenheit
|
||||
}
|
||||
result = result + (0.1 * Settings.temp_comp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -530,6 +551,7 @@ float ConvertTempToCelsius(float c)
|
|||
if (!isnan(c) && Settings.flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
|
||||
result = (c - 32) / 1.8; // Celsius
|
||||
}
|
||||
result = result + (0.1 * Settings.temp_comp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
|
|||
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|"
|
||||
D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|"
|
||||
D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
|
||||
D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|"
|
||||
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
|
||||
|
@ -41,7 +41,7 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
|
|||
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate,
|
||||
&CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig,
|
||||
&CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
|
||||
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower,
|
||||
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset,
|
||||
#ifdef USE_I2C
|
||||
&CmndI2cScan, CmndI2cDriver,
|
||||
#endif
|
||||
|
@ -57,6 +57,13 @@ 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); // Return float value without quotes
|
||||
}
|
||||
|
||||
void ResponseCmndIdxNumber(int value)
|
||||
{
|
||||
Response_P(S_JSON_COMMAND_INDEX_NVALUE, XdrvMailbox.command, XdrvMailbox.index, value);
|
||||
|
@ -496,6 +503,17 @@ void CmndState(void)
|
|||
#endif // USE_HOME_ASSISTANT
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
@ -1540,9 +1558,7 @@ void CmndWifiPower(void)
|
|||
}
|
||||
WifiSetOutputPower();
|
||||
}
|
||||
char stemp1[TOPSZ];
|
||||
dtostrfd((float)(Settings.wifi_output_power) / 10, 1, stemp1);
|
||||
Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp1); // Return float value without quotes
|
||||
ResponseCmndFloat((float)(Settings.wifi_output_power) / 10, 1);
|
||||
}
|
||||
|
||||
#ifdef USE_I2C
|
||||
|
|
|
@ -615,7 +615,6 @@ void WifiShutdown(void)
|
|||
void EspRestart(void)
|
||||
{
|
||||
WifiShutdown();
|
||||
RtcRebootReset();
|
||||
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0
|
||||
ESP.reset();
|
||||
}
|
||||
|
|
|
@ -1500,10 +1500,6 @@ void GpioInit(void)
|
|||
XdrvCall(FUNC_PRE_INIT);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
extern struct rst_info resetInfo;
|
||||
}
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
global_state.data = 3; // Init global state (wifi_down, mqtt_down) to solve possible network issues
|
||||
|
@ -1582,7 +1578,7 @@ void setup(void)
|
|||
Settings.module = SONOFF_BASIC; // Reset module to Sonoff Basic
|
||||
// Settings.last_module = SONOFF_BASIC;
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count);
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_LOG_SOME_SETTINGS_RESET " (%d)"), RtcReboot.fast_reboot_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1605,7 +1601,7 @@ void setup(void)
|
|||
if (POWER_ALL_ALWAYS_ON == Settings.poweronstate) {
|
||||
SetDevicePower(1, SRC_RESTART);
|
||||
} else {
|
||||
if ((resetInfo.reason == REASON_DEFAULT_RST) || (resetInfo.reason == REASON_EXT_SYS_RST)) {
|
||||
if ((ResetReason() == REASON_DEFAULT_RST) || (ResetReason() == REASON_EXT_SYS_RST)) {
|
||||
switch (Settings.poweronstate) {
|
||||
case POWER_ALL_OFF:
|
||||
case POWER_ALL_OFF_PULSETIME_ON:
|
||||
|
|
|
@ -20,6 +20,6 @@
|
|||
#ifndef _TASMOTA_VERSION_H_
|
||||
#define _TASMOTA_VERSION_H_
|
||||
|
||||
const uint32_t VERSION = 0x07000004;
|
||||
const uint32_t VERSION = 0x07000005;
|
||||
|
||||
#endif // _TASMOTA_VERSION_H_
|
||||
|
|
|
@ -497,9 +497,7 @@ void CmndLongitude(void)
|
|||
if (XdrvMailbox.data_len) {
|
||||
Settings.longitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
|
||||
}
|
||||
char lbuff[33];
|
||||
dtostrfd(((float)Settings.longitude) /1000000, 6, lbuff);
|
||||
ResponseCmndChar(lbuff);
|
||||
ResponseCmndFloat((float)(Settings.longitude) /1000000, 6);
|
||||
}
|
||||
|
||||
void CmndLatitude(void)
|
||||
|
@ -507,9 +505,7 @@ void CmndLatitude(void)
|
|||
if (XdrvMailbox.data_len) {
|
||||
Settings.latitude = (int)(CharToFloat(XdrvMailbox.data) *1000000);
|
||||
}
|
||||
char lbuff[33];
|
||||
dtostrfd(((float)Settings.latitude) /1000000, 6, lbuff);
|
||||
ResponseCmndChar(lbuff);
|
||||
ResponseCmndFloat((float)(Settings.latitude) /1000000, 6);
|
||||
}
|
||||
#endif // USE_SUNRISE
|
||||
|
||||
|
|
|
@ -59,15 +59,15 @@ bool DeepSleepEnabled(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
void DeepSleepInit(void)
|
||||
void DeepSleepReInit(void)
|
||||
{
|
||||
if (DeepSleepEnabled()) {
|
||||
RtcRebootReset();
|
||||
if ((ResetReason() == REASON_DEEP_SLEEP_AWAKE) && DeepSleepEnabled()) {
|
||||
if ((RtcSettings.ultradeepsleep > MAX_DEEPSLEEP_CYCLE) && (RtcSettings.ultradeepsleep < 1700000000)) {
|
||||
// Go back to sleep after 60 minutes if requested deepsleep has not been reached
|
||||
RtcSettings.ultradeepsleep = RtcSettings.ultradeepsleep - MAX_DEEPSLEEP_CYCLE;
|
||||
AddLog_P2(LOG_LEVEL_ERROR, PSTR("DSL: Remain DeepSleep %d"), RtcSettings.ultradeepsleep);
|
||||
RtcSettingsSave();
|
||||
RtcRebootReset();
|
||||
ESP.deepSleep(100 * RtcSettings.deepsleep_slip * (MAX_DEEPSLEEP_CYCLE < RtcSettings.ultradeepsleep ? MAX_DEEPSLEEP_CYCLE : RtcSettings.ultradeepsleep), WAKE_RF_DEFAULT);
|
||||
yield();
|
||||
// Sleeping
|
||||
|
@ -198,7 +198,7 @@ bool Xdrv29(uint8_t function)
|
|||
result = DecodeCommand(kDeepsleepCommands, DeepsleepCommand);
|
||||
break;
|
||||
case FUNC_PRE_INIT:
|
||||
DeepSleepInit();
|
||||
DeepSleepReInit();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
|
|
Loading…
Reference in New Issue