まえおき
作ってみた的なものです。
小さいアナログ時計が欲しかったので作ってみました。
タイムラプス動画の片隅に配置したかったのです。
コード
#include <Arduino.h>
#include <M5AtomDisplay.h>
#include <M5Unified.h>
#include <WiFi.h>
const char* WIFI_SSID = "MyHomeWiFi";
const char* WIFI_PASS = "MySecurePassword123";
char ntpServer[] = "ntp.nict.jp";
const long gmtOffset_sec = 9 * 3600;
const int daylightOffset_sec = 0;
struct tm timeinfo;
String second, minute, hour, dayOfWeek, dayOfMonth, month, year;
unsigned long lastNtpSync = 0;
const unsigned long ntpSyncInterval = 3600000; // 1時間 = 3600000ミリ秒
#define CLOCK_CENTER_X 64 // 時計の中心X座標
#define CLOCK_CENTER_Y 45 // 時計の中心Y座標
#define CLOCK_RADIUS 35 // 時計の半径
#define HOUR_LENGTH 20 // 短針の長さ
#define MINUTE_LENGTH 28 // 長針の長さ
#define SECOND_LENGTH 30 // 秒針の長さ
bool connectWiFi() {
if (WiFi.status() == WL_CONNECTED) {
return true;
}
M5.Display.setCursor(0, 24);
M5.Display.println("WiFi再接続中...");
WiFi.begin(WIFI_SSID, WIFI_PASS);
// 接続を最大20秒間試みる
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 100) {
M5.Display.setCursor(12, 36);
M5.Display.print("* ");
delay(200);
M5.Display.setCursor(12, 36);
M5.Display.print(" * ");
delay(200);
M5.Display.setCursor(12, 36);
M5.Display.print(" *");
delay(200);
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
M5.Display.setCursor(0, 36);
M5.Display.print("IP:");
M5.Display.println(WiFi.localIP());
return true;
} else {
M5.Display.setCursor(0, 36);
M5.Display.println("WiFi接続失敗");
return false;
}
}
void getTimeFromNTP() {
if (!connectWiFi()) {
return; // WiFi接続に失敗した場合は時刻同期をスキップ
}
M5.Display.setCursor(0, 48);
M5.Display.println("NTP時刻同期中...");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
int attempts = 0;
bool timeReceived = false;
while (!timeReceived && attempts < 5) {
timeReceived = getLocalTime(&timeinfo);
if (!timeReceived) {
delay(1000);
attempts++;
}
}
if (timeReceived) {
M5.Display.setCursor(0, 60);
M5.Display.println("時刻同期成功");
lastNtpSync = millis(); // 同期時間を記録
} else {
M5.Display.setCursor(0, 60);
M5.Display.println("時刻同期失敗");
}
}
void getTime(String* year, String* month, String* day, String* hour, String* min, String* sec, String* wday) {
char buf[10];
getLocalTime(&timeinfo);
*year = String(timeinfo.tm_year + 1900);
sprintf(buf, "%02d", timeinfo.tm_mon + 1);
*month = String(buf);
sprintf(buf, "%02d", timeinfo.tm_mday);
*day = String(buf);
sprintf(buf, "%02d", timeinfo.tm_hour);
*hour = String(buf);
sprintf(buf, "%02d", timeinfo.tm_min);
*min = String(buf);
sprintf(buf, "%02d", timeinfo.tm_sec);
*sec = String(buf);
strftime(buf, 10, "%a", &timeinfo);
*wday = String(buf);
}
void drawWiFiIcon(int x, int y, bool connected) {
if (connected) {
// WiFi接続中の場合、信号強度に応じたアンテナバーを表示
int rssi = WiFi.RSSI();
int strength = 0;
// RSSIの値に基づいて信号強度(0-4)を計算
if (rssi >= -50) strength = 4; // 非常に強い(-50dBm以上)
else if (rssi >= -65) strength = 3; // 強い(-65dBm以上)
else if (rssi >= -75) strength = 2; // 普通(-75dBm以上)
else if (rssi >= -85) strength = 1; // 弱い(-85dBm以上)
else strength = 0; // とても弱い(-85dBm未満)
// アンテナピクトを描画(縦棒を4本まで)
int barWidth = 2; // 各バーの幅
int barGap = 1; // バー間の隙間
int maxHeight = 8; // 最大の高さ
for (int i = 0; i < 4; i++) {
int barHeight = (i + 1) * 2; // 各バーの高さ(2, 4, 6, 8)
int barX = x + i * (barWidth + barGap);
int barY = y + (maxHeight - barHeight);
// 信号強度に応じて、対応するバーを塗りつぶすか輪郭のみ表示
if (i < strength) {
// 信号あり - 塗りつぶし
M5.Display.fillRect(barX, barY, barWidth, barHeight, WHITE);
} else {
// 信号なし - 輪郭のみ(接続中だが信号が弱い場合)
M5.Display.drawRect(barX, barY, barWidth, barHeight, WHITE);
}
}
} else {
// WiFi未接続の場合、×印付きのアイコンを表示
int iconWidth = 11; // アイコン全体の幅
int iconHeight = 8; // アイコン全体の高さ
// 基本アンテナ形状(輪郭のみ)を描画
for (int i = 0; i < 4; i++) {
int barWidth = 2;
int barGap = 1;
int barHeight = (i + 1) * 2;
int barX = x + i * (barWidth + barGap);
int barY = y + (iconHeight - barHeight);
M5.Display.drawRect(barX, barY, barWidth, barHeight, WHITE);
}
// ×印を重ねて表示
M5.Display.drawLine(x, y, x + iconWidth - 3, y + iconHeight, WHITE);
M5.Display.drawLine(x + iconWidth - 3, y, x, y + iconHeight, WHITE);
}
}
// 時計の針を描画する関数
void drawClockHand(int value, int max_value, int length, uint16_t color) {
float angle = ((float)value / (float)max_value) * 2.0 * PI;
// 時計は12時が上(-PI/2)なので、調整が必要
angle = angle - (PI / 2.0);
int x2 = CLOCK_CENTER_X + length * cos(angle);
int y2 = CLOCK_CENTER_Y + length * sin(angle);
M5.Display.drawLine(CLOCK_CENTER_X, CLOCK_CENTER_Y, x2, y2, color);
}
// アナログ時計の外枠を描画
void drawClockFace() {
// 時計の外枠を描画
M5.Display.drawCircle(CLOCK_CENTER_X, CLOCK_CENTER_Y, CLOCK_RADIUS, WHITE);
// 時間マーカーを描画(12, 3, 6, 9時の位置)
for (int h = 0; h < 12; h++) {
float angle = (h / 12.0) * 2.0 * PI - (PI / 2.0);
int marker_length = (h % 3 == 0) ? 5 : 3; // 3, 6, 9, 12時は長めのマーカー
int x1 = CLOCK_CENTER_X + (CLOCK_RADIUS - marker_length) * cos(angle);
int y1 = CLOCK_CENTER_Y + (CLOCK_RADIUS - marker_length) * sin(angle);
int x2 = CLOCK_CENTER_X + CLOCK_RADIUS * cos(angle);
int y2 = CLOCK_CENTER_Y + CLOCK_RADIUS * sin(angle);
M5.Display.drawLine(x1, y1, x2, y2, WHITE);
}
// 中心点
M5.Display.fillCircle(CLOCK_CENTER_X, CLOCK_CENTER_Y, 2, WHITE);
}
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
M5.Display.init();
M5.Display.setRotation(3); // 表示方向の調整
M5.Display.setFont(&fonts::lgfxJapanGothicP_12);
M5.Display.setTextColor(WHITE, BLACK);
M5.Display.fillScreen(BLACK);
M5.Display.println("起動中...");
getTimeFromNTP();
}
void loop() {
// 1時間ごとにNTP同期を実行
unsigned long currentMillis = millis();
if (currentMillis - lastNtpSync >= ntpSyncInterval || lastNtpSync == 0) {
// ミリ秒カウンタのオーバーフロー対策
if (currentMillis < lastNtpSync) {
lastNtpSync = 0;
}
// 一時的に時計表示を停止し、同期状態を表示
M5.Display.fillScreen(BLACK);
getTimeFromNTP();
delay(2000); // 同期結果の表示を2秒間表示
}
getTime(&year, &month, &dayOfMonth, &hour, &minute, &second, &dayOfWeek);
M5.Display.fillScreen(BLACK);
drawClockFace();
int h = hour.toInt() % 12;
int m = minute.toInt();
int s = second.toInt();
float hour_pos = h + m / 60.0;
drawClockHand(hour_pos, 12, HOUR_LENGTH, WHITE); // 短針
drawClockHand(m, 60, MINUTE_LENGTH, WHITE); // 長針
drawClockHand(s, 60, SECOND_LENGTH, RED); // 秒針
M5.Display.setCursor(35, 90);
M5.Display.printf("%s:%s:%s", hour.c_str(), minute.c_str(), second.c_str());
M5.Display.setCursor(10, 105);
M5.Display.printf("%s/%s/%s %s", year.c_str(), month.c_str(), dayOfMonth.c_str(), dayOfWeek.c_str());
drawWiFiIcon(105, 2, WiFi.status() == WL_CONNECTED);
delay(1000);
}
実行
作ったものより目立つスタックチャンの図
それから
これ作ってるときにディスプレイ部分もスイッチになっていることに気づきました。なんか押してる感があります。
ボタン、インサイドって書いてあるわ。
