/*
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-1 "
"POI-2 "
"POI-3 "
"POI-4 "
"POI-5 "
"POI-6 "
" "
"
"
"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(uint8_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