Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

352 lines
15 KiB

  1. // -----------------------------------------------------------------------------
  2. // BME680 Sensor over I2C
  3. // Copyright (C) 2020 by Rui Marinho <ruipmarinho at gmail dot com>
  4. //
  5. // The BSEC software binaries and includes are only available for use after accepting its software
  6. // license agreement. By enabling this sensor integration, you are agreeing to the terms of the license
  7. // agreement available at the following URL:
  8. //
  9. // https://ae-bst.resource.bosch.com/media/_tech/media/bsec/2017-07-17_ClickThrough_License_Terms_Environmentalib_SW_CLEAN.pdf
  10. //
  11. // The Arduino wrapper and BME680 Sensor API used for this integration are licensed under the following terms:
  12. //
  13. // Copyright (c) 2020 Bosch Sensortec GmbH. All rights reserved.
  14. //
  15. // BSD-3-Clause
  16. //
  17. // Redistribution and use in source and binary forms, with or without
  18. // modification, are permitted provided that the following conditions are met:
  19. //
  20. // 1. Redistributions of source code must retain the above copyright
  21. // notice, this list of conditions and the following disclaimer.
  22. //
  23. // 2. Redistributions in binary form must reproduce the above copyright
  24. // notice, this list of conditions and the following disclaimer in the
  25. // documentation and/or other materials provided with the distribution.
  26. //
  27. // 3. Neither the name of the copyright holder nor the names of its
  28. // contributors may be used to endorse or promote products derived from
  29. // this software without specific prior written permission.
  30. //
  31. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  32. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  33. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  34. // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  35. // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  36. // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  37. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  38. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  39. // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
  41. // IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  42. // POSSIBILITY OF SUCH DAMAGE.
  43. //
  44. // For more details, please refer to https://github.com/BoschSensortec/BSEC-Arduino-library.
  45. // -----------------------------------------------------------------------------
  46. #if SENSOR_SUPPORT && BME680_SUPPORT
  47. #pragma once
  48. #include <Arduino.h>
  49. #include <bsec.h>
  50. #include "I2CSensor.h"
  51. // Available configuration modes based on parameters:
  52. // voltage / maximum time between sensor calls / time considered
  53. // for background calibration.
  54. #define BME680_BSEC_CONFIG_GENERIC_18V_3S_4D 0
  55. #define BME680_BSEC_CONFIG_GENERIC_18V_3S_28D 1
  56. #define BME680_BSEC_CONFIG_GENERIC_18V_300S_4D 2
  57. #define BME680_BSEC_CONFIG_GENERIC_18V_300S_28D 3
  58. #define BME680_BSEC_CONFIG_GENERIC_33V_3S_4D 4
  59. #define BME680_BSEC_CONFIG_GENERIC_33V_3S_28D 5
  60. #define BME680_BSEC_CONFIG_GENERIC_33V_300S_4D 6
  61. #define BME680_BSEC_CONFIG_GENERIC_33V_300S_28D 7
  62. const uint8_t bsec_config_iaq[] = {
  63. #if BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_4D
  64. #include <config/generic_18v_3s_28d/bsec_iaq.txt>
  65. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_28D
  66. #include <config/generic_18v_300s_4d/bsec_iaq.txt>
  67. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D
  68. #include <config/generic_18v_300s_28d/bsec_iaq.txt>
  69. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_28D
  70. #include <config/generic_33v_3s_4d/bsec_iaq.txt>
  71. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_4D
  72. #include <config/generic_33v_3s_28d/bsec_iaq.txt>
  73. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_28D
  74. #include <config/generic_33v_300s_4d/bsec_iaq.txt>
  75. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_4D
  76. #include <config/generic_33v_300s_28d/bsec_iaq.txt>
  77. #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_28D
  78. #include <config/generic_33v_3s_4d/bsec_iaq.txt>
  79. #endif
  80. };
  81. class BME680Sensor : public I2CSensor<> {
  82. public:
  83. // ---------------------------------------------------------------------
  84. // Public
  85. // ---------------------------------------------------------------------
  86. BME680Sensor() {
  87. _error = SENSOR_ERROR_OK;
  88. _sensor_id = SENSOR_BME680_ID;
  89. _count = 9;
  90. }
  91. // ---------------------------------------------------------------------
  92. // Sensor API
  93. // ---------------------------------------------------------------------
  94. void begin() {
  95. if (!_dirty) {
  96. return;
  97. }
  98. // I2C auto-discover
  99. unsigned char addresses[] = {BME680_I2C_ADDR_PRIMARY, BME680_I2C_ADDR_SECONDARY};
  100. _address = _begin_i2c(_address, sizeof(addresses), addresses);
  101. if (_address == 0) return;
  102. iaqSensor.begin(_address, Wire);
  103. DEBUG_MSG_P(PSTR("[BME680] BSEC library version v%u.%u.%u.%u\n"),
  104. iaqSensor.version.major,
  105. iaqSensor.version.minor,
  106. iaqSensor.version.major_bugfix,
  107. iaqSensor.version.minor_bugfix
  108. );
  109. if (!_isOk()) {
  110. _showSensorErrors();
  111. _error = SENSOR_ERROR_OTHER;
  112. return;
  113. }
  114. iaqSensor.setConfig(bsec_config_iaq);
  115. _loadState();
  116. float sampleRate;
  117. // BSEC configuration with 300s allows for the sensor to sleep for 300s
  118. // on the ULP mode in order to minimize power consumption.
  119. if (BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D ||
  120. BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D ||
  121. BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D) {
  122. sampleRate = BSEC_SAMPLE_RATE_ULP;
  123. } else {
  124. sampleRate = BSEC_SAMPLE_RATE_LP;
  125. }
  126. iaqSensor.updateSubscription(sensorList, 12, sampleRate);
  127. if (!_isOk()) {
  128. _showSensorErrors();
  129. _error = SENSOR_ERROR_OTHER;
  130. return;
  131. }
  132. _error = SENSOR_ERROR_OK;
  133. _ready = true;
  134. _dirty = false;
  135. }
  136. // Descriptive name of the sensor
  137. String description() {
  138. char buffer[21];
  139. snprintf(buffer, sizeof(buffer), "BME680 @ I2C (0x%02X)", _address);
  140. return String(buffer);
  141. }
  142. // Type for slot # index
  143. unsigned char type(unsigned char index) {
  144. if (index == 0) return MAGNITUDE_TEMPERATURE;
  145. if (index == 1) return MAGNITUDE_HUMIDITY;
  146. if (index == 2) return MAGNITUDE_PRESSURE;
  147. if (index == 3) return MAGNITUDE_RESISTANCE;
  148. if (index == 4) return MAGNITUDE_IAQ_ACCURACY;
  149. if (index == 5) return MAGNITUDE_IAQ;
  150. if (index == 6) return MAGNITUDE_IAQ_STATIC;
  151. if (index == 7) return MAGNITUDE_CO2;
  152. if (index == 8) return MAGNITUDE_VOC;
  153. return MAGNITUDE_NONE;
  154. }
  155. // The maximum allowed time between two `bsec_sensor_control` calls depends on
  156. // configuration profile `bsec_config_iaq` below.
  157. void tick() {
  158. if (iaqSensor.run()) {
  159. _rawTemperature = iaqSensor.rawTemperature;
  160. _rawHumidity = iaqSensor.rawHumidity;
  161. _temperature = iaqSensor.temperature;
  162. _humidity = iaqSensor.humidity;
  163. _pressure = iaqSensor.pressure / 100;
  164. _gasResistance = iaqSensor.gasResistance;
  165. _iaqAccuracy = iaqSensor.iaqAccuracy;
  166. _iaq = iaqSensor.iaq;
  167. _iaqStatic = iaqSensor.staticIaq;
  168. _co2Equivalent = iaqSensor.co2Equivalent;
  169. _breathVocEquivalent = iaqSensor.breathVocEquivalent;
  170. _saveState();
  171. _error = SENSOR_ERROR_OK;
  172. } else if (_isError()) {
  173. _error = SENSOR_ERROR_OTHER;
  174. }
  175. }
  176. // Ensure we show any possible issues with the sensor, post() is called regardless of sensor status() / error() codes
  177. void post() override {
  178. _showSensorErrors();
  179. }
  180. // Current value for slot # index
  181. double value(unsigned char index) {
  182. if (index == 0) return _temperature;
  183. if (index == 1) return _humidity;
  184. if (index == 2) return _pressure;
  185. if (index == 3) return _gasResistance;
  186. if (index == 4) return _iaqAccuracy;
  187. if (index == 5) return _iaq;
  188. if (index == 6) return _iaqStatic;
  189. if (index == 7) return _co2Equivalent;
  190. if (index == 8) return _breathVocEquivalent;
  191. return 0;
  192. }
  193. protected:
  194. void _loadState() {
  195. String storedState = getSetting("bsecState");
  196. if (!storedState.length()) {
  197. return;
  198. }
  199. DEBUG_MSG_P(PSTR("[BME680] Restoring previous state\n"));
  200. hexDecode(storedState.c_str(), storedState.length(), _bsecState, sizeof(_bsecState));
  201. iaqSensor.setState(_bsecState);
  202. _showSensorErrors();
  203. }
  204. void _saveState() {
  205. if (!BME680_STATE_SAVE_INTERVAL) return;
  206. static unsigned long last_millis = 0;
  207. if (_iaqAccuracy < 3 || (millis() - last_millis < BME680_STATE_SAVE_INTERVAL)) {
  208. return;
  209. }
  210. iaqSensor.getState(_bsecState);
  211. char storedState[BSEC_MAX_STATE_BLOB_SIZE * 2 + 1] = {0};
  212. hexEncode(_bsecState, BSEC_MAX_STATE_BLOB_SIZE, storedState, sizeof(storedState));
  213. setSetting("bsecState", storedState);
  214. last_millis = millis();
  215. }
  216. bool _isError() {
  217. return (iaqSensor.status < BSEC_OK) || (iaqSensor.bme680Status < BME680_OK);
  218. }
  219. bool _isOk() {
  220. return (iaqSensor.status == BSEC_OK) && (iaqSensor.bme680Status == BME680_OK);
  221. }
  222. void _showSensorErrors() {
  223. // see `enum { ... } bsec_library_return_t` values & description at:
  224. // BSEC Software Library/src/inc/bsec_datatypes.h
  225. if (iaqSensor.status != BSEC_OK) {
  226. if (iaqSensor.status < BSEC_OK) {
  227. DEBUG_MSG_P(PSTR("[BME680] BSEC error code (%d)\n"), iaqSensor.status);
  228. } else {
  229. DEBUG_MSG_P(PSTR("[BME680] BSEC warning code (%d)\n"), iaqSensor.status);
  230. }
  231. }
  232. // see `BME680_{W,E}_...` at:
  233. // BSEC Software Library/src/bme680/bme680_defs.h
  234. switch (iaqSensor.bme680Status) {
  235. case BME680_OK:
  236. break;
  237. case BME680_E_COM_FAIL:
  238. case BME680_E_DEV_NOT_FOUND:
  239. DEBUG_MSG_P(PSTR("[BME680] Communication error / device not found (%d)\n"), iaqSensor.bme680Status);
  240. case BME680_W_DEFINE_PWR_MODE:
  241. DEBUG_MSG_P(PSTR("[BME680] Power mode not defined (%d)\n"), iaqSensor.bme680Status);
  242. break;
  243. case BME680_W_NO_NEW_DATA:
  244. DEBUG_MSG_P(PSTR("[BME680] No new data (%d)\n"), iaqSensor.bme680Status);
  245. break;
  246. default:
  247. if (iaqSensor.bme680Status < BME680_OK) {
  248. DEBUG_MSG_P(PSTR("[BME680] Error code (%d)\n"), iaqSensor.bme680Status);
  249. } else {
  250. DEBUG_MSG_P(PSTR("[BME680] Warning code (%d)\n"), iaqSensor.bme680Status);
  251. }
  252. break;
  253. }
  254. }
  255. bsec_virtual_sensor_t sensorList[12] = {
  256. BSEC_OUTPUT_RAW_TEMPERATURE, // Unscaled (raw) temperature (ºC).
  257. BSEC_OUTPUT_RAW_PRESSURE, // Unscaled (raw) pressure (Pa).
  258. BSEC_OUTPUT_RAW_HUMIDITY, // Unscaled (raw) relative humidity (%).
  259. BSEC_OUTPUT_RAW_GAS, // Gas resistance (Ohm). The resistance value changes according to the
  260. // VOC concentration (the higher the concentration of reducing VOCs,
  261. // the lower the resistance and vice versa).
  262. BSEC_OUTPUT_IAQ, // Scaled Indoor Air Quality based on the recent sensor history, ideal
  263. // for mobile applications (e.g. carry-on devices). The scale ranges from
  264. // 0 (clean air) to 500 (heavily polluted air). The automatic background
  265. // calibration process ensures that consistent IAQ performance is achieved
  266. // after certain of days (depending on BSEC configuration - 4d or 28d).
  267. BSEC_OUTPUT_STATIC_IAQ, // Unscaled Indoor Air Quality, optimized for stationary applications
  268. // (e.g. fixed indoor devices).
  269. BSEC_OUTPUT_CO2_EQUIVALENT, // Estimate of CO2 measured in the air.
  270. BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, // Breath VOC represents the most important compounds in an exhaled
  271. // breath of healthy humans.
  272. BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, // Temperature compensated for the influence of sensor heater (ºC).
  273. BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, // Relative humidity compensated for the influence of sensor heater (%).
  274. BSEC_OUTPUT_STABILIZATION_STATUS, // Indicates initial stabilization status of the gas sensor element:
  275. // ongoing (0) or finished (1).
  276. BSEC_OUTPUT_RUN_IN_STATUS, // Indicates power-on stabilization status of the gas sensor element:
  277. // ongoing (0) or finished (1).
  278. };
  279. float _breathVocEquivalent = 0.0f;
  280. float _co2Equivalent = 0.0f;
  281. float _gasResistance = 0.0f;
  282. float _humidity = 0.0f;
  283. float _iaq = 0.0f;
  284. float _pressure = 0.0f;
  285. float _rawHumidity = 0.0f;
  286. float _rawTemperature = 0.0f;
  287. float _temperature = 0.0f;
  288. uint8_t _bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
  289. uint8_t _iaqAccuracy = 0;
  290. float _iaqStatic = 0;
  291. Bsec iaqSensor;
  292. };
  293. #endif // SENSOR_SUPPORT && BME680_SUPPORT