M5StickC Plusでマイポモドーロタイマーを作る


はじめに

タスクを効率的にこなすための時間管理術として、ポモドーロテクニック1が知られています。これは25分間のタスク集中期間と5分間の休憩期間を繰り返し、タスクをこなしていく手法です。

時間の計測手段はキッチンタイマーでも良いですし、ポモドーロテクニックに特化したWebサービスを利用するのも手です。しかしながらキッチンタイマーはハードとして完成している都合上、機能拡張が難しいです。またWebサービスは便利なのですが周りのノイズが気になります(タイマー起動前についついネットサーフィンしてしまったり…)。
そこで今回、M5StickC Plusと呼ばれる製品を利用して、キッチンタイマーよりも少し高機能なポモドーロタイマーを作りました。

開発環境

今回用いた環境は以下となります。Windows PCにM5StickC Plusを接続し、プログラムの書き込みを行っています。M5StickC Plusはスイッチサイエンスで購入しました。

  • Windows10 Pro
  • M5StickC Plus
  • Arduino IDE

M5StickC Plusのセットアップ

M5StickC Plusのセットアップに少し手間取ったので、下記に手順を書いておきます。

ドライバのインストール

M5StickC Plusの開発を始めるにあたって、PCにドライバをインストールする必要があります。通常、CP2104 Driverと呼ばれるものをインストールすることで、デバイスマネージャーから認識されるようですが、私の環境では"不明なデバイス"と表示されている状況でした。
そこで色々と試してみたところ、FTDIドライバの2.12.26をインストールすることで正常に認識されるようになりました(記事執筆時点の最新バージョンは2.12.28ですが、2.12.28では認識されませんでした)。
デバイスマネージャーで正常に認識されることで、Arduino IDEからの書き込みが可能となります。

M5StickC Plusでのサンプルプログラムの実行

PCで正常に認識された後、Arduino IDEからM5Stick Cのライブラリをインストールし、サンプルプログラムを動作させてみました。しかし画面表示が小さく、なんだかおかしいです。

スイッチサイエンスの商品ページにも書かれていますが、M5StickCとM5StickC Plusではライブラリが異なり、M5StickC Plus用のライブラリをインストールする必要があります。
M5StickC Plus用のライブラリはGitHubで公開されています。GitHubのページからzip形式でファイルをダウンロードし、Arduino IDEのスケッチ→ライブラリをインクルード→.ZIP形式のライブラリをインストールから、ダウンロードしたZIPファイルを読み込むことで、M5StickC Plus用のライブラリが利用可能になります。
このライブラリにはFactoryTestのサンプルプログラムしか同梱されていませんが、M5StickCのサンプルプログラムについてもプログラム中の#include <M5StickC.h>#include <M5StickCPlus.h>と変更することで実行できます(簡単にしか検証していないので、動かないものもあるかもしれません)。

ポモドーロタイマーの機能

セットアップが完了したためポモドーロタイマーの製作に入っていきます。今回実装した機能は下記です。

  • 液晶にはタイマー、1日のポモドーロ数、累計ポモドーロ数が表示される
  • 正面のボタン(BtnA)を押すと25分のカウントダウンタイマー(集中期間)が開始する
  • 25分経過するとブザーが鳴り、1日のポモドーロ数と累計ポモドーロ数がカウントアップする。同時に5分のカウントダウンタイマー(休憩期間)が開始する
  • タイマーのカウントダウン中に正面のボタンを押すとタイマーがリセットされる
  • 下部のボタン(BtnB)を押すと1日のポモドーロ数がリセットされる
  • 累計ポモドーロ数は電源OFFされてもリセットされないようにEEPROMに情報を書き込む

プログラム

上記機能を実装したプログラムは下記のようなものです。
タイマー部分はM5Stack TFT_Clock_Digitalのサンプルを参考にしています。

#include <M5StickCPlus.h>
#include "EEPROM.h" 

const uint8_t PomodoroMinute = 25;
const uint8_t BreakMinute = 5;
const uint8_t LcdWidth = 240;
const uint8_t LcdHeight = 135;
const uint8_t Offset = 15;
const int EEPROMAddress = 0;

uint8_t mm = PomodoroMinute;
uint8_t ss = 0;
uint32_t targetTime = 0;

uint8_t dailyPomo = 0;
int totalPomo = 0;

enum Status{
  SLEEP,
  POMODORO,
  BREAK,
};
enum Status status = SLEEP;

void setup(){
  M5.begin();
  M5.Lcd.setRotation(3);
  EEPROM.begin(10);
  totalPomo=EEPROM.read(EEPROMAddress);
  printTime();
  printDailyPomodoro();
  printTotalPomodoro();
}

void loop() {
  M5.update();
  if(M5.BtnA.wasReleased()) {
    // タイマー未起動の場合は起動、起動中の場合はリセット
    alert(1);
    switch(status){
      case SLEEP:
        targetTime = millis() + 1000;
        status = POMODORO;
        break;
      case POMODORO:
      case BREAK:
        mm = PomodoroMinute;
        ss = 0;
        status = SLEEP;
        M5.Lcd.fillScreen(TFT_BLACK);              
        M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
        break;
    }
  }
  // Pomodoroカウントをリセット
  if(M5.BtnB.wasReleased()) {
    dailyPomo=0;
  }
  // タイマー起動中の場合は1秒ごとにカウントダウン
  if (status == POMODORO || status == BREAK){
    if (targetTime < millis()) {
      targetTime += 1000;
      ss--;
      if (ss == 255) {
        ss = 59;
        mm--;
        if(mm == 255) {
          alert(3);
          // カウントが終了した場合
          switch(status){
            case POMODORO:  // BREAKステータスに移行
              status = BREAK;
              mm = BreakMinute;
              ss = 0;
              // pomodoroカウントをカウントアップ
              dailyPomo++;
              totalPomo++;
              EEPROM.write(EEPROMAddress, totalPomo);
              EEPROM.commit();

              M5.Lcd.fillScreen(TFT_DARKCYAN);
              M5.Lcd.setTextColor(TFT_WHITE, TFT_DARKCYAN);
              break;
            case BREAK:  // SLEEPステータスに移行
              status = SLEEP;
              mm = PomodoroMinute;
              ss = 0;
              M5.Lcd.fillScreen(TFT_BLACK);              
              M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
              break;
          }
        }
      }
    }
  }
  printTime();
  printDailyPomodoro();
  printTotalPomodoro();
}

void printTime() {
  M5.Lcd.setTextSize(8);
  M5.Lcd.setCursor(Offset, Offset);
  M5.Lcd.printf("%02d:%02d", mm, ss);
}

void printDailyPomodoro() {
  M5.Lcd.setTextSize(3);
  M5.Lcd.setCursor(LcdWidth/2 + 8, 80);
  M5.Lcd.printf("%4dP", dailyPomo);  
}

void printTotalPomodoro() {
  M5.Lcd.setTextSize(3);
  M5.Lcd.setCursor(LcdWidth/2 + 8, 110);
  M5.Lcd.printf("%4dP", totalPomo);  
}

void alert(int count) {
  for(int i = 0; i < count; i++){
    M5.Beep.tone(800);
    delay(100);
    M5.Beep.mute();
    delay(100);    
  }
}

動作の様子

上記プログラムを動作させた様子が以下です。
ボタンを押すとタイマーが始まります。

25分経過すると、ポモドーロ数がカウントアップし、5分のタイマーが起動します。

おわりに

理想のポモドーロタイマーが欲しい!ということで作ってみましたが、今のところ特に不自由なく活用できています。累計ポモドーロ数が表示されるのが案外良く、数字が徐々に増えていくのが楽しいです。
常時給電しないと25分持たずにバッテリー切れになってしまうので、電源には常に接続しています。万一電源が切れた場合についても、累計ポモドーロ数はEEPROMに記録されているので消失しません。
機能拡張は自由自在であるため、過去の履歴を表示したり、ネットワーク接続してポモドーロ数を外部DBに記録したりしても面白いかもしれません。
マイポモドーロタイマーが欲しい方はぜひ試してみてください!