概要
先日、前編でM5Stackでサーモグラフィを作るまでの手順を紹介しました。
M5Stackでサーモグラフィを作りkintoneにデータを蓄積し可視化する(前編)
https://qiita.com/yukataoka/items/73f1e5c88649bd686093
後編では、M5Stackサーモグラフィのデータをkintoneに保管し、kintone側でもサーモグラフィの表示を再現します。
この記事は kintone2 Advent Calendar 2019 20日目 ですが、デープなIoT話で恐縮ですが、こんなこともできる程度の参考にでもしていただければ幸いです。
利用機器
前編と同じ M5Stack FIREとミニサーマルカメラユニット を用います。
kintoneの準備
以下のようなkintone「サーモグラフィ」アプリを準備します。
フィールド
フィールドは以下のように設定します。
フィールド名 | タイプ | フィードコート・要素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のライブラリィを追加します。
#include <WiFi.h>
#include <HTTPClient.h>
「MLX90640.ino」のソースコード114行目 void setup() 直前辺りに、wifiやkintoneにデータを追加するための作業用変数などの定義を追加します。
// 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アドレスの取得は必須ではないので、お好みで。
// 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用の温度データの作成処理を追加します。
・元のコード
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用の温度データ作成処理を追加
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秒の間隔で実行します。
// 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画像を表示する等の説明は今回省略します。(以外と情報が少ないので、後日まとめてみるかも。)
(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のアプリに保管されています。
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の明細画面にサーモグラフィの画像を表示しています。
Canvasに描画したサーモグラフィは以下のような画像でも保存できます。
実際のM5Stackのサーモグラフィ表示と比べても、kintoneでおおよそ同じ表示を再現できています!
今回はCanvasで表示したのみですが、この画像をkintoneのファイルで保管できると、一覧画面でサーモグラフィ温度変化の推移なども確認できそうですね。
参考
「MLX90640.ino」のソースコード
「MLX90640.ino」をwifi接続し、kintoneのAPIでデータを保管するように機能追加したソースコードです。
見やすくするため、コメントや不用なコードは取り除いています。
試してみたい方はご活用ください。
とはいえ、リファクタリングしたくなるようなコードのままで恐縮ですが。(汗)
#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]);
}
}
}