It's my life
Recent posts 6 / 89
グラスワンダーちゃんを1着にさせてあげられなくてGitHubの草が生えない問題React Hook FormとGetform.ioを使って、お問い合わせフォームを作ろう!2021年の新しいデスクトップはこれだ!!Netlifyのビルド時間をGitHub Actionsで0時間にして月末のヒヤヒヤから解放されよう!Next.jsとVercelとRecoilとMaterial Tableを使ってAWSのステータスダッシュボードを作ってみた話MQTTと電子ペーパーを使って年賀状を作るSearchBox
Search my blog.年賀書きたくないマン
あけましておめでとうございます。昨年は大変お世話になりました。本年もどうぞよろしくお願いします。
さて、お世話になったみなさまに年賀状を送りたいのですが、私は年賀状を書くのが苦手です。
というより、まず手書き文字が苦手なのと、文面を考えるのが苦手なのと、郵便局に行くのがめんどくさいのと、滅多にポストをあけないのが原因なのですが、
ともあって今年も年賀状を締め切りまでに出すことができませんでした。
なんというダメ人間。
年賀状って以下の点が辛いんですね。
ということで、せっかくなのでこれらのお困りごとを解決するツールをさっそく作ってみることにしました。
今回の記事ですが、やたら~BOXというワードが出てきます。
大意はないのですが、筆者が年末にみたとあるネット記事がマイブームになってしまい、使わざるを得えませんでした。
読み苦しい限りですがおつきあいくださいませ。
年末年始という長期休暇は普段忙しくて出来ないアレコレを入れてあるやってみようBOXをあける日です。
仕事のことは考えてもしょうがないBOXです。
ということでこちらのツールですが先に技術選定からしていきます。
MQTTとはMessage Queue Telemetry Transportの略で、pub/subモデルという仕組みに基づいてつくられた軽量なメッセージプロトコルです。
MQTTの開発元であるIBMに良記事MQTT の基本知識がありましたので詳細は割愛しますが、
ヘッダーサイズが最小2byteと軽量なプロトコルであること、同一Topicに対して多対多の通信ができること、ネットワーク品質に応じたQOSを設定できること、ペイロード制約がほぼないことが受けて広くIoT分野で使われています。
ここまでお勉強してこなかったのはIoTに知見がなさすぎたことと、AWS IoTを使ってみたいという気持ちがある一方、まずはMQTTからお勉強しないとという変なこだわりがあったためです。
今回電子ペーパーを実装した端末がRaspberry Piということもあり、コア技術はMQTTで行くことにしました。
React Hooksも今更?感ありますが、Reactのお勉強はGatsby.jsでのブログ実装以来やっていないので、ちょっと動向に触れることにしました。
昔、使ってみようかと少し記事も見てみましたことがあったのですが、クラスコンポーネントの何が悪いのかイマイチよくわかってないので多分クラスコンポーネントで機能がとっ散らかるくらい大規模にReactを触ってないのであまり関心事にならなかったため、使ったことがありませんでした。
使わないにしても知らないと職がさすがに無くなりそうなのでお勉強することにしました。
TrySailのアルバムにTailwindというものがありましたが、Tailwind CSSもよく聞くので使ってみることにします。
軽い、という話を最初に聞き、使ってみたいと思ってましたが、必ずしもそういったこともなくUtility-Firstに共感できるWeb開発者に受けているんだろうな〜と重い腰が上がりませんでした。
が、こちらもそろそろ勉強しないといよいよ無職になるので勉強します。
ちなみに、Tailwindとは追い風という意味だそうです。
本当はM5Paperとかやりたかったのですが、電子ペーパーでなにか作ろうと思ったときにはスイッチサイエンスで売り切れていたのでAmazonで買える電子ペーパーでお安かったWaveshare 2.7inch E-Ink Screen Display HAT for Raspberry Piを購入することにしました。
色々選定時迷いましたが、一度にたくさんのことをやり過ぎるとやれなくてもやもやBOXに入ってしまうので元旦までに実装完了させるためにも、泣く泣くやらないことにしました。
いつかやるBOXだ。
RustのWAF
30秒くらい考えてこんな感じになりました。考えてもしょうがないBOXです。
フロントからAPIコールされるとバックエンドサーバーで画像生成し、ByteArrayに変換後、MQTTブローカーにPublishをします。ByteArrayがMQTTのペイロードに設定できるのはHTTPと比べて魅力ですねー。
MQTTブローカーでは受け取ったメッセージを同一TopicをSubscribeしているSubscriberに投げます。
ここでMQTT over WebSocketなどでTopicに流れるメッセージを待ち受けていれば、MQTT同様にSubscribeできます。
Subscriberではメッセージを受け取るとon_messageイベントが発生するため、受け取ったByteArrayから画像を再生し、電子ペーパーの制御モジュールに渡します。
これは考えてもしょうがないBOXなのかもしれませんが、React HooksでFileを使うの、難しくありません?たんに画像をBase64にしてaxiosでAPIコールしたいだけなのですが、
export const App: React.FC = () => {
const [selectedFileName, setSelectedFileName] = React.useState(null)
とinitial stateをnullにすると、
TS2345: Argument of type 'null' is not assignable to parameter of type 'File'.
という感じに、nullが許容できないですし、
<input type="file" accept="image/jpeg"
className="cursor-pointer relative block opacity-0 w-full h-full p-20 z-50"
onChange={async(e) => {
setSelectedFile(e.target.files)
}}/>
肝心のinputのonChangeでset関数呼ぼうとすると
TS2345: Argument of type 'FileList | null' is not assignable to parameter of type 'SetStateAction<File>'. Type 'null' is not assignable to type 'SetStateAction<File>'.
と言われてしまい、いざFileReaderでreadAsDataURLしようとすると
let b64str: string | ArrayBuffer | null = "";
let reader = new FileReader();
reader.readAsDataURL(file);
return new Promise((resolve, reject) => {
reader.onload = (e) => {
console.log(e.target)
b64str = e.target.result
resolve(b64str)
};
reader.onerror = (error) => {
console.log('Error: ', error);
return reject(error)
;
TS2531: Object is possibly 'null'.
という風にnull避けに苦戦しました。
結局の所、
export const App: React.FC = () => {
const initFile: File = new File([], "")
const [selectedFile, setSelectedFile] = React.useState(initFile);
という具合にinitial stateを空Fileオブジェクトにして、かついたるところに
reader.onload = (e) => {
console.log(e.target)
b64str = e.target !== null ? e.target.result : ''
resolve(b64str)
};
という具合に三項演算子でガードする羽目になりました。クラスコンポーネントでももちろん考えないとなのですが、型厳密おじさんではないので、stateの段階で型でFile | nullみたいなこと平気でしてでnull許容にしてしまうマンなのでこれは痛かったですね。
教えてReact博士!
どうしたら、楽にHooksが使えますか?
知らね。自分で考えろ
Webデザインというか、そういった素養がなさすぎて、むしろ私にはMaterial UIの出来きったデザインのもと実装できるCSSフレームワークのほうが使いやすかったです。
ほぼすべて、結局Tailwind ComponentからコピってReact向けに直して、ちょっと修正して、で作ったのでUtility-Firstの恩恵なしで時間ばかりかかってしまいました。
FastAPIの実装はスムーズでした。
APIを作るときもデコレーター一つで簡単実装です。
from fastapi import FastAPI
from pydantic import BaseModel
class Message(BaseModel):
title: str
message: str
image: str
name: str
app = FastAPI()
@app.post("/preview")
def preview(message: Message):
base64str = create_card_image_b64(message.title, message.message, message.image, message.name)
return {"image": base64str}
簡単なAPIを短時間で作らなければならないようなハッカソンや1dayスプリントなんかにはとてもいいのではないでしょうか?
癖があるとしたらstaticsファイルのホスティングに使う[StaticFiles(https://fastapi.tiangolo.com/tutorial/static-files/)というモジュールを使うときになぜか、追加でaiofilesというライブラリーが必要なことです。
デフォルトでいれないんかい。
とはいえ、しっかりエラーでModuleNotFoundエラーが出るのでそこまで困ることもないです。
公式ドキュメントに
First you need to install aiofiles
って書いてありました。すみません。
くっそ難しいのかな?と思いましたがpahoがいい感じにラップしていてくれるので、何のことはなかったです。
こちらもあっさりでした。
Dockerを使ったからか、ほぼ公式通りのconfigで通りました。
できあがったものの、ソースコードはこちらにあります。
https://github.com/tubone24/mqtt-nenga
一応EKSにのっけて公開しようかと悩みTerraformまで作ったのですが、誰もサービス使わないで年賀状が一通もこないのも悲しいので、Terraformの検証が終わったら壊しました。
というかEKSに載っけるなら最初からAWS IoTにすればよかったですね。
考えてもしょうがないBOXに入れておいて、自分のやるべきことをやるように考えていますね。
環境構築方法はREADMEをご参照くださいませ。
使い方としては、まずWebページに行きますと次のような入力フォームがあります。
名前、タイトル、メッセージと画像を入力フォームに入れていきます。Previewボタンを押すと相手に送信する年賀状のプレビューができます。
問題なければ、Submitします。
相手がTopicをSubscribeしていれば、このように電子ペーパーに年賀状が描画されます。