xsns_18_pms5003.ino 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. xsns_18_pms5003.ino - PMS5003-7003 particle concentration sensor support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #ifdef USE_PMS5003
  16. /*********************************************************************************************\
  17. * PlanTower PMS5003 and PMS7003 particle concentration sensor
  18. * For background information see http://aqicn.org/sensor/pms5003-7003/
  19. *
  20. * Hardware Serial will be selected if GPIO3 = [PMS5003]
  21. \*********************************************************************************************/
  22. #define XSNS_18 18
  23. #include <TasmotaSerial.h>
  24. TasmotaSerial *PmsSerial;
  25. uint8_t pms_type = 1;
  26. uint8_t pms_valid = 0;
  27. struct pms5003data {
  28. uint16_t framelen;
  29. uint16_t pm10_standard, pm25_standard, pm100_standard;
  30. uint16_t pm10_env, pm25_env, pm100_env;
  31. uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  32. uint16_t unused;
  33. uint16_t checksum;
  34. } pms_data;
  35. /*********************************************************************************************/
  36. boolean PmsReadData(void)
  37. {
  38. if (! PmsSerial->available()) {
  39. return false;
  40. }
  41. while ((PmsSerial->peek() != 0x42) && PmsSerial->available()) {
  42. PmsSerial->read();
  43. }
  44. if (PmsSerial->available() < 32) {
  45. return false;
  46. }
  47. uint8_t buffer[32];
  48. uint16_t sum = 0;
  49. PmsSerial->readBytes(buffer, 32);
  50. PmsSerial->flush(); // Make room for another burst
  51. AddLogSerial(LOG_LEVEL_DEBUG_MORE, buffer, 32);
  52. // get checksum ready
  53. for (uint8_t i = 0; i < 30; i++) {
  54. sum += buffer[i];
  55. }
  56. // The data comes in endian'd, this solves it so it works on all platforms
  57. uint16_t buffer_u16[15];
  58. for (uint8_t i = 0; i < 15; i++) {
  59. buffer_u16[i] = buffer[2 + i*2 + 1];
  60. buffer_u16[i] += (buffer[2 + i*2] << 8);
  61. }
  62. if (sum != buffer_u16[14]) {
  63. AddLog_P(LOG_LEVEL_DEBUG, PSTR("PMS: " D_CHECKSUM_FAILURE));
  64. return false;
  65. }
  66. memcpy((void *)&pms_data, (void *)buffer_u16, 30);
  67. pms_valid = 10;
  68. return true;
  69. }
  70. /*********************************************************************************************/
  71. void PmsSecond(void) // Every second
  72. {
  73. if (PmsReadData()) {
  74. pms_valid = 10;
  75. } else {
  76. if (pms_valid) {
  77. pms_valid--;
  78. }
  79. }
  80. }
  81. /*********************************************************************************************/
  82. void PmsInit(void)
  83. {
  84. pms_type = 0;
  85. if (pin[GPIO_PMS5003] < 99) {
  86. PmsSerial = new TasmotaSerial(pin[GPIO_PMS5003], -1, 1);
  87. if (PmsSerial->begin(9600)) {
  88. if (PmsSerial->hardwareSerial()) { ClaimSerial(); }
  89. pms_type = 1;
  90. }
  91. }
  92. }
  93. #ifdef USE_WEBSERVER
  94. const char HTTP_PMS5003_SNS[] PROGMEM = "%s"
  95. // "{s}PMS5003 " D_STANDARD_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
  96. // "{s}PMS5003 " D_STANDARD_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
  97. // "{s}PMS5003 " D_STANDARD_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
  98. "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
  99. "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
  100. "{s}PMS5003 " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"
  101. "{s}PMS5003 " D_PARTICALS_BEYOND " 0.3 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
  102. "{s}PMS5003 " D_PARTICALS_BEYOND " 0.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
  103. "{s}PMS5003 " D_PARTICALS_BEYOND " 1 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
  104. "{s}PMS5003 " D_PARTICALS_BEYOND " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
  105. "{s}PMS5003 " D_PARTICALS_BEYOND " 5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"
  106. "{s}PMS5003 " D_PARTICALS_BEYOND " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_PARTS_PER_DECILITER "{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
  107. #endif // USE_WEBSERVER
  108. void PmsShow(boolean json)
  109. {
  110. if (pms_valid) {
  111. if (json) {
  112. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"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}"), mqtt_data,
  113. pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard,
  114. pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env,
  115. pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um);
  116. #ifdef USE_DOMOTICZ
  117. if (0 == tele_period) {
  118. DomoticzSensor(DZ_COUNT, pms_data.pm10_env); // PM1
  119. DomoticzSensor(DZ_VOLTAGE, pms_data.pm25_env); // PM2.5
  120. DomoticzSensor(DZ_CURRENT, pms_data.pm100_env); // PM10
  121. }
  122. #endif // USE_DOMOTICZ
  123. #ifdef USE_WEBSERVER
  124. } else {
  125. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_PMS5003_SNS, mqtt_data,
  126. // pms_data.pm10_standard, pms_data.pm25_standard, pms_data.pm100_standard,
  127. pms_data.pm10_env, pms_data.pm25_env, pms_data.pm100_env,
  128. pms_data.particles_03um, pms_data.particles_05um, pms_data.particles_10um, pms_data.particles_25um, pms_data.particles_50um, pms_data.particles_100um);
  129. #endif // USE_WEBSERVER
  130. }
  131. }
  132. }
  133. /*********************************************************************************************\
  134. * Interface
  135. \*********************************************************************************************/
  136. boolean Xsns18(byte function)
  137. {
  138. boolean result = false;
  139. if (pms_type) {
  140. switch (function) {
  141. case FUNC_INIT:
  142. PmsInit();
  143. break;
  144. case FUNC_EVERY_SECOND:
  145. PmsSecond();
  146. break;
  147. case FUNC_JSON_APPEND:
  148. PmsShow(1);
  149. break;
  150. #ifdef USE_WEBSERVER
  151. case FUNC_WEB_APPEND:
  152. PmsShow(0);
  153. break;
  154. #endif // USE_WEBSERVER
  155. }
  156. }
  157. return result;
  158. }
  159. #endif // USE_PMS5003