AIアプリ
https://app.ai4all.jp/app/auth/student/login?classCode=IP5KLS
M5Stackに書き込もう
M5Stack CoreS3 サンプル
import M5
from M5 import *
from machine import Pin
import time
import json
import machine
import esp32
import network
import ntptime
import requests
# ============================================================
# --- 設定 ---
# ============================================================
WIFI_SSID = "WIFI_SSID"
WIFI_PASS = "WIFI_PASS"
NTP_HOST = "ntp.nict.jp"
API_BASE = "https://monapi.ichika.net/api"
API_USER = "john"
API_APP = "counter"
API_ENDPOINT = "{}/{}/{}".format(API_BASE, API_USER, API_APP)
mode = 0
max_mode = 3
counter = 0
post_msg = ""
post_msg_timer = 0
last_touch = False
# --- Unit Dual Button (PORT.A: G1=青, G2=赤) ---
BTN_A_PIN = 1
BTN_B_PIN = 2
btn_a = Pin(BTN_A_PIN, Pin.IN, Pin.PULL_UP)
btn_b = Pin(BTN_B_PIN, Pin.IN, Pin.PULL_UP)
btn_a_last = True
btn_b_last = True
# --- 差分更新用・前回値 ---
last_time_str = ""
last_date_str = ""
last_counter = None
last_post_msg = ""
last_bat = None
last_connected = None
# ============================================================
# --- 初期設定 ---
# ============================================================
M5.begin()
M5.Lcd.setRotation(1)
SELECTED_FONT = M5.Lcd.FONTS.EFontJA24
LCD_W = M5.Lcd.width()
LCD_H = M5.Lcd.height()
# --- カラーパレット(ダーク系) ---
C_BG = 0x0A0A0F
C_PANEL = 0x12121A
C_ACCENT = 0x00CFFF
C_GREEN = 0x00E676
C_RED = 0xFF3D5A
C_YELLOW = 0xFFD600
C_WHITE = 0xEEEEEE
C_GRAY = 0x555566
C_BAR_BG = 0x1A1A2E
C_DIVIDER = 0x2A2A3E
C_BTN_L_BG = 0x0D1B2A
C_BTN_R_BG = 0x0A1F0F
C_BTN_L_BD = 0x00CFFF
C_BTN_R_BD = 0x00E676
BAR_H = 44
BAR_Y = LCD_H - BAR_H
HDR_H = 38
CONT_Y = HDR_H + 4
CONT_H = BAR_Y - CONT_Y - 4
# ============================================================
# --- UI ヘルパー ---
# ============================================================
def draw_touch_bar():
M5.Lcd.fillRect(0, BAR_Y, LCD_W, BAR_H, C_BAR_BG)
M5.Lcd.drawLine(0, BAR_Y, LCD_W, BAR_Y, C_ACCENT)
MID = LCD_W // 2 - 2
# 左ボタン(モード切替)
M5.Lcd.fillRect(4, BAR_Y + 4, MID - 4, BAR_H - 8, C_BTN_L_BG)
M5.Lcd.drawRect(4, BAR_Y + 4, MID - 4, BAR_H - 8, C_BTN_L_BD)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_BTN_L_BD)
M5.Lcd.setCursor(14, BAR_Y + 12)
M5.Lcd.print("◀ モード切替")
# 右ボタン(+1)
M5.Lcd.fillRect(MID + 4, BAR_Y + 4, LCD_W - MID - 8, BAR_H - 8, C_BTN_R_BG)
M5.Lcd.drawRect(MID + 4, BAR_Y + 4, LCD_W - MID - 8, BAR_H - 8, C_BTN_R_BD)
M5.Lcd.setTextColor(C_BTN_R_BD)
M5.Lcd.setCursor(MID + 14, BAR_Y + 12)
M5.Lcd.print("タップ +1 ▶")
def flash_touch_btn(side):
MID = LCD_W // 2 - 2
if side == "left":
M5.Lcd.fillRect(4, BAR_Y + 4, MID - 4, BAR_H - 8, C_BTN_L_BD)
else:
M5.Lcd.fillRect(MID + 4, BAR_Y + 4, LCD_W - MID - 8, BAR_H - 8, C_BTN_R_BD)
time.sleep(0.06)
draw_touch_bar()
def draw_header(title):
"""モード切替時のみ呼ぶ・全画面リセット"""
M5.Lcd.fillScreen(C_BG)
M5.Lcd.fillRect(0, 0, LCD_W, HDR_H, C_PANEL)
M5.Lcd.drawLine(0, HDR_H, LCD_W, HDR_H, C_ACCENT)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_ACCENT)
M5.Lcd.setCursor(10, 8)
M5.Lcd.print(title)
# モードインジケーター
for i in range(max_mode):
x = LCD_W - 10 - (max_mode - i) * 12
M5.Lcd.fillRect(x, 14, 8, 8, C_ACCENT if i == mode else C_GRAY)
draw_touch_bar()
# 前回値をリセット(モード切替後に全要素を再描画させる)
reset_last_values()
def reset_last_values():
global last_time_str, last_date_str, last_counter
global last_post_msg, last_bat, last_connected
last_time_str = ""
last_date_str = ""
last_counter = None
last_post_msg = ""
last_bat = None
last_connected = None
def draw_panel(x, y, w, h):
M5.Lcd.fillRect(x, y, w, h, C_PANEL)
M5.Lcd.drawRect(x, y, w, h, C_DIVIDER)
def flash_content(color):
M5.Lcd.fillRect(0, CONT_Y, LCD_W, CONT_H, color)
time.sleep(0.04)
# ============================================================
# --- タッチ判定 ---
# ============================================================
def get_touch_event():
global last_touch
touched = M5.Touch.getCount() > 0
if touched and not last_touch:
last_touch = True
return (M5.Touch.getX(), M5.Touch.getY())
if not touched:
last_touch = False
return None
# ============================================================
# --- Wi-Fi 接続 ---
# ============================================================
def connect_wifi():
draw_header("Wi-Fi 接続中")
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_WHITE)
M5.Lcd.setCursor(10, CONT_Y + 10)
M5.Lcd.print("SSID: " + WIFI_SSID)
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if wlan.isconnected():
return wlan
wlan.connect(WIFI_SSID, WIFI_PASS)
timeout = 15
for i in range(timeout):
if wlan.isconnected():
break
prog_w = int((LCD_W - 20) * (i + 1) / timeout)
M5.Lcd.fillRect(10, CONT_Y + 50, LCD_W - 20, 14, C_PANEL)
M5.Lcd.fillRect(10, CONT_Y + 50, prog_w, 14, C_ACCENT)
M5.Lcd.fillRect(10, CONT_Y + 28, LCD_W - 20, 20, C_BG)
M5.Lcd.setCursor(10, CONT_Y + 28)
M5.Lcd.print("待機中 {}/{}s".format(i + 1, timeout))
time.sleep(1)
return wlan
# ============================================================
# --- NTP 時刻同期(JST) ---
# ============================================================
def sync_ntp():
try:
ntptime.host = NTP_HOST
ntptime.settime()
t = time.localtime(time.time() + 9 * 3600)
machine.RTC().datetime((t[0], t[1], t[2], t[6], t[3], t[4], t[5], 0))
return True
except:
return False
# ============================================================
# --- MonAPI POST / GET ---
# ============================================================
def post_count(count):
try:
body = json.dumps({"count": count})
res = requests.post(
API_ENDPOINT,
headers={"Content-Type": "application/json"},
data=body
)
print("[POST] Status:", res.status_code)
print("[POST] Response:", res.text)
ok = (res.status_code == 201)
res.close()
return ok
except Exception as e:
print("[POST] Exception:", e)
return False
def fetch_latest_count():
try:
res = requests.get(API_ENDPOINT)
print("[GET] Status:", res.status_code)
print("[GET] Response:", res.text)
if res.status_code == 200:
data = json.loads(res.text)
if len(data) > 0 and "count" in data[0]:
val = data[0]["count"]
res.close()
return val
res.close()
return 0
except Exception as e:
print("[GET] Exception:", e)
return 0
# ============================================================
# --- 起動シーケンス ---
# ============================================================
wlan = connect_wifi()
wifi_ok = wlan.isconnected()
rtc = machine.RTC()
if wifi_ok:
draw_header("NTP 同期中")
sync_ntp()
draw_header("データ取得中")
counter = fetch_latest_count()
print("[INIT] 初期カウント:", counter)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_GREEN)
M5.Lcd.setCursor(10, CONT_Y + 10)
M5.Lcd.print("接続成功!")
M5.Lcd.setTextColor(C_WHITE)
M5.Lcd.setCursor(10, CONT_Y + 36)
M5.Lcd.print(wlan.ifconfig()[0])
M5.Lcd.setTextColor(C_ACCENT)
M5.Lcd.setCursor(10, CONT_Y + 62)
M5.Lcd.print("{} から再開".format(counter))
time.sleep(2)
else:
rtc.datetime((2026, 3, 13, 4, 22, 30, 0, 0))
counter = 0
draw_header("Wi-Fi 失敗")
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_RED)
M5.Lcd.setCursor(10, CONT_Y + 20)
M5.Lcd.print("オフラインで継続")
time.sleep(2)
# ============================================================
# --- カウンター操作の共通処理 ---
# ============================================================
def do_count(delta):
global counter, post_msg, post_msg_timer
flash_content(0x003020 if delta > 0 else 0x200010)
counter += delta
ok = post_count(counter)
post_msg = ("+1 送信OK" if delta > 0 else "-1 送信OK") if ok else "送信失敗"
post_msg_timer = 25
if mode != 2:
return True
draw_header("カウンター")
return False
# ============================================================
# --- メインループ ---
# ============================================================
draw_header("時刻と挨拶")
while True:
M5.update()
# --- Unit Dual Button ---
btn_a_now = btn_a.value()
btn_b_now = btn_b.value()
if btn_a_now == False and btn_a_last == True:
if do_count(+1):
mode = 2
draw_header("カウンター")
if btn_b_now == False and btn_b_last == True:
if do_count(-1):
mode = 2
draw_header("カウンター")
btn_a_last = btn_a_now
btn_b_last = btn_b_now
# --- タッチ操作 ---
touch = get_touch_event()
if touch:
tx, ty = touch
if ty >= BAR_Y:
if tx < LCD_W // 2:
flash_touch_btn("left")
mode = (mode + 1) % max_mode
post_msg = ""
if mode == 0: draw_header("時刻と挨拶")
elif mode == 1: draw_header("ネットワーク")
elif mode == 2: draw_header("カウンター")
else:
flash_touch_btn("right")
if do_count(+1):
mode = 2
draw_header("カウンター")
# ============================================================
# --- 差分描画 ---
# ============================================================
if mode == 0:
t = rtc.datetime()
time_str = "{:02d}:{:02d}:{:02d}".format(t[4], t[5], t[6])
date_str = "{:04d}/{:02d}/{:02d}".format(t[0], t[1], t[2])
# 時刻カード(初回のみ枠を描画)
if last_time_str == "":
draw_panel(8, CONT_Y + 4, LCD_W - 16, 56)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_GRAY)
M5.Lcd.setCursor(16, CONT_Y + 10)
M5.Lcd.print("TIME")
if time_str != last_time_str:
M5.Lcd.fillRect(16, CONT_Y + 28, LCD_W - 32, 26, C_PANEL)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_ACCENT)
M5.Lcd.setCursor(16, CONT_Y + 30)
M5.Lcd.print(time_str)
last_time_str = time_str
if last_date_str == "":
draw_panel(8, CONT_Y + 68, LCD_W - 16, 30)
if date_str != last_date_str:
M5.Lcd.fillRect(16, CONT_Y + 76, LCD_W - 32, 18, C_PANEL)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_GRAY)
M5.Lcd.setCursor(16, CONT_Y + 76)
M5.Lcd.print(date_str)
last_date_str = date_str
# 挨拶(固定・初回のみ)
if last_time_str == time_str and last_date_str == date_str:
pass # 変化なし
draw_panel(8, CONT_Y + 106, LCD_W - 16, 30) if last_date_str == date_str and last_time_str == time_str else None
elif mode == 1:
connected = wlan.isconnected()
if last_connected is None:
draw_panel(8, CONT_Y + 4, LCD_W - 16, 30)
draw_panel(8, CONT_Y + 42, LCD_W - 16, 30)
draw_panel(8, CONT_Y + 80, LCD_W - 16, 30)
draw_panel(8, CONT_Y + 118, LCD_W - 16, 30)
if connected != last_connected:
M5.Lcd.fillRect(16, CONT_Y + 12, LCD_W - 32, 18, C_PANEL)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_GREEN if connected else C_RED)
M5.Lcd.setCursor(16, CONT_Y + 12)
M5.Lcd.print("WiFi " + ("接続済" if connected else "未接続"))
# IP
M5.Lcd.fillRect(16, CONT_Y + 50, LCD_W - 32, 18, C_PANEL)
if connected:
M5.Lcd.setTextColor(C_GRAY)
M5.Lcd.setCursor(16, CONT_Y + 50)
M5.Lcd.print(wlan.ifconfig()[0])
last_connected = connected
bat = Power.getBatteryLevel()
if bat != last_bat:
bat_color = C_GREEN if bat > 50 else C_YELLOW if bat > 20 else C_RED
M5.Lcd.fillRect(16, CONT_Y + 88, LCD_W - 32, 18, C_PANEL)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(bat_color)
M5.Lcd.setCursor(16, CONT_Y + 88)
M5.Lcd.print("電池 {}%".format(bat))
last_bat = bat
# CPU温度は毎回更新(変化が多いため差分不要)
temp = esp32.mcu_temperature()
M5.Lcd.fillRect(16, CONT_Y + 126, LCD_W - 32, 18, C_PANEL)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_YELLOW)
M5.Lcd.setCursor(16, CONT_Y + 126)
M5.Lcd.print("CPU {:.1f}度".format(temp))
elif mode == 2:
if last_counter is None:
draw_panel(8, CONT_Y + 4, LCD_W - 16, 76)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_GRAY)
M5.Lcd.setCursor(16, CONT_Y + 12)
M5.Lcd.print("COUNT")
draw_panel(8, CONT_Y + 88, LCD_W - 16, 30)
M5.Lcd.setTextColor(C_GRAY)
M5.Lcd.setCursor(16, CONT_Y + 96)
M5.Lcd.print("青+1 赤-1 右タップ+1")
if counter != last_counter:
num_color = C_GREEN if counter > 0 else C_RED if counter < 0 else C_GRAY
M5.Lcd.fillRect(16, CONT_Y + 36, LCD_W - 32, 38, C_PANEL)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(num_color)
M5.Lcd.setCursor(16, CONT_Y + 40)
M5.Lcd.print("{}".format(counter))
last_counter = counter
# POST結果(タイマーで消える)
if post_msg != last_post_msg or post_msg_timer == 0:
M5.Lcd.fillRect(8, CONT_Y + 126, LCD_W - 16, 24, C_BG)
if post_msg_timer > 0:
draw_panel(8, CONT_Y + 126, LCD_W - 16, 24)
M5.Lcd.setFont(SELECTED_FONT)
M5.Lcd.setTextColor(C_GREEN if "OK" in post_msg else C_RED)
M5.Lcd.setCursor(16, CONT_Y + 132)
M5.Lcd.print(post_msg)
last_post_msg = post_msg
if post_msg_timer > 0:
post_msg_timer -= 1
time.sleep(0.1)