|
// 2回目以降の起動の場合はここをコメントアウトすることで高速に起動できる |
|
#define INITIAL_LAUNCH |
|
|
|
#include <WiFi.h> |
|
|
|
#if __has_include(<esp_sntp.h>) |
|
#include <esp_sntp.h> |
|
#define SNTP_ENABLED 1 |
|
#elif __has_include(<sntp.h>) |
|
#include <sntp.h> |
|
#define SNTP_ENABLED 1 |
|
#endif |
|
|
|
#ifndef SNTP_ENABLED |
|
#define SNTP_ENABLED 0 |
|
#endif |
|
|
|
#include <M5Unified.h> |
|
|
|
#include "sha1.h" |
|
#include "TOTP.h" |
|
|
|
#include "USB.h" |
|
#include "USBHIDKeyboard.h" |
|
|
|
#define WIFI_SSID "YourSSID" |
|
#define WIFI_PASSWORD "your_password" |
|
#define NTP_TIMEZONE "JST-9" |
|
#define NTP_SERVER1 "0.pool.ntp.org" |
|
#define NTP_SERVER2 "1.pool.ntp.org" |
|
#define NTP_SERVER3 "2.pool.ntp.org" |
|
|
|
// シークレットの取得方法 |
|
// 1. 各種Webサービスから2段階認証の設定に移動し、「認証アプリを使う」などの方法を選択する |
|
// 2. 出現するQRコードを(認証アプリではなく)QRコードスキャンアプリで読み取る |
|
// 3. otpauth://totp/ から始まるURLが出現するので、secret= から始まる部分をコピーする |
|
// 4. BASE32でデコードするとシークレットが得られる |
|
// シークレットは5の倍数バイトであることが多い |
|
uint8_t hmacKey[][10] = { |
|
{0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x09, 0x08, 0x07, 0x06}, |
|
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}, |
|
{0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00} |
|
}; |
|
USBHIDKeyboard Keyboard; |
|
TOTP totp[] = { |
|
TOTP(hmacKey[0], 10), |
|
TOTP(hmacKey[1], 10), |
|
TOTP(hmacKey[2], 10) |
|
}; |
|
char code[7]; // 画面をタッチした時に入力されるコード |
|
|
|
void fetchRtc(); |
|
void updateTotp(); |
|
void sleep(); |
|
|
|
void setup() |
|
{ |
|
auto cfg = M5.config(); |
|
cfg.clear_display = false; |
|
// clear_display = falseは現バージョンでは機能しないっぽい?ので、下記のようにローカルでM5GFXのソースコードを編集する |
|
// ただし、Arduino IDEだと他のスケッチにも影響が出るため注意する |
|
// 1. Panel_EPDiy.cpp 内の Panel_EPDiy::init の中身を確認する |
|
// 2. startWrite() と endWrite() で囲まれている部分をコメントアウトする |
|
M5.begin(cfg); |
|
M5.Display.setEpdMode(m5gfx::epd_fastest); |
|
|
|
// USB初期化 |
|
Keyboard.begin(); |
|
USB.begin(); |
|
|
|
if (!M5.Rtc.isEnabled()) |
|
{ |
|
M5.Display.println("RTC not found."); |
|
for (;;) |
|
{ |
|
M5.delay(500); |
|
} |
|
} |
|
|
|
#ifdef INITIAL_LAUNCH |
|
// 画面リフレッシュ |
|
M5.Display.setEpdMode(m5gfx::epd_quality); |
|
M5.Display.fillScreen(TFT_BLACK); |
|
M5.Display.fillScreen(TFT_WHITE); |
|
|
|
// 現在時刻を取得する |
|
fetchRtc(); |
|
|
|
// 基本のUI |
|
M5.Display.startWrite(); |
|
M5.Display.fillScreen(TFT_WHITE); |
|
|
|
// タイトルバー |
|
M5.Display.setFont(&fonts::FreeSans24pt7b); |
|
M5.Display.setTextDatum(TC_DATUM); |
|
M5.Display.drawString("PaperS3 Authenticator", 270, 16); |
|
M5.Display.drawFastHLine(0, 72, 540, TFT_BLACK); |
|
|
|
M5.Display.setTextDatum(TL_DATUM); |
|
// 1個目 |
|
M5.Display.drawString("Dummy1" , 24, 108); |
|
M5.Display.drawFastHLine(0, 232, 540, TFT_BLACK); |
|
|
|
// 2個目 |
|
M5.Display.drawString("Dummy2", 24, 268); |
|
M5.Display.drawFastHLine(0, 392, 540, TFT_BLACK); |
|
|
|
// 3個目 |
|
M5.Display.drawString("Dummy3", 24, 428); |
|
M5.Display.drawFastHLine(0, 552, 540, TFT_BLACK); |
|
|
|
updateTotp(); |
|
M5.Display.endWrite(); |
|
M5.Display.setEpdMode(m5gfx::epd_fastest); |
|
|
|
#else |
|
// 電源オフ時の文言のみをリフレッシュする |
|
M5.Display.startWrite(); |
|
M5.Display.fillRect(24, 160, 410, 48, TFT_BLACK); |
|
M5.Display.fillRect(24, 320, 410, 48, TFT_BLACK); |
|
M5.Display.fillRect(24, 480, 410, 48, TFT_BLACK); |
|
M5.Display.fillRect(0, 960, 540, -80, TFT_BLACK); |
|
M5.Display.endWrite(); |
|
|
|
M5.Display.startWrite(); |
|
M5.Display.fillRect(24, 160, 410, 48, TFT_WHITE); |
|
M5.Display.fillRect(24, 320, 410, 48, TFT_WHITE); |
|
M5.Display.fillRect(24, 480, 410, 48, TFT_WHITE); |
|
M5.Display.fillRect(0, 960, 540, -80, TFT_WHITE); |
|
updateTotp(); |
|
M5.Display.endWrite(); |
|
|
|
#endif |
|
} |
|
|
|
void loop() |
|
{ |
|
M5.update(); |
|
|
|
// 5秒ごとにTOTP更新 |
|
static time_t last = millis(); |
|
if (millis() - last >= 5000) |
|
{ |
|
last = millis(); |
|
updateTotp(); |
|
} |
|
|
|
// 60秒無操作でオフ |
|
static time_t lastInput = millis(); |
|
if (millis() - lastInput >= 60000) |
|
{ |
|
lastInput = millis(); |
|
last = millis() + 5000; |
|
sleep(); |
|
} |
|
|
|
// 画面タッチでコード送信 |
|
auto touch = M5.Touch.getDetail(); |
|
if (touch.wasClicked()) |
|
{ |
|
Keyboard.printf("%s", code); |
|
lastInput = millis(); |
|
} |
|
|
|
M5.delay(16); |
|
} |
|
|
|
void fetchRtc() |
|
{ |
|
configTzTime(NTP_TIMEZONE, NTP_SERVER1, NTP_SERVER2, NTP_SERVER3); |
|
M5.Display.setTextSize(2); |
|
M5.Display.print("WiFi:"); |
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); |
|
for (int i = 20; i && WiFi.status() != WL_CONNECTED; --i) |
|
{ |
|
M5.Display.print("."); |
|
M5.delay(500); |
|
} |
|
if (WiFi.status() == WL_CONNECTED) |
|
{ |
|
M5.Display.println("\r\nWiFi Connected."); |
|
M5.Display.print("NTP:"); |
|
#if SNTP_ENABLED |
|
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) |
|
{ |
|
M5.Display.print("."); |
|
M5.delay(1000); |
|
} |
|
#else |
|
M5.delay(1600); |
|
struct tm timeInfo; |
|
while (!getLocalTime(&timeInfo, 1000)) |
|
{ |
|
M5.Display.print('.'); |
|
}; |
|
#endif |
|
M5.Display.println("\r\nNTP Connected."); |
|
|
|
time_t t = time(nullptr) + 1; // Advance one second. |
|
while (t > time(nullptr)) |
|
; /// Synchronization in seconds |
|
M5.Rtc.setDateTime(gmtime(&t)); |
|
} |
|
else |
|
{ |
|
M5.Display.println("\r\nWiFi none..."); |
|
} |
|
M5.Display.setTextSize(1); |
|
} |
|
|
|
void updateTotp() |
|
{ |
|
// コード算出 |
|
auto t = time(nullptr); |
|
char *newCode[3] = { |
|
totp[0].getCode(t), |
|
totp[1].getCode(t), |
|
totp[2].getCode(t) |
|
}; |
|
|
|
if (strcmp(code, newCode[0]) != 0) { |
|
strcpy(code, newCode[0]); |
|
} |
|
|
|
// 描画 |
|
long remainedTime = t % 30; // timestep = 30(s) |
|
float arcStart = -90 + (remainedTime / 30.0) * 360; |
|
M5.Display.startWrite(); |
|
|
|
M5.Display.setFont(&fonts::FreeSans24pt7b); |
|
M5.Display.setTextDatum(TL_DATUM); |
|
M5.Display.setTextSize(1); |
|
|
|
M5.Display.drawString(newCode[0], 24, 160); |
|
M5.Display.fillCircle(460, 152, 24, TFT_WHITE); |
|
M5.Display.fillArc(460, 152, 24, 0, arcStart, 270, TFT_DARKGRAY); |
|
|
|
M5.Display.drawString(newCode[1], 24, 320); |
|
M5.Display.fillCircle(460, 312, 24, TFT_WHITE); |
|
M5.Display.fillArc(460, 312, 24, 0, arcStart, 270, TFT_DARKGRAY); |
|
|
|
M5.Display.drawString(newCode[2], 24, 480); |
|
M5.Display.fillCircle(460, 472, 24, TFT_WHITE); |
|
M5.Display.fillArc(460, 472, 24, 0, arcStart, 270, TFT_DARKGRAY); |
|
|
|
// 一応、現在時刻も描画する |
|
static constexpr const char *const wd[7] = {"Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat"}; |
|
auto tm = localtime(&t); // for local timezone. |
|
M5.Display.setCursor(0, 960); |
|
M5.Display.setTextDatum(BL_DATUM); |
|
M5.Display.setFont(&fonts::Font4); |
|
M5.Display.printf("%04d/%02d/%02d (%s) %02d:%02d:%02d %s\r\n", |
|
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, |
|
wd[tm->tm_wday], |
|
tm->tm_hour, tm->tm_min, tm->tm_sec, NTP_TIMEZONE); |
|
M5.Display.setTextSize(1); |
|
|
|
M5.Display.endWrite(); |
|
} |
|
|
|
void sleep() |
|
{ |
|
M5.Display.setEpdMode(m5gfx::epd_text); |
|
M5.Display.startWrite(); |
|
|
|
M5.Display.setFont(&fonts::FreeSans24pt7b); |
|
M5.Display.setTextDatum(TL_DATUM); |
|
M5.Display.setTextColor(TFT_DARKGRAY, TFT_WHITE); |
|
M5.Display.setTextSize(1); |
|
|
|
M5.Display.fillCircle(460, 152, 24, TFT_WHITE); |
|
M5.Display.drawString("Power to view code", 24, 160); |
|
|
|
M5.Display.fillCircle(460, 312, 24, TFT_WHITE); |
|
M5.Display.drawString("Power to view code", 24, 320); |
|
|
|
M5.Display.fillCircle(460, 472, 24, TFT_WHITE); |
|
M5.Display.drawString("Power to view code", 24, 480); |
|
|
|
M5.Display.fillRect(0, 960, 540, -80, TFT_WHITE); |
|
|
|
M5.Display.endWrite(); |
|
|
|
// M5.Power.lightSleep(0, true); // タッチで復帰できないかと思ったが不可能だった |
|
M5.Power.powerOff(); |
|
} |