キキマグとカップウォーマーを買った

キキマグとカップウォーマー

コーヒー用のマグカップのキキマグとカップウォーマーを購入したのでその感想など残す

カップウォーマー

購入したのは Kyerlish カップウォーマー コーヒー保温コースター

https://www.amazon.co.jp/gp/product/B0BNM6DMB9/?th=1

セールで2970円。

カップの底面を温めて温度をキープしてくれるタイプのもの。 温度は55, 65, 75で選択できる。 また、重量センサーがついており、一定以上の重さのものがなければ加熱はされない。

去年の同時期に検討した際にはあまり良いデザインのものがなかったのだが、ラインナップが増えた印象がある。 本当は重量センサーのないタイプの黒が欲しかったが在庫がないようなので、温度選択ができてデザインが好みのものを探しこれに決定。

朝活時のミルクティーを冷めずにキープしてくれたので大満足。

マグカップ

マグカップはCoresのキキマグ

https://cores.coffee/product/c811/

コーヒーを飲むのにはカータームーブマグを使っていた。 これも保温性も良いし持ち運びもできて最高なのだが、自宅での作業時には洒落たカップを使いたいなーと前々から思っていた。 カップウォーマーがうまいこと効いてくれることがわかったので、満を持して購入。

風合いも良いし、名前の通り飲むときに香りが楽しめるのが良い。 普段は200-250ml程度を一度に淹れるので、320mlというサイズ感がまたちょうど良かった。

キキマグとカップウォーマーとの組み合わせはどうだったか

キキマグの底面がフラットではないので、カップウォーマーの熱がうまく伝わらないかな?と思ったが、一定は温度をキープしてくれるようだった。多分効果は落ちているが許容できる温かさ。 効果を高めるならコイン状の金属か何かを調達して底面とカップの距離を縮めればいけるかな。いったんは様子見でよさそうな印象。

なんにせよ、これで作業中でもお気に入りのカップにコーヒーを淹れられる環境が整った。 いい買い物をしました。

分割キーボードのChoco60 rev2を導入した

macの外付けキーボードが気に入って10年来使い続けて来ました。 が、世は自作キーボードがブーム。特に分割キーボードに興味がありました。 1度使ってみたいし作ってみたい・・というわけで、HHKBに似た配列の左右分割キーボードのChoco60を作ってみることにしました。

Choco60にした理由

なるべくmacの配列からズレない自作キットを探していました。 その中で、スペースの左右にコマンドとaltを振る余地があり、左上がescのものを探した結果Choco60が一番希望にかなっていそうでした。

またChoco60はrev2になりダイオードを取り付ける手順がなくなり、他のキットよりもビルドが簡単だったのも大きな理由です。

構成

カテゴリ 商品 個数
キット Choco60 rev.2 1
スタビライザー DUROCK V2 2U PCBマウント 4
キースイッチ DUROCK Dolphin サイレントリニアスイッチ 62
キーキャップ PBT DA 143/99 KEYS KEYCAPS SET 1
TRPSケーブル TRRSケーブル(0.3m) 1

f:id:masarusanjp:20210824000252j:plain
材料

ビルド

ビルドガイドに沿って組み立てていきます。

スタビライザーの組み方がわからなくて困りましたが、https://talpkeyboard.net/items/610deaaf2023970a888373a7 に貼られている動画を見たらわかりました。

あとは手順の動画の通りやっていくだけです。動画になっているのがわかりやすく、思ってたより簡単に作ることができました。 全体で2〜3時間かかったかな。

f:id:masarusanjp:20210824000407j:plain
スタビライザーを取り付けた

f:id:masarusanjp:20210824000843j:plain
PCBプレートの取り付け。裏面に飛び出たネジを別のスペーサーで固定する。

f:id:masarusanjp:20210824001002j:plain
スイッチの取り付け

f:id:masarusanjp:20210824001030j:plain
スイッチのピンと基盤を半田付けする

f:id:masarusanjp:20210824001107j:plain
出来上がり

ファームは書き込み済みなので、usb-cケーブルを使ってpcと接続すれば使い始めることができました。

感想

キースイッチのスコスコとした感じがとても気持ち良いです。 久しぶりにmac以外のキーボードを触ったが良いですね。 分割の姿勢は最初は違和感があったが慣れると楽ちんでとても良かった。

カーソルキーがない生活になれないのと、やはりmacのキーボードよりも少し奥行きがあり、軽々押せていた数字キーあたりが指を伸ばさないと届かなくてどうしたもんかなーというのが今の悩み。 レイヤーをうまく使えば解決できるのかな。 せっかくの自作キーボードなので色々試してみたいと思います。

DartでSwiftのResult型のような型を定義する

やりたいこと

Dart言語でSwiftのResult型のようなunion型を定義したい。Swiftの場合は以下のように定義ができる。 (ビルトインタイプがあるので実際には不要だけど)

// 定義
enum Result<T, E: Error> {
    case success(T)
    case failure(E)
}

// 利用

func do(result: Result<String, SomeError>) {
    switch result {
    case let .success(value):
        debugPrint(value)
    case let .failure(error):
        debugPrint(error)
    }
}

swiftのassociated enumは非常に強力で、上記のように成功時・失敗時を名前の通りに分岐でき、そのケースの場合だけにある値を安全に取り出すことができる。

これと同様のことをDartでも行いたい

解決案

以下のようなコードで近いことができる。is演算子で検査することでif文の中ではその変数を検査した型として扱うことができる。ついでにfactoryコンストラクタでResult.successのように書けるようにしてそれっぽくしている。

// 定義

abstract class Result<Value, FailureType extends Exception> {
  factory Result.success(Value value) => ResultSuccess(value);
  factory Result.failure(FailureType error) => ResultFailure(error);
  Result();
}

class ResultSuccess<Value, FailureType extends Exception>
    extends Result<Value, FailureType> {
  final Value value;
  ResultSuccess(this.value);
}

class ResultFailure<Value, FailureType extends Exception>
    extends Result<Value, FailureType> {
  final FailureType error;
  ResultFailure(this.error);
}

// 利用

void process() {
  processResult(Result.success("value"));
  processResult(Result.failure(Exception("message")));
}

void processResult(Result<String, Exception> result) {
  if (result is ResultSuccess<String, Exception>) {
    log(result.value);
  } else if (result is ResultFailure<String, Exception>) {
    log(result.error.toString());
  }
}

まとめ

swift enumのassociated valuesほどではないが、unionぽいものが作れた。 ちなみにunionのように使えるものは、freezedを使えば便利なメソッドまで含めて生成してくれるので自前で定義するよりもそちらのほうが良い。 この記事ではライブラリのコードジェネレータに頼らなくても簡単なことならできるぞ、という実験がしたかったのでした。

AFTERSHOKZ AEROPEXを買った

在宅勤務でイヤホンをして作業をする時間が格段に増えた。 SonyのWI-1000Xを使っていたのだが、カナル型イヤホンのため長時間つけているとどうにも耳が痒くなる。 ノイズキャンセリングや音質はとても良いのだけどね。

というわけで、代替手段を探していて最近流行りの骨伝導ヘッドホンを試すことした。 最終的に買ったものはこちら「AFTERSHOKZ」の「AEROPEX」

f:id:masarusanjp:20210313234903j:plain
f:id:masarusanjp:20210313234924j:plain
どーん

装着は耳の上に引っ掛けて耳たぶのあたりに音源のモジュールで来るような感じになる。 期待していた快適性はとても良かった。耳が塞がれないというのはとても快適。 音質は正直期待していなかったのだけど、思った以上に良いし、ちゃんと聞こえることに驚いた。 オンライン会議でも人の声もはっきりと聞こえるので快適だった。 一方で、耳を塞がないので家の中の雑音・生活音が聞こえてしまうのは少し気になる。まぁこれは当然だしトレードオフで仕方のないところかな。 音質にも快適性も満足だし、つけていることを感じさせない軽さも良い。 買ってよかった一品でした。

ちなみにAEROPEXからはいくつかの機種が出ており、最初は手頃な値段の「OPENMOVE」を購入したが、締付けの強さが自分には合わずこちらは返品した。全額返ってきてくれて、30日以内なら全額返金保証という宣言の通りでした。 もし買うことを検討している人がいたら試着してから買うことをおすすめしたい。

パワーメーターを買ってペダルを交換したときのメモ

Wiggleで4iiiiのパワーメータを買った。左のクランクを交換するタイプのもの

f:id:masarusanjp:20201229224324j:plain
ででん

パワーメータ、めちゃ高いというイメージがあったのだけど、これはセールで29000円ほどだった。Black Fridayだからお得!って思ったがその後も変わらずセールが続いていたので焦る必要はなかったな。

さて買ったはいいがどうやって付け替えるんだ、、ということで備忘録として残す。

自分でやるかお店でやるか

まずこの2択。これは自分でやることにした。

クランクは右のパーツと左のパーツに分かれており、右に軸となる部品がついており左はそれに差し込む形になる。なので左だけであれば難易度は低そうと判断した。

またお店でやってもらう場合、ysroad の場合クランクの交換は4800円(左だけならもうちょい安そう)で持ち込みパーツの場合は倍の工賃となる。

これはちょっと高い。持ち込むのも手間だしなー。まぁそれに何より勉強になって面白そうじゃんかということで、自分でやることに決めた。

左クランクの交換に必要なもの

ではどうやって交換をするのか。

というのはこのサイトが詳しかった。ありがたい。

クランク脱着は自分でできる!シマノクランクの外し方と取り付け方法【ロードバイク】

左クランクの交換に必要な道具は以下の2つ。

  • トルクレンチ
  • TL FC16(シマノのクランクボルトを外す専用の工具)

ペダルも交換するので、+でペダルレンチ。駆動部の交換のためグリスが必要。

ペダルレンチとグリスは持っていたので、トルクレンチとTLFC16を購入した。

こういう駆動部のネジの類は締め付けのための適正な強さというのが決められていて、その力で締める必要があるとのこと。

というわけで実際に使った工具たちがこれ。

f:id:masarusanjp:20201229224240j:plain
買った工具たち

購入したバイクハンドのトルクレンチはつまみでトルクを指定することができ、指定したトルク以上の力で回そうとするとカチっとなってわかるようになっている。これは24NMまで測れる。クランクは12NM-14NMが指定されていた。余談だが大体のパーツはこれで測れるぽいが、シマノスプロケットは40NMが推奨されており、アナログなトルクレンチだと厳しく、デジタルのものが必要そう。

TLFC16は買わなくてもいけるらしいが、安かったので買っておいた。

取り付け

これも先述させて頂いているサイトが詳しい。

実作業のことを書こうと思ったのだけど、写真を撮り忘れてしまったので省略する。

ポイントとしては

  • 外すのは簡単
  • 取り付けは「順番」と「クランクの切れ目とシャフトの穴を合わせる」の2点を注意。作業自体は簡単

という感じだった。

ペアリング

いざペアリング! ペアリングは他のセンサーと同様に検出モードにしてペダルを回すだけで特に難なくできた。

f:id:masarusanjp:20201229224343j:plain
無事に認識された図

感覚でしかないがwahoo kickrとの誤差もないように思う。ちなみにwahoo kickrに乗った際にパワーセンサーが競合してしまうが、使Garmin Edgeはあとから検出した方に切り替えるかどうかを聞いてきてくれるので、特に困りは生じなかった。

感想

取り付けは左クランクくらいなら予想通り楽で良かった。とはいえ、道具を揃えるコストと自己責任になることのバランスではあるかなぁ。

パワーセンサーそものは買ってよかったの一言。自分が疲れてしまう強さというのが可視化されるのは便利。特にこれを取り付けてから行った初のヒルクライムが完走できたのはパワーセンサーのおかげだったと思う。 FTPで自分の脚力が定量化できたしだいぶ低いのも認識できたので鍛えていくぞ。

Wahoo Kickrを買った

f:id:masarusanjp:20201203224318p:plain
Wahoo Kickr

前から狙っていたスマートサイクルトレーナーのwahoo kickrを買いました。型式は2018年のWFBKTR118。

子供が産まれて自転車に乗る機会が減ってしまったのだが、もうちょっと乗りたいじゃんというのと、毎年続けている友人とのロングライドのために足を強くしたいというのが理由。

f:id:masarusanjp:20201203224600j:plain
取り付けてみた

ダイレクトドライブ式はインドアトレーナーの中でもお値段はだいぶと高いけれど、音が静かなのが良い。子供が寝たあとにやることが多いだろうし。それにzwiftとの連携もバッチリだしね。

メーカーの選択肢としてwahooとtacxの2つの絞ったが、あとあとkickr climbと連携したいので、wahooにした。climbは継続できたら買い足すつもり。

ちなみに検討中にwahoo kickrの新型が出てだいぶと悩んだけど性能にほぼ変わりがなく、kickr axisはWFBKTR118にもつけられるのでコスト面で有利な旧型にした。

f:id:masarusanjp:20201203224633j:plain
zwift対応した図。この向きで置くの無理だと思ったけど置けた。なお鍵盤へのアクセスが最悪になっ

早速乗ってみた感じ、たしかに音が静かで良かった。ほぼチェーンのシャーという音くらい。別の部屋にいた家族からも特に気にならなかったとのコメントで一安心。

zwiftもやってみたけど、こちらはまだちょっとわからない。 思いっきり漕いでる割にはスピードがでなくてなんで?って思ったのだが、スピンダウンをしていないからかな。

zwiftに限らないが他にもgarminの腕時計の心拍が取れないとか、フレームへの汗対策とか色々必要そう。

毎日乗れる環境は整ったのでちょっとずつ整えていこう。

BLEを使ってiosとm5stack間で通信する

やりたいこと

iOS端末とM5StackでBLEを使って通信を行いたい。 ここではiOSとM5のそれぞれで最小構成のコードを書いて試すことをゴールにする。

TL:DR

基礎知識

コーディングに入る前にBLEについてざっくり理解をしておきたい。 以下のサイトを参照している。理解のためにまとめ直しているが、詳細は元サイトを見てもらいたい。 https://www.musen-connect.co.jp/blog/course/trial-production/

BLEとは

Bluetooth Low Energyの略。Bluetoothは近距離無線通信の規格の1つである。BLEはBluetoothの規格の中で省電力に特化するために出来た通信方式。(補足.それまでのBluetoothの通信方式はBLEと区別するために、Bluetooth Classicと呼ばれる)

登場人物

大きく、CentralとPeripheralが登場する。 無線通信は大抵が[親機]と[子機]に分かれる。BLEでは親機のことを[Central]、子機のことを[Peripheral]と呼ぶ。 セントラルは大抵スマホやPCなどの高機能デバイスがなり、センサーやビーコン等はペリフェラルになる。 1つのセントラルには複数のペリフェラルが同時に接続できるようになっている。

通信

通信は2段階で行われる。まずペリフェラルが接続待ち状態になり自分自身を認識させるために単方向通信を定期的に行い続ける。このことをアドバタイズという。

セントラルは発信された情報をスキャンすることで周辺に存在するペリフェラルを認識する。 セントラルがペリフェラルに対して接続要求を出すと第2段階に移行する。 要求を受けたペリフェラルはアドバタイズを終了し、セントラルとの接続を確立する。 接続が確立したあとはデータチャンネルを通じてデータのやり取りが行われる。

このデータのやり取りのことをGATTというプロトコルを使って行う。 GATTにはServiceとCharacterristicという概念がある。 ServiceとはCharacteristicを包含しているラベルのようなもの。 Characteristicは属性が決められている。Read, Write, Notifyなどがある。セントラルはRead属性がないCharacteristicの内容を読むことはできず、Write属性がないCharacteristicの内容を書くことができない。 ペリフェラルは複数ののService, Characteristicを持つことができる。それぞれUUIDが振られており、セントラルはUUIDを指定してCharacteristicのデータ内容にアクセスする。

このやり方ではセントラルとペリフェラルで双方向でデータの通信ができ、「コネクトモード」と呼ばれる。 もう1つ「ブロードキャストモード」と呼ばれるものがある。これはペリフェラルがセンサーデータなどをアドバタイジングに乗せて送り、セントラルがそれを読むというもの。iBeacon等もコレ。

M5Stackで BLEのやり取りができる状態にする

BLEについてなんとなくわかったところでコードを書いてみる。 完成品はこちらにおいてある

https://github.com/masarusanjp/ble-sample-ios-and-m5stack

構成

基本的なことを実装をしながら確認しつつ最終的にはiOSで渡した文字列をM5Stackのディスプレイに表示することをゴールにする。

文字列を受け取るとlcdに表示するperipheralの実装

Peripheral側から実装していく。 M5Stackにはesp32というチップが載っておりこのチップ用のプログラムを書く。 ESP32のArduinoにはBLEモジュールが提供されている。 exampleやドキュメントへのリファレンスはgithubで公開されている。

https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE

※ 余談だが、エラーハンドリングについて書かれておらずちょっと困った。 swiftだと失敗する可能性も含めて型で表されているのでドキュメントがなくても困らないのだが。

まずはアドバタイズができるようにする。

#include <M5Stack.h>
#include <BLEServer.h>
#include <BLEDevice.h>

void setup() {
  setupM5Stack();
  setupBLE();
}

void loop() {
  delay(1000);
}

void setupM5Stack() {
  M5.begin();
  Serial.begin(115200);
}

void setupBLE() {
  Serial.println("Starting BLE");
  BLEDevice::init("my-peripheral");
  BLEServer *server = BLEDevice::createServer();
  BLEDevice::startAdvertising();
}

これで接続はできるはずだが、確認のために接続・非接続をシリアルポートに吐き出すようにしたい。 BLEServersetCallbacksBLEServerCallbacksを継承したクラスのポインタを渡すことで接続・非接続のイベントを受け取ることができる。

class ServerCallbacks: public BLEServerCallbacks {
public:
  virtual void onConnect(BLEServer* pServer) {
    Serial.println("connected");
  }
  virtual void onDisconnect(BLEServer pServer) {
    Serial.println("disconnected");
  }
};

void setupBLE() {
  ...
  BLEServer *server = BLEDevice::createServer();
  server->setCallbacks(new ServerCallbacks());
  ...
}

ここで正常に待ち受けが出来ているか確認する。 確認にはBLEScannerというアプリが便利。

BLE Scanner 4.0

BLE Scanner 4.0

  • Bluepixel Technologies LLP
  • 仕事効率化
  • 無料
apps.apple.com

BLEScannerを起動し、一覧からmy-peripheralconnectを選択すると以下の画面に遷移する。

f:id:masarusanjp:20201202213805p:plain

同時にシリアルモニタにもconnectedが出力された。この画面から離れると接続が切断されるようでシリアルモニタdisconnectedと出力された。

次にクライアントから文字列を受け取ってlcdに表示するようにする。 まずサービスとキャラクタリスティックを作成する。

BLEService *service = server->createService(serviceUUID);
BLECharacteristic *characteristic = service->createCharacteristic(
  characteristicUUID, // 予め生成しておいたUUID。ハードコーディングしてある
  BLECharacteristic::PROPERTY_READ |
  BLECharacteristic::PROPERTY_WRITE
);

BLECharacteristicもBLEServerと同様にsetCallbacksを持っている。このメソッドにBLECharacteristicCallbacksを継承したクラスのポインタを渡すことで、writeが発生した場合のイベントを受け取ることができる。

class CharacteristicCallbacks: public BLECharacteristicCallbacks {
public:
  virtual void onRead(BLECharacteristic* pCharacteristic) {
    Serial.println("read");
  }
  virtual void onWrite(BLECharacteristic* pCharacteristic) {
    Serial.println("write");
    std::string value = pCharacteristic->getValue();
    M5.Lcd.setCursor(10, 10);
    M5.Lcd.printf(value.c_str());
  }
};

...

void setupBLE() {
  ...
  characteristic->setCallbacks(new CharacteristicCallbacks());
  ...
}

onWriteのコールバック時にM5.Lcd.printfメソッドでlcdに渡されたテキストを表示している。 ここまででファームに書き込み、BLEScannerアプリから[CUSTOM SERVICE] > [Write, Read] > [Write Value] でテキストを書き込むと、書き込んだ内容がLcdに表示される。

最後にiOSからのスキャンをやりやすくする修正を先に加えておく。iOSにはスキャンを行う際にserviceのuuidで発見するperipheralを絞ることができる仕組みが用意されている。これを機能させるためアドバタイズにserviceのuuidを加え、アドバタイズを開始するようにする。

void setupBLE() {
  ...
  // BLEDevice::startAdvertising();
  BLEAdvertising *advertising = server->getAdvertising();
  advertising->addServiceUUID(service->getUUID());
  advertising->start();
}

iOSをセントラルとして実装する

iOS側の実装をしていく。 スキャンしたペリフェラルの一覧を表示し、選択をさせる。選択した先でテキストの入力をするとM5Stackのlcdにテキストが表示される。というアプリを作る。 LCDへの表示は前項で作ったM5Stack上のBLEServerのあるキャラクタリスティックに値を書き込めば行えるようになっている。

以下それぞれどういう処理を行うのか書いていくが、簡単のために相当はしょって書いてあるので、詳しくはgithubのコードを見てほしい。 ペリフェラルに接続するまでのクラスと、サービス、キャラクタリスティックを手に入れテキストを書き込むまでを責務としているクラスの2つに分けて作ってある。 ViewはSwiftUIで作ってあるが、トピック外なのでこちらもソースを参照してほしい。

https://github.com/masarusanjp/ble-sample-ios-and-m5stack/tree/main/ios-ble-sample/BLECentral/ble

まずはスキャンを行いペリフェラルのリストを手に入れる処理。

class BLESample: NSObject, CBCentralManagerDelegate {
    private var manager: CBCentralManager!
    override init() {
        super.init()
        manager = CBCentralManager(delegate: self, queue: nil)        
    }

    func scan() {
        // manager.state が .poweredOnでないと成功しないので、実際には.poweredOnになるのを待って実行する
        manager.scanForPeripherals(withServices: [CBUUID(string: serviceUUID), options: nil)
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        peripherals.append(peripheral)
    }
}

scanForPeripheralsの第一引数にはスキャン対象のサービスのUUIDを渡すことで見つける対象を絞ることができる(nilを渡すと絞り込みをせずにadvertiseしているものをすべて見つけてくる) リストが手に入るのでなく見つけたCBPeripheralが1つ1つ渡される。 stopScanを呼びスキャンを停止するまで見つける度にコールバックされる。

次に見つけたペリフェラルに接続をする。CBCentralManagerにconnectというメソッドがあり、これを利用する。

class BLESample: NSObject, CBCentralManagerDelegate {
    func connect(peripheral: CBPeripheral) {
        manager.conect(peripheral, options: nil)
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        // move to peripheral view
    }
}

ペリフェラルへの接続成功後はサービスを見つける。CBPeripheralのdiscoverServicesを呼び出すことで行うことができる。このメソッドは非同期でCBPeripheralのdelegateにコールバックされる。

func discoverServices(peripheral: CBPeripheral) {
    peripheral.delegate = self
    peripheral.disoverServices([CBUUID(string: serviceUUID)])
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    discoverCharacteristic(service: peripheral.services[0])
}

キャラクタリスティックもサービスと同様のフローで処理する。

func discoverCharacteristic(service: CBService) {
    peripheral.discoverCharacteristics(
        [CBUUID(string: BLESample.characteristicUUID)], 
        for: service
    )
}

func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    writeText(text: text, characteristic: service.characteristics[0])
}

最後にキャラクタリスティックに対してデータを書き込む

func writeText(text: String, characteristic: CBCharacteristic) {
    guard let data = text.data(using: .utf8) else {
        return
    }
    peripheral.writeValue(data, for: characteristic, type: .withResponse)
}

まとめ

BLEとは何か、どういう概念が存在するのかの基礎をざっくりと学び、M5StackとiOSで動作を確認した。 何か理解が違っているところがあれば指摘してください。

以下感想。 セントラル側もペリフェラル側も想像していたよりも実装のインターフェースがシンプルであることがわかったのが面白かった。 iOSもesp32の方も大きくハマることなくサクッと作れたし思った通りに動いてくれる。 (簡単なサンプルを実装しただけなので、実際にはもっとハマるのだと思うけど) 難しかったのはiOSで非同期処理をシリアルに呼ぶ必要があったところくらい。 コードを書く前にBLE自体には仕様を読み込んでいたことが良かったと思う。