LoginSignup
6
5

More than 3 years have passed since last update.

M5Stackでサーモグラフィを作りkintoneにデータを蓄積し可視化する(後編)

Posted at

概要

先日、前編でM5Stackでサーモグラフィを作るまでの手順を紹介しました。

M5Stackでサーモグラフィを作りkintoneにデータを蓄積し可視化する(前編)
https://qiita.com/yukataoka/items/73f1e5c88649bd686093
IMG_6464-1024-2.jpg

後編では、M5Stackサーモグラフィのデータをkintoneに保管し、kintone側でもサーモグラフィの表示を再現します。
この記事は kintone2 Advent Calendar 2019 20日目 ですが、デープなIoT話で恐縮ですが、こんなこともできる程度の参考にでもしていただければ幸いです。
サーモグラフィー01.png

利用機器

前編と同じ M5Stack FIREとミニサーマルカメラユニット を用います。

kintoneの準備

以下のようなkintone「サーモグラフィ」アプリを準備します。
サーモグラフィー02.png

フィールド

フィールドは以下のように設定します。

フィールド名 タイプ フィードコート・要素ID
日時 日時 日時
MACアドレス 文字列(1行) mac
- スペース dysplayData
列数(X) 数値 col
行数(Y) 数値 row
最高温度 数値 max
最低温度 数値 min
サーモグラフ生データ 文字列(複数行) data

APIトークン

「レコード追加」アクセス権を設定したAPIトークンを生成します。

開発環境

前編の開発環境と、kintoneのJavaScriptファイルを編集する環境があれば十分です。

開発手順

先ず、M5StackからkintoneのWeb APIでサーモグラフィーのデータをPOSTするように、前編 のソースコードを変更します。
続いて、kintoneのアプリの詳細画面でサーモグラフィデータからサーモグラフィ画像を表示します。

M5StackでkintoneのWeb APIを実行し、kintoneにデータを保管するためのプログラム改良

M5Stackをwifi接続し、kintoneのAPIを実行してkintoneにデータを追加するための改良を、前編の「MLX90640.ino」ソースコードに行います。
※完成した「MLX90640.ino」のソースコードはコピペで使えるように、この記事最後の「参考」に掲載しています。

前編で使った「MLX90640.ino」のソースコードの32行目辺りにwifiとHTTPClientのライブラリィを追加します。

MLX90640.c
#include <WiFi.h>
#include <HTTPClient.h>

「MLX90640.ino」のソースコード114行目 void setup() 直前辺りに、wifiやkintoneにデータを追加するための作業用変数などの定義を追加します。

MLX90640.c
// Wifi 設定
const char* ssid       = "wifiのSSID";
const char* password   = "wifiのPASSWORD";

// kintone データ更新用設定
int dataUpTime = 0;
char macAd[24];
char csv[2310];
char json[4096];
HTTPClient http;

「MLX90640.ino」のソースコード130行目 void setup() 内にwifiアクセスポイントへの接続と、IPアドレスとMacアドレスを取得する処理を追加します。
IPアドレスやMACアドレスの取得は必須ではないので、お好みで。

MLX90640.c
  // Wifi AP に接続
  WiFi.begin(ssid, password);  //  Wi-Fi APに接続
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
    delay(100);
    Serial.println("Wifi connecting...");
    if(i > 100){
      Serial.println("Wifi connect faild.");
      break;
    }
    i++;
  }

  // IPアドレスとMacアドレスを取得
  M5.Lcd.print(F("IP addr:"));
  IPAddress ipaddr = WiFi.localIP();
  M5.Lcd.println(ipaddr);
  delay(1000);
  byte mac[6];
  WiFi.macAddress(mac);
  sprintf(macAd, "%02X-%02X-%02X-%02X-%02X-%02X", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);

「MLX90640.ino」のソースコード375行目 void loop() 内の最大温度、最小温度を求める部分に、kintone用の温度データの作成処理を追加します。

・元のコード

MLX90640.c
   for ( int itemp = 0; itemp < sizeof(pixels) / sizeof(pixels[0]); itemp++ )
  {
    if ( pixels[itemp] > max_v )
    {
      max_v = pixels[itemp];
    }
    if ( pixels[itemp] < min_v )
    {
      min_v = pixels[itemp];
    }
  }

・kintone用の温度データ作成処理を追加

MLX90640.c
  int csvCount = 0; // kintone POST 用CSVデータ作成用
  for ( int itemp = 0; itemp < sizeof(pixels) / sizeof(pixels[0]); itemp++ )
  {
    if ( pixels[itemp] > max_v )
    {
      max_v = pixels[itemp];
    }
    if ( pixels[itemp] < min_v )
    {
      min_v = pixels[itemp];
    }

    // kintone POST 用CSVデータ作成
    // (32 * 24) * 3 = 2304
    char str[6];
    int pix = pixels[itemp];
    sprintf(str,"%d,", pix);
    for(int count=0; str[count] != '\0'; count++ ){
      if(csvCount < 2304){
        csv[csvCount] = str[count];
        ++csvCount;
      }
    }
  }

「MLX90640.ino」のソースコードの432行目 void loop() の最後に、kintone にデータをPUTする処理を追加します。
kintone にデータをPUTするタイミングは、最初の計測時と、約60秒の間隔で実行します。

MLX90640.c
  // kintone に保管
  dataUpTime++;
  if(dataUpTime == 1) {
    sprintf(json,"{\"app\":\"kintoneアプリのID\",\"record\":{\"mac\":{\"value\":\"%s\"},\"max\":{\"value\":\"%d\"},\"min\":{\"value\":\"%d\"},\"col\":{ \"value\":\"%d\"},\"row\":{\"value\":\"%d\"},\"data\":{\"value\":\"%s\"}}}",macAd,max_v,min_v,COLS,ROWS,csv);

    int httpResponseCode = 0;
    http.begin("https://yukataoka.cybozu.com/k/v1/record.json");
    http.addHeader(F("X-Cybozu-API-Token"), F("APIトークン"));
    http.addHeader(F("Content-type"), F("application/json"));
    httpResponseCode = http.POST(json);
    Serial.printf("httpResponseCode = %d \n", httpResponseCode);
    Serial.println(http.getString());
  }

  // fps は1秒間の計測回数
  if(dataUpTime > (60 * fps)) dataUpTime = 0;

以上で、「MLX90640.ino」への処理追加は完了です。

kintoneのJavaScriptでサーモグラフィを表示

kintoneの詳細画面でサーモグラフィの画像を表示する以下の処理を追加します。
1.「サーモグラフ生データ」のフィールドのサーモグラフィデータ(1行に横32x縦24座標分のデータをカンマ区切りで保管)から、指定の行、列の温度を取得
2.所得した温度に対応したカラーコードに変換
3. カラーコードをCanvasの該当する行、列の座標に描画
4. 32x24の全座標分を1.から3.の手順でCanvasに描画し、kintoneのスペース「dysplayData」に表示

kintoneでCanvas画像を表示する等の説明は今回省略します。(以外と情報が少ないので、後日まとめてみるかも。)

heatMapV1.0.js
(function() {
  "use strict";

  const Scale  = 12;
  const Width  = 32;
  const Height = 24;

  // 温度に対応したカラーコードを取得
  function GetColor(maxData, minData, data) {
    const color = [
      "#8000FF", // 15以下
      "#4000FF","#4000FF","#4000FF",  // 16
      "#0000FF","#0000FF","#0000FF",  // 19
      "#0040FF","#0040FF",  // 22
      "#0080FF",  // 23
      "#00BFFF",  // 24
      "#00FFFF",  // 25
      "#00FF00",  // 26
      "#BFFF00",  // 27
      "#FFFF00",  // 28
      "#FFBF00",  // 29
      "#FF8000","#FF8000",  // 30
      "#FF4000","#FF4000", // 32
      "#FF0000"  // 34以上
    ];
    var colorNo = data - 15;
    if(colorNo < 0) colorNo = 0;
    if(colorNo > color.length) colorNo = color.length - 1;
    return color[colorNo];
  }

  // 配列を左右反転(人の見た目に合わせる)
  function ReversThermo(thermo) {
    var respons = [];
    for(var i=0; i < (thermo.length - 1); i++){
      var x = (Width - 1) - (i % Width);
      var y = Math.floor(i / Width);
      var z = y * Width + x;
      respons[z] = thermo[i];
    }
    return respons;
  }

  // レコード詳細表示時イベント
  var eventsDetailShow = [
    'app.record.detail.show',
    'mobile.app.record.detail.show'];
  kintone.events.on(eventsDetailShow, function(event) {

    // データがない場合は処理しない
    var record = event.record;
    if(record.data.value.length < 1) return event;

    // CSV データを配列に展開 LOWS(Y)=24 COLS(X)=32
    var thermo = record.data.value.split(',');
    var rThermo = ReversThermo(thermo);

    // キャンバスにサーモグラフィー画像表示用の設定
    var img = new Image();
    var canvas = document.createElement('canvas');
    canvas.id = 'myCanvas';
    canvas.width  = Width  * Scale;
    canvas.height = Height * Scale;

    var ctx = canvas.getContext('2d');
    for(var i=0; i < (rThermo.length - 1); i++){
      var x = (i % Width) * Scale;
      var y = Math.floor(i / Width) * Scale;
      ctx.fillStyle = GetColor(record.max.value, record.min.value, rThermo[i]);
      ctx.fillRect(x, y, (x + Scale), (y + Scale));
    }

    // キャンバスをkintoneスペースに追加
    if (event.type === 'app.record.detail.show') {
      kintone.app.record.getSpaceElement('dysplayData').appendChild(canvas);
    } else if (event.type === 'mobile.app.record.detail.show') {
      kintone.mobile.app.record.getSpaceElement('dysplayData').appendChild(canvas);
    }

    return event;
  });

})();

このJavaScriptファイルをkintoneに登録するだけです。

結果

M5Stackをwifi接続し、kintoneにサーモグラフィのデータを追加した結果

M5Stackを起動するとwifi接続を行い、M5Stack画面でサーモグラフィ表示が始まると同時にCSV形式でデータがkintoneのアプリに保管されています。
サーモグラフィー06.png

kintoneアプリ「サーモグラフ生データ」に保管されたデータは、以下のようなCSV形式です。
見やすくするため、サーモグラフィの横幅分の32明細で改行しています。

26,29,22,23,26,28,22,21,23,23,20,19,24,25,21,20,25,26,22,21,24,25,21,20,25,24,20,19,24,24,13,12,
28,26,23,22,27,26,23,20,24,23,21,20,24,25,22,21,26,25,22,20,24,22,21,20,26,24,20,19,23,24,14,15,
27,28,22,22,25,26,21,22,23,24,21,22,24,26,22,21,25,26,21,20,23,25,21,21,23,23,19,18,22,23,16,15,
28,26,22,21,27,25,23,21,24,23,22,21,25,24,22,22,25,25,21,21,24,24,21,21,24,24,19,19,23,23,17,15,
27,28,22,22,26,26,22,21,25,25,22,22,24,25,22,21,24,24,21,22,25,25,21,21,22,24,19,18,22,23,16,16,
27,25,22,22,26,25,23,22,25,24,22,21,25,24,22,21,24,24,23,21,25,24,21,21,24,23,19,19,22,22,17,16,
26,27,22,22,25,25,22,22,25,25,22,22,24,25,22,22,24,26,23,23,25,25,21,21,22,23,19,18,22,22,17,16,
28,26,22,22,25,24,23,22,25,24,23,22,25,24,23,21,24,25,23,23,25,24,22,20,23,23,19,18,22,21,18,18,
26,26,22,22,25,25,22,22,24,26,22,22,25,25,22,22,24,24,22,22,24,25,21,21,23,24,19,19,21,23,17,16,
27,26,23,22,26,26,23,23,25,25,23,22,25,24,23,23,25,24,23,22,25,25,22,21,23,22,19,18,22,21,16,16,
25,27,22,22,25,26,23,23,25,26,23,23,25,25,23,23,25,25,23,22,25,25,21,21,23,23,19,18,22,22,16,17,
26,23,23,22,26,26,23,23,26,25,24,23,26,25,24,23,25,25,23,22,25,25,22,21,23,23,20,18,21,21,18,16,
24,25,21,21,26,26,24,24,26,26,23,24,26,27,25,24,26,26,23,23,25,26,22,22,24,24,19,18,21,22,16,17,
25,24,22,20,27,26,25,24,26,26,25,24,27,27,25,25,27,26,24,24,25,25,24,23,25,23,19,20,21,22,17,15,
27,28,22,23,28,29,27,26,28,28,26,26,28,29,27,27,29,29,26,25,27,27,25,24,25,24,19,19,21,22,16,16,
27,26,23,22,28,28,28,27,29,29,28,28,30,30,29,29,32,30,27,27,28,28,26,25,25,24,19,19,22,22,18,18,
25,27,23,24,29,30,27,27,30,30,28,28,31,32,30,30,32,32,29,28,29,29,25,24,27,26,20,19,22,23,18,17,
26,26,26,26,33,31,30,29,32,31,30,29,32,31,30,29,32,32,30,30,31,31,28,27,29,28,21,20,22,23,20,19,
28,31,30,30,35,35,32,33,34,36,34,34,36,37,34,34,36,36,33,32,34,34,29,28,31,30,25,23,25,23,19,17,
32,31,31,31,35,35,34,33,36,35,34,34,36,36,34,34,36,35,33,32,33,33,29,28,30,29,25,25,26,25,19,17,
31,32,29,28,33,34,30,31,34,35,32,32,34,35,32,31,34,33,31,30,32,32,29,27,30,29,25,25,28,28,19,16,
31,31,29,28,33,31,30,29,33,33,31,31,33,33,31,31,34,33,30,30,32,31,27,27,29,29,26,25,28,28,21,19,
30,32,27,27,30,31,28,27,31,31,29,29,31,32,29,29,31,31,28,27,30,30,27,26,29,29,25,25,28,29,21,14,
32,31,28,27,31,30,28,28,31,31,29,28,31,31,29,28,30,31,28,27,30,29,26,25,29,29,25,26,29,30,17,15,

kintoneの詳細画面でサーモグラフィを表示

kintoneの明細画面にサーモグラフィの画像を表示しています。
サーモグラフィー04.png
Canvasに描画したサーモグラフィは以下のような画像でも保存できます。
サーモグラフィー05.png
実際のM5Stackのサーモグラフィ表示と比べても、kintoneでおおよそ同じ表示を再現できています!
IMG_6478.jpg
今回はCanvasで表示したのみですが、この画像をkintoneのファイルで保管できると、一覧画面でサーモグラフィ温度変化の推移なども確認できそうですね。

参考

「MLX90640.ino」のソースコード

「MLX90640.ino」をwifi接続し、kintoneのAPIでデータを保管するように機能追加したソースコードです。
見やすくするため、コメントや不用なコードは取り除いています。
試してみたい方はご活用ください。
とはいえ、リファクタリングしたくなるようなコードのままで恐縮ですが。(汗)

MLX90640.c
#include <M5Stack.h>
#include <Wire.h>
#include <WiFi.h>
#include <HTTPClient.h>

#include "MLX90640_API.h"
#include "MLX90640_I2C_Driver.h"

const byte MLX90640_address = 0x33; //Default 7-bit unshifted address of the MLX90640
#define TA_SHIFT 8 //Default shift for MLX90640 in open air

#define COLS 32
#define ROWS 24
#define COLS_2 (COLS * 2)
#define ROWS_2 (ROWS * 2)

float pixelsArraySize = COLS * ROWS;
float pixels[COLS * ROWS];
float pixels_2[COLS_2 * ROWS_2];
float reversePixels[COLS * ROWS];

byte speed_setting = 2 ; // High is 1 , Low is 2
bool reverseScreen = true;

#define INTERPOLATED_COLS 32
#define INTERPOLATED_ROWS 32

static float mlx90640To[COLS * ROWS];
paramsMLX90640 mlx90640;
float signedMag12ToFloat(uint16_t val);

//low range of the sensor (this will be blue on the screen)
int MINTEMP = 24; // For color mapping
int min_v = 24; //Value of current min temp
int min_cam_v = -40; // Spec in datasheet

//high range of the sensor (this will be red on the screen)
int MAXTEMP = 35; // For color mapping
int max_v = 35; //Value of current max temp
int max_cam_v = 300; // Spec in datasheet
int resetMaxTemp = 45;

//the colors we will be using
const uint16_t camColors[] = {0x480F,
                              0x400F, 0x400F, 0x400F, 0x4010, 0x3810, 0x3810, 0x3810, 0x3810, 0x3010, 0x3010,
                              0x3010, 0x2810, 0x2810, 0x2810, 0x2810, 0x2010, 0x2010, 0x2010, 0x1810, 0x1810,
                              0x1811, 0x1811, 0x1011, 0x1011, 0x1011, 0x0811, 0x0811, 0x0811, 0x0011, 0x0011,
                              0x0011, 0x0011, 0x0011, 0x0031, 0x0031, 0x0051, 0x0072, 0x0072, 0x0092, 0x00B2,
                              0x00B2, 0x00D2, 0x00F2, 0x00F2, 0x0112, 0x0132, 0x0152, 0x0152, 0x0172, 0x0192,
                              0x0192, 0x01B2, 0x01D2, 0x01F3, 0x01F3, 0x0213, 0x0233, 0x0253, 0x0253, 0x0273,
                              0x0293, 0x02B3, 0x02D3, 0x02D3, 0x02F3, 0x0313, 0x0333, 0x0333, 0x0353, 0x0373,
                              0x0394, 0x03B4, 0x03D4, 0x03D4, 0x03F4, 0x0414, 0x0434, 0x0454, 0x0474, 0x0474,
                              0x0494, 0x04B4, 0x04D4, 0x04F4, 0x0514, 0x0534, 0x0534, 0x0554, 0x0554, 0x0574,
                              0x0574, 0x0573, 0x0573, 0x0573, 0x0572, 0x0572, 0x0572, 0x0571, 0x0591, 0x0591,
                              0x0590, 0x0590, 0x058F, 0x058F, 0x058F, 0x058E, 0x05AE, 0x05AE, 0x05AD, 0x05AD,
                              0x05AD, 0x05AC, 0x05AC, 0x05AB, 0x05CB, 0x05CB, 0x05CA, 0x05CA, 0x05CA, 0x05C9,
                              0x05C9, 0x05C8, 0x05E8, 0x05E8, 0x05E7, 0x05E7, 0x05E6, 0x05E6, 0x05E6, 0x05E5,
                              0x05E5, 0x0604, 0x0604, 0x0604, 0x0603, 0x0603, 0x0602, 0x0602, 0x0601, 0x0621,
                              0x0621, 0x0620, 0x0620, 0x0620, 0x0620, 0x0E20, 0x0E20, 0x0E40, 0x1640, 0x1640,
                              0x1E40, 0x1E40, 0x2640, 0x2640, 0x2E40, 0x2E60, 0x3660, 0x3660, 0x3E60, 0x3E60,
                              0x3E60, 0x4660, 0x4660, 0x4E60, 0x4E80, 0x5680, 0x5680, 0x5E80, 0x5E80, 0x6680,
                              0x6680, 0x6E80, 0x6EA0, 0x76A0, 0x76A0, 0x7EA0, 0x7EA0, 0x86A0, 0x86A0, 0x8EA0,
                              0x8EC0, 0x96C0, 0x96C0, 0x9EC0, 0x9EC0, 0xA6C0, 0xAEC0, 0xAEC0, 0xB6E0, 0xB6E0,
                              0xBEE0, 0xBEE0, 0xC6E0, 0xC6E0, 0xCEE0, 0xCEE0, 0xD6E0, 0xD700, 0xDF00, 0xDEE0,
                              0xDEC0, 0xDEA0, 0xDE80, 0xDE80, 0xE660, 0xE640, 0xE620, 0xE600, 0xE5E0, 0xE5C0,
                              0xE5A0, 0xE580, 0xE560, 0xE540, 0xE520, 0xE500, 0xE4E0, 0xE4C0, 0xE4A0, 0xE480,
                              0xE460, 0xEC40, 0xEC20, 0xEC00, 0xEBE0, 0xEBC0, 0xEBA0, 0xEB80, 0xEB60, 0xEB40,
                              0xEB20, 0xEB00, 0xEAE0, 0xEAC0, 0xEAA0, 0xEA80, 0xEA60, 0xEA40, 0xF220, 0xF200,
                              0xF1E0, 0xF1C0, 0xF1A0, 0xF180, 0xF160, 0xF140, 0xF100, 0xF0E0, 0xF0C0, 0xF0A0,
                              0xF080, 0xF060, 0xF040, 0xF020, 0xF800,
                             };

float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols, float *dest, uint8_t dest_rows, uint8_t dest_cols);

long loopTime, startTime, endTime, fps;

// Wifi の設定(利用可能なSSIDとPASSWORDを設定してください)
const char* ssid       = "wifiのSSID";
const char* password   = "wifiのPASSWORD";

// kintone データ更新用設定
int dataUpTime = 0;
char macAd[24];
char csv[2310];
char json[4096];
HTTPClient http;

void setup()
{
  M5.begin();
  Wire.begin();
  Wire.setClock(450000); //Increase I2C clock speed to 400kHz
  Serial.begin(115200);
  M5.Lcd.begin();
  M5.Lcd.setRotation(1);

  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(YELLOW, BLACK);

  Serial.println("M5Stack MLX90640 IR Camera");
  M5.Lcd.setTextSize(2);

  // Wifi AP に接続
  WiFi.begin(ssid, password);  //  Wi-Fi APに接続
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
    delay(100);
    Serial.println("Wifi connecting...");
    if(i > 100){
      Serial.println("Wifi connect faild.");
      break;
    }
    i++;
  }

  // IPアドレスとMacアドレスを取得
  M5.Lcd.print(F("IP addr:"));
  IPAddress ipaddr = WiFi.localIP();
  M5.Lcd.println(ipaddr);
  delay(1000);
  byte mac[6];
  WiFi.macAddress(mac);
  sprintf(macAd, "%02X-%02X-%02X-%02X-%02X-%02X", mac[5], mac[4], mac[3], mac[2], mac[1], mac[0]);
  Serial.printf("MAC: %s\n", macAd);

  //Get device parameters - We only have to do this once
  int status;
  uint16_t eeMLX90640[832];//32 * 24 = 768
  status = MLX90640_DumpEE(MLX90640_address, eeMLX90640);
  if (status != 0)
    Serial.println("Failed to load system parameters");

  status = MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
  if (status != 0)
    Serial.println("Parameter extraction failed");

  int SetRefreshRate;
  //Setting MLX90640 device at slave address 0x33 to work with 16Hz refresh rate:
  // 0x05 – 16Hz
  SetRefreshRate = MLX90640_SetRefreshRate (0x33, 0x05);
  //Once params are extracted, we can release eeMLX90640 array

  //Display bottom side colorList and info
  M5.Lcd.fillScreen(TFT_BLACK);
  int icolor = 0;
  for (int icol = 0; icol <= 248;  icol++)
  {
    // 彩色条
    M5.Lcd.drawRect(36, 208, icol, 284 , camColors[icolor]);
    icolor++;
  }
  infodisplay();
}


void loop()
{
  loopTime = millis();
  startTime = loopTime;

  // Power Off
  if (M5.BtnB.pressedFor(1000)) {
    M5.Lcd.fillScreen(TFT_BLACK);
    M5.Lcd.setTextColor(YELLOW, BLACK);
    M5.Lcd.drawCentreString("Power Off...", 160, 80, 4);
    delay(1000);
    M5.powerOFF();
  }

  M5.update();

  for (byte x = 0 ; x < speed_setting ; x++) // x < 2 Read both subpages
  {
    uint16_t mlx90640Frame[834];
    int status = MLX90640_GetFrameData(MLX90640_address, mlx90640Frame);
    if (status < 0)
    {
      Serial.print("GetFrame Error: ");
      Serial.println(status);
    }

    float vdd = MLX90640_GetVdd(mlx90640Frame, &mlx90640);
    float Ta = MLX90640_GetTa(mlx90640Frame, &mlx90640);
    float tr = Ta - TA_SHIFT; //Reflected temperature based on the sensor ambient temperature
    float emissivity = 0.95;
    MLX90640_CalculateTo(mlx90640Frame, &mlx90640, emissivity, tr, pixels); //save pixels temp to array (pixels)
  }

  //Reverse image (order of Integer array)
  if (reverseScreen == 1)
  {
    for (int x = 0 ; x < pixelsArraySize ; x++)
    {
      if (x % COLS == 0) //32 values wide
      {
        for (int j = 0 + x, k = (COLS-1) + x; j < COLS + x ; j++, k--)
        {
          reversePixels[j] = pixels[k];
        }
      }
    }
  }

  float dest_2d[INTERPOLATED_ROWS * INTERPOLATED_COLS];
  int ROWS_i,COLS_j;

  // reversePixels
  if (reverseScreen == 1)
  {
    interpolate_image(reversePixels, ROWS, COLS, dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);
  }
  else
  {
    interpolate_image(pixels, ROWS, COLS, dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);

    //pixels_2
    for(int y = 0;y < ROWS;y++)
    {
      for(int x = 0;x < COLS;x++)
      {
        // 原始数据
        pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] = pixels[y*COLS+x];

        if(x != 31)
          pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+1)] = ( pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] + pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+2)]) / 2;
        else
          pixels_2[(((y * 2) * (COLS*2)) + (x * 2)+1)] = ( pixels_2[(((y * 2) * (COLS*2)) + (x * 2))] );
      }
    }

    // 计算y间隔插入数据 
    for(int y = 0;y < ROWS;y++)//24
    {
      for(int x = 0;x < COLS_2;x++)//64
      {
        if(y != 23)
          pixels_2[(((y * 2) + 1) * (COLS_2)) + x] = ( pixels_2[(((y * 2) * COLS_2) + x)] + pixels_2[((((y * 2) + 2) * COLS_2) + x)] ) / 2;
        else
          pixels_2[(((y * 2) + 1) * (COLS_2)) + x] = ( pixels_2[(((y * 2) * COLS_2) + x)] + pixels_2[(((y * 2) * COLS_2) + x)] ) / 2;
      }
    }   
  }

  uint16_t boxsize = min(M5.Lcd.width() / INTERPOLATED_ROWS, M5.Lcd.height() / INTERPOLATED_COLS);
  uint16_t boxWidth = M5.Lcd.width() / INTERPOLATED_ROWS;
  uint16_t boxHeight = (M5.Lcd.height() - 31) / INTERPOLATED_COLS; // 31 for bottom info
  drawpixels(dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS, boxWidth, boxHeight, false);
  max_v = MINTEMP;
  min_v = MAXTEMP;
  int spot_v = pixels[360];
  spot_v = pixels[768/2-16];

  int csvCount = 0; // kintone POST 用CSVデータ作成用
  for ( int itemp = 0; itemp < sizeof(pixels) / sizeof(pixels[0]); itemp++ )
  {
    if ( pixels[itemp] > max_v )
    {
      max_v = pixels[itemp];
    }
    if ( pixels[itemp] < min_v )
    {
      min_v = pixels[itemp];
    }

    // kintone POST 用CSVデータ作成
    // (32 * 24) * 3 = 2304
    char str[6];
    int pix = pixels[itemp];
    sprintf(str,"%d,", pix);
    for(int count=0; str[count] != '\0'; count++ ){
      if(csvCount < 2304){
        csv[csvCount] = str[count];
        ++csvCount;
      }
    }

  }

  M5.Lcd.setTextSize(2);
  M5.Lcd.fillRect(164, 220, 75, 18, TFT_BLACK);  // clear max temp text
  M5.Lcd.fillRect(60, 220, 200, 18, TFT_BLACK); // clear spot temp text

  M5.Lcd.setCursor(60, 222);      // update min & max temp
  M5.Lcd.setTextColor(TFT_WHITE);

  if (max_v > max_cam_v | max_v < min_cam_v ) {
    M5.Lcd.setTextColor(TFT_RED);
    M5.Lcd.printf("Error", 1);
  }
  else
  {
    M5.Lcd.printf("Min:", 1);
    M5.Lcd.print(min_v, 1);
    M5.Lcd.printf("C  " , 1);
    M5.Lcd.printf("Max:", 1);
    M5.Lcd.print(max_v, 1);
    M5.Lcd.printf("C" , 1);
    M5.Lcd.setCursor(180, 94); // update spot temp text
    M5.Lcd.print(spot_v, 1);
    M5.Lcd.printf("C" , 1);
    M5.Lcd.drawCircle(160, 100, 6, TFT_WHITE);     // update center spot icon
    M5.Lcd.drawLine(160, 90, 160, 110, TFT_WHITE); // vertical line
    M5.Lcd.drawLine(150, 100, 170, 100, TFT_WHITE); // horizontal line
  }
  loopTime = millis();
  endTime = loopTime;
  fps = 1000 / (endTime - startTime);
  M5.Lcd.fillRect(300, 209, 20, 12, TFT_BLACK); //Clear fps text area
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(284, 210);
  M5.Lcd.print("fps:" + String( fps ));
  M5.Lcd.setTextSize(1);

  // kintone に保管
  dataUpTime++;
  if(dataUpTime == 1) {
    sprintf(json,"{\"app\":\"kintoneアプリのID\",\"record\":{\"mac\":{\"value\":\"%s\"},\"max\":{\"value\":\"%d\"},\"min\":{\"value\":\"%d\"},\"col\":{ \"value\":\"%d\"},\"row\":{\"value\":\"%d\"},\"data\":{\"value\":\"%s\"}}}",macAd,max_v,min_v,COLS,ROWS,csv);

    int httpResponseCode = 0;
    http.begin("https://yukataoka.cybozu.com/k/v1/record.json");
    http.addHeader(F("X-Cybozu-API-Token"), F("APIトークン")); // <= kintoneアプリのAPI-Tokenを設定
    http.addHeader(F("Content-type"), F("application/json"));
    httpResponseCode = http.POST(json);
    Serial.printf("httpResponseCode = %d \n", httpResponseCode);
    Serial.println(http.getString());
  }

  // fps は1秒間の計測回数
  if(dataUpTime > (60 * fps)) dataUpTime = 0;
}

// 画面下部に情報を表示
void infodisplay(void) {
  M5.Lcd.fillRect(0, 198, 320, 4, TFT_WHITE);
  M5.Lcd.setTextColor(TFT_WHITE);
  M5.Lcd.fillRect(284, 223, 320, 240, TFT_BLACK); //Clear MaxTemp area
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(284, 222); //move to bottom right
  M5.Lcd.print(MAXTEMP , 1);  // update MAXTEMP
  M5.Lcd.printf("C" , 1);
  M5.Lcd.setCursor(0, 222);  // update MINTEMP text
  M5.Lcd.fillRect(0, 222, 36, 16, TFT_BLACK);
  M5.Lcd.print(MINTEMP , 1);
  M5.Lcd.printf("C" , 1);
  M5.Lcd.setCursor(106, 224);
}

// 画面に温度を表示する
void drawpixels(float *p, uint8_t rows, uint8_t cols, uint8_t boxWidth, uint8_t boxHeight, boolean showVal) {
  int colorTemp;
  for (int y = 0; y < rows; y++) 
  {
    for (int x = 0; x < cols; x++) 
    {
      float val = get_point(p, rows, cols, x, y);

      if (val >= MAXTEMP) 
        colorTemp = MAXTEMP;
      else if (val <= MINTEMP) 
        colorTemp = MINTEMP;
      else colorTemp = val;

      uint8_t colorIndex = map(colorTemp, MINTEMP, MAXTEMP, 0, 255);
      colorIndex = constrain(colorIndex, 0, 255);// 0 ~ 255
      //draw the pixels!
      uint16_t color;
      color = val * 2;
      M5.Lcd.fillRect(boxWidth * x, boxHeight * y, boxWidth, boxHeight, camColors[colorIndex]);
    }
  }
}
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5