xsns_11_veml6070.ino 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. xsns_11_veml6070.ino - VEML6070 ultra violet light 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. Some words to the meaning of the UV Risk Level:
  16. -----------------------------------------------------
  17. D_UV_INDEX_1 = "Low" = sun->fun
  18. D_UV_INDEX_2 = "Mid" = sun->glases advised
  19. D_UV_INDEX_3 = "High" = sun->glases a must
  20. D_UV_INDEX_4 = "Danger" = sun->skin burns Level 1
  21. D_UV_INDEX_5 = "BurnL1/2" = sun->skin burns level 1..2
  22. D_UV_INDEX_6 = "BurnL3" = sun->skin burns with level 3
  23. D_UV_INDEX_7 = "OoR" = out of range or unknown
  24. --------------------------------------------------------------------------------------------
  25. Version Date Action Description
  26. --------------------------------------------------------------------------------------------
  27. 1.0.0.3 20181006 fixed - missing "" around the UV Index text
  28. - thanks to Lisa she had tested it on here mqtt system.
  29. --
  30. 1.0.0.2 20180928 tests - same as in version 1.0.0.1
  31. cleaned - source code
  32. changed - snprintf_P for json and web server output
  33. - much more compressed and more professional code
  34. added - uv_risk_text to json and web server output
  35. changed - switch (function) to be 100% compatible
  36. - added Veml6070EverySecond in thought of compatibile
  37. added - Veml6070UvTableInit to do this only once to spare time
  38. debugging - @Adrian helped me out in case of a %s%s in mqtt_data. Thank You Adrian
  39. next - possible i will add the calculation for LAT and LONG coordinates for much more precission (TBD)
  40. - show not only the UV Power value in W/m2, possible a @define value to show it as joule value (TBD)
  41. - add a #define to select how many characters are shown benhind the decimal point for the UV Index (TBD)
  42. ---
  43. 1.0.0.1 20180925 tests - all tests are done with 1x sonoff sv, 2x Wemos D1 (not the mini)
  44. - 3 different VEMl6070 sensors from 3 different online shops
  45. - all the last three test where good and all looks working so far
  46. - all tests are done at high noon with blue sky and a leaned UV light source
  47. sience - a special Thank You to my friend the professor. He works in the aerospace industrie. Thank You R.G.T.
  48. - all calculations are based on the very good work of Karel Vanicek. Thank You Karel
  49. - more information about UV Index and the irradiation power calculation can be found on the internet
  50. info - all calculations are based on the effective irradiation from Karel Vanicek
  51. - all this was not possible without the work of @arendst. He has done really a lot of basic work/code. Thank You Theo
  52. cleaned - source code a little bit
  53. added - missing void in function calls: void name(void)
  54. added - UV Risk level now defined as UV Index, 0.00 based on NASA standard with text behind the value
  55. added - UV Power level now named as UV Power, used W/m2 because official standards
  56. added - automatic fill of the uv-risk compare table based on the coefficient calculation
  57. added - suspend and wakeup mode for the uv seonsor
  58. - current drain in wake-up-ed mode was around 180uA incl. I2C bus
  59. - current drain in suspend mode was around 70..80uA incl. I2C bus
  60. changed - 2x the power calculation about some incorrent data sheet values
  61. changed - float to double calculation because a rare effect on uv compare map filling
  62. - in that case @andrethomas was a big help too (while(work){output=lot_of_fun};)
  63. added - USE_VEML6070_RSET
  64. - in user_config as possible input, different resistor values depending on PCB types
  65. added - USE_VEML6070_SHOW_RAW
  66. - in user_config, show or show-NOT the uv raw value
  67. added - lots of #defines for automatic calulations to get the best possible values
  68. added - error messages for LOG_LEVEL_DEBUG
  69. added - lots of information in one of the last postings in: https://github.com/arendst/Sonoff-Tasmota/issues/3844
  70. debugging - without the softly hit ;-) from @andrethomas about Serial.print i would never done it. Thank You Andre
  71. safety - personal, please read this: http://www.segurancaetrabalho.com.br/download/uv_index_karel_vanicek.pdf
  72. next - possible i will add the calculation for LAT and LONG coordinates for much more precission
  73. - show not only the UV Power value in W/m2, possible a @define value to show it as joule value
  74. - add a #define to select how many characters are shown benhind the decimal point for the UV Index
  75. ---
  76. 1.0.0.0 20180912 started - further development by mike2nl - https://github.com/mike2nl/Sonoff-Tasmota
  77. forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota
  78. base - code base from arendst too
  79. */
  80. #ifdef USE_I2C
  81. #ifdef USE_VEML6070
  82. /*********************************************************************************************\
  83. * VEML6070 - Ultra Violet Light Intensity (UV-A, 100% output by 255nm)
  84. *
  85. * I2C Address: 0x38 and 0x39
  86. \*********************************************************************************************/
  87. #define XSNS_11 11
  88. #define VEML6070_ADDR_H 0x39 // on some PCB boards the address can be changed by a solder point,
  89. #define VEML6070_ADDR_L 0x38 // to have no address conflicts with other I2C sensors and/or hardware
  90. #define VEML6070_INTEGRATION_TIME 3 // IT_4 = 500msec integration time, because the precission is 4 times higher then IT_0.5
  91. #define VEML6070_ENABLE 1 //
  92. #define VEML6070_DISABLE 0 //
  93. #define VEML6070_RSET_DEFAULT 270000 // 270K default resistor value 270000 ohm, range from 220K..1Meg
  94. #define VEML6070_UV_MAX_INDEX 15 // normal 11, internal on weather laboratories and NASA it's 15 so far the sensor is linear
  95. #define VEML6070_UV_MAX_DEFAULT 11 // 11 = public default table values
  96. #define VEML6070_POWER_COEFFCIENT 0.025 // based on calculations from Karel Vanicek and reorder by hand
  97. #define VEML6070_TABLE_COEFFCIENT 32.86270591 // calculated by hand with help from a friend of mine, a professor which works in aero space things
  98. // (resistor, differences, power coefficients and official UV index calculations (LAT & LONG will be added later)
  99. /********************************************************************************************/
  100. // globals
  101. const char kVemlTypes[] PROGMEM = "VEML6070"; // in preperation of veml6075
  102. double uv_risk_map[VEML6070_UV_MAX_INDEX] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  103. double uvrisk = 0;
  104. double uvpower = 0;
  105. uint16_t uvlevel = 0;
  106. uint8_t veml6070_addr_low = VEML6070_ADDR_L;
  107. uint8_t veml6070_addr_high = VEML6070_ADDR_H;
  108. uint8_t itime = VEML6070_INTEGRATION_TIME;
  109. uint8_t veml6070_type = 0;
  110. uint8_t veml6070_valid = 0;
  111. char veml6070_name[9];
  112. char str_uvrisk_text[10];
  113. /********************************************************************************************/
  114. void Veml6070Detect(void)
  115. {
  116. if (veml6070_type) {
  117. return;
  118. }
  119. // init the UV sensor
  120. Wire.beginTransmission(VEML6070_ADDR_L);
  121. Wire.write((itime << 2) | 0x02);
  122. uint8_t status = Wire.endTransmission();
  123. // action on status
  124. if (!status) {
  125. veml6070_type = 1;
  126. uint8_t veml_model = 0;
  127. GetTextIndexed(veml6070_name, sizeof(veml6070_name), veml_model, kVemlTypes);
  128. snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "VEML6070", VEML6070_ADDR_L);
  129. AddLog(LOG_LEVEL_DEBUG);
  130. }
  131. }
  132. /********************************************************************************************/
  133. void Veml6070UvTableInit(void)
  134. {
  135. // fill the uv-risk compare table once, based on the coefficient calculation
  136. for (uint8_t i = 0; i < VEML6070_UV_MAX_INDEX; i++) {
  137. #ifdef USE_VEML6070_RSET
  138. if ( (USE_VEML6070_RSET >= 220000) && (USE_VEML6070_RSET <= 1000000) ) {
  139. uv_risk_map[i] = ( (USE_VEML6070_RSET / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1);
  140. } else {
  141. uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1);
  142. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "VEML6070 resistor error %d"), USE_VEML6070_RSET);
  143. AddLog(LOG_LEVEL_DEBUG);
  144. }
  145. #else
  146. uv_risk_map[i] = ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (i+1);
  147. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "VEML6070 resistor default used %d"), VEML6070_RSET_DEFAULT);
  148. AddLog(LOG_LEVEL_DEBUG);
  149. #endif
  150. }
  151. }
  152. /********************************************************************************************/
  153. void Veml6070EverySecond(void)
  154. {
  155. // all = 10..15[ms]
  156. if (11 == (uptime %100)) {
  157. Veml6070ModeCmd(1); // on = 1[ms], wakeup the UV sensor
  158. Veml6070Detect(); // 1[ms], check for sensor and init with IT time
  159. Veml6070ModeCmd(0); // off = 5[ms], suspend the UV sensor
  160. } else {
  161. Veml6070ModeCmd(1); // 1[ms], wakeup the UV sensor
  162. uvlevel = Veml6070ReadUv(); // 1..2[ms], get UV raw values
  163. uvrisk = Veml6070UvRiskLevel(uvlevel); // 0..1[ms], get UV risk level
  164. uvpower = Veml6070UvPower(uvrisk); // 2[ms], get UV power in W/m2
  165. Veml6070ModeCmd(0); // off = 5[ms], suspend the UV sensor
  166. }
  167. }
  168. /********************************************************************************************/
  169. void Veml6070ModeCmd(boolean mode_cmd)
  170. {
  171. // mode_cmd 1 = on = 1[ms]
  172. // mode_cmd 0 = off = 2[ms]
  173. Wire.beginTransmission(VEML6070_ADDR_L);
  174. Wire.write((mode_cmd << 0) | 0x02 | (itime << 2));
  175. uint8_t status = Wire.endTransmission();
  176. // action on status
  177. if (!status) {
  178. snprintf_P(log_data, sizeof(log_data), S_LOG_I2C_FOUND_AT, "VEML6070 mode_cmd", VEML6070_ADDR_L);
  179. AddLog(LOG_LEVEL_DEBUG);
  180. }
  181. }
  182. /********************************************************************************************/
  183. uint16_t Veml6070ReadUv(void)
  184. {
  185. uint16_t uv_raw = 0;
  186. // read high byte
  187. if (Wire.requestFrom(VEML6070_ADDR_H, 1) != 1) {
  188. return -1;
  189. }
  190. uv_raw = Wire.read();
  191. uv_raw <<= 8;
  192. // read low byte
  193. if (Wire.requestFrom(VEML6070_ADDR_L, 1) != 1) {
  194. return -1;
  195. }
  196. uv_raw |= Wire.read();
  197. // high and low done
  198. return uv_raw;
  199. }
  200. /********************************************************************************************/
  201. double Veml6070UvRiskLevel(uint16_t uv_level)
  202. {
  203. double risk = 0;
  204. if (uv_level < uv_risk_map[VEML6070_UV_MAX_INDEX-1]) {
  205. risk = (double)uv_level / uv_risk_map[0];
  206. // generate uv-risk string
  207. if ( (risk >= 0) && (risk <= 2.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_1); }
  208. else if ( (risk >= 3.0) && (risk <= 5.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_2); }
  209. else if ( (risk >= 6.0) && (risk <= 7.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_3); }
  210. else if ( (risk >= 8.0) && (risk <= 10.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_4); }
  211. else if ( (risk >= 11.0) && (risk <= 12.9) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_5); }
  212. else if ( (risk >= 13.0) && (risk <= 25.0) ) { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_6); }
  213. else { snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7); }
  214. return risk;
  215. } else {
  216. // out of range and much to high - it must be outerspace or sensor damaged
  217. snprintf_P(str_uvrisk_text, sizeof(str_uvrisk_text), D_UV_INDEX_7);
  218. return ( risk = 99 );
  219. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "VEML6070 out of range %d"), risk);
  220. AddLog(LOG_LEVEL_DEBUG);
  221. }
  222. }
  223. /********************************************************************************************/
  224. double Veml6070UvPower(double uvrisk)
  225. {
  226. // based on calculations for effective irradiation from Karel Vanicek
  227. double power = 0;
  228. return ( power = VEML6070_POWER_COEFFCIENT * uvrisk );
  229. }
  230. /********************************************************************************************/
  231. // normaly in i18n.h, Line 520 .. 525
  232. #ifdef USE_WEBSERVER
  233. // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
  234. #ifdef USE_VEML6070_SHOW_RAW
  235. const char HTTP_SNS_UV_LEVEL[] PROGMEM = "%s{s}VEML6070 " D_UV_LEVEL "{m}%s " D_UNIT_INCREMENTS "{e}";
  236. #endif // USE_VEML6070_SHOW_RAW
  237. // different uv index level texts
  238. const char HTTP_SNS_UV_INDEX[] PROGMEM = "%s{s}VEML6070 " D_UV_INDEX " {m}%s %s{e}";
  239. const char HTTP_SNS_UV_POWER[] PROGMEM = "%s{s}VEML6070 " D_UV_POWER "{m}%s " D_UNIT_WATT_METER_QUADRAT "{e}";
  240. #endif // USE_WEBSERVER
  241. /********************************************************************************************/
  242. void Veml6070Show(boolean json)
  243. {
  244. if (veml6070_type) {
  245. // convert double values to string
  246. char str_uvlevel[33]; // e.g. 99999 inc = UVLevel
  247. dtostrfd((double)uvlevel, 0, str_uvlevel);
  248. char str_uvrisk[33]; // e.g. 25.99 text = UvIndex
  249. dtostrfd(uvrisk, 2, str_uvrisk);
  250. char str_uvpower[33]; // e.g. 0.399 W/m² = UvPower
  251. dtostrfd(uvpower, 3, str_uvpower);
  252. if (json) {
  253. #ifdef USE_VEML6070_SHOW_RAW
  254. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_JSON_UV_LEVEL "\":%s,\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"),
  255. mqtt_data, veml6070_name, str_uvlevel, str_uvrisk, str_uvrisk_text, str_uvpower);
  256. #else
  257. snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_JSON_UV_INDEX "\":%s,\"" D_JSON_UV_INDEX_TEXT "\":\"%s\",\"" D_JSON_UV_POWER "\":%s}"),
  258. mqtt_data, veml6070_name, str_uvrisk, str_uvrisk_text, str_uvpower);
  259. #endif // USE_VEML6070_SHOW_RAW
  260. #ifdef USE_DOMOTICZ
  261. if (0 == tele_period) { DomoticzSensor(DZ_ILLUMINANCE, uvlevel); }
  262. #endif // USE_DOMOTICZ
  263. #ifdef USE_WEBSERVER
  264. } else {
  265. #ifdef USE_VEML6070_SHOW_RAW
  266. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_UV_LEVEL, mqtt_data, str_uvlevel);
  267. #endif // USE_VEML6070_SHOW_RAW
  268. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_UV_INDEX, mqtt_data, str_uvrisk, str_uvrisk_text);
  269. snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_UV_POWER, mqtt_data, str_uvpower);
  270. #endif // USE_WEBSERVER
  271. }
  272. }
  273. }
  274. /*********************************************************************************************\
  275. * Interface
  276. \*********************************************************************************************/
  277. boolean Xsns11(byte function)
  278. {
  279. boolean result = false;
  280. if (i2c_flg) {
  281. switch (function) {
  282. case FUNC_INIT:
  283. Veml6070Detect(); // 1[ms], detect and init the sensor
  284. Veml6070UvTableInit(); // 1[ms], initalize the UV compare table only once
  285. break;
  286. case FUNC_EVERY_SECOND:
  287. Veml6070EverySecond(); // 10..15[ms], tested with OLED display, do all the actions needed to get all sensor values
  288. break;
  289. case FUNC_JSON_APPEND:
  290. Veml6070Show(1);
  291. break;
  292. #ifdef USE_WEBSERVER
  293. case FUNC_WEB_APPEND:
  294. Veml6070Show(0);
  295. break;
  296. #endif // USE_WEBSERVER
  297. }
  298. }
  299. return result;
  300. }
  301. #endif // USE_VEML6070
  302. #endif // USE_I2C