工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

リモート水位センサシステム ver4(1) --- 課題整理、センサ部構想検討 ---

概要

リモート水位センサシステムの2022年版構想についてまとめ、センサ部の設計を行った。



背景と目的

昨年、リモート水位センサシステムのバージョンアップを行い、ソーラー発電対応がうまくいった。今年も、バージョンアップを行いたい。本記事では、今年のバージョンアップ内容について整理する。



詳細

1. 課題整理

ここまでの稼働状況を鑑みて、課題と考えていることは以下。

  • 水位センサ部の構造と傷み
  • 基板周りが少し傷んできた

1.1 水位センサ部の構造

現行の水位センサ部は、通信基板を格納する本体とは別で、プラスチック製のモールにセンサを取り付け、鉢底ネットと洗濯ネットで囲いを作ってごみの侵入を防ぐ構造。別体とした理由は、取付位置を選びやすくするためだったが、田んぼに設置する際は、本体と別に取り付け作業が必要で、取り付け深さの調整機構をあまり考えていないので、都度現物合わせで調整が必要だった。

しかし、3年間の実績から、本体部の足元についていても問題ないこと、一緒のほうが当然設置が楽ということがわかった。

囲いを構成する鉢底ネットは、安さと手軽に手に入ることで選んだが、囲いを構成するほどの剛性があるものではないのでヤワな作りで少し不安だ。

そこで、今年はこのセンサ部をバージョンアップし、

  • 本体の足に取り付け可能で高さ調整もしやすい ‐ 囲いの強度も確保

を達成したいと考えている。

f:id:dekuo-03:20220306163746j:plain f:id:dekuo-03:20220306163751j:plain f:id:dekuo-03:20220306163754j:plain

1.2 基板周りが少し傷んできた

基板は、3年間同じものを使用してきたのだが、昨年本体の筐体内温度上昇を抑えるつもりで少し通気用の穴を確保したせいで、やはり多少のほこりや小さな虫がついたりして、あまりきれいな状態ではなくなった。また、もともと初めてver1を作ったときに実験的に部品をつけたり外したりした部分もあって、信頼性の面でも少し不安だ。

そこで、今年は基板自体を作り変えたい。

↓これはまだきれいな状態2021年の状態(参考) f:id:dekuo-03:20220306165023j:plain


2. センサ部の構想検討、設計

センサ部は、以下のイメージで作ることとした。従来の鉢底ネットによるカバーの代わりに、細長いケースを使用し、内側の側面にフロートセンサを固定する。ケースは、従来と同様のポリプロピレン製を使い、本体を支える足(PVC製の26mmパイプ)に固定することで、本体の設置と一緒に取付できる。また、従来よりだいぶしっかりした構造になることが期待できる。 さらに、このイメージ図には描いていないが、前面には洗濯ネットを設置しゴミの侵入を防ぐ。底面は大部分を切り欠いてオープンとすることで、従来と同じく泥がたまったままにならないように配慮。 大きさは、従来90mm程度の円柱だったのに対し、67mm×40mm程度と二回りくらいコンパクトにできる。 取り付けは、ケース裏側で金折れの穴にネジを通し、蝶ナットで締め付けるので、高さ調整は無段階でできる。

表側 f:id:dekuo-03:20220306175509p:plain

裏側 f:id:dekuo-03:20220306231907p:plain


まとめと今後の課題

2022年版のリモート水位センサシステムバージョンアップ構想をまとめた。次回は、水位センサ部の製作を行う。


水耕栽培の成長記録システム(2) --- 成長記録システムのハード組み立て ---

概要

成長記録システムのハードウェア組み立てを行った。



背景と目的

前回の構想で描いた撮影装置のハードウェア組み立てを行う。

https://cdn-ak.f.st-hatena.com/images/fotolife/d/dekuo-03/20220211/20220211205634.png



詳細

1. 台座とポール

材料は、とりあえずある程度硬い材質であれば何でもいいので、手持ちのシナ合板を使う。 台座は9mm厚、ポール部分は4mm厚合板を細長く切り出して中空の角柱とした。


2. Unit-CAM用筐体

こちらは、ちょうどいい大きさのものがないので、しばらく眠らせていた3Dプリンタを活用した。右の2つはUnit-CAMを収納する筐体。左の2本が、筐体をポールに固定するための棒。ステージをしっかり押さえなかったせいか少し歪んでしまった。

f:id:dekuo-03:20220224233802j:plain


3. 完成

製作途中の写真を撮り忘れたので、いきなり完成の写真。まあまあイメージ図通り。温湿度計は、少しカメラに近づけたかったので、段ボールで足を作って少し高さを稼いだ。ここはこだわらない。Unit-CAMへの配線類は、写真では見えないがポールの中を通して根元の裏側から外に出ようにしてある。

f:id:dekuo-03:20220224233523j:plain



まとめと今後の課題

ハードウェアの組み立てができた。次は、Unit-CAMがクラウドに写真を送れるようにソフトウェアを書き込む。


APIGatewayとLambdaを一緒に使うためのメモ

背景と目的

APIGatewayとLambdaを一緒に使う場合、設定項目がたくさん用意されていて自由度は高いのだが、いろいろありすぎてどんな時に何をすればいいかわからなくなる。公式ドキュメントを読まずともある程度のことが分かるようにメモしておく。



前提

  • APIGatewayに入ってきたリクエストを、APIGateway>統合リクエスト>マッピングテンプレートを用いてJSONに成型して、Lambdaの引数eventに引き渡す


APIGateway

設定

バイナリメディアタイプ

バイナリとして受け入れるContent-Typeを設定する。たとえば、application/octet-streamやimage/jpegなど。設定しておかないと、Unsupported Media Typeというレスポンスが帰ってきてしまう。ここで設定したら、統合リクエスト>マッピングテンプレートの$input.bodyにbase64エンコードされた文字列が入るようになる。


リソース


メソッドリクエス

編集中


統合リクエス

HTTPヘッダー

  • Lambdaの引数event.params.headerに渡すデータの設定ができる。
  • クエリパラメータ、パスパラメータ、リクエストヘッダーなどいろいろなものをマッピング可能。

マッピングテンプレート

リクエストのパススルー

  • そのままだと、リクエストボディがJSONであることを期待して、jsonパースしてLambdaの引数event.body-jsonに入れる。JSONパースできない場合、Lambdaに到達する前に怒られる。
Content-Typeがtext/plainの場合

以下のようにすれば文字列を入れられる。

"body-json": "$input.body"
Content-Typeがapplication/octet-streamなどのバイナリの場合

以下のようにすれば、base64エンコードされた文字列がLambdaに渡される。もちろん、先述のバイナリメディアタイプの設定が事前にされている必要がある。

"body-json": "$input.body"

統合レスポンス

統合レスポンスは、Lambdaからのレスポンス内容に応じて、メソッドレスポンスに伝える形式を定義する部分。おそらくエラー時の整形の時に一番気を使うと思われるので、エラー処理に絞ってメモする。

Lambda エラーの正規表現

まず、PythonランタイムのLambdaで、エラーが起きた時や以下のようにわざとエラーを起こしてみると、

raise Exception("設定した文字列")

Lambdaからのレスポンスとして、統合レスポンスに以下のようなJSONが伝わる。(自前でカスタム定義することも可能だが、特に何もしなければ)

{
    "errorMessage": "設定した文字列",
    "errorType": "エラータイプ",
    "stackTrace": [
        スタックトレース,
        :
    ]
}
  • Lambda エラーの正規表現は、errorMessageの中の文字列を捕まえる条件。この条件に当てはまる場合に、メソッドレスポンスのどのステータスコードに割り当てるかも設定する。

マッピングテンプレート

Lambda エラーの正規表現で捕まえたときに、適用される。

基本形式として、Empty、Errorが定義されているので、これを少し改造することで好きなものが作れる。

  • Empty

空のJSON

#set($inputRoot = $input.path('$'))
{}
  • Errorのとき

メッセージだけ。

#set($inputRoot = $input.path('$'))
{
  "message" : "foo"
}

$inputRootは、先述のLambdaからのエラー時のレスポンスが入っているので、

{
  "message" : "$inputRoot.errorMessage"
}

などとすれば、エラーメッセージだけ取り出せる。

メソッドレスポンス

編集中


Lambda

引数event

マッピングテンプレートの生成 = リクエストのパススルーのとき

{
    "body-json": "{ボディ}",
    "params": {
        "path": {
            "pathParameter": "value",
            :
        },
        "querystring": {
            "queryParameter": "value",
            :
        },
        "header": {
            "Accept": "*/*",
            "Accept-Encoding": "gzip, deflate, br",
            "Cache-Control": "no-cache",
            "CloudFront-Forwarded-Proto": "https",
            "CloudFront-Is-Desktop-Viewer": "true",
            "CloudFront-Is-Mobile-Viewer": "false",
            "CloudFront-Is-SmartTV-Viewer": "false",
            "CloudFront-Is-Tablet-Viewer": "false",
            "CloudFront-Viewer-Country": "US",
            "Content-Type": "{Content-Type}",
            "Host": "{api-id}.execute-api.ap-northeast-1.amazonaws.com",
            "User-Agent": "PostmanRuntime/7.29.0",
            "Via": "1.1 22512dca1de1fae848b2509fed0309aa.cloudfront.net (CloudFront)",
            "X-Amz-Cf-Id": "FXfKr5sbNKhgGGfyTwehKew20UNqXsoPp7xyDL-y8TqgAkwCnWr94w==",
            "X-Amzn-Trace-Id": "Root=1-6215f7c0-12c8260b2b11d6fe7f74d914",
            "X-Forwarded-For": "54.86.50.139, 130.176.137.78",
            "X-Forwarded-Port": "443",
            "X-Forwarded-Proto": "https"
        }
    },
    "stage-variables": {
        "type": "object",
        "description": "ステージ変数"
    },
    "context": {
        "account-id": "",
        "api-id": "{api-id}",
        "api-key": "",
        "authorizer-principal-id": "",
        "caller": "",
        "cognito-authentication-provider": "",
        "cognito-authentication-type": "",
        "cognito-identity-id": "",
        "cognito-identity-pool-id": "",
        "http-method": "POST",
        "stage": "{デプロイ先ステージ}",
        "source-ip": "{アクセス元IPアドレス}",
        "user": "",
        "user-agent": "********************",
        "user-arn": "",
        "request-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "resource-id": "{リソースID}",
        "resource-path": "{リソースパス}"
    }
}

event.params.header

  • リクエストヘッダーは、何もしなければそのままここに入る。
  • APIGateway>統合リクエスト>HTTPヘッダーで対応を定義すると、別のヘッダー名に変えて追加することもできる。
  • クエリパラメータや、

LambdaでCognitoユーザーの情報を取り出す

APIGatewayのオーソライザーをCognitoのユーザープールと連携させた場合、Authorizationヘッダに入っているトークンからCognitoのユーザー情報を取り出すことができる。※event["context"]["user"]などは空になっているので取り出せないので、この方法で取り出すことになる。

# Authorizationヘッダの一部を取り出して、base64デコード、JSONパースする
authorization = event["params"]["header"]["Authorization"].split(".")
paddingLength = len(authorization[1]) % 4
for _ in range(paddingLength):
    authorization[1] += "="
cognitoData = json.loads(base64.b64decode(authorization[1]).decode("utf-8"))
# 出力
print(f'email={cognitoData["email"]}, username={cognitoData["cognito:username"]}')

event.body-json

  • APIGateway>統合リクエスト>マッピングテンプレートで、リクエストのパススルーを選んだ場合、リクエストボディがそのまま入るが、JSONである必要がある。
  • JSON以外の場合は、APIGateway>統合リクエスト>マッピングテンプレートを参照。

event.stage-variables

APIGatewayのステージ変数で定義した値が入る。

{
    "stage-variables": {
        "abc": "value",
        :
    }
}


CORS

Lambdaプロキシ統合のとき

APIGatewayの統合レスポンスをいじれないので、Lambdaの戻り値に以下を付加する。

{'headers':
{
'Access-Control-Allow-Origin': '*'
}
}