技術メモ

プログラミングとか電子工作とか

LPCXpresso4337のCortex-M4/Cortex-M0マルチコアのサンプルプロジェクトをビルド+書き込みしてみる

お恥ずかしいことにLPC4337を使ったボードをよく開発しているにもかかわらず、マルチコア開発はしたことがありませんでした。
ただ最近になってマルチコアの需要がでてきた(とはいえ要求スペック的にはシングル処理でも間に合ってはいるんですが・・・)のでマルチコア開発の一歩としてサンプルプロジェクトをビルド+書き込みしてみます。

f:id:ysmn_deus:20200612183508j:plain

環境

前提としては

  • 使用するマイコンはLPC4337で、サンプルなのでボードはLPCXpresso4337を使用する
  • 上記につき、デバッガプローブはボード上にあるLPC-Link2相当のものをそのまま利用する
  • 開発IDEはMCUXpresso IDE v11.1.1

という感じです。
LPC-Link2を使ってもいいんですが、まぁ最初は軽い気持ちでやりたいので。

プロジェクトの作成

LPCOpenからインポート

NXP製のマイコンをいじるならいまだとLPCOpenを使って開発することが多いと思います。
ここでは郷に従ってLPCOpenからライブラリとサンプルプロジェクトをインポートします。

MCUXpressoのおそらく左下にある「Quickstart Panel」から「Import projetc(s) from file system...」を選択します。

f:id:ysmn_deus:20200612171407p:plain

出てくるウィンドウの「Project archive (zip)」から「Browse」でLPCOpenのzipファイルを選択します。

f:id:ysmn_deus:20200612171453p:plain

f:id:ysmn_deus:20200612171739p:plain

f:id:ysmn_deus:20200612171749p:plain

f:id:ysmn_deus:20200612171759p:plain

今回はマルチコアのLチカのみ利用するので、LPC43xxのチップ用のプロジェクト(lpc_chip_43xx(m0))とLPCXpressoボード情報のプロジェクト(lpc_board_nxp_lpcxpresso_4337(m0))とマルチコアのサンプルプロジェクト(multicore_mc_sa_blinky_m4(0app))の6プロジェクトをインポートします。

f:id:ysmn_deus:20200612172257p:plain

「Finish」を選択すればIDEに戻り、Project Explorerにプロジェクトが6つ増えていると思います。

f:id:ysmn_deus:20200612172411p:plain

プロジェクトの設定を調整する

チップとボードのプロジェクトには手直しが必要ありませんが、サンプルプロジェクトはそのままではビルドが通らないのでマルチコアの設定を行っていきます。
最初にCortex-M0側から設定します。

Cortex-M0側のプロジェクト(Slave)

メモリ設定

こちらから設定するのは、Cortex-M4のプロジェクトでこちらのビルド済みオブジェクトファイルを指定する必要があるためです。
まずはプロジェクト設定を開きます。

f:id:ysmn_deus:20200612172801p:plain

C/C++ Build」→「MCU settings」の箇所を修正します。
一見「Cortex-M0」などを選択されてるのでよさそうですが、メモリ設定が適応されていないようです。
設定をインポートするため、下の方にある「Import」でメモリ設定をインポートします。

f:id:ysmn_deus:20200612173025p:plain

インポートするファイルはデフォルトの設定でIDEをインストールしている場合は「C:\nxp\MCUXpressoIDE_11.1.1_3241\ide\Wizards\MemConfigs\NXP」にある「LPC43x7-M0_BankB.xml」を読み込みます。
(MCUXpressoIDE_11.1.1_3241の箇所はバージョン毎に違います。)
メモリの設定が写真の様に変わっていればたぶん大丈夫です。(Cortex-M0のプログラム領域はLPC43x7では0x1b000000からなのでよさそう)

f:id:ysmn_deus:20200612173401p:plain

「Apply」を選択して適応しておきます。

MCU Linkerの設定

あとはMCU Linkerの設定だけしておきます。
同じ様にプロジェクトの設定で「C/C++ Build」→「Settings」→「MCU Linker」→「Multicore」を参照します。

f:id:ysmn_deus:20200612173931p:plain

デフォルトでは「None」になっていると思いますが、「M0APP」にしておきます。
設定は以上なので「Apply and Close」で設定ウィンドウを閉じておきます。

以上の設定を行い、プロジェクトのビルドをしておきます。
プロジェクトを右クリックし、「Build Project」を選択します。

f:id:ysmn_deus:20200612174159p:plain

関連プロジェクトは勝手にビルドされると思います。
問題なければCortex-M0の方のプログラムのオブジェクトが「Debug」下に生成されているかと思います。

f:id:ysmn_deus:20200612174359p:plain

ここまで出来ればCortex-M0の方は終わりです。

Cortex-M4側のプロジェクト(Master)

基本的な動作としてはCortex-M4側のプログラムが起動し、Cortex-M0を起動するという流れなので、こちらのプロジェクトがメインになります。

メモリ設定

基本的にデフォルトの設定で問題ありません。
念のためCortex-M0に割り当てるFlashの領域が0x1b000000になってるかぐらいはチェックしましょう。
該当箇所は先ほどと同様にプロジェクトの設定を開いて「C/C++ Build」→「MCU settings」です。

f:id:ysmn_deus:20200612174705p:plain

MCU Linkerの設定

こちらも上記と同様に「C/C++ Build」→「Settings」→「MCU Linker」→「Multicore」を参照します。

f:id:ysmn_deus:20200612174856p:plain

先ほどとはちょっと表示が違います。
この「M0APP」にチェックを付け、残り二項目も設定します。

f:id:ysmn_deus:20200612175120p:plain

f:id:ysmn_deus:20200612175132p:plain

設定できたら「Apply and Close」で設定を適応させてウィンドウを閉じます。

Cortex-M4側のプロジェクトのビルド

プロジェクトのビルドはこちらから行います。
上記までの設定をしていれば、通常のビルドで問題無い筈です。プロジェクトを右クリックし、「Build Project」しましょう。
M0APP execute address differs from address provided in source imageと怒られる場合はCortex-M0側のメモリ設定ができていない可能性が高いです。

問題無くビルドができれば実際にデバッガを起動して書き込んでみます。
簡単な動作なのでGUI Flash Toolを使って書き込みだけするのでも良いと思います。

f:id:ysmn_deus:20200612175705p:plain

接続しているデバッガが出てくるので、該当するデバッガを選択してOKします。

f:id:ysmn_deus:20200612175916p:plain

とりあえずマルチコアデバッグもしたいのでJTAGがいいです。

f:id:ysmn_deus:20200612180045p:plain

以上でM4の方のデバッガが起動し、書き込みまで完了しています。
ここでM0のほうもマルチコアデバッグする場合は、デフォルトのデバッグ設定に一箇所設定を追加します。
Cortex-M0側のプロジェクトを右クリックして「Debug Configurations」を開きます。

f:id:ysmn_deus:20200612181713p:plain

開かれた時にはM4のデバッグ設定しかないと思いますので、「C/C++ (NXP Semiconductors) MCU Application」をダブルクリックします。

f:id:ysmn_deus:20200612182026p:plain

ダブルクリックするとプロジェクト名でデバッグ設定が新しく追加されます。

f:id:ysmn_deus:20200612182102p:plain

このままではデバッグできないので、「Search Project」から対象ファイルを選択します。

f:id:ysmn_deus:20200612182242p:plain

更に、「LinkServer Debugger」のタブで「LinkServer Options」の「Debug Cnnection」にある「Attach only」にチェックを付けておきます。

f:id:ysmn_deus:20200612182430p:plain

まず最初にCortex-M4側のデバッグを開始してから、デバッグを停止せずにCortex-M0のデバッグ(上記で設定したやつ)を開始します。
基本的にはメニューバーの虫ボタンをクリックすればデバッグできると思いますが、Cortex-M0側のデバッグの初回は表示されてないかもしれないので再度「Debug Condiguration」を開いてみてください。たぶん先ほど設定したものがありますので、そちらを選択して「Debug」すれば大丈夫だと思います。

Debugのウィンドウに2個スレッドが表示されていればマルチコアデバッグ出来てると思います。

f:id:ysmn_deus:20200612182935p:plain

リモートワーク蔓延る世の中になったので、改めてRedmineを導入してみる

f:id:ysmn_deus:20200503142752p:plain

学生の頃に団体でのタスクを管理するためにRedmineを導入していたのですが、正直活用しきれていなかったのとPassengerのエラーにおびえていたので今まで前向きにRedmineの活用を検討してきませんでした。
(正直作業も一人で完結することが多いので・・・)
しかし、昨今の情勢に有効活用できる対象の方々が増えてるんじゃないかと思い、防備録を兼ねて記事にしてみます。

そもそもRedmineとは?

もしかしたらRedmineを知らずに見て下さっている方の為にざっくりと説明しておきますと、Redmineとはブラウザ上で利用できるプロジェクト管理アプリケーションです。
具体的な説明などは公式サイトを一読して頂ければ有り難いです。

redmine.jp

デモなんかもあります

my.redmine.jp

どういう人におすすめ?

基本的に複数人で一つのプロジェクトを進める人は活用を検討するのが良さそうです。

一人での進捗管理にも使えるかもしれませんが、Redmine特有の癖というか運用に対して色々と学ばねばならない点がありますので、正直面倒だと思います。
(本記事では余り言及しない予定です)

導入

前提

基本的に

  • 複数人で使うので外部からアクセスできるサーバーにセットアップ
  • 小規模(2~10人程度?)のアクセスを想定

という想定でセットアップします。
小規模なので今回はAWSのLightsailを選定しました。
中規模や転送量が多い場合はさくらのVPSなどもいいかもしれませんが、外向けの転送量は1TBまでは込みで3.5ドル(初月は無料)なので試してみる分にはワンコイン以下で運用できると思います。

サーバーのセットアップ

LightsailにはBitnamiによるセットアップが可能となっています。
本来であればデータベースなどセットアップがいるんですが、この機能を利用することで基本的なセットアップに関しては何も考えなくてもよくなります。

※ ただし、2021年4月以降も含む長期運用まで考えている方はLightsailのBitnamiでセットアップされる環境がUbuntuの16.04なのでバックアップとリストアの方法も合わせて確認しておいた方が良いかもしれません。

Lightsailでインスタンスの作成

Lightsailのコンソールから「インスタンスの作成」からインスタンスの作成画面に映ります。

f:id:ysmn_deus:20200502183720p:plain

「設計図の選択」のところで「Redmine」を選択します。
ロケーションは日本の方を想定しているのでデフォルトの東京リージョンから変更してません。

f:id:ysmn_deus:20200502184215p:plain

初めてLightsailを利用する方はSSHキーペアが設定されていないと思いますので新規作成して下さい。

f:id:ysmn_deus:20200502184418p:plain

インスタンスプランは一番最小の3.5USDのプランを選択してます。
リソース名は分かりやすい名前を設定しておいて下さい。(よく分からない人はデフォルトの「Redmine-1」で問題無いかとおもいます。)

f:id:ysmn_deus:20200502184623p:plain

これで「インスタンスの作成」をすると、基本的なセットアップが全て完了します。
なんと便利な・・・

f:id:ysmn_deus:20200502185038p:plain

ホームに先ほど設定したリソース名のインスタンスが作成されていると思います。

IPの固定

今のままだとサーバーのIPが変わって鬱陶しいです。
IPを固定してドメインを割り当てる(割り当てなくても利用はできますが)場合にはIPを固定しておく必要があります。
LightsailでDNSゾーンを作成してドメインを割り当てる方法もありますが、今回DNSの設定は別途することとしてIPだけ固定します。

ホーム画面から作成したインスタンスの設定画面にいきます。

f:id:ysmn_deus:20200502190251p:plain

ネットワーキングのタブに移動します。

f:id:ysmn_deus:20200502190605p:plain

IPアドレス」と書いてある項目から「静的IPのアタッチ」を選択します。

f:id:ysmn_deus:20200502190905p:plain

他に静的IPのリソースが余っていない場合は「静的IPアドレスの作成」画面へ移動すると思います。
ロケーションはそのままで、インスタンスは先ほど設定したリソース名(ここでは「Redmine-1」)を選択して、「静的IPの指定」と書かれた項目のリソース名は分かりやすい名前を付けておきます。
画像では「StaticIp-1」となっていますが、「Redmine-1-StaticIp」などRedmine-1に利用している事を明示しておくのが良いと思います。

f:id:ysmn_deus:20200502191238p:plain

「作成」を選択すると静的IPが作成完了します。
とりあえずホームに戻り、先ほどと同様「Redmine-1」の設定画面の「ネットワーキング」を確認します。

f:id:ysmn_deus:20200502191521p:plain

画像のように「静的IPのデタッチ」が表示されていれば、IPの固定は完了しています。

IPへドメインを設定する

ドメインを何で運用しているかによりますが、基本的にDNSレコードにAレコードとして該当するIPを設定すると良いかと思います。
自分はRoute53を利用していますが、基本的になんでもいいとは思います。
(要望がありましたら記事化しますが、ここでは割愛します。)

基本的にHTTPSでの運用を考えてない人はココまでで問題ないかと思います。
(基本設定は公式サイトのドキュメントなどをご参照ください。)

redmine.jp

Redmineのセットアップ

管理者ユーザーでログイン

とりあえずHTTPS化したいのでその準備をします。
まずは管理ユーザでログインします。ドメインを指定した場合はそのドメインに、指定していない場合は http://[固定したIPアドレス]/ にアクセスして下さい。

f:id:ysmn_deus:20200503134438p:plain

たぶんこんな画面が表示されると思います。
右上の「ログイン」からログインします。
上記のようにBitnamiの機能でセットアップした場合は、ログインIDは「user」、パスワードはSSHでログインしたホームディレクトリにある"bitnami_application_password"というファイルに記載されているものを利用します。
Windows10であれば、コマンドプロンプトからSSHが使えますので、下記で取得可能です。
(キーペアはユーザーフォルダC:\Users\ユーザー名の直下の.sshというディレクトリに保存したとします。)

C:\Users\username> ssh bitnami@[設定したドメインor固定したIPアドレス] -i .\.ssh\[保存したキーペア].pem
$ cat bitnami_application_password

取得したパスワードでログインします。

f:id:ysmn_deus:20200503135614p:plain

ログインが完了すると、左上の項目に「管理」などが追加されています。
この「管理」を選択し、管理画面を表示します。

f:id:ysmn_deus:20200503135733p:plain

管理画面の「設定」を選択します。

f:id:ysmn_deus:20200503135944p:plain

この画面の「プロトコル」をHTTPからHTTPSにしておきます。

Bitnami HTTPS Configuration Toolを利用したHTTPS化

勿論この設定だけでHTTPS運用できるわけではないです。今回はLet's Encryptを活用したSSLを想定していますが、一応AWS Certificate ManagerとAmazon CloudFrontを利用した運用もできると思います。AWS信者の自分としては後者の方が安心安全な気がしますが、極力ミニマムに始めるという想定でLightsail上で完結する方法を採っています。

基本的なツールはなんと最初から入っており、一発で自動更新コマンドまで追加してくれます。
SSHでセットアップしたサーバーにログインしてコマンドを実行します。

sudo /opt/bitnami/bncert-tool

最初にドメイン名を聞かれるのでSSL化したいドメイン名を入力します。

Domain list []: redmine.hogefuga.com

次にwwwの扱いをどうするか(マルチドメインとして運用するのか否か)を聞かれるのでnでキャンセルしておきます。
(あんまりwww.redmine.hogefuga.comのような運用をしたい人はいないと思いますので)

The following domains were not included: www.hogefuga.com. Do you want to add them? [Y/n]: n

次にHTTPへのアクセスがあった場合にHTTPSにリダイレクトするか聞かれますので、yで許諾しておきます。

Enable HTTP to HTTPS redirection [Y/n]: y

次に設定のステップが表示され「これでええんか?」と聞かれますのでyで許諾しておきます。

Do you agree to these changes? [Y/n]: y

次に期限切れになりそうなときに通知が来るメールアドレスを登録しておきます。

E-mail address []: hoge@hogefuga.com

ここまで来たら基本の設定は完了です。最後に利用規約に同意するか否か聞かれるのでyで許諾します。

Do you agree to the Let's Encrypt Subscriber Agreement? [Y/n]: y

これでSSL化は完了です。
このツールは基本的に自動更新のコマンドも登録してくれています。一応確認する場合は、そのまま

crontab -e

とcronを確認するとコマンドが追加されていることが分かります。

オンラインでの外部とのやりとりが増えてきたので、GW中にチケット駆動など開発手法を学習しておきたいところです。
Redmineの運用に関してはド素人なので・・・(:3 」∠ )

3Dプリンタでマスクを作る「PITATT 3D print mask」を試してみた

普段はAirinum Urban Air Maskを利用しているのですが、正直フィルターが枯渇しそうです。
6月ぐらいに在庫復活する、と公式サイトに記載がありますがあやしいところ。
そこで、3Dプリンタで出力可能なマスク「PITATT 3D print mask」が公開されているという事で、これを試してみることに。

公式ウェブサイトからSTLファイル(インナーとアウターの2つのデータ)をダウンロードしてきて、スライサーで処理して出力するだけ!簡単!
と思っていましたが、アウターの出力がどうしてもサポート無しだと最後の方の積層で失敗するのでサポートありで出力しました。
(フィラメントは三菱ケミカルメディア Verbatimの白PLAを利用しました。今は白はないみたいです。)

f:id:ysmn_deus:20200418161431j:plain
実際に出力したマスク

このマスクは2個の部品に分かれており、インナーを外すことでフィルターを交換することができます。

f:id:ysmn_deus:20200418161609j:plain

フィルターはティッシュやガーゼを使って下さいとのこと。
この辺ならまだ手に入るのでありがたいです。

想像してたよりきっちり密着します。
相当試行錯誤したんではないでしょうか?頭が下がります。

医療従事者や専門職の方には不適だと思いますが、それ以外の方の応急対応としてはありだと思います。
コレを機に3Dプリンタを購入してみたいな~なんて思う人は、少なくとも150mm x 100mm x 100mm の造形エリアが確保できる3Dプリンタを購入する事をオススメします。
(最近は造形エリア広いのでだいたい大丈夫だと思いますが)

AWS Lambdaのローカル開発で環境変数を取り扱うとき(SAM テンプレート利用)

f:id:ysmn_deus:20190318182949p:plain

Lambdaで他サービスのAPIを度々利用したくなると思います。
(例えば、S3にアクセスしたり、DynamoDBからデータを取り出したり等)
ローカルでは権限を設定したIAMユーザーの認証情報を利用して実行し、運用時にはロールをLambdaに設定して走らせたいところです。
TypeScriptのwebpackでのコンパイル分岐の為にdotenvを利用しているので、そこに設定してもいいんですが、SAMのテンプレートに記述する方法もあるのでそちらをまとめておきます。

テンプレートを分ける

開発環境と運用環境などを分けたくなるので、テンプレートと環境変数ファイルを複数用意したいです。
なのでディレクトリ(もしくはファイル)で分けておきます。
具体的には

プロジェクトディレクト
├ sam/
│├ develop/ ←開発用ディレクト
││├ env.json
││└ template.yaml
│├ test/ ←自動テスト用ディレクト
││├ env.json
││└ template.yaml
│└ deploy/ ←運用版ディレクト
│ ├ env.json
│ └ template.yaml
├ built/ ←TypeScriptのコンパイル
├・・・

のようなイメージです。
(全てに環境変数が要るかは状況次第だとは思いますが)
これでとりあえず.gitignore**/env.jsonとでも追記しておけばリポジトリにヤバイ情報をアップロードしなくて済みそうです。

テンプレートの修正

ここで、SAMのテンプレートファイルを修正しておきます。
テンプレートのResources関数名PropertiesCodeUriは、テンプレートからみた相対パスなので、上記のディレクトリ構成を取っている場合は

~
    Properties:
      CodeUri: ../../built/
~

となります。

試しにローカル実行してみる

とりあえず色々動かしたのでローカルで実行してみます。
テンプレートを何個か使い分けるので、プロジェクトのルートディレクトリからテンプレートを明示して実行します。

sam local start-api -p 3001 -t sam/develop/template.yaml

今回はポート(3001)も明示しました。(よくReactで3000番は使ってるので)
とりあえず今は動けばOKです。

環境変数の定義

早速環境変数を設定していきます。
SAMでローカル実行する際に環境変数を使うには

  1. テンプレート上で変数を定義する
  2. 読み込むJSONを作成する

という手順が必要です。
コマンドですんなり入ってくれればいいんですが、テンプレート上で変数を定義するということをお忘れ無きよう。

テンプレート上で変数を定義する

テンプレートに変数の情報を書き込んでいきます。
環境変数の適応には2通りあり

  • Globalsに記述してResources全てで適応できる変数を定義する
  • Resourcesの関数毎に変数を定義する

のどちらかになります。
自分の記憶では、以前は両方を両立できたと思うんですが、現状どちらかを選べば片方の環境変数がうまく注入されないような気がします。
(例えば、Globalsに環境変数を定義すると、Resourcesの環境変数がうまく変更できない)
基本的には公式ドキュメントで例のある後者をオススメします。

Globalsに記述してResources全てで適応できる変数を定義する

テンプレートには下記の様に記載します。

~
Globals:
  Function:
    Runtime: nodejs10.x
    Timeout: 3
    Environment:
      Variables:
        GLOBAL_ENV: DUMMY
~

注入する環境変数JSONファイルは下記のように作成します。

{
  "Parameters": {
    "GLOBAL_ENV": "hoge"
  }
}

Resourcesの関数毎に変数を定義する

テンプレートには下記の様に記載します。

~
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../../built/
      Handler: development.lambdaHandler
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
      Environment:
        Variables:
          FUNC_ENV: DUMMY
~

注入する環境変数JSONファイルは下記のように作成します。

{
  "HelloWorldFunction": {
    "FUNC_ENV": "fuga"
  }
}

環境変数を注入して実行

上記の2通りどちらでも実行方法は同じです。

sam local start-api -p 3001 -t sam/develop/template.yaml -n sam/develop/env.json

上記のコマンドをpackage.jsonに書いておけば、簡単にテストできます。
自分はnpm-run-allを利用してSAMのローカルサーバーとwebpackのwatchを走らせてますのでpackage.jsonscripts

~
  "scripts": {
    "dev:localhost": "sam local start-api -p 3001 -t sam/develop/template.yaml -n sam/develop/env.json",
    "dev:watch": "webpack --watch",
    "dev": "run-p dev:*",
~

としておけば、yarn dev でSAMとwebpackが走ります。

AWS LambdaをTypeScriptで開発する

f:id:ysmn_deus:20190318182949p:plain

JavaScriptはオワコン!とまでは言いたくないんですが、やはり普段からTypeScriptを利用していると極力JavaScriptは避けたいところ。
ということで基本JavaScriptで開発されているであろうAWS LambdaのNode環境ですが、TypeScriptで開発を始めるまでをまとめておきます。
Serverless Frameworkを使えば比較的容易にTypeScript環境を構築できるようですが、またそれは別途試してみます。
今回はSAM CLIで対応します。
(デプロイはJavaScriptと変わらないので割愛します)

SAM開発環境を整える

SAM CLIのインストール

まずはSAM CLIをインストールする必要があります。
Dockerのインストールも必要になってきますが、基本的には公式ドキュメント(Installing the AWS SAM CLI)に一通り書いてあるのでそちらを参照して下さい。

最終的に

sam --version

がきちんと動作すれば問題ないと思います。

(補足)IAMユーザー情報の登録

AWS CLIを普段利用していない方などはIAMユーザーの認証情報をPCにセットアップする必要があります。
AWSを利用していてLambdaだけしか使わない人も珍しいと思いますので、詳細は割愛しますが公式ドキュメント(Setting Up AWS Credentials)AWS CLIを利用しない認証情報のセットアップも掲載されておりますのでご参照ください。
個人的にはAWS CLIをセットアップすることをオススメしますが。

SAM CLIでプロジェクトを作成する

SAMでプロジェクトを作成します。
とりあえずはHello World的なものを生成する所まではJavaScriptで対応します。

sam init --runtime nodejs10.x --name hello-sam

上記コマンドのオプションをとりあえず説明しておきますと、
--runtime:Lambdaの実行ランタイムを選択します。(python3.7とか)ホントはv12を使っていきたいんですが、一応安牌をとってv10で進めて行きます。
--name:プロジェクトのディレクトリ名です。Lambdaの関数名などは別途修正する必要があります。

上記コマンドを実行すると対話形式でウィザードが進みます。
とりあえず今回は1, 1をチョイスしました。

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
        1 - Hello World Example
        2 - Quick Start: From Scratch
        3 - Quick Start: Scheduled Events
        4 - Quick Start: S3
        5 - Quick Start: SNS
        6 - Quick Start: SQS
        7 - Quick Start: Web Backend
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: hello-sam
Runtime: nodejs10.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./hello-sam/README.md

S3などの処理を書きたいときはその辺を選んだりするのが良いんでしょう。
とりあえず今回は無難なチョイスを選択しましたが、ウィザードが完了すると--nameで指定したディレクトリにファイルが出力されていると思います。

テストを実行する

SAM CLIでセットアップすると、mocha+chaiがセットアップできるようになっています。とりあえず走らせる為にpackage.jsonの情報でセットアップします。

cd .\hello-sam\
cd .\hello-world\
yarn

ドキュメントではnpmコマンドを利用している例が多いですが普段はyarnを利用しているのでそのまま使います。
基本問題無いのでそのままテストを走らせます。

yarn test

yarn run v1.22.0
$ mocha tests/unit/


  Tests index
    √ verifies successful response


  1 passing (7ms)

Done in 0.34s.

問題無さそうです。

ローカルで関数を実行する

SAMを利用すればローカルで関数を実行するのは非常に簡単です。
一応sam local invokeで関数を実行する方法と、sam local start-apiで実行する2種類の方法がありますが、大体後者かと思いますので後者で動かしてみます。
(Dockerが起動してないと怒られますので起動しておきましょう)

cd ..
sam local start-api

問題が無ければブラウザでhttp://127.0.0.1:3000/helloを入力するか、curlコマンドでGETリクエストを投げると{"message":"hello world"}が返ってきます。
基本的にJavaScriptのファイル群はここで不要となりますので、hello-worldディレクトリはこの段階で削除してしまって構いません。

ちなみに、Dockerをセットアップしただけの状態だとC:\Users以下以外のディレクトリは権限がなくてマウントされないので、プロジェクトのディレクトリがC:\Users以下でない場合はsam local Error: Cannot find module 'app' docker toolboxのようなエラーが発生します。
対処法としましてはDokcer Toolboxでは下記のサイトを参考にさせていただきました。

coffee-nominagara.com

ToolboxでないDokcerでは警告が出てきちんと設定していた気がします。

TypeScript

tsconfig.jsonの作成

まずはプロジェクトのルートディレクトリにtsconfig.jsonを作っておきます。
特にこだわりのない人は

tsc --init

tsconfig.jsonを作成するのが良いと思います。
一応今回用意しているtsconfig.jsonは下記の通りにしました。

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "outDir": "./built",
    "allowJs": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "esModuleInterop": true
  },
  "include": [
    "./src/**/*"
  ],
    "exclude": ["node_modules"]
}

入力対象を./src/以下、出力先を./builtにしています。

パッケージを追加

TypeScriptを使っているので型が必要です。開発用の型をまずは追加していきます。

yarn add -D @types/aws-lambda

最小限のパッケージはこれだけでOKです。

index.tsを編集

index.tsを編集します。基本的にはデフォルトのapp.jsを踏襲して作成してみます。

import {APIGatewayEvent, Context} from "aws-lambda"

const lambdaHandler = async (event: APIGatewayEvent, context: Context) => {
    let response: any
    try {
        response = {
            'statusCode': 200,
            'body': JSON.stringify({
                message: 'hello ts world',
            })
        }
    } catch (err) {
        console.log(err)
        return err
    }

    return response
}

export {lambdaHandler}

とりあえずという意味ではeventcontextanyで受けてしまえばいいんですが、まぁそんぐらいは・・・
これでコンパイルしてみます。tsconfig.jsonを設定してるのでルートディレクトリでtscコマンドを実行します。

tsc

これでbuiltディレクトリが作成され、index.jsが生成されたかと思います。

template.yamlの修正

JavaScriptの時との差異はコンパイル後のファイル名ファイルの場所です。
これをtemplate.yamlに修正していきます。

~
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: built/
      Handler: index.lambdaHandler
~

CodeUriHandlerを修正しました。

ローカルで関数を実行する

これでローカル関数を実行してみます。

sam local start-api

JavaScriptの時と同様にブラウザかcurlコマンドで確認してみて下さい。
返ってくるのが{"message":"hello world"}のままの人はもしかするとsam buildして.aws-samディレクトリに履歴が残ってる可能性がありますので消しちゃって下さい。
無事{"message":"hello ts world"}が返ってきたら最低限度の環境構築はできたのではないでしょうか。

その他

ESLint + Prettier

基本的にはReactの設定と同じなのですが、ReactのJSXなどは不要です。
一応eslintrcなどを掲載しておきます。

パッケージの追加

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -D prettier @types/prettier @types/eslint-plugin-prettier eslint-config-prettier eslint-plugin-prettier

eslintrc.js

module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    sourceType: "module"
  },
  plugins: [
    'prettier',
    '@typescript-eslint'
  ],
  extends: [
    "eslint:recommended",
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint'
  ],
  rules: {
    'prettier/prettier': [
      'error',
      {
        'semi': false,
        'trailingComma': "all"
      }
    ]
  },
};

.eslintignore

node_modules/
*.config.js
built/

webpack

今回はwebpack+ts-loaderで構成します。

パッケージの追加

yarn add -D ts-loader webpack webpack-cli dotenv

今回は.env環境変数を管理する想定でdotenvを利用しています。
直で書いたりする場合はこの辺は必要無いと思います。
とりあえず.envを作っておきます。

NODE_ENV=development

webpack.config.js

const path = require("path")
const dotenv = require("dotenv")
dotenv.config()

const BUILT_PATH = path.resolve(__dirname, "./built")
const BUILD_VARIANT = process.env.NODE_ENV

module.exports = {
  target: "node",
  mode: BUILD_VARIANT === "production" ? "production" : "development",
  resolve: {
    extensions: [".ts", ".js"],
  },
  entry: "./src/index.ts",
  output: {
    filename: `${BUILD_VARIANT}.js`,
    path: BUILT_PATH,
    library: "[name]",
    libraryTarget: "commonjs2",
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: "ts-loader",
      },
    ],
  },
}

これで、ファイルを分割して開発することができます。

React Hook Form入門 その2(基本編)

f:id:ysmn_deus:20200224171811p:plain

前回の続きです。

基本的な使い方

バリデーションなどを無視するなら、基本的にはフォームの要素のref属性にuseFormから得られるregister関数を登録し、formのonSubmithandleSubmitを登録するだけで利用できます。
あえてフォームを作る順序を書くなら

  1. フォームの要素を書き出す
  2. フィールドを登録する
  3. スタイリング(ここでは取り扱わない)

という流れになるかと思います。最初から順にやっていきます。

フォームの要素を書き出す

基本的には普通のHTMLとなんら変わり無い要素をReactで表現します。

import React from "react"

export default function App() {
  return (
    <form>
      <input name="firstName" />
      <select name="gender">
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
      <input type="submit" />
    </form>
  )
}

f:id:ysmn_deus:20200224173934p:plain

非常にシンプルなフォームですが、今回はこれに機能を実装していきます。

フィールドを登録する

ここでは

  • input(name)
  • select(gender)

という2種類のフォームの要素があります。これらをReact Hook Formのフィールドに登録していきます。

import React from "react"
import { useForm } from "react-hook-form" // useFormをreact-hook-formからインポート

export default function App() {
  const { register, handleSubmit } = useForm() // register, handleSubmitをuseFormというHooksから得る
  const onSubmit = (data: any) => console.log(data) // フォームが送信されたときに実行される関数

  return (
    // form要素にhandleSubmitを渡す。なお、引数にはフォーム送信時に実行される関数を渡す。
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* input 要素にref属性を付与、registerを渡す*/}
      <input name="firstName" ref={register} />
      {/* select 要素にref属性を付与、registerを渡す*/}
      <select name="gender" ref={register}>
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
      <input type="submit" />
    </form>
  )
}

基本的にはこれだけでフォームが利用可能です。
フォームのデータを利用してなにがしかしたいときはonSubmitの中に処理を書いていきます。

心がTSに支配されてる方は(data: any)で発狂しそうかと思いますが、後で言及するので我慢して下さい。僕も発狂寸前なんです。

非同期処理

おそらくフォームの送信の後にはAPIと通信したりするので非同期処理が来ると思います。
上記のhandleSubmitの引数にはPromiseも渡せるので、async関数がそのまま渡せます。

import React from "react"
import { useForm } from "react-hook-form"

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

export default function App() {
  const { register, handleSubmit } = useForm()
  const onSubmit = async (data: any) => {
    console.log(data)
    await sleep(1000)
    console.log("complete.")
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <select name="gender" ref={register}>
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
      <input type="submit" />
    </form>
  )
}

バリデーションを適応する

基本的には上記までで良さそうなものですが、バリデーションぐらいはフロントでもやっておくれ、という気持ちが湧いてきます。
React Hooks FormはHTML 標準のフォームバリデーションのバリデーションには対応しているようです。
対応しているルールは公式サイトを閲覧してもらえればと思いますが、大雑把に言えば

などのルールを適応し、バリデーションに通らない場合は即座にエラーを得ることができるようです。

import React from "react"
import { useForm } from "react-hook-form"

export default function App() {
  const { register, handleSubmit, errors } = useForm()
  const onSubmit = (data: any) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true })} />
      {errors.firstName && "First name is required"}
      <input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} />
      {errors.lastName && "Last name is not mached [A-Za-z]"}
      <input name="age" type="number" ref={register({ min: 18, max: 99 })} />
      {errors.age && "age is in ragnge 18-99"}
      <input type="submit" />
    </form>
  )
}

errors.の後にname属性で決めたプロパティを参照するとバリデーションのルールを適応した可否が得られます。
上記の例ではboolean &&の形で、もしバリデーションが通ってなければ警告文を表示する様な形式にしています。
バリデーションに適合していなければ、送信してもonSubmitが実行されません。

UIライブラリの利用

一応方法としては

  1. 対応するUIライブラリ(material-uiなど)を利用する
  2. Controllerを利用してカスタム登録処理をする
  3. useEffectを利用する

の3種類ありますが、自分は基本的にmaterial-uiしか利用しないので上記2点にのみ言及します。

対応するUIライブラリを利用する

これが最も簡単です。material-uiを利用している人は、属性を追加するだけで利用できます。
まずはmaterial-uiをインストールします。

yarn add @material-ui/core

もうこれでフォームが作成可能です。
ソースを変更していきます。

import React from "react"
import { useForm } from "react-hook-form"
import TextField from "@material-ui/core/TextField"

export default function App() {
  const { register, handleSubmit } = useForm()
  const onSubmit = (data: any) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField inputRef={register} label="First name" name="firstName" />
      <input type="submit" />
    </form>
  )
}

これで、TextFieldコンポーネントに入力された値がonSubmitdataの中に放り込まれます。

Controllerを利用してカスタム登録処理をする

テキストフィールドやチェックボックスぐらいであれば上記の処理で問題無いんですが、ラジオボタンなどのフォーム要素が制御されたもの等を利用する場合は上記の用にはいきません。
例えば、ラジオボタンの例を出せば

import React, { useState } from "react"
import { useForm } from "react-hook-form"
import {
  TextField,
  FormControl,
  FormLabel,
  FormControlLabel,
  Radio,
  RadioGroup,
} from "@material-ui/core"

export default function App() {
  const { register, handleSubmit } = useForm()
  const onSubmit = (data: any) => console.log(data)
  const radioList: string[] = ["male", "female", "other"]
  const [value, setValue] = useState(radioList[0])
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue((event.target as HTMLInputElement).value)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField inputRef={register} label="First name" name="firstName" />
      <FormControl>
        <FormLabel>Gender</FormLabel>
        <RadioGroup name="gender1" value={value} onChange={handleChange}>
          {radioList.map(item => (
            <FormControlLabel
              key={item}
              value={item}
              control={<Radio />}
              label={item}
            />
          ))}
        </RadioGroup>
      </FormControl>
      <input type="submit" />
    </form>
  )
}

といった場合にはinputRefregisterを登録すればOKという訳にもいきません。
そこで、React Hook FormにはControllerというものが用意されています。

import React from "react"
import { useForm, Controller } from "react-hook-form"
import {
  TextField,
  FormLabel,
  FormControlLabel,
  Radio,
  RadioGroup,
} from "@material-ui/core"

const radioList: string[] = ["male", "female", "other"]

export default function App() {
  const { register, handleSubmit, control } = useForm()
  const onSubmit = (data: any) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField inputRef={register} label="First name" name="firstName" />
      {/* 上記と同じ、Controllerを利用してTextFieldを使い場合の例 */}
      <Controller
        name="firstName2"
        as={TextField}
        control={control}
        defaultValue=""
      />
      {/* FormControlのセクションを<div></div>と想定 */}
      <div>
        <FormLabel>Gender</FormLabel>
        <Controller
          name="radioGroup"
          as={
            <RadioGroup name="gender1">
              {radioList.map(item => (
                <FormControlLabel
                  key={item}
                  value={item}
                  control={<Radio />}
                  label={item}
                />
              ))}
            </RadioGroup>
          }
          control={control}
          defaultValue={radioList[0]}
        />
      </div>

      <input type="submit" />
    </form>
  )
}

やや複雑になりましたが、<FormControl></FormControl>の例と比べても、そこまで記述量が増えていないにもかかわらずフォームデータのやりとりができるのが確認できます。
なお、ここではしれっとdefaultValueを指定していますが、こいつを指定していないとMaterial-UIはコンパイル時にエラーを吐きます。
(たぶんa component is changing an uncontrolled radiogroup to be controlled react hook formみたいなやつ)
フォームの要素を記述する時にdefaultValue={}として値を指定するか、useFormの歳に引数に初期値を入力するかのどちらかで対応して下さい。

TypeScriptの対応

上記までは、onSubmitにany型をたたき込んだりとTypeScriptの良さを出し切れてない使い方でしたが、useFormに型を適応することでuseFormから得られるsetValueerrorsに型を適応することができます。

import * as React from "react"
import { useForm } from "react-hook-form"

// フォームにあるフィールドの型の定義
type FormData = {
  firstName: string
  lastName: string
}

export default function App() {
  const { register, setValue, handleSubmit, errors } = useForm<FormData>() // フィールドの型定義をuseFormに適応する
  // handleSubmitの引数にはフィールドの型定義が適応されている
  const onSubmit = handleSubmit(({ firstName, lastName }) => {
    console.log(firstName, lastName)
  })

  return (
    <form onSubmit={onSubmit}>
      <label>First Name</label>
      <input name="firstName" ref={register} />
      <label>Last Name</label>
      <input name="lastName" ref={register} />
      <button
        type="button"
        onClick={() => {
          // setValueにも型定義が適応されている
          setValue("lastName", "luo") // OK
          setValue("firstName", true) // コンパイルエラー
          errors.bill // errorsにも型定義が適応されているのでコンパイルエラー
        }}
      >
        SetValue
      </button>
    </form>
  )
}

React Hook Form入門 その1(導入編)

f:id:ysmn_deus:20200224171811p:plain

去年はNuxt.jsやNext.jsなど色々触ってたんですが、結局プレーンなReactが扱いやすいんじゃないかと思い色々試行錯誤しています。(Nextは結構良い感じだったんですが、Amplifyとの相性が悪くて不採用となりました)
基本的にはReact + Reduxの組み合わせが便利だと思い利用していたのですが、極力React Hooksを利用した方がシンプルな状態管理ができそうなので(きちんと理解してないのでレンダリングのタイミングでStateを変更してしまったり・・・)フォームはRedux FormからReact Hook Formに移行しようと思います。

この記事ではReact Hook Formに慣れるためにアプリケーションを作成しながら基本的な機能を確認していきます。

初期設定

Reactのプロジェクト作成(create-react-app)

プロジェクトを作成します。この辺は基本的なReactのプロジェクト作成と同じで、create-react-appを利用します。

create-react-app . --template typescript

(コマンドはプロジェクト用のディレクトリ内で実行してます。)

とりあえず実行

まずはプロジェクトとして問題無いことを確認しておきましょう。
基本的にパッケージマネージャーはyarnを利用します。

yarn start

f:id:ysmn_deus:20200224133318p:plain
いつもの

プロジェクトとしてはOKです。
一応ガッツリ開発するわけではないですが、ESLint + Prettierの設定なども個人的によく忘れるので行っておきます。

ESLint + Prettier

ESLintのインストール

まずはESLintをインストールしていきます。
TypeScriptでの開発を想定しているので、@typescript-eslint/parser@typescript-eslint/eslint-pluginも合わせてインストールします。

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

ESLintの設定

プロジェクトのルートディレクトリ(create-react-appした場所)に.eslintrc.jsを作成します。

module.exports = {
    env: {
        browser: true,
        node: true,
        es6: true
    },
    parser: '@typescript-eslint/parser',
    parserOptions: {
        ecmaFeatures: {
            jsx: true
        },
        sourceType: "module"
    },
    plugins: [
        "react",
        '@typescript-eslint'
    ],
    extends: [
        "eslint:recommended",
        "plugin:react/recommended",
        'plugin:@typescript-eslint/recommended',
    ],
    rules: {
    },
    overrides: [
        {
            'files': ["**/*.tsx"],
            'rules': {
                'react/prop-types': 'off'
            }
        }
    ]
};

とりあえず動けばいいのでこんなもんで。
詳しい説明とかは今回は省きます。

package.jsonにLint用のコマンドを記述します。

~
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "lint": "eslint --ext .ts,.js,.tsx,.jsx --ignore-path .gitignore ."
  },
~

ここまでできたら、試しに実行します。

yarn lint

errorとwarningが色々出てくると思いますが、eslintが動いてる証拠なので問題無いです。

Prettierのインストール

ESLintの設定ができたら次はPrettierをインストールします。
ESLintとの競合などにも対応するためeslint-config-prettiereslint-plugin-prettierも合わせてインストールしておきます。

yarn add -D prettier @types/prettier @types/eslint-plugin-prettier eslint-config-prettier eslint-plugin-prettier

Prettierの設定

基本的にESLintの補助機能として利用するため、ESLintの設定に追記します。

module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    sourceType: "module"
  },
  plugins: [
    'prettier',
    "react",
    '@typescript-eslint'
  ],
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint'
  ],
  rules: {
    'prettier/prettier': [
      'error',
      {
        'semi': false,
        'trailingComma': "all"
      }
    ]
  },
  overrides: [
    {
      'files': ["**/*.tsx"],
      'rules': {
        'react/prop-types': 'off'
      }
    }
  ]
};

基本的に上記の設定で動くとは思います。
試しに実行してみましょう。

yarn lint

errorやwarningがでれば問題ありません。
おそらくこの後保存されれば自動でlintが走るようにすると思いますが、lintの設定で勝手に整形するには--fixオプションを付けます。
試しに付けて実行します。

yarn lint --fix

大部分が勝手に修正されました。
ここで、直らないファイルで、errorのものは

  • App.test.tsx
  • serviceWorker.ts

の二種類かと思います。
今回テストは書かないので、App.test.tsxは削除して対応しますが、testexpectが未定義というエラーなので、おそらくeslint-plugin-jestとか入れれば直ると思います。
serviceWorker.tsに関しては、基本触らないファイルなので、該当箇所のみ無視する様に修正します。

~
        checkValidServiceWorker(swUrl, config) // eslint-disable-line @typescript-eslint/no-use-before-define
~
        registerValidSW(swUrl, config) // eslint-disable-line @typescript-eslint/no-use-before-define
~

対応後にもう一度lintを走らせます。

yarn lint

errorは消えてwarningだけになったかと思います。
warningも気になる人は対応してください。(serviceWorker.tsはファイルごと無視してもいいかもしれません。)

ESLint + Prettierのその他の設定

ファイルウォッチャーに登録する(保存したら勝手に処理する)のは、開発環境によって異なるので省きます。

TypeScript

tsconfig.json

incrementalの設定とexcludeの設定を追記しておきます。

~
    "jsx": "react",
    "baseUrl": "src",
    "incremental": true
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
~

React Hook Form

ようやく本題です。

インストール

インストールは至って簡単です。

yarn add react-hook-form

これで使用可能になります。

基本的な利用方法

試しにクイックスタートのサンプルを利用してみます。
基本的には書いてある通りでいいんですが、TypeScriptだとコンパイル時にエラーが出るので、余計なコメントなどを修正します。

import React from "react"
import { useForm } from "react-hook-form"

export default function App() {
  const { register, handleSubmit, watch, errors } = useForm()
  const onSubmit = (data: any) => {
    console.log(data)
  }

  console.log(watch("example"))

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="example" defaultValue="test" ref={register} />

      <input name="exampleRequired" ref={register({ required: true })} />
      {errors.exampleRequired && <span>This field is required</span>}

      <input type="submit" />
    </form>
  )
}

とりあえず使えるか実行して確認します。

yarn start

超シンプルですが、フォームが出てきました。

f:id:ysmn_deus:20200224144121p:plain

2個目の入力フォームが必須のようなので、適当に値を入れて「送信」を押します。

f:id:ysmn_deus:20200224144425p:plain

基本的にはformのonSubmituseFormというHookを利用して得たhandleSubmitという関数を通じて、handleSubmitに渡した関数がフォームのデータを引数に貰って実行されるようです。
また、watchという関数はフォームの変化を監視しているようで、testと記載されているフォームを編集するとconsole.log(watch('example'))が毎回発火します。

f:id:ysmn_deus:20200224144752p:plain

長くなったのでバリデーションとかはまた次回に。