From abdbe628dbd1c7f7ebd31699fd81dcbbf93f5f45 Mon Sep 17 00:00:00 2001 From: arendst Date: Sat, 27 Jan 2018 17:52:48 +0100 Subject: [PATCH] v5.11.1g - Add PMS5003 support and updates 5.11.1g * Add support for PMS5003 particle concentration sensor * Reinstate console weblog to 20 lines after some webpage rewrite * Add command SetOption20 to allow update of Dimmer/Color/Ct without turning power on (#1719) * Update language files nl-NL (#1723) and es-AR (#1722) --- README.md | 2 +- sonoff/_releasenotes.ino | 8 +- sonoff/language/de-DE.h | 4 +- sonoff/language/en-GB.h | 4 +- sonoff/language/es-AR.h | 4 +- sonoff/language/nl-NL.h | 8 +- sonoff/settings.h | 11 +-- sonoff/settings.ino | 8 +- sonoff/sonoff.h | 8 +- sonoff/sonoff.ino | 3 +- sonoff/user_config.h | 2 + sonoff/webserver.ino | 75 +++++++++++++--- sonoff/xdrv_01_light.ino | 4 +- sonoff/xsns_18_pms5003.ino | 172 +++++++++++++++++++++++++++++++++++++ 14 files changed, 270 insertions(+), 43 deletions(-) create mode 100644 sonoff/xsns_18_pms5003.ino diff --git a/README.md b/README.md index 710fe1359..eab749266 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Sonoff-Tasmota Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. -Current version is **5.11.1f** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information. +Current version is **5.11.1g** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information. ### ATTENTION All versions diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 5fabdbf57..00d2ad660 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,10 @@ -/* 5.11.1f +/* 5.11.1g + * Add support for PMS5003 particle concentration sensor + * Reinstate console weblog to 20 lines after some webpage rewrite + * Add command SetOption20 to allow update of Dimmer/Color/Ct without turning power on (#1719) + * Update language files nl-NL (#1723) and es-AR (#1722) + * + * 5.11.1f * Revert chunked webserver pages as it fails on many browsers due to chunks being too small (#1706) * Reduce initial console weblog from 20 to 13 lines due to memory constraints * diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h index db6ee314f..3e749f1b4 100644 --- a/sonoff/language/de-DE.h +++ b/sonoff/language/de-DE.h @@ -376,8 +376,8 @@ #define D_SHT1X_FOUND "SHT1X gefunden" // xsns_18_pms5003.ino -#define D_STANDARD_CONCENTRATION "Standard Concentration" -#define D_ENVIRONMENTAL_CONCENTRATION "Environmental Concentration" +#define D_STANDARD_CONCENTRATION "Std Concentration" +#define D_ENVIRONMENTAL_CONCENTRATION "Env Concentration" #define D_PARTICALS_BEYOND "Particals beyond" // sonoff_template.h diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h index 018923612..0204dd882 100644 --- a/sonoff/language/en-GB.h +++ b/sonoff/language/en-GB.h @@ -376,8 +376,8 @@ #define D_SHT1X_FOUND "SHT1X found" // xsns_18_pms5003.ino -#define D_STANDARD_CONCENTRATION "Standard Concentration" -#define D_ENVIRONMENTAL_CONCENTRATION "Environmental Concentration" +#define D_STANDARD_CONCENTRATION "Std Concentration" +#define D_ENVIRONMENTAL_CONCENTRATION "Env Concentration" #define D_PARTICALS_BEYOND "Particals beyond" // sonoff_template.h diff --git a/sonoff/language/es-AR.h b/sonoff/language/es-AR.h index 1d70b4230..a9f1964ff 100644 --- a/sonoff/language/es-AR.h +++ b/sonoff/language/es-AR.h @@ -54,7 +54,7 @@ #define D_BLINK "Blink" #define D_BLINKOFF "BlinkOff" #define D_BOOT_COUNT "Conteo Reinicios" -#define D_BRIGHTLIGHT "Brillo" +#define D_BRIGHTLIGHT "Brillante" #define D_BUTTON "Botón" #define D_BY "por" // Written by me #define D_BYTES "Bytes" @@ -79,7 +79,7 @@ #define D_ERASE "Borrar" #define D_ERROR "Error" #define D_FAHRENHEIT "Fahrenheit" -#define D_FAILED "Fallo" +#define D_FAILED "Falló" #define D_FALLBACK "Fallback" #define D_FALLBACK_TOPIC "FallbackTopic" #define D_FALSE "Falso" diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h index 155e05de6..14c32755b 100644 --- a/sonoff/language/nl-NL.h +++ b/sonoff/language/nl-NL.h @@ -47,7 +47,7 @@ // Common #define D_ADMIN "Admin" -#define D_AIR_QUALITY "Lucht kwalitiet" +#define D_AIR_QUALITY "Lucht kwaliteit" #define D_AP "AP" // Access Point #define D_AS "als" #define D_AUTO "AUTO" @@ -376,9 +376,9 @@ #define D_SHT1X_FOUND "SHT1X gevonden" // xsns_18_pms5003.ino -#define D_STANDARD_CONCENTRATION "Standard Concentration" -#define D_ENVIRONMENTAL_CONCENTRATION "Environmental Concentration" -#define D_PARTICALS_BEYOND "Particals beyond" +#define D_STANDARD_CONCENTRATION "Std concentratie" +#define D_ENVIRONMENTAL_CONCENTRATION "Omg concentratie" +#define D_PARTICALS_BEYOND "Deeltjes groter dan" // sonoff_template.h // Max string length is 8 characters including suffixes diff --git a/sonoff/settings.h b/sonoff/settings.h index 381e7ee9f..4921b6a51 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -45,7 +45,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t decimal_text : 1; // bit 17 (v5.8.1) uint32_t light_signal : 1; // bit 18 (v5.10.0c) uint32_t hass_discovery : 1; // bit 19 (v5.11.1a) - uint32_t voltage_resolution : 1; // Replaced by below + uint32_t not_power_linked : 1; // bit 20 (v5.11.1f) uint32_t spare21 : 1; uint32_t spare22 : 1; uint32_t spare23 : 1; @@ -57,15 +57,6 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t spare29 : 1; uint32_t spare30 : 1; uint32_t spare31 : 1; - /* - uint32_t wattage_resolution : 1; - uint32_t voltage_resolution : 1; - uint32_t emulation : 2; - uint32_t energy_resolution : 3; - uint32_t pressure_resolution : 2; - uint32_t humidity_resolution : 2; - uint32_t temperature_resolution : 2; -*/ }; } SysBitfield; diff --git a/sonoff/settings.ino b/sonoff/settings.ino index 0e8937a43..1c090ef00 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -430,7 +430,7 @@ void SettingsDefaultSet2() 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.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE; + Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE; Settings.flag2.emulation = EMULATION; @@ -845,13 +845,17 @@ void SettingsDelta() if (Settings.version < 0x05090102) { Settings.flag2.data = Settings.flag.data; Settings.flag2.data &= 0xFFE80000; - Settings.flag2.voltage_resolution = Settings.flag.voltage_resolution; + Settings.flag2.voltage_resolution = Settings.flag.not_power_linked; Settings.flag2.current_resolution = 3; Settings.ina219_mode = 0; } if (Settings.version < 0x050A0009) { SettingsDefaultSet_5_10_1(); } + if (Settings.version < 0x050B0107) { + Settings.flag.not_power_linked = 0; + } + Settings.version = VERSION; SettingsSave(1); diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index 54813c7eb..ff741090c 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -90,11 +90,11 @@ typedef unsigned long power_t; // Power (Relay) type #ifdef USE_MQTT_TLS #define MAX_LOG_LINES 10 // Max number of lines in weblog #else - #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 +// #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 #define MAX_LOG_LINES 20 // Max number of lines in weblog - #else - #define MAX_LOG_LINES 13 // Max number of lines in weblog (less due to more memory usage) - #endif +// #else +// #define MAX_LOG_LINES 13 // Max number of lines in weblog (less due to more memory usage which prohibits full webpage load) +// #endif #endif #define MAX_BACKLOG 16 // Max number of commands in backlog (chk backlog_index and backlog_pointer code) #define MIN_BACKLOG_DELAY 2 // Minimal backlog delay in 0.1 seconds diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 047d416a2..cf211650d 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -25,7 +25,7 @@ - Select IDE Tools - Flash Size: "1M (no SPIFFS)" ====================================================*/ -#define VERSION 0x050B0106 // 5.11.1f +#define VERSION 0x050B0107 // 5.11.1g // Location specific includes #include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0) @@ -1027,6 +1027,7 @@ void MqttDataCallback(char* topic, byte* data, unsigned int data_len) case 16: // ws_clock_reverse case 17: // decimal_text case 18: // light_signal + case 20: // not_power_linked bitWrite(Settings.flag.data, index, payload); } if (12 == index) { // stop_flash_rotate diff --git a/sonoff/user_config.h b/sonoff/user_config.h index 225e11725..80e91730c 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -216,6 +216,8 @@ #define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1) #define CO2_HIGH 1200 // Above this CO2 value show red light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1) +#define USE_PMS5003 // Add support for PMS5003 particle concentration sensor (+1k3 code) + #define USE_IR_REMOTE // Send IR remote commands using library IRremoteESP8266 and ArduinoJson (+4k code, 0k3 mem, 48 iram) // #define USE_IR_HVAC // Support for HVAC system using IR (+2k code) #define USE_IR_RECEIVE // Support for IR receiver (+5k5 code, 264 iram) diff --git a/sonoff/webserver.ino b/sonoff/webserver.ino index 0010f1020..64596e3c4 100644 --- a/sonoff/webserver.ino +++ b/sonoff/webserver.ino @@ -60,7 +60,7 @@ const char HTTP_HEAD[] PROGMEM = "x=new XMLHttpRequest();" "x.onreadystatechange=function(){" "if(x.readyState==4&&x.status==200){" - "var s=x.responseText.replace(/{s}/g,\"\").replace(/{m}/g,\"\").replace(/{e}/g,\"\").replace(/{t}/g,\"%'>
\").replace(/{s}/g,\"\").replace(/{m}/g,\"\").replace(/{e}/g,\"\").replace(/{c}/g,\"%'>
arg("k").c_str()); ExecuteCommand(svalue); } - +/* String page = ""; mqtt_data[0] = '\0'; XsnsCall(FUNC_WEB_APPEND); @@ -568,13 +568,58 @@ void HandleAjaxStatusRefresh() uint8_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32; for (byte idx = 1; idx <= devices_present; idx++) { snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1)); - snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s
"), // {t} = %'>
%s
"), // {c} = %'>
")); + XsnsCall(FUNC_WEB_APPEND); + if (D_DECIMAL_SEPARATOR[0] != '.') { + for (int i = 0; i < strlen(mqtt_data); i++) { + if ('.' == mqtt_data[i]) { + mqtt_data[i] = D_DECIMAL_SEPARATOR[0]; + } + } + } + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s"), mqtt_data); + if (devices_present) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s"), mqtt_data); + uint8_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32; + for (byte idx = 1; idx <= devices_present; idx++) { + snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1)); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s"), // {c} = %'>
"), mqtt_data); + if (devices_present) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s{t}
"), mqtt_data); + uint8_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32; + for (byte idx = 1; idx <= devices_present; idx++) { + snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(power, idx -1)); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s"), // {c} = %'>
%s " D_GPIO "%d %s
"), - (WEMOS==Settings.module)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i, i); - part2 += mqtt_data; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("sk(%d,%d);"), my_module.gp.io[i], i); // g0 - g16 page += mqtt_data; } } page += F("}"); - page += part2; + + page += FPSTR(HTTP_HEAD_STYLE); page.replace(F(""), F("")); + page += FPSTR(HTTP_FORM_MODULE); + snprintf_P(stemp, sizeof(stemp), kModules[MODULE].name); + page.replace(F("{mt"), stemp); + page += F("
%s
%s
"); + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + if (GPIO_USER == cmodule.gp.io[i]) { + snprintf_P(stemp, 3, PINS_WEMOS +i*2); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(""), + (WEMOS==Settings.module)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i, i); + page += mqtt_data; + } + } page += F("
%s " D_GPIO "%d %s
"); page += FPSTR(HTTP_FORM_END); page += FPSTR(HTTP_BTN_CONF); diff --git a/sonoff/xdrv_01_light.ino b/sonoff/xdrv_01_light.ino index 96f9c2f1d..9971f8e53 100644 --- a/sonoff/xdrv_01_light.ino +++ b/sonoff/xdrv_01_light.ino @@ -568,7 +568,9 @@ void LightState(uint8_t append) void LightPreparePower() { if (Settings.light_dimmer && !(light_power)) { - ExecuteCommandPower(light_device, POWER_ON_NO_STATE); + if (!Settings.flag.not_power_linked) { + ExecuteCommandPower(light_device, POWER_ON_NO_STATE); + } } else if (!Settings.light_dimmer && light_power) { ExecuteCommandPower(light_device, POWER_OFF_NO_STATE); diff --git a/sonoff/xsns_18_pms5003.ino b/sonoff/xsns_18_pms5003.ino new file mode 100644 index 000000000..3d73729f0 --- /dev/null +++ b/sonoff/xsns_18_pms5003.ino @@ -0,0 +1,172 @@ +/* + xsns_18_pms5003.ino - PMS5003 particle concentration sensor support for Sonoff-Tasmota + + Copyright (C) 2018 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_PMS5003 +/*********************************************************************************************\ + * PlanTower PMS5003 particle concentration sensor +\*********************************************************************************************/ + +#include + +TasmotaSerial *PmsSerial; + +uint8_t pms_type = 1; +uint8_t pms_valid = 0; + +struct pms5003data { + uint16_t framelen; + uint16_t pm10_standard, pm25_standard, pm100_standard; + uint16_t pm10_env, pm25_env, pm100_env; + uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um; + uint16_t unused; + uint16_t checksum; +} pms_data; + +boolean PmsReadData() +{ + if (! PmsSerial->available()) { + return false; + } + while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) { + PmsSerial->read(); + } + if (PmsSerial->available() < 32) { + return false; + } + + uint8_t buffer[32]; + uint16_t sum = 0; + PmsSerial->readBytes(buffer, 32); + PmsSerial->flush(); // Make room for another burst + + // get checksum ready + for (uint8_t i = 0; i < 30; i++) { + sum += buffer[i]; + } + // The data comes in endian'd, this solves it so it works on all platforms + uint16_t buffer_u16[15]; + for (uint8_t i = 0; i < 15; i++) { + buffer_u16[i] = buffer[2 + i*2 + 1]; + buffer_u16[i] += (buffer[2 + i*2] << 8); + } + if (sum != buffer_u16[14]) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("PMS: " D_CHECKSUM_FAILURE)); + return false; + } + + memcpy((void *)&pms_data, (void *)buffer_u16, 30); + pms_valid = 10; + + return true; +} + +/*********************************************************************************************/ + +void PmsSecond() // Every second +{ + if (PmsReadData()) { + pms_valid = 10; + } else { + if (pms_valid) { + pms_valid--; + } + } +} + +/*********************************************************************************************/ + +void PmsInit() +{ + pms_type = 0; + + if (pin[GPIO_PMS5003] < 99) { + PmsSerial = new TasmotaSerial(pin[GPIO_PMS5003], -1); + if (PmsSerial->begin()) { + pms_type = 1; + } + } +} + +#ifdef USE_WEBSERVER +const char HTTP_PMS5003_SNS[] PROGMEM = "%s" + "{s}" D_STANDARD_CONCENTRATION " 1" D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}" D_STANDARD_CONCENTRATION " 2" D_DECIMAL_SEPARATOR "5" D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}" D_STANDARD_CONCENTRATION " 10" D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}" D_ENVIRONMENTAL_CONCENTRATION " 1" D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}" D_ENVIRONMENTAL_CONCENTRATION " 2" D_DECIMAL_SEPARATOR "5" D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}" D_ENVIRONMENTAL_CONCENTRATION " 10" D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}" D_PARTICALS_BEYOND " 0" D_DECIMAL_SEPARATOR "3" D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}" D_PARTICALS_BEYOND " 0" D_DECIMAL_SEPARATOR "5" D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}" D_PARTICALS_BEYOND " 1" D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}" D_PARTICALS_BEYOND " 2" D_DECIMAL_SEPARATOR "5" D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}" D_PARTICALS_BEYOND " 5" D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}" + "{s}" D_PARTICALS_BEYOND " 10" D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"; // {s} = , {m} = , {e} = +#endif // USE_WEBSERVER + +void PmsShow(boolean json) +{ + if (pms_valid) { + if (json) { + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"PMS5003\":{\"SC1\":%d,\"SC2.5\":%d,\"SC10\":%d,\"EC1\":%d,\"EC2.5\":%d,\"EC10\":%d,\"PB0.3\":%d,\"PB0.5\":%d,\"PB1\":%d,\"PB2.5\":%d,\"PB5\":%d,\"PB10\":%d}"), mqtt_data, + pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard, + pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env, + pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um); +#ifdef USE_WEBSERVER + } else { + snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_PMS5003_SNS, mqtt_data, + 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 // USE_WEBSERVER + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +#define XSNS_18 + +boolean Xsns18(byte function) +{ + boolean 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_APPEND: + PmsShow(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_PMS5003