0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M5Stack Core2とThermal2をUIFlow 1でサーマルカメラ化する

0
Last updated at Posted at 2026-09-21

はじめに

M5Stack Core2とサーマルカメラユニット2(MLX90640)を組み合わせ、UIFlow 1で32×24画素の温度分布をヒートマップ表示しました。
image.png

実装した機能は次のとおりです。

  • 32×24画素のサーマル画像表示
  • 青→水色→緑→黄→赤のカラーマップ
  • 最高温度・平均温度の表示
  • 温度レベルバーの表示
  • 設定温度を超えた場合の警告表示
  • Thermal2本体の警告LED制御

使用したハードウェア

  • M5Stack Core2
  • サーマルカメラユニット2(MLX90640)
  • Groveケーブル
  • Core2のPORT A

Thermal2の主な仕様は次のとおりです。

項目 内容
温度センサー MLX90640
解像度 32×24画素
画角 110°×75°
測定範囲 -40~300℃
通信方式 I2C

開発環境

  • UIFlow 1
  • MicroPython
  • M5Stack Core2
  • Thermal2 Unit

UIFlow 1でUnitから「THERMAL2」を追加すると、初期コードは次の形になります。

from m5stack import *
from m5stack_ui import *
from uiflow import *
import unit

screen = M5Screen()
screen.clean_screen()
screen.set_screen_bg_color(0xFFFFFF)
thermal2_0 = unit.get(unit.THERMAL2, unit.PORTA)

AIモデルを比較して感じたこと

最初にGemini 3.1 Proで開発したプログラムは、センサーには反応したものの、サーマル画像がほぼ真っ赤になり、手の形も把握できませんでした。

一方、GPT 5.6 Solでは、768バイトのデータ構造、16bit温度値、サブページの合成、色形式という原因に短時間でたどり着けました。今回のThermal2/UIFlow 1の開発に限っては、GPT 5.6 Solの方が短時間で正解に到達でき、優秀でした。

AIが生成したコードは、そのまま動くかどうかだけでなく、センサーの通信仕様やデータ形式と一致しているかを確認することが重要だと感じました。

完成したプログラム(残像対策・人体検知最適化版)

以下のコードをUIFlow 1のPython編集画面へ貼り付けます。

# =========================================================
# M5Stack Core2 + Thermal2
# 残像対策・人体検知最適化版
# =========================================================

from m5stack import *
from m5stack_ui import *
from uiflow import *
import unit
import time


# =========================================================
# 画面とThermal2の初期化
# =========================================================

screen = M5Screen()
screen.clean_screen()
screen.set_screen_bg_color(0x0F172A)

# Thermal2をCore2のPORT Aへ接続
thermal2_0 = unit.get(unit.THERMAL2, unit.PORTA)


# =========================================================
# センサー・表示設定
# =========================================================

SENSOR_W = 32
SENSOR_H = 24
PIXEL_COUNT = SENSOR_W * SENSOR_H

MAP_X = 12
MAP_Y = 55
PIXEL_SIZE = 6

BACKGROUND_COLOR = 0x0F172A
PANEL_COLOR = 0x1E293B
BORDER_COLOR = 0x475569
TEXT_COLOR = 0xE2E8F0
SUB_TEXT_COLOR = 0x94A3B8
NORMAL_COLOR = 0x4ADE80
ALERT_COLOR = 0xF87171
PERSON_BOX_COLOR = 0xFFFFFF

# カメラの向きに応じて変更
FLIP_X = False
FLIP_Y = False


# =========================================================
# 人体検知設定
# =========================================================

# 周囲温度より1.5℃以上高い画素を人体候補とする
HUMAN_DELTA_TEMP = 1.5

# 人体候補として扱う最低温度
HUMAN_MIN_TEMP = 26.0

# 10画素以上が閾値を超えた場合に人体と判定
MIN_HUMAN_PIXELS = 10

# 高温警告温度
ALERT_TEMP = 38.0

# 新しい完全フレームが届かない場合に画像を消す時間
STALE_TIMEOUT_MS = 1500


# =========================================================
# フレームバッファ
# =========================================================

# subpage 0と1を合成する32×24画素のバッファ
build_frame = [20.0] * PIXEL_COUNT

# bit0=subpage 0、bit1=subpage 1
received_subpages = 0

last_complete_frame_ms = time.ticks_ms()
stale_screen_cleared = False


# =========================================================
# 固定画面の描画
# =========================================================

lcd.font(lcd.FONT_DejaVu18)
lcd.setTextColor(0x38BDF8, BACKGROUND_COLOR)
lcd.print("THERMAL HUMAN", 12, 9)

lcd.font(lcd.FONT_Default)
lcd.setTextColor(SUB_TEXT_COLOR, BACKGROUND_COLOR)
lcd.print("Waiting for full frame...", 12, 34)

lcd.drawRect(
    MAP_X - 1,
    MAP_Y - 1,
    SENSOR_W * PIXEL_SIZE + 2,
    SENSOR_H * PIXEL_SIZE + 2,
    BORDER_COLOR
)

lcd.fillRect(210, 55, 105, 145, PANEL_COLOR)
lcd.drawRect(210, 55, 105, 145, BORDER_COLOR)


# =========================================================
# RGB888色生成
# =========================================================

def rgb888(r, g, b):
    r = max(0, min(255, int(r)))
    g = max(0, min(255, int(g)))
    b = max(0, min(255, int(b)))
    return (r << 16) | (g << 8) | b


# =========================================================
# 温度をヒートマップ色へ変換
# =========================================================

def temperature_to_color(temp, display_min, display_max):
    if display_max <= display_min:
        display_max = display_min + 1.0

    ratio = (temp - display_min) / (display_max - display_min)
    ratio = max(0.0, min(1.0, ratio))

    # 青 → 水色 → 緑 → 黄 → 赤
    if ratio < 0.25:
        p = ratio / 0.25
        r = 0
        g = 255 * p
        b = 255
    elif ratio < 0.50:
        p = (ratio - 0.25) / 0.25
        r = 0
        g = 255
        b = 255 * (1.0 - p)
    elif ratio < 0.75:
        p = (ratio - 0.50) / 0.25
        r = 255 * p
        g = 255
        b = 0
    else:
        p = (ratio - 0.75) / 0.25
        r = 255
        g = 255 * (1.0 - p)
        b = 0

    return rgb888(r, g, b)


# =========================================================
# Thermal2の生データを摂氏へ変換
# =========================================================

def raw_to_temperature(raw_value):
    # Thermal2公式変換式
    return (float(raw_value) / 128.0) - 64.0


# =========================================================
# temp_data_buffer()を384個の温度へ変換
# =========================================================

def decode_buffer(raw_buffer):
    if raw_buffer is None:
        return []

    data = list(raw_buffer)
    data_length = len(data)
    temperatures = []

    # 768バイトの場合は2バイトで1温度
    if data_length == 768:
        for index in range(0, 768, 2):
            low_byte = int(data[index])
            high_byte = int(data[index + 1])
            raw_value = low_byte | (high_byte << 8)
            temperatures.append(raw_to_temperature(raw_value))

    # UIFlowのバージョンにより384要素で返る場合にも対応
    elif data_length == 384:
        for value in data:
            numeric_value = float(value)
            if numeric_value > 500:
                numeric_value = raw_to_temperature(numeric_value)
            temperatures.append(numeric_value)

    return temperatures


# =========================================================
# 1サブページを32×24フレームへ合成
# =========================================================

def merge_subpage(subpage, temperatures):
    global received_subpages

    if len(temperatures) != 384:
        return False

    subpage = int(subpage) & 1

    for y in range(SENSOR_H):
        for half_x in range(16):
            source_index = y * 16 + half_x

            # 市松模様状のサブページを実際のX座標へ戻す
            x_parity = subpage ^ (y & 1)
            x = half_x * 2 + x_parity

            frame_index = y * SENSOR_W + x
            build_frame[frame_index] = temperatures[source_index]

    received_subpages |= (1 << subpage)

    # subpage 0と1が両方揃った場合だけTrue
    return received_subpages == 3


# =========================================================
# 周囲温度と人体候補を解析
# =========================================================

def analyze_frame(frame):
    # 中央値を周囲温度として使用する
    sorted_values = list(frame)
    sorted_values.sort()

    ambient_temp = sorted_values[len(sorted_values) // 2]
    maximum_temp = sorted_values[-1]

    # 室温に応じて人体検知閾値を自動調整する
    human_threshold = max(
        HUMAN_MIN_TEMP,
        ambient_temp + HUMAN_DELTA_TEMP
    )

    human_pixel_count = 0
    min_x = SENSOR_W
    min_y = SENSOR_H
    max_x = -1
    max_y = -1

    for y in range(SENSOR_H):
        for x in range(SENSOR_W):
            temp = frame[y * SENSOR_W + x]

            if temp >= human_threshold:
                human_pixel_count += 1

                if x < min_x:
                    min_x = x
                if x > max_x:
                    max_x = x
                if y < min_y:
                    min_y = y
                if y > max_y:
                    max_y = y

    person_detected = human_pixel_count >= MIN_HUMAN_PIXELS

    if not person_detected:
        min_x = -1
        min_y = -1
        max_x = -1
        max_y = -1

    return (
        ambient_temp,
        maximum_temp,
        human_threshold,
        human_pixel_count,
        person_detected,
        min_x,
        min_y,
        max_x,
        max_y
    )


# =========================================================
# 完全な32×24フレームを全画素再描画
#
# サブページ単位では描画せず、0と1が揃ってから描画する。
# 古い半フレームが残る「残像」を防止できる。
# =========================================================

def draw_complete_frame(frame, display_min, display_max):
    for y in range(SENSOR_H):
        for x in range(SENSOR_W):
            temperature = frame[y * SENSOR_W + x]

            draw_x = x
            draw_y = y

            if FLIP_X:
                draw_x = SENSOR_W - 1 - draw_x
            if FLIP_Y:
                draw_y = SENSOR_H - 1 - draw_y

            screen_x = MAP_X + draw_x * PIXEL_SIZE
            screen_y = MAP_Y + draw_y * PIXEL_SIZE

            color = temperature_to_color(
                temperature,
                display_min,
                display_max
            )

            # 全画素を上書きして以前の画像を消す
            lcd.fillRect(
                screen_x,
                screen_y,
                PIXEL_SIZE,
                PIXEL_SIZE,
                color
            )


# =========================================================
# 人体候補の範囲を白枠で表示
# =========================================================

def draw_person_box(min_x, min_y, max_x, max_y):
    if min_x < 0:
        return

    if FLIP_X:
        box_min_x = SENSOR_W - 1 - max_x
        box_max_x = SENSOR_W - 1 - min_x
    else:
        box_min_x = min_x
        box_max_x = max_x

    if FLIP_Y:
        box_min_y = SENSOR_H - 1 - max_y
        box_max_y = SENSOR_H - 1 - min_y
    else:
        box_min_y = min_y
        box_max_y = max_y

    x = MAP_X + box_min_x * PIXEL_SIZE
    y = MAP_Y + box_min_y * PIXEL_SIZE
    width = (box_max_x - box_min_x + 1) * PIXEL_SIZE
    height = (box_max_y - box_min_y + 1) * PIXEL_SIZE

    lcd.drawRect(x, y, width, height, PERSON_BOX_COLOR)


# =========================================================
# 情報パネルを更新
# =========================================================

def draw_information(
    maximum_temp,
    ambient_temp,
    human_threshold,
    human_pixel_count,
    person_detected
):
    # パネルを毎回消去して古い文字を残さない
    lcd.fillRect(211, 56, 103, 143, PANEL_COLOR)

    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(SUB_TEXT_COLOR, PANEL_COLOR)
    lcd.print("MAX", 217, 64)
    lcd.print("ROOM", 217, 100)
    lcd.print("THRESH", 217, 136)

    lcd.font(lcd.FONT_DejaVu18)
    lcd.setTextColor(ALERT_COLOR, PANEL_COLOR)
    lcd.print("{:.1f}".format(maximum_temp), 217, 76)

    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(TEXT_COLOR, PANEL_COLOR)
    lcd.print("{:.1f}C".format(ambient_temp), 217, 113)
    lcd.print("{:.1f}C".format(human_threshold), 217, 149)

    if person_detected:
        lcd.setTextColor(NORMAL_COLOR, PANEL_COLOR)
        lcd.print("PERSON:{}".format(human_pixel_count), 217, 178)
    else:
        lcd.setTextColor(SUB_TEXT_COLOR, PANEL_COLOR)
        lcd.print("NO TARGET", 217, 178)

    lcd.fillRect(190, 8, 125, 22, BACKGROUND_COLOR)
    lcd.font(lcd.FONT_DejaVu18)

    if maximum_temp >= ALERT_TEMP:
        lcd.setTextColor(0xFF0000, BACKGROUND_COLOR)
        lcd.print("ALERT", 242, 9)
    elif person_detected:
        lcd.setTextColor(NORMAL_COLOR, BACKGROUND_COLOR)
        lcd.print("PERSON", 230, 9)
    else:
        lcd.setTextColor(SUB_TEXT_COLOR, BACKGROUND_COLOR)
        lcd.print("CLEAR", 242, 9)


# =========================================================
# 古い画像を消去
# =========================================================

def clear_stale_image():
    lcd.fillRect(
        MAP_X,
        MAP_Y,
        SENSOR_W * PIXEL_SIZE,
        SENSOR_H * PIXEL_SIZE,
        BACKGROUND_COLOR
    )

    lcd.drawRect(
        MAP_X - 1,
        MAP_Y - 1,
        SENSOR_W * PIXEL_SIZE + 2,
        SENSOR_H * PIXEL_SIZE + 2,
        BORDER_COLOR
    )

    lcd.fillRect(12, 33, 190, 15, BACKGROUND_COLOR)
    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(ALERT_COLOR, BACKGROUND_COLOR)
    lcd.print("Waiting for fresh frame", 12, 34)


# =========================================================
# Thermal2設定
# =========================================================

try:
    device_id = thermal2_0.device_info(0x00)

    thermal2_0.func_ctrl(0x02, True)

    # 8Hzにして動きへの追従を改善する
    thermal2_0.refresh_rate(4)

    # フィルターを弱め、時間方向の残りを抑える
    thermal2_0.noise_filter(1)

    thermal2_0.temp_threshold(0x30, ALERT_TEMP)
    thermal2_0.temp_alarm_led(0x35, 200, 0, 0)
    thermal2_0.temp_alarm_ctrl(0x80, True)

    lcd.fillRect(12, 33, 190, 15, BACKGROUND_COLOR)
    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(NORMAL_COLOR, BACKGROUND_COLOR)
    lcd.print("Sensor Ready ID:{}".format(device_id), 12, 34)

except Exception as initialization_error:
    lcd.fillRect(12, 33, 190, 15, BACKGROUND_COLOR)
    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(ALERT_COLOR, BACKGROUND_COLOR)
    lcd.print(
        "Init:{}".format(str(initialization_error)[:24]),
        12,
        34
    )


# =========================================================
# メインループ
# =========================================================

while True:
    try:
        update_available = thermal2_0.data_refresh_ctrl()

        if update_available:
            subpage = thermal2_0.subpage_info()
            raw_buffer = thermal2_0.temp_data_buffer()
            temperatures = decode_buffer(raw_buffer)

            if len(temperatures) == 384:
                full_frame_ready = merge_subpage(
                    subpage,
                    temperatures
                )

                # 2つのサブページが揃った場合だけ描画
                if full_frame_ready:
                    analysis = analyze_frame(build_frame)

                    ambient_temp = analysis[0]
                    maximum_temp = analysis[1]
                    human_threshold = analysis[2]
                    human_pixel_count = analysis[3]
                    person_detected = analysis[4]
                    min_x = analysis[5]
                    min_y = analysis[6]
                    max_x = analysis[7]
                    max_y = analysis[8]

                    # 室温に応じて表示温度範囲も自動調整
                    display_min = ambient_temp - 2.0
                    display_max = max(
                        ambient_temp + 8.0,
                        maximum_temp + 1.0
                    )

                    display_min = max(5.0, display_min)
                    display_max = min(45.0, display_max)

                    draw_complete_frame(
                        build_frame,
                        display_min,
                        display_max
                    )

                    if person_detected:
                        draw_person_box(
                            min_x,
                            min_y,
                            max_x,
                            max_y
                        )

                    draw_information(
                        maximum_temp,
                        ambient_temp,
                        human_threshold,
                        human_pixel_count,
                        person_detected
                    )

                    last_complete_frame_ms = time.ticks_ms()
                    stale_screen_cleared = False

                    # 次の完全フレームを待つ
                    received_subpages = 0

        # 完全フレームが一定時間届かなければ古い画像を消す
        elapsed_ms = time.ticks_diff(
            time.ticks_ms(),
            last_complete_frame_ms
        )

        if elapsed_ms > STALE_TIMEOUT_MS:
            if not stale_screen_cleared:
                clear_stale_image()
                stale_screen_cleared = True
                received_subpages = 0

        wait_ms(10)

    except Exception as error:
        lcd.fillRect(12, 207, 303, 14, BACKGROUND_COLOR)
        lcd.font(lcd.FONT_Default)
        lcd.setTextColor(ALERT_COLOR, BACKGROUND_COLOR)
        lcd.print(
            "Error:{}".format(str(error)[:34]),
            12,
            207
        )
        print("Thermal2 error:")
        print(error)
        wait_ms(300)
旧版コード(参考)
# =========================================================
# ライブラリの読み込み
# =========================================================

# M5Stack本体、画面制御、UIFlowの標準機能を読み込む
from m5stack import *
from m5stack_ui import *
from uiflow import *

# Unit制御用ライブラリ
import unit
import time


# =========================================================
# Core2の画面とThermal2を初期化
# =========================================================

# Core2の画面オブジェクトを作成し、既存の表示を消去する
screen = M5Screen()
screen.clean_screen()

# 画面背景をディープネイビーに設定する
screen.set_screen_bg_color(0x0F172A)

# Thermal2をCore2のPORT Aに接続して初期化する
thermal2_0 = unit.get(unit.THERMAL2, unit.PORTA)

# =========================================================
# サーマル画像と画面表示に関する設定
# =========================================================

# MLX90640のセンサー解像度は32×24画素
SENSOR_W = 32
SENSOR_H = 24

# Core2画面上でサーマル画像を描き始める座標
START_X = 12
START_Y = 55

# センサーの1画素をCore2画面上の6×6ピクセルで表示する
# 32×6=192、24×6=144の表示領域になる
PIXEL_SIZE = 6

# カラーマップへ割り当てる温度範囲
# 15℃以下を青、40℃以上を赤として表示する
DISPLAY_MIN_TEMP = 15.0
DISPLAY_MAX_TEMP = 40.0

# 高温警告を出す温度
ALERT_TEMP = 38.0

# カメラの取り付け方向に合わせて画像を反転する設定
FLIP_X = False
FLIP_Y = False

# 32×24=768画素分の温度を保持するフレームバッファ
# 最初は全画素を20℃で初期化する
# Thermal2から届く2つのサブページをこの配列へ順番に合成する
temperature_frame = [20.0] * 768

# =========================================================
# 画面の固定部分を描画
# =========================================================

# 画面上部へタイトルを表示する
lcd.font(lcd.FONT_DejaVu18)
lcd.setTextColor(0x38BDF8, 0x0F172A)
lcd.print("UNIT THERMAL 2", 15, 10)

lcd.font(lcd.FONT_Default)
lcd.setTextColor(0x94A3B8, 0x0F172A)
lcd.print("Connecting to sensor...", 15, 34)

lcd.drawRect(
    START_X - 1,
    START_Y - 1,
    SENSOR_W * PIXEL_SIZE + 2,
    SENSOR_H * PIXEL_SIZE + 2,
    0x475569
)

lcd.drawRect(210, 55, 105, 145, 0x475569)

lcd.font(lcd.FONT_Default)
lcd.setTextColor(0x94A3B8, 0x0F172A)
lcd.print("MAX", 218, 62)
lcd.print("AVG", 218, 105)
lcd.print("LEVEL", 275, 62)


# =========================================================
# 色変換関数
# =========================================================

def rgb888(r, g, b):
    # RGBの各値を0~255の範囲に制限する
    r = max(0, min(255, int(r)))
    g = max(0, min(255, int(g)))
    b = max(0, min(255, int(b)))

    # lcd.fillRect()で使用する0xRRGGBB形式へ変換する
    # RGB565へ変換しないことが重要
    return (r << 16) | (g << 8) | b


def get_color(temp):
    # 温度を0.0~1.0の割合へ正規化する
    ratio = (
        (temp - DISPLAY_MIN_TEMP)
        / (DISPLAY_MAX_TEMP - DISPLAY_MIN_TEMP)
    )
    ratio = max(0.0, min(1.0, ratio))

    # 表示範囲外の温度を0.0~1.0に収める
    ratio = max(0.0, min(1.0, ratio))

    # 低温から高温へ、青→水色→緑→黄→赤と変化させる
    if ratio < 0.25:
        # 青から水色
        p = ratio / 0.25
        r = 0
        g = 255 * p
        b = 255
    elif ratio < 0.50:
        p = (ratio - 0.25) / 0.25
        r = 0
        g = 255
        b = 255 * (1.0 - p)
    elif ratio < 0.75:
        p = (ratio - 0.50) / 0.25
        r = 255 * p
        g = 255
        b = 0
    else:
        p = (ratio - 0.75) / 0.25
        r = 255
        g = 255 * (1.0 - p)
        b = 0

    return rgb888(r, g, b)


# =========================================================
# Thermal2の生データを温度へ変換
# =========================================================

def raw_to_temperature(raw_value):
    # Thermal2の公式変換式
    # 例:8192なら0℃、12032なら30℃になる
    return (float(raw_value) / 128.0) - 64.0


def decode_buffer(raw_buffer):
    # データを取得できなかった場合は空配列を返す
    if raw_buffer is None:
        return []

    data = list(raw_buffer)
    data_length = len(data)
    temperatures = []

    # 768バイトの場合:2バイトで1画素なので384画素分
    if data_length == 768:
        for index in range(0, 768, 2):
            # little endian形式の下位・上位バイトを取り出す
            low_byte = int(data[index])
            high_byte = int(data[index + 1])

            # 2バイトを1つの16bit整数へ戻す
            raw_value = low_byte | (high_byte << 8)

            # 生データを摂氏へ変換して配列へ追加する
            temperatures.append(raw_to_temperature(raw_value))

    # UIFlowのバージョンによって384要素で返る場合にも対応する
    elif data_length == 384:
        for value in data:
            numeric_value = float(value)
            if numeric_value > 500:
                temperature = raw_to_temperature(numeric_value)
            else:
                temperature = numeric_value
            temperatures.append(temperature)

    return temperatures


# =========================================================
# サブページを32×24画素のフレームへ合成
# =========================================================

def update_temperature_frame(subpage, temperatures):
    # 1サブページは16×24=384画素
    if len(temperatures) != 384:
        return False

    # サブページ番号を0または1に正規化する
    subpage = int(subpage) & 1

    for y in range(SENSOR_H):
        for half_x in range(16):
            # 16×24配列内の読み取り位置
            source_index = y * 16 + half_x

            # MLX90640は市松模様状にsubpage 0/1を更新するため、
            # 行の偶奇とサブページ番号から実際のX座標を計算する
            x_parity = subpage ^ (y & 1)
            x = half_x * 2 + x_parity

            # 32×24フレーム上の位置へ温度を書き込む
            frame_index = y * SENSOR_W + x
            temperature_frame[frame_index] = temperatures[source_index]

    return True


# =========================================================
# 更新されたサブページをCore2画面へ描画
# =========================================================

def draw_subpage(subpage):
    subpage = int(subpage) & 1

    for y in range(SENSOR_H):
        for half_x in range(16):
            x_parity = subpage ^ (y & 1)
            x = half_x * 2 + x_parity
            frame_index = y * SENSOR_W + x
            temperature = temperature_frame[frame_index]

            draw_x = x
            draw_y = y

            if FLIP_X:
                draw_x = SENSOR_W - 1 - draw_x
            if FLIP_Y:
                draw_y = SENSOR_H - 1 - draw_y

            screen_x = START_X + draw_x * PIXEL_SIZE
            screen_y = START_Y + draw_y * PIXEL_SIZE
            color = get_color(temperature)

            lcd.fillRect(
                screen_x,
                screen_y,
                PIXEL_SIZE,
                PIXEL_SIZE,
                color
            )


# =========================================================
# 最高温度、平均温度、警告、レベルバーを描画
# =========================================================

def draw_information(high_temp, average_temp):
    # 前回表示した数値とステータスを背景色で消去する
    lcd.fillRect(215, 77, 58, 24, 0x0F172A)
    lcd.fillRect(215, 120, 58, 24, 0x0F172A)
    lcd.fillRect(215, 158, 58, 20, 0x0F172A)

    lcd.font(lcd.FONT_DejaVu18)

    if high_temp >= ALERT_TEMP:
        lcd.setTextColor(0xFF0000, 0x0F172A)
    else:
        lcd.setTextColor(0xF87171, 0x0F172A)

    lcd.print("{:4.1f}".format(high_temp), 218, 78)

    lcd.setTextColor(0x38BDF8, 0x0F172A)
    lcd.print("{:4.1f}".format(average_temp), 218, 121)

    lcd.font(lcd.FONT_Default)

    if high_temp >= ALERT_TEMP:
        lcd.setTextColor(0xFF0000, 0x0F172A)
        lcd.print("ALERT", 218, 160)
    else:
        lcd.setTextColor(0x4ADE80, 0x0F172A)
        lcd.print("NORMAL", 218, 160)

    bar_x = 282
    bar_y = 78
    bar_w = 18
    bar_h = 100

    ratio = (
        (high_temp - DISPLAY_MIN_TEMP)
        / (DISPLAY_MAX_TEMP - DISPLAY_MIN_TEMP)
    )
    ratio = max(0.0, min(1.0, ratio))
    fill_height = int(ratio * bar_h)

    lcd.fillRect(bar_x, bar_y, bar_w, bar_h, 0x1E293B)

    if fill_height > 0:
        bar_color = get_color(high_temp)
        lcd.fillRect(
            bar_x,
            bar_y + bar_h - fill_height,
            bar_w,
            fill_height,
            bar_color
        )

    lcd.drawRect(
        bar_x - 1,
        bar_y - 1,
        bar_w + 2,
        bar_h + 2,
        0x64748B
    )


# =========================================================
# Thermal2本体の動作設定
# =========================================================

try:
    # 接続確認を兼ねてデバイスIDを取得する
    device_id = thermal2_0.device_info(0x00)

    # Thermal2本体のRGB LED機能を有効化する
    thermal2_0.func_ctrl(0x02, True)

    # センサーの更新速度を4Hzに設定する
    thermal2_0.refresh_rate(3)

    # ノイズフィルターを設定する
    thermal2_0.noise_filter(4)
    thermal2_0.temp_threshold(0x30, ALERT_TEMP)
    thermal2_0.temp_alarm_led(0x35, 200, 0, 0)
    thermal2_0.temp_alarm_ctrl(0x80, True)

    lcd.fillRect(15, 34, 290, 16, 0x0F172A)
    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(0x4ADE80, 0x0F172A)
    lcd.print("Sensor Ready ID:{}".format(device_id), 15, 34)

except Exception as initialization_error:
    lcd.fillRect(15, 34, 290, 16, 0x0F172A)
    lcd.font(lcd.FONT_Default)
    lcd.setTextColor(0xF87171, 0x0F172A)
    lcd.print(
        "Init:{}".format(str(initialization_error)[:25]),
        15,
        34
    )


# =========================================================
# メインループ
# =========================================================

while True:
    try:
        # Thermal2に新しい温度データがあるか確認する
        update_available = thermal2_0.data_refresh_ctrl()

        if update_available:
            # 現在更新されたサブページ番号を取得する
            subpage = thermal2_0.subpage_info()

            # 16×24画素分の温度バッファを取得する
            raw_buffer = thermal2_0.temp_data_buffer()

            # 生データを384個の摂氏温度へ変換する
            temperatures = decode_buffer(raw_buffer)

            if len(temperatures) == 384:
                update_temperature_frame(subpage, temperatures)
                draw_subpage(subpage)

                high_temp = float(
                    thermal2_0.temp_measure(0x0C, False)
                )
                average_temp = float(
                    thermal2_0.temp_measure(0x02, False)
                )

                draw_information(high_temp, average_temp)

            else:
                lcd.fillRect(12, 207, 295, 14, 0x0F172A)
                lcd.font(lcd.FONT_Default)
                lcd.setTextColor(0xF87171, 0x0F172A)
                lcd.print(
                    "Buffer length:{}".format(
                        len(raw_buffer)
                        if raw_buffer is not None
                        else 0
                    ),
                    12,
                    207
                )

        wait_ms(20)

    except Exception as error:
        lcd.fillRect(12, 207, 295, 14, 0x0F172A)
        lcd.font(lcd.FONT_Default)
        lcd.setTextColor(0xFF0000, 0x0F172A)
        lcd.print(
            "Error:{}".format(str(error)[:35]),
            12,
            207
        )
        print("Thermal2 error:")
        print(error)
        wait_ms(500)

参考資料

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?