/*
  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 <http://www.gnu.org/licenses/>.


  --------------------------------------------------------------------------------------------
  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 <MLX90640_API.h>

const char MLX90640type[] PROGMEM = "MLX90640";

#ifdef USE_WEBSERVER
#define WEB_HANDLE_MLX90640 "mlx"
const char HTTP_BTN_MENU_MLX90640[] PROGMEM = "<p><form action='" WEB_HANDLE_MLX90640 "' method='get'><button>MLX90640</button></form></p>";
#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 =
    "<script type='text/javascript'>"
        "const map=(value,x1,y1,x2,y2)=>(value-x1)*(y2-x2)/(y1-x1)+x2;"
        "const image = new Image;"
        "var canvas,ctx,grd;"
        "var gPx,poi=[];var rA=[];" //gradient pixel, POI's, rawArray
    "function getMousePos(canvas, evt) {"
        "const rect = canvas.getBoundingClientRect();"
        "var x = evt.clientX-rect.left;"
        "if(x>320){"
          "x=319;"
        "}"
        "return {"
          "x: Math.floor(map(x,0,320,0,31.9)),"
          "y: Math.floor(map((evt.clientY - rect.top),0,240,0,23.9))};"
        "}"
  ;
#endif //USE_UNISHOX_COMPRESSION
#ifdef USE_UNISHOX_COMPRESSION
const size_t HTTP_MLX90640_2a_SNS_SIZE = 632;
const char HTTP_MLX90640_2a_SNS_COMPRESSED[] PROGMEM = "\x33\xBF\xA0\xB7\x9A\x3E\x23\x8C\xF0\x5E\x74\x5B\xD4\xFE\x67\x61\x1D\xD3\x02\xF8"
                             "\x3A\xDC\xE3\xBA\x77\x91\xED\xF8\x47\x74\xFB\x16\x11\xF6\x75\x05\xBC\xCE\xF1\xE0"
                             "\xF7\x1D\x47\x29\xB3\xBC\x78\x20\x43\xBA\xBE\x11\xDD\xF1\xD4\x66\x77\x8F\x69\x9D"
                             "\xFD\x1B\x3E\x7C\xE6\x3E\x88\xD8\x43\x48\x22\x15\x54\x30\xBE\xCD\x42\xDF\xA8\xEE"
                             "\x9D\xE3\xC1\xB3\xE7\x4C\xEF\xBB\x10\xCB\xD5\x74\xC3\x15\x7C\x3C\xCF\x80\x8B\xA0"
                             "\x1E\xDD\x30\x77\x4D\x9F\x3A\x7D\xD8\x86\x45\xEA\xBA\x67\xC3\xE1\xCC\x3F\x47\xE8"
                             "\x8D\x9F\x3A\x7A\xAE\x85\xF8\xF8\x7C\x39\x4D\x90\x20\xE7\xD6\x43\x91\xF1\x1B\x3E"
                             "\x74\xFB\xF0\xCC\xEF\x33\xC1\x9D\xFD\x69\xE3\x4C\x23\xBB\x64\x38\xE8\x38\xCA\x99"
                             "\x04\xF8\x7A\x85\x1F\x0F\x87\x2B\x99\xDE\x3B\x87\xB4\x8C\xEF\xE8\xC1\x5A\x3E\x2E"
                             "\x63\xE8\x8C\x05\x97\x47\x2E\x88\xAF\xFF\xB3\x23\xBB\x64\x38\xEF\x1E\x02\xDE\x67"
                             "\xC3\x05\x67\xBC\x71\x9E\xF9\xE0\xB4\xC1\xDD\x0B\x79\x9F\x87\x23\x6C\xEF\x1E\xD2"
                             "\x0B\x79\x9F\x02\xDE\x67\x59\xC8\xDB\x3C\x13\x1C\x77\x4F\xBB\x39\x0F\xB3\xBC\x74"
                             "\x2D\xEE\x7F\x21\x45\x44\x34\x82\x3E\x05\xBC\xCE\x95\x84\x63\x4D\x8C\x43\xBA\x72"
                             "\x88\x10\xB2\x73\x8C\xF7\x11\x8C\xFA\x3B\xBA\x7C\x39\x0F\x07\x70\xB5\x1E\x88\xC1"
                             "\x59\xD0\x27\xC3\xD4\x28\xF0\xB4\xED\x9D\xB3\xBC\x8F\x6A\xF9\x59\xEF\x69\xDB\x3B"
                             "\xA1\x6F\x33\xD6\x73\xB0\xEF\x1D\x70\xF7\xCE\xE1\xF0\xEE\x11\x82\xB3\xDE\xD3\xDF"
                             "\x3C\x1E\xE0\x42\xE3\x90\x2D\xE6\x76\xCE\x42\x04\x5D\xB0\xE4\x3B\xC7\x70\x81\x07"
                             "\x6B\x38\xCF\x04\x37\x3C\x77\x4E\xF1\xE0\xF7\x1E\xE0\x4E\xE1\x28\xE4\xA2\x04\x1E"
                             "\x1F\x0C\x8F\x9C\xC7\xD1\x0B\xDE\xA3\x9F\x20\x45\xE1\x0C\x10\xB7\x13\x8C\x81\x07"
                             "\x71\x32\x04\x8D\xC0\xF6\x8C\xCD\x3D\xED\x3B\x0E\x51\xEF\x9F\x0F\x78\x8C\x8F\x7B"
                             "\x4F\x7C\xEA\x46\x47\xBD\xA8\xED\xA3\x90\xF7\xCF\x7C\xF0\x77\x0E\xD9\xDB\x2D\x3C"
                             "\x1E\xE3\xDC\x7B\x8F\x07\xB8\xF0\x08\xDC\x67\x15\x19\x0C\x68\xF8\x8F\xBB\xFF\xEC"
                             "\xC8\x70\xB3\x06\x1F\xCF\xB3\xC1\xB3\xE7\x4C\x18\xF8\xEE\x9F\x64\x3C\x4C\xA8\xFB"
                             "\x3A\x8F\xB3\xB0\x68\x46\xC3\xB4\x7D\x9D\xBF\x1D\xB3\xEC\xF8\x7D\x9D\xB3\x33\xAA"
                             "\xBE\x2D\x9D\xE3\xC1\xB3\xE7\x4F\x3E\x10\xEE\x9D\xE3\xC1\xEE\x3C\x1B";
#define  HTTP_MLX90640_2a_SNS       Decompress(HTTP_MLX90640_2a_SNS_COMPRESSED,HTTP_MLX90640_2a_SNS_SIZE).c_str()
#else
const char HTTP_MLX90640_2a_SNS[] PROGMEM =
  "var line = 0;"
  "setInterval(()=>{"
  "rl('ul',line);"   // 0 = do NOT force refresh
  "},200);"
  "function rl(s,v){"         //source, value
    "var xr=new XMLHttpRequest();"
    "xr.onreadystatechange=()=>{"
      "if(xr.readyState==4&&xr.status==200){"
            "var aB = xr.response;" // arrayBuffer
            "var i;"
            "if (aB.byteLength==260) {" // 2 lines of pixel data
              "var fA = new Float32Array(aB);" //floatArray
              "line=fA[0];"
              "if(line>1000){line=line-1000;eb('a1').innerHTML=line.toFixed(2);line=0}" //ambient hack
              "for (i=1; i < fA.length; i++) { "
                "rA[i+(line*64)-1] = fA[i];"
              "}"
              "line = line+1;"
              "if(line>11) {line=0;mos();"
              //"console.log(rA);"
              "}"
            "}"
            "if (aB.byteLength==12)"
              "{var y=new Uint8Array(aB);"
            	"for (i=0; i < y.length; i++){"
                "poi[i/2]=[y[i], y[i + 1]]; ++i;"
              "}"
            "}"
        "};"
      "};"
    "xr.responseType = 'arraybuffer';"
    "xr.open('GET','/mlx?'+s+'='+v,true);"
    "xr.send();"
    "};"
  ;
#endif //USE_UNISHOX_COMPRESSION
#ifdef USE_UNISHOX_COMPRESSION
const size_t HTTP_MLX90640_2b_SNS_SIZE = 389;
const char HTTP_MLX90640_2b_SNS_COMPRESSED[] PROGMEM = "\x30\x2F\x83\xAD\xCE\x43\x73\xC7\x74\xEF\x1E\xD3\x3B\xFA\xCF\x06\x8F\x88\x4C\x0C"
                             "\x58\xD7\xD4\x74\x0F\xEE\xE9\x93\x09\x8D\x7D\x47\x74\xFB\x0E\xF8\xCE\xFC\x7D\x9D"
                             "\xE3\xC6\x78\x33\xA0\xFE\x89\x42\x91\xF1\x1C\xBA\x3C\x16\x78\x33\xA0\xA7\xA3\xC2"
                             "\xA9\x1F\x11\xCA\xC3\xC1\x19\xDF\xD6\x07\x46\xC4\x7C\x59\xE0\xCE\x83\xCE\x88\x3C"
                             "\xEA\x66\xCA\x3B\xA7\xD9\xCA\x21\x0F\xB3\xBC\x78\x31\x9F\x47\x74\xCE\xFE\xB4\xF8"
                             "\x71\x9E\x0B\x4F\x43\x95\x87\x82\xD3\xB6\x76\xCE\xF1\xED\x04\x4A\x66\xB3\xE1\xC6"
                             "\x78\x23\x59\xE8\x72\xE8\xF0\x46\xB3\xB6\x76\xC1\x07\x74\x32\x47\xC4\x72\xAD\x1D"
                             "\xC3\xAC\xEE\x11\x0D\xBA\x14\x74\x30\x16\x67\xCE\xE8\xDB\x18\x77\x7E\x56\x7B\xC7"
                             "\x77\x4F\x59\xCB\xA3\xBC\x76\xC8\xD6\x7B\xE7\x51\xC6\x75\x63\x3A\x99\xD5\x56\x8E"
                             "\xF1\xDE\x3C\x16\x07\x46\xC3\xA1\x8D\x08\x22\xF5\x19\x04\xD1\xF1\x1F\x7F\x1E\x1C"
                             "\x77\x4F\xB4\x76\xD0\xF1\x0C\x36\x1D\x04\xBA\xB3\xDE\x3B\xA6\x47\xAC\xE6\x1D\xE3"
                             "\xDF\x3B\x87\x6C\xEE\x1F\x67\x51\x02\x6D\x43\xB6\x72\x1E\xF9\xDC\x3B\x64\x0A\x15"
                             "\x4E\x51\xEF\x9D\xC3\xB6\x77\x0F\xB3\xBC\x7D\x90\x22\xE7\xE5\xF6\x1D\x1D\xD3\x59"
                             "\xEB\x39\x0E\xA2\xD3\xD6\x72\x1D\x50\xEA\x87\x78\xF0\x7B\x8F\x71\x68\xDB\x1E\x67"
                             "\x4F\x7C\x34\x7C\xCF\x06\x74\xAC\x21\x2E\xAC\x85\x97\xC8\x23\xBA\x7D\xE8\xDB\x1E"
                             "\x67\x60\xCE\x1E\x3E\xCE\xF1\xE0\xF7\x1B";
#define  HTTP_MLX90640_2b_SNS       Decompress(HTTP_MLX90640_2b_SNS_COMPRESSED,HTTP_MLX90640_2b_SNS_SIZE).c_str()
#else
const char HTTP_MLX90640_2b_SNS[] PROGMEM =
  "function mos(){" //map off screen"
      "var osc = document.createElement('canvas');" //offscreen
      "osc.width = 32;"
      "osc.height = 24;"
          "var octx = osc.getContext('2d');"
          "for (var i=0;i<24;i++){"
              "for (var j=0;j<32;j++){"
                  "var y = 239 - Math.floor(map(rA[(i*32)+j],0,40,0,239));" // 40 is max. temp for heat map
                  // console.log(gPx.data[y],gPx.data[y+1],gPx.data[y+2]);
                  "octx.fillStyle = 'rgb(' + gPx.data[(y*4)] + ',' + gPx.data[(y*4)+1] +',' + gPx.data[(y*4)+2] + ')';"
                  "octx.fillRect(j*1,i*1,1,1);"
              "}"
      "}"
      "image.src =osc.toDataURL('image/png');"
  "}"
   ;
#endif //USE_UNISHOX_COMPRESSION
#ifdef USE_UNISHOX_COMPRESSION
const size_t HTTP_MLX90640_3a_SNS_SIZE = 664;
const char HTTP_MLX90640_3a_SNS_COMPRESSED[] PROGMEM = "\x30\x2F\x83\xAD\xCE\x43\x33\x48\x43\xBA\x77\x8F\x68\xF7\xC4\x21\x0E\xE9\xDE\x3C"
                             "\x07\x46\xC3\xA0\xF0\x58\x3A\xC2\x20\xF0\x68\xCC\xF6\xD3\x2C\x18\xFF\x75\xB9\xC8"
                             "\xF8\x8F\xBF\x60\xBF\x86\xCE\xBC\x33\x7F\x3E\xCF\x06\x77\xF5\xF2\xC4\x7C\x5C\xC7"
                             "\xD1\x7E\xF8\x79\x9D\xD3\xBC\x78\xF9\x61\xD3\xCC\x26\x1D\x17\x60\x8C\x83\xCE\xA7"
                             "\xD5\xE3\xBA\xC7\x1D\xD3\xEC\x69\xCA\x3E\xCE\xF1\xDE\x3C\x17\xCB\x0E\x82\x30\x9D"
                             "\x02\x2D\x83\xBC\x78\x31\x9F\x3B\xA6\x77\xF4\x31\x6F\x21\x99\xA7\x78\xF6\x99\xDF"
                             "\xD0\x67\xC3\x93\x59\xE0\xB4\xC1\xDD\x63\x8E\xE9\xF6\x33\x34\x82\x3E\xCE\xF1\xD0"
                             "\xCE\xC2\x16\xCF\x87\xC1\x87\x78\xF6\x86\x7C\x39\x5B\xA7\x83\xDC\x41\xD1\xB0\xE8"
                             "\x63\x42\x08\xBD\x46\x41\x34\x7C\x47\xDF\xC7\x87\x59\xDD\x3E\xCE\xD8\x67\x6C\xFB"
                             "\x3A\x81\x06\x10\x20\xC2\x38\xCE\x9C\x77\x8F\xB3\xC1\x07\x46\xC3\xA0\xE6\x3D\xBC"
                             "\x43\x2E\x85\x02\x17\x09\x77\xF0\xCE\xE8\xCC\xD3\xDE\x18\x7B\xE7\xBC\x71\x9E\xF9"
                             "\xDB\x67\x4D\x3A\x8E\xE1\x02\x14\xB3\x90\x81\x06\x59\xC8\x75\x33\xAA\x8F\x59\x10"
                             "\xDB\xA1\x47\x42\x18\x5A\x08\x38\x81\x8D\x08\x20\x42\xC6\xCC\x67\x52\x3E\x23\xEC"
                             "\xE4\x3A\x69\x0C\x36\x22\x33\x7F\x12\xFA\xCF\xB3\xC1\xB0\xF8\x32\xFF\xE6\x5B\xD4"
                             "\x77\x46\x1D\xE3\xB6\x72\x10\x22\x62\x45\x4C\xD9\x47\x74\xD8\x08\x99\xF9\xC6\x7B"
                             "\xE7\x6E\x10\x24\xE9\xC7\x21\xD2\x8E\xF1\xE0\x8C\xEF\xEB\xB0\x46\x8F\x88\x4C\x0C"
                             "\x58\xD7\xD4\x74\x0F\xEE\xE9\x93\x09\x8D\x7D\x47\x74\xFB\x20\x8B\x4F\xB0\x41\xC1"
                             "\x29\x9B\x28\x14\x70\x82\xA6\x6C\xA2\xEC\x11\x9D\xD3\xEC\x86\x16\x16\x9D\x67\xDA"
                             "\x3B\x68\xD8\x8E\xDA\x3E\xCF\x34\x8F\xB4\x76\xD2\x3B\xBF\x2B\x3D\xE0\x43\xE1\x58"
                             "\xE4\x3D\xF3\xD7\x74\x77\x8E\xD9\x02\x26\xE4\x7B\xE7\x78\xE9\x58\x46\x34\xD8\xC4"
                             "\x3B\xA7\x28\xEF\x1D\xC3\xB6\x77\x0F\xB3\xB8\x53\x1D\x99\x79\x06\xAE\x91\x0C\xCF"
                             "\x1E\x68\xFB\x47\x6C\x11\x78\x1D\x47\x6D\x1F\x68\xEA\x04\x7A\x07\x21\xEF\x9D\xE3"
                             "\xC1\x76\x08\xCE\x96\x30\x63\xE1\x08\x31\x5A\x10\x87\x74\x10\xF8\x03\x3B\xC7\x80"
                             "\x43\xE5\xA0\x12\x2D\x82\x0D\x73\xDC\x7B\x8D";
#define  HTTP_MLX90640_3a_SNS       Decompress(HTTP_MLX90640_3a_SNS_COMPRESSED,HTTP_MLX90640_3a_SNS_SIZE).c_str()
#else
const char HTTP_MLX90640_3a_SNS[] PROGMEM =
  	"function poiD(){" //poi draw
          "grdD();"
          "ctx.globalCompositeOperation = 'source-over';"
			    "var rO = new Range();" //rangeObject
			    "rO.selectNodeContents(eb('m2'));"
			    "rO.deleteContents();"
  				"for(var p in poi){"
  					//"console.log('poi:'+ poi[p][0]);"
            "var c=150;"
            "if(eb('poiL').value==p){c=255;}"
            // "console.log(c);"
            "ctx.fillStyle = 'rgba('+c+','+c+','+c+',0.6)';"
            "ctx.beginPath();"
            "ctx.arc(poi[p][0]+0.5, poi[p][1]+0.5,1,0,2*Math.PI);"
            "ctx.fill();"
            "ctx.font = '1.5px Verdana';"
            "x=parseInt(p)+1;"
            "ctx.fillText(x, poi[p][0]+1.5, poi[p][1]+1.2);"
            "var node = document.createElement('LI');"
            "var textnode = document.createTextNode('POI-' + x + ':  ' +  (rA[(poi[p][1]*32)+poi[p][0]]).toFixed(2) + ' °C at Pos: ' + poi[p][0] + ' , ' + poi[p][1]);"
            "node.appendChild(textnode);"
            "eb('m2').appendChild(node);"
  				"}"
  			"}"
    ;
#endif //USE_UNISHOX_COMPRESSION
#ifdef USE_UNISHOX_COMPRESSION
const size_t HTTP_MLX90640_3b_SNS_SIZE = 477;
const char HTTP_MLX90640_3b_SNS_COMPRESSED[] PROGMEM = "\x30\x2F\x83\xAD\xCE\x5E\x74\x2C\x61\xDD\x3B\xC7\xB7\xE1\x1D\xD3\xEC\x58\xC3\xEC"
                             "\xEA\x38\xCE\xF1\xE0\x83\xBE\x33\xBF\x23\xE2\x63\x8E\xE9\xF6\x34\x23\x61\xF6\x77"
                             "\x8F\x01\xD1\xB1\x1F\x10\x20\xD5\x3A\x0F\x3A\x20\xF3\xA9\x9B\x28\xEE\x9F\x67\x28"
                             "\x84\x3E\xC1\x0F\x0D\x3A\x58\x82\x13\x33\x7D\x44\x16\xFA\x9F\x3F\x9D\xD3\xEC\x6E"
                             "\x0B\xF3\x1B\x86\x6C\xFB\x3A\x90\x21\xE9\xC7\x75\x99\xD1\xDE\x47\xB4\xCE\xFE\x86"
                             "\x90\xC4\x7C\x43\xCE\x88\x6E\x0B\xF3\x21\x99\xE3\xBA\x08\x39\x29\xD4\x99\x9D\x1D"
                             "\xE3\xC3\x1C\x77\x4F\xB1\xA7\x21\xF6\x77\x8E\x85\xBD\xCF\xE4\x28\xA8\x86\x90\x48"
                             "\xF8\x8F\xB2\xA6\x34\x63\xFD\xD0\xBF\xB3\xCD\x1F\x68\xED\xA3\xBB\xF2\xB3\xDE\x3B"
                             "\xA3\x48\x61\xD0\xC8\xF5\x9C\xBA\x3B\xC7\x6C\x86\x90\xC3\xA1\xB0\xF7\xCE\x95\x84"
                             "\x63\x4D\x8C\x43\xBA\x72\x8E\xF1\xDE\x3B\x87\x6C\xEE\x1F\x67\x70\xAE\x91\x0C\xCF"
                             "\x02\x0E\x18\x34\x86\x1D\x0D\x88\xED\xA3\xED\x1D\x40\x87\x2C\xC8\xF0\x7B\x81\x6F"
                             "\x82\x01\x30\x7F\x81\x4B\x82\x02\x10\x15\xF8\x20\x33\xBF\xAD\x10\xD8\x08\x5C\x54"
                             "\x0C\xCD\x20\x8F\xB3\xBC\x74\x33\xB0\x85\xB3\xC0\xCC\xD3\xDE\xD1\x0D\x87\xBE\x7B"
                             "\xC7\x19\xEF\x9F\x08\x69\x08\x74\x36\x02\x2C\xD3\x90\xF7\xCF\x84\x34\x84\x3A\x19"
                             "\x1E\x06\xE7\x8E\xE9\xDE\x3C\x02\x1F\x1E\xA0\x97\x8E\x9E\xB3\x91\xB6\xCE\xD9\xDD"
                             "\x21\xA4\x21\xD0\xD8\x7A\xCE\x46\xCE\xF1\xDB\x21\xA4\x21\xD0\xC8\xEF\x1E\x0F\x71"
                             "\xDE\x3C\x1B";
#define  HTTP_MLX90640_3b_SNS       Decompress(HTTP_MLX90640_3b_SNS_COMPRESSED,HTTP_MLX90640_3b_SNS_SIZE).c_str()
#else
const char HTTP_MLX90640_3b_SNS[] PROGMEM =
      "function setup(){"
        "rl('up',0);"
        "canvas = eb('mlx');"
		    "ctx = canvas.getContext('2d');"
        "canvas.addEventListener('mousemove', function(evt) {"
          "var mP = getMousePos(canvas, evt);"
          // console.log((mP.y*32)+mP.x);
          "eb('m1').innerHTML = 'Temperature: ' + (rA[(mP.y*32)+mP.x].toFixed(2)) + ' at Pos: ' + mP.x + ' , ' + mP.y;"
        "});"
        "canvas.addEventListener('mousedown', function(evt) {"
          "var mD = getMousePos(canvas, evt);"
          "var idx = eb('poiL').value;"
          "poi[idx][0]=mD.x;"
          "poi[idx][1]=mD.y;"
		      //"console.log(poi);"
		      "mos();"
          "rl('up',eb('poiL').value*10000+(mD.x*100)+mD.y);" //poi-1: 2,14 -> 10214
        "});"
  ;
#endif //USE_UNISHOX_COMPRESSION
#ifdef USE_UNISHOX_COMPRESSION
const size_t HTTP_MLX90640_4a_SNS_SIZE = 468;
const char HTTP_MLX90640_4a_SNS_COMPRESSED[] PROGMEM = "\xD3\x08\xEE\x87\x7C\x67\x7E\x3A\x0F\x3A\x20\xF3\xA9\x9B\x28\xEF\x23\xDA\x1D\x1B"
                             "\x0E\x9E\x0E\xC2\x67\x74\xE4\x67\x54\x67\x78\xF0\x41\xD1\xB0\xE9\xA3\x6C\x79\x97"
                             "\x86\xE6\x50\xAD\xE1\xE2\x7D\x63\x82\x62\x23\xE2\xAF\x8B\x67\x81\xEF\x88\x8F\x88"
                             "\x3A\x36\x1D\x03\xFB\xBA\x64\x16\xF3\xBF\x90\xF7\xEC\x4D\x7D\x47\x74\xE3\x3A\x8E"
                             "\xE3\x3A\x8E\xE3\x3A\x8E\xE5\x61\xDE\x3C\x10\xF7\xC4\x3A\x58\x82\x10\x78\x16\x7C"
                             "\xBD\x58\x30\xEE\x9C\x67\x51\xDC\x3E\xC8\xC9\x84\x16\x0F\x9F\x60\x9D\x68\xE8\x71"
                             "\xFB\x4E\xA3\xB8\x7D\x96\x7E\xF8\x79\x82\x85\xD3\x95\xA7\x51\xDC\x3E\xC8\xCF\x70"
                             "\x27\x40\xA1\x70\xE6\x69\xD4\x77\x0F\xB2\x12\xFE\x68\x38\x21\x60\xA1\x8F\x1C\x87"
                             "\x51\xDC\x3E\xC8\x70\x56\x19\xA0\x20\xD9\x21\x0E\xE9\xDE\x3C\x0F\x10\xC3\x60\x21"
                             "\x70\x5A\x3C\xE8\xB4\x6D\x8F\x32\x12\xEA\xCE\xE9\xCB\xAD\x3A\x8E\xE3\x3A\x8E\xE4"
                             "\x3A\xAA\xD1\xDE\x3C\x10\xDC\xF1\xDD\x3B\xC7\x8D\x1B\x63\xCC\xE9\x9C\x16\x58\x88"
                             "\xF8\x8C\x0B\xE0\xEB\x73\x91\xDD\x04\x2E\x29\xC4\xFD\x8F\x96\x8D\xB1\xE6\x77\x74"
                             "\x6D\x8F\x33\xA8\xE3\x3A\x99\xD5\x74\x75\x56\x1D\xE3\xC1\x0C\xCD\x21\x0E\xE9\xDE"
                             "\x3C\x1E\xE3\xDC\x7B\x81\x13\x12\x04\x1D\x74\xF6\x87\x46\xC3\xA1\x8D\x08\x22\xF5"
                             "\x19\x04\xD1\xF1\x0F\x7C\x43\xC0\x21\xD0\x2F\xB0\xE8\xEE\x9C\xBA\x20\x41\xE2\xB8"
                             "\xEA\x39\x58\x77\x8F\x07\xB8\xF4\x3B\x0B\xC1\xFF\x46\x51\xF8";
#define  HTTP_MLX90640_4a_SNS       Decompress(HTTP_MLX90640_4a_SNS_COMPRESSED,HTTP_MLX90640_4a_SNS_SIZE).c_str()
#else
const char HTTP_MLX90640_4a_SNS[] PROGMEM =
        "if (canvas.getContext) {"
          "ctx.scale(10,10);"
          "ctx.imageSmoothingEnabled = true;"
          "grd = ctx.createLinearGradient(0, 0, 0, 24);" // gradient
          "grd.addColorStop(0, 'yellow');"
          "grd.addColorStop(.075, 'orange');"
          "grd.addColorStop(.25, 'violet');"
          "grd.addColorStop(.45, 'darkblue');"
          "grd.addColorStop(1, 'black');"
          "grdD();"
          "gPx = ctx.getImageData(325, 0, 1,239);"
          "mos();"
            "image.onload = function () {"
                "ctx.drawImage(image,0,0,32,24);"
                "poiD();"
            "}"
        "}"
      "}"
    "function grdD(){"  //gradient draw()
      "ctx.fillStyle = grd;"
      "ctx.fillRect(32, 0, 2,24);}"
    "</script>"
;
#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 =
  "<body onload='setup();'>"
  "<canvas id='mlx' width='340' height='240'></canvas>"
  "<div></div>"
  "<select id='poiL' onchange='mos()'>"
	  "<option value='0'>POI-1</option>"
	  "<option value='1'>POI-2</option>"
	  "<option value='2'>POI-3</option>"
	  "<option value='3'>POI-4</option>"
	  "<option value='4'>POI-5</option>"
	  "<option value='5'>POI-6</option>"
	"</select>"
    "<div id='m1'></div>"
    "<div>POI-0: <span id='a1'></span>°C (sensor)</div>"
    "<div id='m2'></div>"
  "</body>"
  ;
#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;i++){
    AddLog(LOG_LEVEL_DEBUG, PSTR("POI-%u: x: %u, y: %u"),i+1,MLX90640.pois[i*2],MLX90640.pois[(i*2)+1]);
    }
    return;
  }
}

void MLX90640HandleWebGui(void){
  if (!HttpCheckPriviledgedAccess()) { return; }
  MLX90640HandleWebGuiResponse();
  MLX90640UpdateGUI();
}
#endif // USE_WEBSERVER

/************************************************************************\
 * Command
\************************************************************************/
bool MLX90640Cmd(void){
  char command[CMDSZ];
  bool serviced = true;
  uint8_t disp_len = strlen(D_CMND_MLX90640);

  if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_MLX90640), disp_len)) {  // prefix
    int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kMLX90640_Commands);
    uint32_t _idx;
    switch (command_code) {
      case CMND_MLX90640_POI:
        if(XdrvMailbox.index>(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;i<MLX90640_POI_NUM;i++){
    char obj_tstr[FLOATSZ];
    dtostrfd(MLX90640.To[MLX90640.pois[i*2]+(MLX90640.pois[(i*2)+1]*32)], Settings->flag2.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;
      case FUNC_ACTIVE:
        result = true;
        break;
    }
  }
  return result;
}

#endif  // USE_MLX90640_SENSOR
#endif // USE_I2C
