/* xdrv_43_mlx90640.ino - MLX90640 support for Tasmota Copyright (C) 2021 Christian Baars and 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 . -------------------------------------------------------------------------------------------- Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- 0.9.0.0 20200827 started - based on https://github.com/melexis/mlx90640-library */ #ifdef USE_I2C #ifdef USE_MLX90640 #define MLX90640_ADDRESS 0x33 #define MLX90640_POI_NUM 6 //some parts of the JS are hardcoded for 6!! /*********************************************************************************************\ * MLX90640 \*********************************************************************************************/ #define XDRV_43 43 #define XI2C_53 53 // See I2CDEVICES.md #include const char MLX90640type[] PROGMEM = "MLX90640"; #ifdef USE_WEBSERVER #define WEB_HANDLE_MLX90640 "mlx" const char HTTP_BTN_MENU_MLX90640[] PROGMEM = "

"; #endif // USE_WEBSERVER struct { uint32_t type:1; uint32_t ready:1; uint32_t dumpedEE:1; uint32_t extractedParams:1; paramsMLX90640 *params; float Ta; uint16_t Frame[834]; float To[768]; uint8_t pois[2*MLX90640_POI_NUM] = {2,1, 30,1, 10,12, 22,12, 2,23, 30,23}; // {x1,y1,x2,y2,...,x6,y6} } MLX90640; /*********************************************************************************************\ * commands \*********************************************************************************************/ #define D_CMND_MLX90640 "MLX" const char S_JSON_MLX90640_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MLX90640 "%s\":%d}"; const char S_JSON_MLX90640_COMMAND[] PROGMEM = "{\"" D_CMND_MLX90640 "%s\"}"; const char kMLX90640_Commands[] PROGMEM = "POI"; enum MLX90640_Commands { // commands useable in console or rules CMND_MLX90640_POI // MLXPOIn xxyy - set POI number n to x,y }; /************************************************************************\ * Web GUI \************************************************************************/ #ifdef USE_WEBSERVER #ifdef USE_UNISHOX_COMPRESSION const size_t HTTP_MLX90640_1_SNS_SIZE = 389; const char HTTP_MLX90640_1_SNS_COMPRESSED[] PROGMEM = "\x3D\x3C\x1F\xF4\x65\x2A\x2B\x32\x18\xCF\x87\xDD\x33\x65\x1D\x86\xBB\x33\xB0\x41" "\xA4\x7D\x9F\x81\xE7\x7A\x90\xDB\x18\x7C\x3B\xA6\x76\x10\xB6\x75\x1B\x0E\x43\xA8" "\x8C\x8E\x43\xA8\x8D\x87\x28\xEA\x23\x23\x94\x77\x8F\x87\xE1\x02\x0D\x13\xAC\xD8" "\x72\x1D\xE3\xD6\x77\x48\xC8\xE5\x1D\x64\x6C\x39\x47\x78\xEC\x3B\xA4\x64\x72\x1D" "\x64\x6C\x39\x0E\xF1\xDB\x23\x61\xCA\x3C\x10\x20\xE3\x3A\x36\xC7\x9A\x3E\x2E\x63" "\xE8\xB4\x6D\x8F\x33\xC1\x9D\xFD\x07\x7C\x67\x7E\x3A\x83\xA3\x61\xD4\x3D\xF1\x0F" "\x06\x77\xF4\x3C\x43\x0D\x87\x50\xCC\xD3\xE1\xEF\x1E\xF9\xE0\xCE\xFE\xBE\x56\x7C" "\x3D\xE3\xDF\x3C\x18\x17\xC1\xD6\xE7\x21\xE7\x44\x37\x05\xF9\x90\xCC\xF1\xDD\x04" "\x2C\x65\x33\x3A\x3B\xC8\xF6\x82\x0E\x87\xF6\x1D\x23\xE0\x21\x66\x87\x41\xE7\x44" "\x3B\x05\xF0\x9B\xC3\xC4\x18\x5A\xFA\x8B\xEC\x3A\x3B\xA7\x78\xF0\x67\x7F\x46\xC4" "\x7C\x4C\xCE\x8E\x81\x85\xAF\xA8\x8D\x87\x5F\xD8\x74\x74\x09\x98\xA3\xC6\x98\x3B" "\xA6\xC3\xF0\xE5\xD3\x3B\xC7\xB4\x8D\x87\xC3\x97\x11\xE0\xF7\x17\xDD\x0B\xFF\x23" "\xDA\x6C\x3C\xD1\x0D\xBA\x14\x74\x30\x16\x67\xCE\xE8\xDB\x18\x77\x4D\x87\x51\xC6" "\x75\x5D\x33\xA9\x9D\x57\x0E\x88\xEF\x1D\xE3\xA8\x8C\x81\x32\xF9\xDD\x04\x5D\x04" "\x8C\x91\xD6\xBE\xC3\xA3\xA5\x60\xC3\xBC\x75\x1C\x67\x55\x63\x3A\x99\xD5\x56\x74" "\x47\x78\xEF\x1E\xE3\xC1\xEE"; #define HTTP_MLX90640_1_SNS Decompress(HTTP_MLX90640_1_SNS_COMPRESSED,HTTP_MLX90640_1_SNS_SIZE).c_str() #else const char HTTP_MLX90640_1_SNS[] PROGMEM = "" ; #endif //USE_UNISHOX_COMPRESSION #ifdef USE_UNISHOX_COMPRESSION const size_t HTTP_MLX90640_4b_SNS_SIZE = 418; const char HTTP_MLX90640_4b_SNS_COMPRESSED[] PROGMEM = "\x3D\x07\x60\x86\x4B\x38\x2C\xB1\x0F\x87\xDF\x9D\x0B\x18\x77\x4E\xF1\xE0\xFB\x3F" "\x0F\x40\xEF\x8C\xEF\xCB\x44\x3E\x1F\x63\x42\x36\x1F\x68\x7F\x44\xA1\x47\xC3\xEC" "\xE5\xE3\x3E\xCE\xE1\x0A\x7A\x3C\x2A\x2B\x8F\x87\xD9\xCA\xC6\x7D\x9F\x87\xA1\xD8" "\x40\x83\x83\x9F\x87\xA0\x9A\x66\x7E\x1E\x87\x60\x9A\x66\x7E\x1E\x9E\x61\x30\xE9" "\x68\x87\xC3\xEC\x66\x69\x04\x7D\xAC\xE0\xC5\x5F\x0F\x33\xE1\xF6\x37\x3C\x77\x4E" "\xF1\xF6\x7E\x1E\x98\x32\xB7\x39\x19\xD8\x42\xD9\xF0\xFB\x38\xCF\xB3\xF0\x88\x61" "\x61\x69\xD6\x72\x1E\x87\x61\x02\x0D\x40\x4B\xB8\x72\x10\x20\xDC\x39\x44\x0A\x77" "\x0E\x51\x02\x0D\xC3\x96\x40\xA7\x70\xE5\x90\x20\xDC\x39\x84\x0A\x77\x0E\x61\x02" "\x0D\xC3\x9A\x40\xA7\x70\xE6\x90\x20\xDC\x39\xC4\x08\xB7\x0E\xC0\x41\xE1\x2A\x01" "\xFC\x3D\x04\xD3\x30\x41\xE2\x0C\xE4\x3E\xC8\x10\xF8\x5B\x13\x4C\xCF\xC2\x18\x58" "\x5A\x75\x9C\x67\x99\xDC\x3D\x0B\xC3\x2F\x96\x88\x7C\x3E\xEC\xE4\x3E\xCF\xC3\xD0" "\xEC\x2F\x0C\xBE\x3F\x26\x3B\x32\xF2\x0D\x1D\xDF\x3E\xF6\x7C\xEF\x02\x2E\x1E\x08" "\x39\x11\xCA\x20\x44\xC8\x8E\xC1\xD8\x21\x91\xF8"; #define HTTP_MLX90640_4b_SNS Decompress(HTTP_MLX90640_4b_SNS_COMPRESSED,HTTP_MLX90640_4b_SNS_SIZE).c_str() #else const char HTTP_MLX90640_4b_SNS[] PROGMEM = "" "" "
" "" "
" "
POI-0: °C (sensor)
" "
" "" ; #endif //USE_UNISHOX_COMPRESSION void MLX90640UpdateGUI(void){ WSContentStart_P("mlx"); WSContentSendStyle(); WSContentSend_P(HTTP_MLX90640_1_SNS); WSContentSend_P(HTTP_MLX90640_2a_SNS); WSContentSend_P(HTTP_MLX90640_2b_SNS); WSContentSend_P(HTTP_MLX90640_3a_SNS); WSContentSend_P(HTTP_MLX90640_3b_SNS); WSContentSend_P(HTTP_MLX90640_4a_SNS); WSContentSend_P(HTTP_MLX90640_4b_SNS); WSContentSpaceButton(BUTTON_MAIN); WSContentStop(); } void MLX90640HandleWebGuiResponse(void){ char tmp[(MLX90640_POI_NUM*2)+4]; WebGetArg("ul", tmp, sizeof(tmp)); // update line if (strlen(tmp)) { uint8_t _line = atoi(tmp); // AddLog(LOG_LEVEL_DEBUG, "MLX90640: send line %u", _line); float _buf[65]; if(_line==0){_buf[0]=1000+MLX90640.Ta;} //ambient temperature modulation hack else{_buf[0]=(float)_line;} memcpy((char*)&_buf[1],(char*)&MLX90640.To[_line*64],64*4); Webserver->send_P(200,PSTR("application/octet-stream"),(const char*)&_buf,65*4); return; } WebGetArg("up", tmp, sizeof(tmp)); // update POI to browser if (strlen(tmp)==1) { Webserver->send_P(200,PSTR("application/octet-stream"),(const char*)&MLX90640.pois,MLX90640_POI_NUM*2); return; } else if (strlen(tmp)>2) { // receive updated POI from browser uint32_t _poi = atoi(tmp); uint32_t _poiNum = (_poi-(_poi%10000))/10000; MLX90640.pois[_poiNum*2] = (_poi%10000)/100; MLX90640.pois[(_poiNum*2)+1] = _poi%100; // AddLog(LOG_LEVEL_DEBUG, PSTR("RAW: %u, POI-%u: x: %u, y: %u"),_poi,_poiNum,MLX90640.pois[_poiNum],MLX90640.pois[_poiNum+1]); for(int i = 0;i(MLX90640_POI_NUM-1)&&XdrvMailbox.index<1) return false; _idx = (XdrvMailbox.index-1)*2; if (XdrvMailbox.data_len > 0) { uint32_t _coord = TextToInt(XdrvMailbox.data); MLX90640.pois[_idx] = (_coord%10000)/100; if(MLX90640.pois[_idx]>31) MLX90640.pois[_idx]=31; MLX90640.pois[_idx+1] = _coord%100; if(MLX90640.pois[_idx+1]>23) MLX90640.pois[_idx+1]=23; } AddLog(LOG_LEVEL_INFO, PSTR("POI-%u = x:%u,y:%u"),XdrvMailbox.index,MLX90640.pois[_idx],MLX90640.pois[_idx+1]); Response_P(S_JSON_MLX90640_COMMAND_NVALUE, command, XdrvMailbox.payload); break; default: // else for Unknown command serviced = false; break; } } else { return false; } return serviced; } /************************************************************************\ * Init \************************************************************************/ void MLX90640init() { if (MLX90640.type || !I2cSetDevice(MLX90640_ADDRESS)) { return; } Wire.setClock(400000); int status = -1; if(!MLX90640.dumpedEE){ status = MLX90640_DumpEE(MLX90640_ADDRESS, MLX90640.Frame); if (status != 0){ AddLog(LOG_LEVEL_INFO, PSTR("Failed to load system parameters")); } else { AddLog(LOG_LEVEL_INFO, PSTR("MLX90640: started")); MLX90640.type = true; } MLX90640.params = new paramsMLX90640; } } /************************************************************************\ * Run loop \************************************************************************/ void MLX90640every100msec(){ static uint32_t _job = 0; int status; uint32_t _time; if(!MLX90640.extractedParams){ static uint32_t _chunk = 0; AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: will read chunk: %u"), _chunk); _time = millis(); status = MLX90640_ExtractParameters(MLX90640.Frame, MLX90640.params, _chunk); if (status == 0){ AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: parameter received after: %u msec, status: %u"), TimePassedSince(_time), status); } if (_chunk == 5) MLX90640.extractedParams = true; _chunk++; return; } switch(_job){ case 0: if(MLX90640_SynchFrame(MLX90640_ADDRESS)!=0){ _job=-1; AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: frame not ready")); break; } // _time = millis(); status = MLX90640_GetFrameData(MLX90640_ADDRESS, MLX90640.Frame); // AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: got frame 0 in %u msecs, status: %i"), TimePassedSince(_time), status); break; case 1: MLX90640.Ta = MLX90640_GetTa(MLX90640.Frame, MLX90640.params); break; case 2: // _time = millis(); MLX90640_CalculateTo(MLX90640.Frame, MLX90640.params, 0.95f, MLX90640.Ta - 8, MLX90640.To, 0); // AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: calculated temperatures in %u msecs"), TimePassedSince(_time)); break; case 5: if(MLX90640_SynchFrame(MLX90640_ADDRESS)!=0){ _job=4; break; } // _time = millis(); status = MLX90640_GetFrameData(MLX90640_ADDRESS, MLX90640.Frame); // // AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: got frame 1 in %u msecs, status: %i"), TimePassedSince(_time), status); break; case 7: // _time = millis(); MLX90640_CalculateTo(MLX90640.Frame, MLX90640.params, 0.95f, MLX90640.Ta - 8, MLX90640.To, 1); // AddLog(LOG_LEVEL_DEBUG, PSTR("MLX90640: calculated temperatures in %u msecs"), TimePassedSince(_time)); break; default: break; } _job++; if(_job>10) _job=0; } /*********************************************************************************************\ * Interface \*********************************************************************************************/ void MLX90640Show(uint8_t json) { char amb_tstr[FLOATSZ]; dtostrfd(MLX90640.Ta, Settings->flag2.temperature_resolution, amb_tstr); if (json) { ResponseAppend_P(PSTR(",\"MLX90640\":{\"" D_JSON_TEMPERATURE "\":[%s"), amb_tstr); for(int i = 0;iflag2.temperature_resolution, obj_tstr); ResponseAppend_P(PSTR(",%s"),obj_tstr); // AddLog(LOG_LEVEL_DEBUG, PSTR("Array pos: %u"),MLX90640.pois[i*2]+(MLX90640.pois[(i*2)+1]*32)); AddLog(LOG_LEVEL_DEBUG, PSTR("POI-%u: x: %u, y: %u"),i+1,MLX90640.pois[i*2],MLX90640.pois[(i*2)+1]); } ResponseAppend_P(PSTR("]}")); } } /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv43(uint32_t function) { bool result = false; if (FUNC_INIT == function) { MLX90640init(); } if(MLX90640.type){ switch (function) { case FUNC_EVERY_100_MSECOND: MLX90640every100msec(); break; case FUNC_JSON_APPEND: MLX90640Show(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_ADD_MAIN_BUTTON: WSContentSend_P(HTTP_BTN_MENU_MLX90640); break; case FUNC_WEB_ADD_HANDLER: WebServer_on(PSTR("/mlx"), MLX90640HandleWebGui); break; #endif // USE_WEBSERVER case FUNC_COMMAND: result = MLX90640Cmd(); break; } } return result; } #endif // USE_MLX90640_SENSOR #endif // USE_I2C