<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
  <title>IoT カテゴリ | フューチャー技術ブログ</title>
  <subtitle>IoT カテゴリの記事一覧</subtitle>
  <icon>https://future-architect.github.io/feed_icon.png</icon>
  <logo>https://future-architect.github.io/apple-touch-icon.png</logo>
  <webfeeds:icon>https://future-architect.github.io/apple-touch-icon.png</webfeeds:icon>
  <webfeeds:accentColor>258fb8</webfeeds:accentColor>
  <link href="https://future-architect.github.io/categories/IoT/atom.xml" rel="self"/>
  <link href="https://future-architect.github.io/categories/IoT/"/>
  <updated>2026-01-13T15:00:00.000Z</updated>
  <id>https://future-architect.github.io/categories/IoT/</id>
  <generator uri="https://hexo.io/">Hexo</generator>
  <entry>
    <title>EDIFACTメッセージ処理をJavaでスクラッチ実装した試行錯誤</title>
    <link href="https://future-architect.github.io/articles/20260114a/"/>
    <id>https://future-architect.github.io/articles/20260114a/</id>
    <published>2026-01-13T15:00:00.000Z</published>
    <updated>2026-01-13T15:00:00.000Z</updated>
    <author><name>辻大志郎</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>製造・エネルギーグループの辻です。Web系のシステム開発やデータ連携においてデータフォーマットにJSONを利用することは一般的ですが、製造系の基幹システムとのデータ連携においてEDIFACTを利用するケースがあります。EDIFACTはあまり馴染みのない、あるいは聞きなれないデータフォーマットかと思いますが、製造業あるいは他業種においても歴史あるデータフォーマットです。またEDIFACTデータを処理するにあたってはエンタープライズ向けのETL製品やEDIミドルウェアが導入されているケースがよくあると思います。ただ、今回、EDIFACTを扱うJavaアプリケーションをスクラッチ開発するニーズがありました。</p>
<p>そこで本記事では、まずEDIFACTとは何か？ということを簡単に触れたのち、JavaでEDIFACTデータを処理するためのライブラリ選定や実装アプローチの概要を紹介します。</p>
<h2 id="そもそもEDIFACTとは？">そもそもEDIFACTとは？</h2><p>EDIFACTとは、国連が定めた電子データ交換の国際標準規格です。正式名称はUN&#x2F;EDIFACT（the United Nations rules for Elec­tronic Data Interchange for Administration, Commerce and Transport）<sup id="fnref:3">1</sup>と呼ばれます。テキストベースのデータ構造ですが、JSONのようにキー名を持たず、位置と順序でデータの意味が決まります。<code>&#39;</code> 、 <code>+</code> 、 <code>:</code> などの文字を用いて文字列を区切り、メッセージ仕様に基づいて階層構造やデータの詳細がわかるデータフォーマットです。</p>
<p>EDIFACTのサンプルデータを以下に記載しました。なお、実際の伝送データは改行されていませんが、可視性向上のために改行しています。</p>
<figure class="highlight text"><figcaption><span>EDIFACTサンプルデータ</span></figcaption><table><tr><td class="code"><pre><span class="line">UNB+UNOA:2+SENDER_ID+RECEIVER_ID+251228:1500+1234&#x27;</span><br><span class="line">UNH+ME001+ORDERS:D:96A:UN&#x27;</span><br><span class="line">BGM+220+PO12345+9&#x27;</span><br><span class="line">DTM+137:20251228:102&#x27;</span><br><span class="line">NAD+BY+BUYER_CODE::92&#x27;</span><br><span class="line">NAD+SE+SELLER_CODE::92&#x27;</span><br><span class="line">LIN+1+1+PRODUCT_A:EN&#x27;</span><br><span class="line">QTY+21:100:PCE&#x27;</span><br><span class="line">PRI+AAA:500::NTP&#x27;</span><br><span class="line">LIN+2+1+PRODUCT_B:EN&#x27;</span><br><span class="line">QTY+21:50:PCE&#x27;</span><br><span class="line">PRI+AAA:1200::NTP&#x27;</span><br><span class="line">UNS+S&#x27;</span><br><span class="line">CNT+2:2&#x27;</span><br><span class="line">UNT+14+ME001&#x27;</span><br><span class="line">UNZ+1+1234&#x27;</span><br></pre></td></tr></table></figure>

<p><code>UNB</code> や <code>UNH</code>、 <code>DTM</code> といった謎の単語（EDIFACTの用語でセグメントと呼びます）と付随する文字列が現れました。何を意味しているか一見、理解不能のように見えます。</p>
<p>ところが、JSONフォーマットに変換するとなんとなくイメージがわく方も多いのではないでしょうか？以下は先ほどのEDIFACTメッセージを擬似的にJSONに変換したデータです。</p>
<figure class="highlight json"><figcaption><span>EDIFACTサンプルデータ（疑似変換）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;interchangeHeader&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;senderId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SENDER_ID&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;receiverId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;RECEIVER_ID&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-12-28T15:00:00&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;controlReference&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1234&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;syntaxIdentifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;UNOA:2&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;messageBody&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;messageHeader&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;messageType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ORDERS&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;D:96A:UN&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;messageReferenceNumber&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ME001&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;documentDetails&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;documentNameCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;220&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;documentNumber&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PO12345&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;messageFunctionCode&quot;</span><span class="punctuation">:</span> <span class="string">&quot;9&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;documentDate&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2025-12-28&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;parties&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;BUYER&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;BUYER_CODE&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;codeListQualifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;92&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;role&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELLER&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SELLER_CODE&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;codeListQualifier&quot;</span><span class="punctuation">:</span> <span class="string">&quot;92&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;lineItems&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;lineNumber&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;productId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRODUCT_A&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;quantity&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="number">100</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;unit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PCE&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;amount&quot;</span><span class="punctuation">:</span> <span class="number">500</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;NetPrice&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;lineNumber&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;productId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PRODUCT_B&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;quantity&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;value&quot;</span><span class="punctuation">:</span> <span class="number">50</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;unit&quot;</span><span class="punctuation">:</span> <span class="string">&quot;PCE&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;price&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="attr">&quot;amount&quot;</span><span class="punctuation">:</span> <span class="number">1200</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;NetPrice&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;summary&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;totalLineCount&quot;</span><span class="punctuation">:</span> <span class="number">2</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>EDIFACTの文字列を、上記のように階層構造に変換できる理由は、メッセージタイプ（業務プロセスごとに定義されたデータ構造の仕様）により構造が定義されているためです。上記で紹介した例は、UN&#x2F;EDIFACTの <code>ORDERS</code> （発注）というメッセージタイプに基づくサンプルメッセージです。</p>
<p>https://service.unece.org/trade/untdid/d96a/trmd/orders_t.htm</p>
<p>その他にも <code>DESADV</code>（出荷通知）、<code>INVOIC</code>（請求）など、様々な業務プロセスに対応するメッセージタイプが定義されています。それぞれのメッセージタイプごとに、どのセグメントを、どの順序で、何回繰り返すかというデータ構造が定義されています。</p>
<p>ただ、実務上はUN&#x2F;EDIFACTの国際標準をベースにしつつも、業界ごとに独自の規約（サブセット）を定義して運用することが一般的です。たとえば自動車業界では JAMA（日本自動車工業会） <sup id="fnref:1">2</sup> がEDIFACT導入ガイドラインを制定しています。さらに、業界内でも個社ごとに仕様がカスタマイズされているケースは少なくありません。</p>
<h2 id="JavaのEDIFACTライブラリ選定">JavaのEDIFACTライブラリ選定</h2><p>EDIFACTの概要を紹介しました。次は、EDIFACTメッセージをJavaアプリケーションで処理するにあたって、ベースとなるライブラリ選定プロセスを紹介します。Javaライブラリをざっくりと調査&amp;簡易検証した結果、候補として以下の2つに絞られました（ただ調査&amp;簡易検証範囲は限定的です。他にもこのライブラリが便利だよ、という知見をお持ちの方、こっそりと教えて下さい）。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>案</th>
<th>ライブラリ名</th>
<th>リポジトリ</th>
<th>特徴</th>
</tr>
</thead>
<tbody><tr>
<td>案1</td>
<td>Smooks</td>
<td>https://github.com/smooks/smooks</td>
<td>・高機能なデータ変換フレームワーク<br>・EDIFACTメッセージの仕様を定義したXMLファイルを用意することで、メッセージのパースとBeanへデータマッピングが可能</td>
</tr>
<tr>
<td>案2</td>
<td>Staedi</td>
<td>https://github.com/xlate/staedi</td>
<td>・ストリームベースの軽量EDIパーサ<br>・処理フローをアプリケーションコードで制御する必要あり<br>・Beanへのマッピング機能はない</td>
</tr>
</tbody></table></div>
<p>結論としては、案2のStaediを採用しました。当初、案1のSmooksを主として検討していましたが、EDIFACTメッセージの定義ファイルを用意することが困難であると判明しました。UN&#x2F;EDIFACTの標準規格の定義ファイル自体はSmooksの標準機能として提供されており、当初はよさそうに見えていました<sup id="fnref:2">3</sup>。</p>
<p>しかし、実務上は個社別にカスタマイズされているEDIFACTの定義ファイルを用意する必要がありました。いくつか試行錯誤しましたが、この個社別にカスタマイズされている定義ファイルを作成することが困難であるとわかりました。これがノックアウトファクターとなり、代替案であるStaediを採用することにしました。</p>
<h2 id="実装アプローチ">実装アプローチ</h2><p>Staediは、EDIFACTデータの読み書きに特化したストリームベースのパーサライブラリです。メッセージ構造の解釈や処理フローはアプリケーションコードに委ねられています。実装方針は大きく2通り考えられました。</p>
<ol>
<li>StaediのREADMEにあるような、イベント駆動（ステートマシン）的なアプローチ</li>
<li>全データをメモリ上のツリー構造として扱う、構文解析的なアプローチ</li>
</ol>
<p>今回は2の方針を採用しました。実務で採用されている複雑なEDIFACT仕様にそって処理することを考えたとき、主に以下の理由から2のアプローチのほうがコードの保守性が高いと判断したためです。</p>
<ul>
<li>コードの構造からメッセージ仕様を直接的に読み取れる</li>
<li>入力データの並び順とシステムが必要とする出力データの順序が異なる場合でも、ツリー構造であれば柔軟にデータを取り出せる</li>
<li>構文木としてデバッグできるため、構造定義とデータ抽出定義のどちらに問題があるかを特定しやすい</li>
</ul>
<p>2のアプローチではEDIFACTのセグメントを一度JSONノードのリストとして展開し、そのリストをカーソルを使って走査しながら、階層構造を構築します。EDIFACTのメッセージは <code>&#39;</code> や <code>+</code> や <code>:</code> などの文字により区切られていますが、このような字句解析はStaediにまかせます。字句解析結果のJSONノードをアプリケーションコードで構造解析し、ビジネスロジックで必要なデータを抽出します（なお、本記事ではデータの抽出部分は割愛します）。</p>
<p><em>Java処理フロー概要</em><br><img fetchpriority="high" src="/images/2026/20260114a/Java処理フロー概要.jpg" alt="Java処理フロー概要.jpg" width="1200" height="371"></p>
<h3 id="StaediとJacksonを用いたセグメントのリスト化">StaediとJacksonを用いたセグメントのリスト化</h3><p>まず、Staediの <code>EDIStreamReader</code> とJacksonの <code>ObjectMapper</code> を組み合わせて、EDIFACTメッセージを <code>List&lt;JsonNode&gt;</code> に変換します。EDIFACTの各セグメントを扱いやすいJSONオブジェクトのリストにします。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">ObjectMapper</span> <span class="variable">MAPPER</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">/***</span></span><br><span class="line"><span class="comment"> * EDIFACTデータを読み込み、セグメントのリストを取得する.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> edifactStr EDIFACT形式の文字列データ</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IOException 入出力例外</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> セグメントのリスト</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> List&lt;JsonNode&gt; <span class="title function_">readSegments</span><span class="params">(String edifactStr)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="type">var</span> <span class="variable">factory</span> <span class="operator">=</span> EDIInputFactory.newFactory();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 実データは標準仕様と細部の違いがあるため</span></span><br><span class="line">    factory.setProperty(EDIInputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, <span class="literal">false</span>);</span><br><span class="line">    <span class="type">var</span> <span class="variable">stream</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(edifactStr.getBytes(StandardCharsets.UTF_8));</span><br><span class="line"></span><br><span class="line">    JsonNode rootNode;</span><br><span class="line">    <span class="keyword">try</span> (<span class="type">var</span> <span class="variable">ediReader</span> <span class="operator">=</span> factory.createEDIStreamReader(stream);</span><br><span class="line">         <span class="type">var</span> <span class="variable">jsonParser</span> <span class="operator">=</span> factory.createJsonParser(ediReader, JsonParser.class)) &#123;</span><br><span class="line">        <span class="comment">// StaediのReaderをJacksonのParserとして扱う</span></span><br><span class="line">        rootNode = MAPPER.readValue(jsonParser, JsonNode.class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 各セグメントをJsonノードのリストとして取得</span></span><br><span class="line">    List&lt;JsonNode&gt; segmentList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">for</span> (JsonNode node : rootNode.path(<span class="string">&quot;data&quot;</span>)) &#123;</span><br><span class="line">        segmentList.add(node);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> segmentList;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="カーソルの用意">カーソルの用意</h3><p>次に現在どのセグメントを参照していているか？を管理するカーソルクラスを用意します。パース処理中に、次のセグメントが <code>UNH</code> メッセージであれば <code>UNH</code> メッセージ処理をする、といった先読みをできるようにしています。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * EDIFACTデータのカーソル操作クラス.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EdifactCursor</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;JsonNode&gt; nodes;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">position</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * コンストラクタ.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nodes EDIFACTセグメントのリスト</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">EdifactCursor</span><span class="params">(List&lt;JsonNode&gt; nodes)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.nodes = nodes;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 現在位置のセグメント名を確認.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> セグメント名</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">peek</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (position &gt;= nodes.size()) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">return</span> nodes.get(position).path(<span class="string">&quot;name&quot;</span>).asText();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 指定したタグであれば取得して位置を進める.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> expectedTag 期待するセグメント名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> JsonNode</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> JsonNode <span class="title function_">match</span><span class="params">(String expectedTag)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (expectedTag.equals(peek())) &#123;</span><br><span class="line">            <span class="keyword">return</span> nodes.get(position++);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="パース処理">パース処理</h3><p>カーソルを用いて、メッセージをパースする処理を実装します。複数回登場しうるセグメント（<code>NAD</code>、<code>LIN</code> など）は先読みしながらループを回して処理します。コードの構造から、セグメントの流れ（<code>UNH</code> の次に <code>BGM</code> が登場するなど）や登場回数（1回なのか複数回）が、わかることが嬉しいポイントです。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * ツリー構造のノードとなるグループクラス</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EdifactGroup</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;JsonNode&gt; segments = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;EdifactGroup&gt; children = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * コンストラクタ.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name グループ名</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">EdifactGroup</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * ルートのパース処理.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> cursor EDIFACTカーソル</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> EdifactGroup <span class="title function_">parseInterchange</span><span class="params">(EdifactCursor cursor)</span> &#123;</span><br><span class="line">        <span class="type">EdifactGroup</span> <span class="variable">interchange</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EdifactGroup</span>(<span class="string">&quot;Interchange&quot;</span>)</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;UNB&quot;</span>));</span><br><span class="line">        <span class="keyword">while</span> (<span class="string">&quot;UNH&quot;</span>.equals(cursor.peek())) &#123;</span><br><span class="line">            interchange.addChild(parseMessage(cursor));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> interchange.addSegment(cursor.match(<span class="string">&quot;UNZ&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Message単位のパース.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> cursor EDIFACTカーソル</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> EdifactGroup <span class="title function_">parseMessage</span><span class="params">(EdifactCursor cursor)</span> &#123;</span><br><span class="line">        <span class="type">EdifactGroup</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EdifactGroup</span>(<span class="string">&quot;Message&quot;</span>)</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;UNH&quot;</span>))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;BGM&quot;</span>))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;DTM&quot;</span>));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span> (<span class="string">&quot;NAD&quot;</span>.equals(cursor.peek())) &#123;</span><br><span class="line">            message.addSegment(cursor.match(<span class="string">&quot;NAD&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> message.addChild(parseDetailSection(cursor))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;UNS&quot;</span>))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;CNT&quot;</span>))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;UNT&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 明細行単体のパース.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> cursor EDIFACTカーソル</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 明細行グループ</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> EdifactGroup <span class="title function_">parseLineItem</span><span class="params">(EdifactCursor cursor)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">EdifactGroup</span>(<span class="string">&quot;LineItem&quot;</span>)</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;LIN&quot;</span>))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;QTY&quot;</span>))</span><br><span class="line">                .addSegment(cursor.match(<span class="string">&quot;PRI&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 明細セクションのパース.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> cursor EDIFACTカーソル</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 明細セクショングループ</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> EdifactGroup <span class="title function_">parseDetailSection</span><span class="params">(EdifactCursor cursor)</span> &#123;</span><br><span class="line">        <span class="type">EdifactGroup</span> <span class="variable">detail</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EdifactGroup</span>(<span class="string">&quot;Detail&quot;</span>);</span><br><span class="line">        <span class="keyword">while</span> (<span class="string">&quot;LIN&quot;</span>.equals(cursor.peek())) &#123;</span><br><span class="line">            detail.addChild(parseLineItem(cursor));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> detail;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * セグメント追加.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> seg 追加するセグメント</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 追加したセグメント</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> EdifactGroup <span class="title function_">addSegment</span><span class="params">(JsonNode seg)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (seg != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="built_in">this</span>.segments.add(seg);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 子グループ追加.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> group 追加する子グループ</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 追加した子グループ</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> EdifactGroup <span class="title function_">addChild</span><span class="params">(EdifactGroup group)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (group != <span class="literal">null</span> &amp;&amp; !group.isEmpty()) &#123;</span><br><span class="line">            <span class="built_in">this</span>.children.add(group);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> segments.isEmpty() &amp;&amp; children.isEmpty();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>実行クラスや <code>toString()</code> などの実装は省略しますが、これらを組み合わせると、以下のようにEDIFACTメッセージを構造化して扱えます。</p>
<figure class="highlight txt"><figcaption><span>構造解析結果</span></figcaption><table><tr><td class="code"><pre><span class="line">[Interchange]</span><br><span class="line">  UNB : UNOA:2 + SENDER_ID + RECEIVER_ID + 251228:1500 + 1234</span><br><span class="line">  [Message]</span><br><span class="line">    UNH : ME001 + ORDERS:D:96A:UN</span><br><span class="line">    BGM : 220 + PO12345 + 9</span><br><span class="line">    DTM : 137:20251228:102</span><br><span class="line">    NAD : BY + BUYER_CODE::92</span><br><span class="line">    NAD : SE + SELLER_CODE::92</span><br><span class="line">    UNS : S</span><br><span class="line">    CNT : 2:2</span><br><span class="line">    [Detail]</span><br><span class="line">      [LineItem]</span><br><span class="line">        LIN : 1 + 1 + PRODUCT_A:EN</span><br><span class="line">        QTY : 21:100:PCE</span><br><span class="line">        PRI : AAA:500::NTP</span><br><span class="line">      [LineItem]</span><br><span class="line">        LIN : 2 + 1 + PRODUCT_B:EN</span><br><span class="line">        QTY : 21:50:PCE</span><br><span class="line">        PRI : AAA:1200::NTP</span><br><span class="line">    UNT : 14 + ME001</span><br><span class="line">  UNZ : 1 + 1234</span><br></pre></td></tr></table></figure>

<h2 id="まとめ">まとめ</h2><p>JavaによるEDIFACT処理のスクラッチ開発について、ライブラリ選定から実装までの一連の流れを紹介しました。ストリームパーサであるStaediで字句解析、アプリケーション側で構文解析するアプローチを採用することで、EDIFACTの複雑なネスト構造を直感的なコードで表現できるようになりました。少々ニッチな領域ではありますが、モダンな開発環境でレガシーな仕様と向き合う際の参考になれば幸いです。</p>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:3"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">https://unece.org/trade/uncefact/introducing-unedifact</span> ↩</li><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">2.</span><span style="vertical-align: top;">https://www.jama.or.jp/</span> ↩</li><li id="fn:2"><span style="vertical-align: top; padding-right: 10px;">3.</span><span style="vertical-align: top;">https://github.com/smooks/smooks-edi-cartridge/tree/b60b715cfc84cfffdecb1cfbbf7065af08107ecd/edifact-schemas/src/main/resources</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">Web系のシステム開発やデータ連携においてデータフォーマットにJSONを利用することは一般的ですが、製造系の基幹システムとのデータ連携においてEDIFACTを利用するケースがあります。EDIFACTはあまり馴染みのない、あるいは聞きなれないデータフォーマットかと思いますが...</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Java" scheme="https://future-architect.github.io/tags/Java/"/>
    <category term="業界ドメイン" scheme="https://future-architect.github.io/tags/%E6%A5%AD%E7%95%8C%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3/"/>
  </entry>
  <entry>
    <title>RaspberryPiと赤外線反射センサーを用いて遊んでみよう（入門編）</title>
    <link href="https://future-architect.github.io/articles/20250522a/"/>
    <id>https://future-architect.github.io/articles/20250522a/</id>
    <published>2025-05-21T15:00:00.000Z</published>
    <updated>2025-05-21T15:00:00.000Z</updated>
    <author><name>生駒開</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250522a/image.png" alt="" width="1200" height="675">

<p>春の入門祭り2025の21本目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>はじめまして。最近視力が 2.0 から 1.8 くらいに下がった気がする 24 卒 10 月入社の生駒開です。ブルーライトカットレンズは効果ありますか？そろそろ眼鏡市場に行ってみようと思います。</p>
<p>2025 年 2 月より現所属プロジェクトに参画し、Java と<strong>にらめっこ</strong>の毎日を過ごしています。コーディングの機会に恵まれることは大変嬉しく、充実感を感じる日々ですが、一方で、画面の中で完結してしまう作業に少し寂しさを感じていました。</p>
<p>そんな背景から、「自分の手で作ったコードが、現実世界のモノを動かす」という体験をしてみたくなりました。そこで目をつけたのが <strong>Raspberry Pi</strong> です。この小さなデバイスは、センサーやLEDなどの電子機器をプログラミングで制御できるうえ、電子工作の基礎を学ぶのにも最適です。</p>
<p>このブログでは、初心者向けに私が Raspberry Pi を使って行った障害物検知センサーの制作プロセスをご紹介します。同じように「画面の中だけでないコーディングの楽しさ」を共有できれば嬉しいです。ラズパイを通じて、電子回路やハードウェアの世界へ一歩踏み出すきっかけになれば幸いです！</p>
<h2 id="１．Raspberry-Pi-って何？">１．Raspberry Pi って何？</h2><h3 id="Raspberry-Piとは">Raspberry Piとは</h3><p>イギリスの Raspberry Pi 財団が開発した超小型のシングルボードコンピュータです。クレジットカードほどの大きさながら、Linux が動作し、通常のパソコンのように使うことができます。また Raspberry Pi には Model A や Zero、Pico など、サイズや性能によってモデル分けされており、用途に合わせて選定することができます。</p>
<ul>
<li><strong>主な特徴</strong>：小型で電力効率が良い</li>
<li><strong>用途</strong>：IoT デバイス開発、教育、ホームオートメーションなど</li>
</ul>
<h3 id="使用方法">使用方法</h3><p>Raspberry Pi を活用するまでには、いくつかステップを踏む必要があります。</p>
<ol>
<li><strong>OSのセットアップ</strong><ul>
<li>Raspberry Pi Pico W は MicroPython を使用することが多いです。この OS を作業用 PC から書き込み準備します</li>
<li>Thonny などを使うと便利です</li>
</ul>
</li>
<li><strong>電子回路の設計と構築</strong><ul>
<li>Raspberry Pi の GPIO ピンを使い、外部デバイスを接続します。初めての方は、シンプルな回路設計（例: LED の点滅）から始めるのがおすすめです</li>
<li><strong>注意</strong>：GPIO ピンの電流制御が必要なので、抵抗を適切に導入します</li>
</ul>
</li>
<li><strong>実行環境の整備</strong><ul>
<li>コードは MicroPython や C を利用して開発。シンプルな LED 制御から徐々に複雑なセンサー動作へ応用します</li>
</ul>
</li>
</ol>
<p>GPIO ピンの役割や基本的な回路の接続方法について、第３項で詳しく触れます。</p>
<h2 id="２．今回のチャレンジ内容">２．今回のチャレンジ内容</h2><p>今回のプロジェクトでは、Raspberry Pi Pico W と赤外線反射センサーを使用して、障害物検知センサーを作成します。最終的には、障害物を検知した際に LED で信号を出力する仕組みを実現することを目指します。</p>
<ul>
<li>Raspberry Pi と外部センサー（赤外線反射センサー）を連携させる</li>
<li>Raspberry Pi のGPIOを活用して入出力の基本を覚える</li>
<li>チュートリアルを通じて初歩的な電子工作を学ぶ</li>
</ul>
<h2 id="３．障害物検知センサーの開発">３．障害物検知センサーの開発</h2><p>本番開発を行う前に、まずは必要なものを揃えてチュートリアル開発をクリアしましょう！</p>
<h3 id="3-1-必要なもの">3.1 必要なもの</h3><p>今回使用するものは以下の通りです。</p>
<ul>
<li><strong>Raspberry Pi Pico W</strong>: 主人公。コンパクトかつ省電力でとにかく安い。¥1,300 くらい</li>
<li><strong>ブレッドボード</strong>: 回路構築用。¥400 くらい</li>
<li><strong>抵抗器</strong>: LEDへの過電流を防ぐため。今回は安全な電流量を計算し 220Ω を使用。¥40 くらい</li>
<li><strong>LED</strong>: 障害物検知時の信号出力用。¥10 くらい</li>
<li><strong>ジャンパーワイヤー</strong>: 配線用。10 本で ¥700 くらい</li>
<li><strong>作業用PC</strong>: コードの作成・転送用。（セキュリティ事故が怖いので私用 PC ）</li>
<li><strong>マイクロUSBアダプタ</strong>: 電源供給兼データ通信用。（忘れがち）（充電用だけでは×）</li>
<li><strong>赤外線反射センサー</strong>: 障害物検知用。一個 ¥600 くらい</li>
<li><strong>電池ボックス</strong>:  Pi の外部電源として使用</li>
</ul>
<h3 id="3-2-環境構築">3.2 環境構築</h3><p>Raspberry Pi Pico W を使うためには、まず開発環境を整える必要があります。以下の手順で環境構築を行います。</p>
<h4 id="1-MicroPython-のインストール">1. MicroPython のインストール</h4><p>Raspberry Pi Pico W に MicroPython をインストールします。これはPicoをプログラムするために必要な標準的な OS です。MicroPython公式サイトからUF2形式のファイルをダウンロードし、Pico を PC に接続した後、ドライブへドラッグ＆ドロップするだけでインストールが完了します。</p>
<h4 id="2-開発ツールの準備">2. 開発ツールの準備</h4><p>作業用 PC に Python IDE（統合開発環境）である<strong>Thonny</strong>をインストールします。Thonny は Raspberry Pi Pico W に標準対応しており、コードを書いて直接転送するための便利なツールです。以下のステップを行います。</p>
<ol>
<li>Thonny公式サイトからインストールする</li>
<li>適切なボードを選択（例: Raspberry Pi Pico W）</li>
<li>MicroPython を選択して開発開始テスト</li>
</ol>
<h4 id="3-接続確認">3. 接続確認</h4><p>Thonny を起動し、接続ができたことを確認します。</p>
<p>準備が整ったら次のステージへ進みます！</p>
<h3 id="3-3-チュートリアル開発">3.3 チュートリアル開発</h3><h4 id="3-3-1-Raspberry-Pi-Pico-W-の-GPIO">3.3.1 Raspberry Pi Pico W の GPIO</h4><img src="/images/2025/20250522a/117947.jpg" alt="117947.jpg" width="640" height="480" loading="lazy">

<p>図１　Raspberry Pi Pico WH 本体</p>
<p>GPIO（General Purpose Input&#x2F;Output）は、「汎用入力&#x2F;出力ピン」を指します。このピンを使用することで、さまざまな外部デバイス（センサー、LED、モータなど）と通信したり制御したりすることができます。Raspberry Pi Pico W には<strong>40ピンのヘッダ</strong>があり、それぞれ異なる役割を持っています。以下は、全体のピン配置と役割の概要です。</p>
<ol>
<li><strong>GPIO（汎用入出力ピン）</strong><ul>
<li>ピン番号: GPIO 0〜25</li>
<li>外部デバイスとの通信や信号の受け渡しに使用します</li>
<li>GPIO ピンは「デジタル入出力（HIGH&#x2F;LOW）」の動作を設定できます</li>
<li>特定のピンは通信プロトコルのための特殊機能を持つ（SPI&#x2F;I2C&#x2F;UARTなど）</li>
</ul>
</li>
<li><strong>電源系ピン</strong><ul>
<li><strong>GND（グランド）</strong><ul>
<li>複数箇所に配置されています。（例: ピン3, ピン8, ピン13など）</li>
<li>電子回路を完成させるために必要な基準電位を提供します</li>
</ul>
</li>
<li><strong>3V3（3.3Vピン）</strong><ul>
<li>ピン番号: 36, 37</li>
<li>Raspberry Pi Pico W 自身や外部デバイスへの供給電圧として使用します</li>
<li>Pico は 3.3V で動くデバイス用に設計されています</li>
</ul>
</li>
<li><strong>VBUS（USBバス電源）</strong><ul>
<li>ピン番号: 40</li>
<li>USB から提供される電源（5V）を使用したい場合に利用</li>
</ul>
</li>
</ul>
</li>
<li><strong>ADC（アナログ入力ピン）</strong><ul>
<li>ピン番号: GPIO 26〜28</li>
<li>アナログ信号（電圧値）をデジタル値に変換するためのピン</li>
<li>電圧範囲: 0〜3.3Vのアナログ入力を取得できます</li>
<li>例: センサーの出力を読み取りたいときに使用</li>
</ul>
</li>
<li><strong>通信プロトコル専用ピン</strong><ul>
<li><strong>SPI（Serial Peripheral Interface）</strong><ul>
<li>GPIO 10（MOSI）、GPIO 11（SCK）、GPIO 8（CS）</li>
<li>高速通信が可能で、センサーやディスプレイとの接続に適しています</li>
</ul>
</li>
<li><strong>I2C（Inter-Integrated Circuit）</strong><ul>
<li>GPIO 4（SDA）、GPIO 5（SCL）</li>
<li>複数のデバイスを並列で接続する場合に便利</li>
</ul>
</li>
<li><strong>UART（Universal Asynchronous Receiver&#x2F;Transmitter）</strong><ul>
<li>GPIO 0（TX）、GPIO 1（RX）</li>
<li>シリアル通信に使用</li>
</ul>
</li>
</ul>
</li>
</ol>
<h5 id="ピン配置マップ（概要）">ピン配置マップ（概要）</h5><img src="/images/2025/20250522a/117947_(1).jpg" alt="117947_(1).jpg" width="640" height="480" loading="lazy">

<p>図２　Raspberry Pi Pico WH 本体の裏側</p>
<p>以下は、Raspberry Pi Pico W の40ピンの役割を簡単にまとめたものです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>ピン番号</th>
<th>ピン名</th>
<th>説明</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>3V3 OUT</td>
<td>3.3V電源供給</td>
</tr>
<tr>
<td>2</td>
<td>GPIO 0 (UART TX)</td>
<td>UART通信の送信ピン &#x2F; 汎用デジタル入出力ピン</td>
</tr>
<tr>
<td>3</td>
<td>GND</td>
<td>グラウンド（回路の基準電位）</td>
</tr>
<tr>
<td>4</td>
<td>GPIO 1 (UART RX)</td>
<td>UART通信の受信ピン</td>
</tr>
<tr>
<td>5</td>
<td>GPIO 2</td>
<td>汎用デジタル入出力ピン</td>
</tr>
<tr>
<td>…</td>
<td>…</td>
<td>…</td>
</tr>
<tr>
<td>36</td>
<td>3V3 OUT</td>
<td>3.3V電源供給</td>
</tr>
<tr>
<td>37</td>
<td>3V3 OUT</td>
<td>追加の3.3Vピン</td>
</tr>
<tr>
<td>38</td>
<td>ADC3 &#x2F; GPIO28</td>
<td>アナログ入力ピン</td>
</tr>
<tr>
<td>39</td>
<td>GND</td>
<td>グラウンド</td>
</tr>
<tr>
<td>40</td>
<td>VBUS</td>
<td>USBバス電源（5V）</td>
</tr>
</tbody></table></div>
<div class="note-container warn"><span class="fa-check-circle"></span><div><p>重要な電源系ピンの使い分け</p>
<ol>
<li><strong>3V3 ピン</strong><ul>
<li>Pico 自身やセンサー、LED など、3.3V が動作電圧のデバイスに接続します</li>
<li>注意: 3.3V のデバイス以外は必ず電圧変換器を使用！</li>
</ul>
</li>
<li><strong>VBUS ピン</strong><ul>
<li>USB 経由で供給される 5V を取り出したい場合に使います</li>
<li>Pico が外部機器の供給源として機能する際に便利</li>
</ul>
</li>
<li><strong>GND ピン</strong><ul>
<li>どの回路にも必ず接続します。電子回路の基礎となるため、忘れずに取り扱います</li>
</ul>
</li>
</ol></div></div>

<h4 id="3-3-2-ブレッドボード電子回路">3.3.2 ブレッドボード電子回路</h4><img src="/images/2025/20250522a/image_2.png" alt="image.png" width="1200" height="775" loading="lazy">

<p>図３　ブレッドボードの電気的関係</p>
<p>ブレッドボードは、電子部品をハンダ付けせずに接続・テストできる基板です。電源レールと端子列を使って部品を配置し、ジャンパーワイヤで接続します。</p>
<ol>
<li><strong>電源レール</strong><ul>
<li>長い列（赤と青のライン）で、電源（例: 3.3V、GND など）を供給するために使います。</li>
<li>赤: プラス（+）</li>
<li>青: マイナス（-）</li>
</ul>
</li>
<li><strong>端子列（作業スペース）</strong><ul>
<li>中央の多数の穴が5つずつ連結されており、部品や配線を接続できます。</li>
<li>部品を差し込むと、5つの穴が電気的に結ばれる。</li>
</ul>
</li>
<li><strong>溝（中央のノッチ）</strong><ul>
<li>真ん中の溝は「左側」と「右側」を分け、電気的に互い独立したエリアです。</li>
</ul>
</li>
</ol>
<h5 id="使用方法-1">使用方法</h5><p>ブレッドボードを使う際の基本的な手順は以下の通りです。</p>
<h6 id="1-電源を供給">1. <strong>電源を供給</strong></h6><p>電源レールに適切な電圧（例: 3.3V、GND）を供給します。Raspberry Pi Pico W の場合、以下のピンを利用します：</p>
<ul>
<li><strong>3V3 ピン</strong>：赤い電源レールへ接続。</li>
<li><strong>GND ピン</strong>：青い電源レールへ接続。</li>
</ul>
<h6 id="2-部品を配置">2. <strong>部品を配置</strong></h6><p>電子部品（LED、抵抗、センサーなど）を端子列の適切な穴に差し込みます。配線が正確になるよう配置を確認しましょう。</p>
<h6 id="3-ジャンパーワイヤで接続">3. <strong>ジャンパーワイヤで接続</strong></h6><p>ジャンパーワイヤを使い、部品同士をつなぎます。</p>
<ul>
<li>GPIO ピンや電源レールを正しく接続。</li>
<li>必要に応じて抵抗器やコンデンサを挿入。</li>
</ul>
<h6 id="4-動作確認">4. <strong>動作確認</strong></h6><p>回路が完成したら Raspberry Pi Pico W の電源をオンにして動作をチェックします。</p>
<div class="note-container warn"><span class="fa-check-circle"></span><div><p><strong>注意点:</strong></p>
<ol>
<li><strong>部品がショートしないようにする</strong><ul>
<li>誤って電源レールを直結するとショートする危険があります。確認を怠らないように注意！</li>
</ul>
</li>
<li><strong>適切な抵抗を使用</strong><ul>
<li>LEDを使う場合、過電流防止のために 200Ω〜220Ω の抵抗を入れる必要があります。</li>
</ul>
</li>
<li><strong>電圧や電流を確認</strong><ul>
<li>ブレッドボード回路で使用する部品の動作電圧を確認してください。例えばRaspberry Pi Pico W は 3.3V に対応しています。</li>
</ul>
</li>
</ol></div></div>

<h4 id="3-3-3-「Lチカ」をやってみよう">3.3.3 「Lチカ」をやってみよう</h4><p>ここでは Raspberry Pi Pico W の基本機能を試します。「Lチカ」とはプログラミング言語学習における「Hello World !」です。「LEDを点滅（チカチカ）させる」作業を行い、GPIO ピンが正しく動作していることを確認します。電子回路は以下のように設計し、構築していきます。</p>
<img src="/images/2025/20250522a/img_7902_720.jpg" alt="img_7902_720.jpg" width="719" height="424" loading="lazy">

<p>図４　Lチカ時の配線概要図</p>
<p>以下はMicroPythonで記述したコードです。</p>
<figure class="highlight py"><figcaption><span>例１</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> machine <span class="keyword">import</span> Pin</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">led = Pin(<span class="number">25</span>, Pin.OUT)  <span class="comment"># Raspberry Pi Pico W内蔵LEDのピン番号</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    led.high()           <span class="comment"># LEDを点灯</span></span><br><span class="line">    time.sleep(<span class="number">1</span>)        <span class="comment"># 1秒待機</span></span><br><span class="line">    led.low()            <span class="comment"># LEDを消灯</span></span><br><span class="line">    time.sleep(<span class="number">1</span>)        <span class="comment"># 1秒待機</span></span><br></pre></td></tr></table></figure>

<p>LED が1秒間隔で点滅すれば成功です！これは GPIO ピンの基本動作の確認にも役立ちます。</p>
<h4 id="3-3-4-赤外線反射センサーの動作確認">3.3.4 赤外線反射センサーの動作確認</h4><p>次は、赤外線反射センサーの動作を検証します。センサーが反応すると LED が発光するよう、以下の通りに電子回路を設計し、構築していきます。</p>
<img src="/images/2025/20250522a/img_7903_720.jpg" alt="img_7903_720.jpg" width="720" height="471" loading="lazy">

<p>図５　赤外線反射センサーの動作確認時の配線概要図</p>
<p>以下は本動作確認におけるコードです。</p>
<figure class="highlight py"><figcaption><span>例２</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> machine <span class="keyword">import</span> Pin</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">sensor = Pin(<span class="number">15</span>, Pin.IN)  <span class="comment"># 接続したセンサーのGPIOピン番号</span></span><br><span class="line">led = Pin(<span class="number">21</span>, Pin.OUT)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    var = sensor.value()</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;センサー値：&quot;</span>, val)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> val == <span class="number">0</span>: <span class="comment">#反射が無い</span></span><br><span class="line">        led.on()</span><br><span class="line">    <span class="keyword">else</span>: <span class="comment">#反射有り</span></span><br><span class="line">        led.off()</span><br><span class="line"></span><br><span class="line">    time.sleep(<span class="number">0.2</span>) <span class="comment">#0.2秒のインターバル</span></span><br></pre></td></tr></table></figure>

<p>動作確認ができたら、いよいよ障害物検知センサーの開発です！</p>
<h3 id="3-4-本番開発">3.4 本番開発</h3><h4 id="3-4-1-目的と目標">3.4.1 目的と目標</h4><p>本番開発では、チュートリアルで学習した内容を応用して、以下のシステムを完成させることを目指します。</p>
<ol>
<li><strong>障害物の検知</strong>：赤外線反射センサーを２つ使用して障害物の有無を正確に確認する</li>
<li><strong>信号の処理</strong>：検知した信号に応じて、LED を利用して視覚的なフィードバックを提供する</li>
<li><strong>動作タイミングの調整</strong>：プログラムとハードウェアが一体となってシームレスに動作するシステムを設計する</li>
</ol>
<h4 id="3-4-2-回路設計">3.4.2 回路設計</h4><p>本番開発の回路は電子工作初心者にもわかりやすい設計です。以下に手順を示します。</p>
<img src="/images/2025/20250522a/img_7904_720.png" alt="img_7904_720.png" width="720" height="656" loading="lazy">

<p>図６　障害物検知センサーの配線概要図</p>
<ol>
<li><strong>障害物検知センサーの接続</strong><ul>
<li>センサーの<strong>出力ピン</strong>を Raspberry Pi Pico W の GPIO に接続</li>
<li><strong>入力ピン</strong>を 3.3V ピンに接続し、センサーの動作電圧を供給</li>
<li><strong>GND ピン</strong>を Pico の GND ピンへ接続</li>
</ul>
</li>
<li><strong>LED の接続</strong><ul>
<li>LED の長い足を抵抗（220Ω）を介して GPIO ピンに接続する</li>
<li>LED の短い足を GND に接続し、過電流を防ぐ</li>
</ul>
</li>
<li><strong>電源供給</strong><ul>
<li>電池ボックスを使用して安定した電源を提供する</li>
</ul>
</li>
</ol>
<p>回路完成後は再確認を行い、誤接続がないことを確認してください。</p>
<h4 id="3-4-3-プログラム設計">3.4.3 プログラム設計</h4><p>以下は、本番環境向けのコードです。障害物検知時、LED が光る動作を実装します。</p>
<figure class="highlight py"><figcaption><span>例３</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> machine <span class="keyword">import</span> Pin</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">left_sensor = Pin(<span class="number">10</span>, Pin.IN)  <span class="comment"># 左センサー DOUT</span></span><br><span class="line">right_sensor = Pin(<span class="number">11</span>, Pin.IN) <span class="comment"># 右センサー DOUT</span></span><br><span class="line">led = Pin(<span class="number">21</span>, Pin.OUT)         <span class="comment"># LED</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    left_val = left_sensor.value()</span><br><span class="line">    right_val = right_sensor.value()</span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;&quot;</span>, left_val, <span class="string">&quot;&quot;</span>, right_val)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> left_val == <span class="number">0</span> <span class="keyword">or</span> right_val == <span class="number">0</span>:</span><br><span class="line">        led.on()  <span class="comment"># センサーどちらかが反射無し</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        led.off() <span class="comment"># センサーどちらとも反射有り</span></span><br><span class="line"></span><br><span class="line">    time.sleep(<span class="number">0.1</span>)</span><br></pre></td></tr></table></figure>

<p>このコードでは、各センサからの信号値を確認し、それに基づいて LED をオン・オフ切り替えます。</p>
<h4 id="3-4-4-動作検証">3.4.4 動作検証</h4><ol>
<li><strong>センサーの応答性チェック</strong><ul>
<li>受信したセンサーの値を確認できました</li>
<li>左右のセンサーが独立して障害物を検知できていました</li>
</ul>
</li>
<li><strong>LED 動作のタイミング</strong><ul>
<li>LED が障害物検知してから 0.1 秒以内に発光しました</li>
</ul>
</li>
<li><strong>電源の安定性確認</strong><ul>
<li>電池ボックスの電源供給は安定していました</li>
</ul>
</li>
</ol>
<img src="/images/2025/20250522a/kai_4763_720.jpg" alt="kai_4763_720.jpg" width="720" height="479" loading="lazy">

<p>図７　障害物がない状態</p>
<img src="/images/2025/20250522a/kai_4765_720.jpg" alt="kai_4765_720.jpg" width="479" height="720" loading="lazy">

<p>図８　片側のセンサーのみ障害物を検知している状態</p>
<img src="/images/2025/20250522a/kai_4758_720.jpg" alt="kai_4758_720.jpg" width="720" height="479" loading="lazy">

<p>図９　両センサーが障害物を検知した状態</p>
<h4 id="3-4-5-のびしろ">3.4.5 のびしろ</h4><ol>
<li><strong>センサーの信号をアナログに変更</strong><ul>
<li>信号強度によって今後組み込むモーターの制御を可能にできます</li>
</ul>
</li>
<li><strong>Raspberry Pi Pico W の Wi-Fi 機能の活用</strong><ul>
<li>障害物を検知した際に LINE で通知する機能を実装しようと思います</li>
</ul>
</li>
</ol>
<h2 id="４．今後の展望">４．今後の展望</h2><p>今回のプロジェクトでは、障害物検知センサーシステムを作成しましたが、これをさらに発展させて、火星探査ローバーのような機能を持つ移動式ロボットの開発にチャレンジしたいと思います。また以下のような要素を追加することで、より高度で有意義なプロジェクトになることを望んでいます。</p>
<ol>
<li><strong>モータ制御で移動機能を追加</strong><ul>
<li>Raspberry Pi Pico W を使ってモータを制御し、センサーが検知した障害物を回避できるロボットを作成</li>
<li>ロボットが自律的に動く仕組みを実現することで、探査ローバーのようなプロジェクトに挑戦</li>
</ul>
</li>
<li><strong>無線通信の導入</strong><ul>
<li>Pico W の Wi-Fi 機能を活用し、リアルタイムで障害物データを遠隔地に送信するシステムを構築</li>
<li>火星探査ローバーのように、データ収集とその送信を組み合わせたプロジェクトを展開</li>
</ul>
</li>
<li><strong>AI 技術の応用</strong><ul>
<li>カメラモジュールや画像認識技術を追加し、物体の種類を判別可能なロボットを試作</li>
<li>機械学習を組み合わせることで、よりインテリジェントなシステムを作り上げる</li>
</ul>
</li>
</ol>
<p>またローバーにフォーカスするだけでなく、他の分野へ応用できる可能性も見つけられると思います。まだまだ始まったばかりのプロジェクト（笑）ですが、より面白いものが作れるよう取り組んでまいります。</p>
<h2 id="５．まとめ">５．まとめ</h2><p>障害物検知センサーの開発を通じて、Raspberry Pi Pico W の GPIO 操作や電子工作の基礎を学ぶことができました。特に、コードを使ったプログラムだけでなく、実際にハードウェアを動かす楽しさを味わえたのではないでしょうか。電子工作の魅力は、コーディングが<strong>画面の中だけ</strong>に留まらず、実世界で形になり、動作するものを生み出せることにあります。その瞬間、センサーやモータなどが動く様子は、まるで魔法をかけたかのようにワクワクさせてくれるものです。</p>
<p>みなさまも、ぜひ自分らしいプロジェクトに挑戦してみてください。小さな成功が自信につながり、次のステップへの扉を開きます。そして、楽しい人生を彩る最高の体験になるはずです！</p>
]]></content>
    <summary type="html">「自分の手で作ったコードが、現実世界のモノを動かす」という体験をしてみたくなりました。そこで目をつけたのが Raspberry Pi です。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="MicroPython" scheme="https://future-architect.github.io/tags/MicroPython/"/>
    <category term="RaspberryPi" scheme="https://future-architect.github.io/tags/RaspberryPi/"/>
    <category term="センサー" scheme="https://future-architect.github.io/tags/%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC/"/>
  </entry>
  <entry>
    <title>Embedded Swiftを用いた組み込み開発入門</title>
    <link href="https://future-architect.github.io/articles/20250416a/"/>
    <id>https://future-architect.github.io/articles/20250416a/</id>
    <published>2025-04-15T15:00:00.000Z</published>
    <updated>2025-04-15T15:00:00.000Z</updated>
    <author><name>橋本竜我</name></author>
    <content type="html"><![CDATA[<p>こんにちは！HealthCare Innovation Group(HIG)<sup id="fnref:1">1</sup>所属の橋本です。</p>
<p>本記事は、春の入門祭り2025の3本目です。</p>
<h2 id="はじめに">はじめに</h2><p>昨年のWWDC2024のセッションEmbedded Swiftでサイズを縮小でSwiftで組み込み開発ができることを知り、今回実際にEmbedded Swiftを用いてRaspberry Pi Pico WをLチカさせてみました！</p>
<p>実際に、サンプルプロジェクトを動作させるまでに組み込み開発初心者視点で詰まった箇所や知らなかったことなども一緒に紹介したいと思います。</p>
<div class="note-container warn"><span class="fa-check-circle"></span><div><p>注意: Embedded Swiftはまだ試験的な段階のため、この記事の情報は古くなっている可能性があります。</p></div></div>

<h3 id="Embedded-Swiftとは">Embedded Swiftとは</h3><p>Embedded Swiftとは、メモリやストレージに制約のある環境でも使えるように、Swiftの柔軟性を保ちつつ、動的機能を制限してサイズを小さくしたSwiftのサブセット言語です。</p>
<p><strong>Embedded Swiftの強み:</strong></p>
<p>Swiftの強みである型安全・所有権管理・自動メモリ管理といった利点を組み込み開発にも適用できることが最大のメリットです。これはCやC++での組み込み開発におけるメモリリークやバッファオーバーフローといった不具合が発生しやすいことや安全性の確保が難しいという課題を解消することにもつながります。</p>
<p><strong>Embedded Swiftの制約:</strong></p>
<p>組み込み開発の現場では、メモリやストレージが極めて限られており、従来のSwiftランタイム（約5MB）では到底収まりません。たとえば、ArduinoやSTM32などのボードでは、使用可能なRAMがたった数十〜数百KBというケースも珍しくありません。Embedded Swiftは、このような制約環境でも動作可能な、軽量なSwiftの実行モデルを提供します。</p>
<p>Embededed Swiftでは、以下のSwiftが提供する動的機能を制限することでコードサイズを小さくすることを実現しています。</p>
<ul>
<li>動的リフレクション（reflection）<ul>
<li>Mirror型</li>
<li>as?を用いたダウンキャスト</li>
</ul>
</li>
<li>存在型（existentials）<ul>
<li>any型</li>
</ul>
</li>
<li>ABIの安定性とライブラリの進化への対応</li>
<li>別コンパイルされたジェネリクス</li>
<li>動的コード読み込み（プラグインなど）</li>
</ul>
<p>次の図は、Swiftランタイムと標準ライブラリのサイズ縮小に関する既存の取り組みとEmbedded Swiftがどのようにアプローチしているかを示しています。（swiftlang&#x2F;swift-evolution&#x2F;visions&#x2F;embedded-swift.mdより引用）</p>
<img fetchpriority="high" src="/images/2025/20250416a/image.png" alt="" width="1200" height="651">

<p>より詳細なEmbedded Swiftの制約は以下を参照してください。</p>
<p>swift&#x2F;docs&#x2F;EmbeddedSwift&#x2F;EmbeddedSwiftStatus.md</p>
<h2 id="Embedded-Swiftを用いてRaspberry-Pi-Pico-WをLチカさせる">Embedded Swiftを用いてRaspberry Pi Pico WをLチカさせる</h2><p>レポジトリapple&#x2F;swift-embedded-examplesにいくつかのサンプルプロジェクトがあるなかで、今後色々電子工作で楽しめそう、かつ情報も豊富そうなRaspberry Pi Pico Wに関するプロジェクトを実際に試して見ることにしました。</p>
<p>以下のサンプルプロジェクトを使って、Embedded Swiftを用いてRaspberry Pi Pico WをLチカさせたいと思います。</p>
<ul>
<li>apple&#x2F;swift-embedded-examples&#x2F;pico-w-blink-sdk</li>
</ul>
<p>このプロジェクトは、公式が提供しているPico-sdkを用いることで、基本的な機能等はSDKに持たせ、どのように動作させるかなどはEmbedded Swiftで実装していくようなものです。</p>
<div class="note-container info"><span class="fa-check-circle"></span><div><p>推奨: Embedded Swiftを試す前に、Raspberry Pi公式のチュートリアルを参考にRaspberry Pi Pico WでLチカさせることをおすすめします。Embedded Swiftのドキュメンテーションでも、推奨されています。</p></div></div>

<h3 id="実行環境">実行環境</h3><ul>
<li>macOS: 15.4（24E248）</li>
<li>Swift: Apple Swift version 6.2-dev (LLVM 3f3fde0d5f85709, Swift 0c5fd6a3017961d)</li>
<li>MacBookPro 13-inch M1(2020)</li>
<li>Raspberry Pi Pico W</li>
</ul>
<h3 id="事前準備">事前準備</h3><p>次のドキュメントに沿って事前準備行います。</p>
<ul>
<li>apple&#x2F;swift-embedded-examples</li>
</ul>
<p>その後、今回使用するRaspberry Pi Pico Wに対するサンプルプロジェクトの<code>README.md</code>に沿ってビルドランまで行います。</p>
<ul>
<li>apple&#x2F;swift-embedded-examples&#x2F;pico-w-blink-sdk</li>
</ul>
<h3 id="最新のdevelopment-snapshotのToolchainをインストール">最新のdevelopment snapshotのToolchainをインストール</h3><p>次のサイトから、最新のdevelopment snapshotのToolchainをインストールします。<br>https://www.swift.org/install/macos/#development-snapshots</p>
<img src="/images/2025/20250416a/スクリーンショット_2025-04-08_9.48.27.png" alt="" width="1200" height="580" loading="lazy">

<p>Toolchainがインストールされたことを確認します。<code>swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain</code>が追加した Toolchainです。タイミングによっては、より新しいものになっていると思います。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ <span class="built_in">ls</span> /Library/Developer/Toolchains/</span><br><span class="line"></span><br><span class="line">swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain	swift-latest.xctoolchain</span><br></pre></td></tr></table></figure>

<p>追加したToolchainが使用できるように、パスを通します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ vi ~/.zshrc</span><br><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/bin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"><span class="built_in">export</span> TOOLCHAINS=<span class="string">&quot;/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/bin&quot;</span></span><br></pre></td></tr></table></figure>

<p><code>Swift</code>のバージョンが次のように変わっていることを確認します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">$ swift -version</span><br><span class="line"></span><br><span class="line">Apple Swift version 6.2-dev (LLVM 3f3fde0d5f85709, Swift 0c5fd6a3017961d)</span><br><span class="line">Target: arm64-apple-macosx15.0</span><br><span class="line">Build config: +assertions</span><br></pre></td></tr></table></figure>

<h3 id="Raspberry-Pi-Pico-SDK（pico-sdk）の準備">Raspberry Pi Pico SDK（pico-sdk）の準備</h3><p>続いて、pico-sdkの準備をします。</p>
<ul>
<li><p>pico-sdkをクローン</p>
  <figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/raspberrypi/pico-sdk.git</span><br><span class="line"><span class="built_in">cd</span> pico-sdk</span><br><span class="line">git submodule update --init</span><br></pre></td></tr></table></figure>
</li>
<li><p>pico-examplesをクローン</p>
  <figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/raspberrypi/pico-examples.git</span><br></pre></td></tr></table></figure>
</li>
<li><p>CMake、ninja、The Arm Embedded Toolchainのインストール<br>HomeBrewを使ってそれぞれインストールします。</p>
  <figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">brew install cmake</span><br><span class="line">brew install armmbed/formulae/arm-none-eabi-gcc　←ここ注意</span><br><span class="line">brew install ninja</span><br></pre></td></tr></table></figure></li>
</ul>
<div class="note-container warn"><span class="fa-check-circle"></span><div><p>注意: Homebrewのcore tapではなく、ARMmbedが管理するtapの<code>arm-none-eabi-gcc</code>をインストールしてください。</p></div></div>

<h3 id="環境変数の設定">環境変数の設定</h3><p>次のように設定します。</p>
<figure class="highlight sh"><figcaption><span>~/.zshrc</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/bin:<span class="variable">$PATH</span>&quot;</span></span><br><span class="line"><span class="built_in">export</span> TOOLCHAINS=<span class="string">&quot;/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2025-04-03-a.xctoolchain/usr/bin&quot;</span></span><br><span class="line"><span class="built_in">export</span> PICO_BOARD=pico_w</span><br><span class="line"><span class="built_in">export</span> PICO_SDK_PATH=&#123;任意のディレクトリ&#125;/pico-sdk</span><br><span class="line"><span class="built_in">export</span> PICO_TOOLCHAIN_PATH=/opt/homebrew/opt/arm-none-eabi-gcc/lib/</span><br></pre></td></tr></table></figure>

<h2 id="ビルドラン">ビルドラン</h2><p>準備が完了したので、サンプルプロジェクト(apple&#x2F;swift-embedded-examples&#x2F;pico-w-blink-sdk)をビルドランしたいと思います。</p>
<p>CMakeを実行します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> pico-w-blink-sdk</span><br><span class="line">cmake -B build -G Ninja . -DCMAKE_EXPORT_COMPILE_COMMANDS=On</span><br></pre></td></tr></table></figure>

<p>完了すると、次のようなメッセージが出力されます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">-- Configuring <span class="keyword">done</span> (0.3s)</span><br><span class="line">-- Generating <span class="keyword">done</span> (0.1s)</span><br><span class="line">-- Build files have been written to: &#123;任意のディレクトリ&#125;/build</span><br></pre></td></tr></table></figure>

<p>次に、ビルドします。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">cmake --build build</span><br></pre></td></tr></table></figure>

<p>無事ビルドが成功すれば、次のようなメッセージが末尾に表示されます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">[X/X] Linking CXX executable swift-blinky.elf</span><br></pre></td></tr></table></figure>

<p><code>build</code>フォルダ配下に<code>swift-blinky.uf2</code>ファイルができます。<br>このファイルをデバイスにコピーします。</p>
<p>そうすると、LEDが点滅します。</p>
<img src="/images/2025/20250416a/blink_led_(1).gif" alt="" width="856" height="536" loading="lazy">

<p>LEDを点滅させる処理は、<code>Main.swift</code>に次のように書かれています。</p>
<figure class="highlight swift"><figcaption><span>Main.swift</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">@main</span></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">Main</span> &#123;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">func</span> <span class="title function_">main</span>() &#123;</span><br><span class="line">    <span class="keyword">let</span> led <span class="operator">=</span> <span class="type">UInt32</span>(<span class="type">CYW43_WL_GPIO_LED_PIN</span>)</span><br><span class="line">    <span class="keyword">if</span> cyw43_arch_init() <span class="operator">!=</span> <span class="number">0</span> &#123;</span><br><span class="line">      <span class="built_in">print</span>(<span class="string">&quot;Wi-Fi init failed&quot;</span>)</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> dot <span class="operator">=</span> &#123;</span><br><span class="line">      cyw43_arch_gpio_put(led, <span class="literal">true</span>)</span><br><span class="line">      sleep_ms(<span class="number">250</span>)</span><br><span class="line">      cyw43_arch_gpio_put(led, <span class="literal">false</span>)</span><br><span class="line">      sleep_ms(<span class="number">250</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">let</span> dash <span class="operator">=</span> &#123;</span><br><span class="line">      cyw43_arch_gpio_put(led, <span class="literal">true</span>)</span><br><span class="line">      sleep_ms(<span class="number">500</span>)</span><br><span class="line">      cyw43_arch_gpio_put(led, <span class="literal">false</span>)</span><br><span class="line">      sleep_ms(<span class="number">250</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">true</span> &#123;</span><br><span class="line">      dot()</span><br><span class="line">      dot()</span><br><span class="line">      dot()</span><br><span class="line"></span><br><span class="line">      dash()</span><br><span class="line">      dash()</span><br><span class="line">      dash()</span><br><span class="line"></span><br><span class="line">      dot()</span><br><span class="line">      dot()</span><br><span class="line">      dot()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="ビルド時詰まった箇所とその解決策">ビルド時詰まった箇所とその解決策</h2><p><code>cmake --build build</code>を実行したときに、以下のエラーに遭遇したので、その原因と解決策について記載します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">arm-none-eabi-gcc: fatal error: cannot <span class="built_in">read</span> spec file <span class="string">&#x27;nosys.specs&#x27;</span>: No such file or directory</span><br><span class="line">error: failed to emit precompiled header <span class="string">&#x27;.../BridgingHeader.pch&#x27;</span> <span class="keyword">for</span> bridging header <span class="string">&#x27;.../BridgingHeader.h&#x27;</span></span><br><span class="line">error: <span class="string">&#x27;inttypes.h&#x27;</span> file not found</span><br></pre></td></tr></table></figure>

<h3 id="1-arm-none-eabi-gcc-の-nosys-specs-が見つからない">1. arm-none-eabi-gcc の nosys.specs が見つからない</h3><p><code>arm-none-eabi-gcc: fatal error: cannot read spec file &#39;nosys.specs&#39;: No such file or directory</code>というエラーは、はじめに、次のコマンドでHomeBrew版arm-none-eabi-gccをインストールしてしまったが良くありませんでした。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">brew install arm-none-eabi-gcc</span><br></pre></td></tr></table></figure>

<p>もう一度、apple&#x2F;swift-embedded-examples&#x2F;pico-w-blink-sdkを確認してみると<code>The Arm Embedded Toolchain.</code>とリンク付きで書いてありました。</p>
<img src="/images/2025/20250416a/スクリーンショット_2025-04-12_14.40.39.png" alt="" width="1200" height="361" loading="lazy">

<p>正しくは、以下のコマンドでARMmbed版のarm-none-eabi-gccをインストールします。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">brew tap armmbed/formulae</span><br><span class="line">brew install armmbed/formulae/arm-none-eabi-gcc</span><br></pre></td></tr></table></figure>

<p>これでARM Toolchainのリンカを使ってバイナリファイルが無事生成できるはずです。</p>
<h3 id="2-C言語の標準ヘッダが見つからない">2. C言語の標準ヘッダが見つからない</h3><p>次のエラーは、<code>sysroot</code>を認識しないことが一つの要因だと考えました。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">error: failed to emit precompiled header <span class="string">&#x27;.../BridgingHeader.pch&#x27;</span> <span class="keyword">for</span> bridging header <span class="string">&#x27;.../BridgingHeader.h&#x27;</span></span><br><span class="line">error: <span class="string">&#x27;inttypes.h&#x27;</span> file not found</span><br></pre></td></tr></table></figure>

<p>そこで、Swiftコンパイラに<code>sysroot</code>を明示的に指定することで解決を行いました。</p>
<p><code>CMakeLists.txt</code>に以下を追加します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">-Xcc --sysroot=/opt/homebrew/Cellar/arm-none-eabi-gcc/14.2.0/arm-none-eabi</span><br></pre></td></tr></table></figure>

<p>具体的には、次のようになります。</p>
<details><summary>CMakeLists.txt</summary>

<figure class="highlight diff"><table><tr><td class="code"><pre><span class="line">cmake_minimum_required(VERSION 3.13)</span><br><span class="line">include($ENV&#123;PICO_SDK_PATH&#125;/external/pico_sdk_import.cmake)</span><br><span class="line"></span><br><span class="line">project(swift-blinky)</span><br><span class="line">pico_sdk_init()</span><br><span class="line"></span><br><span class="line">if(APPLE)</span><br><span class="line">execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)</span><br><span class="line">else()</span><br><span class="line">execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)</span><br><span class="line">endif()</span><br><span class="line"></span><br><span class="line">add_executable(swift-blinky)</span><br><span class="line">target_link_libraries(swift-blinky</span><br><span class="line">    pico_stdlib hardware_uart hardware_gpio pico_lwip_arch pico_cyw43_arch_none</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"># Gather compile definitions from all dependencies</span><br><span class="line">set_property(GLOBAL PROPERTY visited_targets &quot;&quot;)</span><br><span class="line">set_property(GLOBAL PROPERTY compilerdefs_list &quot;&quot;)</span><br><span class="line"></span><br><span class="line">function(gather_compile_definitions_recursive target)</span><br><span class="line">    # Get the current value of visited_targets</span><br><span class="line">    get_property(visited_targets GLOBAL PROPERTY visited_targets)</span><br><span class="line"></span><br><span class="line">    # make sure we don&#x27;t visit the same target twice</span><br><span class="line">    # and that we don&#x27;t visit the special generator expressions</span><br><span class="line">    if ($&#123;target&#125; MATCHES &quot;\\$&lt;&quot; OR $&#123;target&#125; MATCHES &quot;::@&quot; OR $&#123;target&#125; IN_LIST visited_targets)</span><br><span class="line">        return()</span><br><span class="line">    endif()</span><br><span class="line"></span><br><span class="line">    # Append the target to visited_targets</span><br><span class="line">    list(APPEND visited_targets $&#123;target&#125;)</span><br><span class="line">    set_property(GLOBAL PROPERTY visited_targets &quot;$&#123;visited_targets&#125;&quot;)</span><br><span class="line"></span><br><span class="line">    # Get the current value of compilerdefs_list</span><br><span class="line">    get_property(compilerdefs_list GLOBAL PROPERTY compilerdefs_list)</span><br><span class="line"></span><br><span class="line">    get_target_property(target_definitions $&#123;target&#125; INTERFACE_COMPILE_DEFINITIONS)</span><br><span class="line">    if (target_definitions)</span><br><span class="line">        # Append the target definitions to compilerdefs_list</span><br><span class="line">        list(APPEND compilerdefs_list $&#123;target_definitions&#125;)</span><br><span class="line">        set_property(GLOBAL PROPERTY compilerdefs_list &quot;$&#123;compilerdefs_list&#125;&quot;)</span><br><span class="line">    endif()</span><br><span class="line"></span><br><span class="line">    get_target_property(target_linked_libs $&#123;target&#125; INTERFACE_LINK_LIBRARIES)</span><br><span class="line">    if (target_linked_libs)</span><br><span class="line">        foreach(linked_target $&#123;target_linked_libs&#125;)</span><br><span class="line">            # Recursively gather compile definitions from dependencies</span><br><span class="line">            gather_compile_definitions_recursive($&#123;linked_target&#125;)</span><br><span class="line">        endforeach()</span><br><span class="line">    endif()</span><br><span class="line">endfunction()</span><br><span class="line"></span><br><span class="line">gather_compile_definitions_recursive(swift-blinky)</span><br><span class="line">get_property(COMPILE_DEFINITIONS GLOBAL PROPERTY compilerdefs_list)</span><br><span class="line"></span><br><span class="line"># Parse compiler definitions into a format that swiftc can understand</span><br><span class="line">list(REMOVE_DUPLICATES COMPILE_DEFINITIONS)</span><br><span class="line">list(PREPEND COMPILE_DEFINITIONS &quot;&quot;) # adds a semicolon at the beginning</span><br><span class="line">string(REPLACE &quot;$&lt;TARGET_PROPERTY:PICO_TARGET_BINARY_TYPE&gt;&quot; &quot;$&lt;TARGET_PROPERTY:swift-blinky,PICO_TARGET_BINARY_TYPE&gt;&quot; COMPILE_DEFINITIONS &quot;$&#123;COMPILE_DEFINITIONS&#125;&quot;)</span><br><span class="line">string(REPLACE &quot;;&quot; &quot;;-Xcc;-D&quot; COMPILE_DEFINITIONS &quot;$&#123;COMPILE_DEFINITIONS&#125;&quot;)</span><br><span class="line"></span><br><span class="line">add_custom_command(</span><br><span class="line">    OUTPUT $&#123;CMAKE_CURRENT_BINARY_DIR&#125;/_swiftcode.o</span><br><span class="line">    COMMAND</span><br><span class="line">        $&#123;SWIFTC&#125;</span><br><span class="line">        -target armv6m-none-none-eabi -Xcc -mfloat-abi=soft -Xcc -fshort-enums</span><br><span class="line">        $&#123;COMPILE_DEFINITIONS&#125;</span><br><span class="line">        -Xcc -DCYW43_LWIP</span><br><span class="line">        -Xcc -DPICO_CYW43_ARCH_THREADSAFE_BACKGROUND</span><br><span class="line"><span class="addition">+        -Xcc --sysroot=//opt/homebrew/Cellar/arm-none-eabi-gcc/10.3-2021.10/gcc/bin/../arm-none-eabi</span></span><br><span class="line">        -Xcc -I$ENV&#123;PICO_SDK_PATH&#125;/lib/lwip/src/include</span><br><span class="line">        -Xcc -I$&#123;CMAKE_CURRENT_LIST_DIR&#125;/include</span><br><span class="line">        -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library</span><br><span class="line">        $$\( echo &#x27;$&lt;TARGET_PROPERTY:swift-blinky,INCLUDE_DIRECTORIES&gt;&#x27; | tr &#x27;\;&#x27; &#x27;\\n&#x27; | sed -e &#x27;s/\\\(.*\\\)/-Xcc -I\\1/g&#x27; \)</span><br><span class="line">        $$\( echo &#x27;$&#123;CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES&#125;&#x27;             | tr &#x27; &#x27;  &#x27;\\n&#x27; | sed -e &#x27;s/\\\(.*\\\)/-Xcc -I\\1/g&#x27; \)</span><br><span class="line">        -import-bridging-header $&#123;CMAKE_CURRENT_LIST_DIR&#125;/BridgingHeader.h</span><br><span class="line">        $&#123;CMAKE_CURRENT_LIST_DIR&#125;/Main.swift</span><br><span class="line">        -c -o $&#123;CMAKE_CURRENT_BINARY_DIR&#125;/_swiftcode.o</span><br><span class="line">    DEPENDS</span><br><span class="line">        $&#123;CMAKE_CURRENT_LIST_DIR&#125;/BridgingHeader.h</span><br><span class="line">        $&#123;CMAKE_CURRENT_LIST_DIR&#125;/Main.swift</span><br><span class="line">)</span><br><span class="line">add_custom_target(swift-blinky-swiftcode DEPENDS $&#123;CMAKE_CURRENT_BINARY_DIR&#125;/_swiftcode.o)</span><br><span class="line"></span><br><span class="line">target_link_libraries(swift-blinky</span><br><span class="line">    $&#123;CMAKE_CURRENT_BINARY_DIR&#125;/_swiftcode.o</span><br><span class="line">)</span><br><span class="line">add_dependencies(swift-blinky swift-blinky-swiftcode)</span><br><span class="line">pico_add_extra_outputs(swift-blinky)</span><br></pre></td></tr></table></figure>

</details>

<h2 id="おわりに">おわりに</h2><p>Embedded Swiftを用いてLaspberry Pi Pico WをLチカさせることができました。</p>
<p>Laspberry Pi Pico Wを買うときに、電子工作のスターターキットで購入していたので、Embedded Swiftを使って様々な電子工作にもチャレンジしてみたいと思います。</p>
<h2 id="参考">参考</h2><ul>
<li>Embedded Swiftでサイズを縮小-WWDC24-ビデオ-Apple Developer</li>
<li>https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md</li>
<li>https://github.com/apple/swift-embedded-examples/</li>
<li>https://github.com/apple/swift-embedded-examples/tree/main/pico-w-blink-sdk</li>
<li>GCCのsysrootオプションについて調べてみた</li>
<li>Raspberry Pi PicoとEmbedded Swiftを試してみる - ANDPAD Tech Blog</li>
<li>iOSDC Japan 2024: Raspberry Pi Pico を Swift で操作する &#x2F; yochidros</li>
</ul>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">医療・ヘルスケア分野での案件や新規ビジネス創出を担う、2020年に誕生した事業部です。設立エピソードは未来報の記事をご覧ください。</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">Embedded Swiftを用いてRaspberry Pi Pico WをLチカさせてみました！</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="RaspberryPi" scheme="https://future-architect.github.io/tags/RaspberryPi/"/>
    <category term="Swift" scheme="https://future-architect.github.io/tags/Swift/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>自作40%キーボードへの入門〜キーボードも断捨離の時代へ〜</title>
    <link href="https://future-architect.github.io/articles/20240502a/"/>
    <id>https://future-architect.github.io/articles/20240502a/</id>
    <published>2024-05-01T15:00:00.000Z</published>
    <updated>2024-05-01T15:00:00.000Z</updated>
    <author><name>山本竜玄</name></author>
    <content type="html"><![CDATA[<p>春の入門連載の16記事目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。180度開脚に憧れてストレッチを続けている、HealthCare Innovation Group(HIG)所属の山本です。</p>
<p>以前から自作キーボード、分割キーボード、XX％キーボードといった単語には興味を惹かれていたのですが、電子工作初心者の自分としてはなかなか敷居を高く感じていました。</p>
<p>この度、自作キーボードに初めて挑戦し、40％分割キーボードを使いこなすに至ったので、その動機や経緯を含め、魅力をお伝えしていきたいです。</p>
<p>※自作キーボードについては既存記事も面白いものが多いのでぜひ御覧ください！</p>
<p>https://future-architect.github.io/tags/自作キーボード/</p>
<h2 id="作成したもの">作成したもの</h2><p>今回私が作成したキーボードは、以下のようなものです。</p>
<p>キーキャップに無刻印のものを使用しているためちょっと分かりづらいですが、通常のキーボードよりもキー数が少ないです。</p>
<p>ファンクションキーや数字キーもないので、かなりスッキリした見た目になる反面、初めて見る方にはどう入力するのかと困惑されるのではないでしょうか？</p>
<img fetchpriority="high" src="/images/2024/20240502a/PXL_20240501_204920074.MP.jpg" alt="PXL_20240501_204920074.MP.jpg" width="1200" height="675">

<p>製品としては、foostanさんが作成されているCorne Cherryシリーズのものを使わせていただきました。</p>
<p>日本で有数の自作キーボードの店舗＆オンラインショップである、遊舎工房さんのサイトでもいくつかシリーズが販売されています。<br>https://shop.yushakobo.jp/search?type=product&amp;options%5Bprefix%5D=last&amp;options%5Bunavailable_products%5D=last&amp;q=Corne+Cherry</p>
<h2 id="作成までの経緯">作成までの経緯</h2><p>このセクションでは、作成に至るまでの動機と経緯をポエミーに書きます。</p>
<h3 id="動機編">動機編</h3><p>はじめに、今回挑戦した動機について軽く触れておきます。</p>
<p>今までの多くをリモートワークで過ごしてきたわけですが、以下のような思いをモヤモヤと抱えていました。</p>
<ol>
<li>デスクに向かいすぎて、首肩こりが強くなってきた</li>
<li>せっかくリモートワークをしているから、至高のデスク環境を極めたい</li>
<li>一般的なキーボードでタッチタイピング程度であればできるが、より高みを目指せるのではないか…？</li>
</ol>
<p>1についての対応としては、「分割キーボード」を導入することがよいという一説があります。通常のキーボードではどうしても肩幅より小さい位置へ手を置くことになりますが、分割されていることにより各々の肩幅や手の位置似合わせて配置できるという利点ですね。</p>
<p>2, 3については、より高みを目指したいという心の奥底のコダワリ心ですね。つまり私的には自己満足と浪漫の世界です。そんな折、「40％キーボード」の魅力を先人が書いた記事を見かけました。いわく、ホームポジションからほとんど手を動かさずにすむためキー入力が効率的になるということです。これに強い魅力を感じました。</p>
<p>これらの思いから、自作40％キーボードの世界には強い興味を持っていました。</p>
<h3 id="見学編">見学編</h3><p>とはいえ、自作キーボードの作成経験もなく、電子工作の経験もあまりない私には右も左もわからない状態でした。</p>
<p>そのため、実際に国内でも有数の自作キーボードショップである、遊舎工房さんの実店舗(秋葉原にある)に行ってみました。</p>
<p>https://yushakobo.jp/shopinfo/</p>
<p>こちらの店舗では、様々な種類の自作キーボードが店内に展示されており、実際に触ることができます。キー入力の感触や外観、他にも形状など己のフィーリングを確かめることができるので、気分は伝説の聖剣を抜きにきた村人Aの気持ちになれます。</p>
<p>店員さんも親切で、各種部品やキットの販売なども行っているため、同じように入門される方は一度行ってみることをおすすめします。</p>
<h3 id="作成編">作成編</h3><p>残念なお知らせとして、このブログ記事の執筆時点ではすでにキーボードは完成してしまいました。(記録など取ること忘れてました。。)</p>
<p>そのため、組み立てやはんだ付けについては他の方の記事や動画などを参照ください。</p>
<p>日本で販売されている組み立てタイプの自作キーボード(自分ではんだ付けから行うもの)の多くは、ビルドガイドがGithub上で公開されていたりします。</p>
<p>(例：今回扱ったfoostanさんのビルドガイド)</p>
<p>自作キーボードに挑戦される方は、これらの事前情報があるタイプのキーボードかも確認されると良いかもしれません。</p>
<p>また、はんだ付けに不安がある方へはキーボードの組立サービスであったり、そもそもはんだ付けがされた状態で発売されている製品などもあるので挑まない選択肢もあるため安心できます。<br>(私はYouTubeのはんだ付け動画を繰り返しみつづけました)</p>
<h2 id="使いこなすまでの試行錯誤">使いこなすまでの試行錯誤</h2><p>ここまでの経緯で、なんとか動作する自作40％キーボードについては作成できました。</p>
<p>しかしなんと、製作したから終わりではなく、まだまだ拘れるポイントがあるので紹介します。<br>(これがキーボード沼と呼ばれる理由かと思ってます。時間と金を無限に溶かすことができそうです)</p>
<h3 id="1-キースイッチの試行錯誤">1. キースイッチの試行錯誤</h3><p>キーボードの打鍵感は非常に個人的なもので、それを自由にカスタマイズできるのが自作キーボードの強みの1つかと思っています。</p>
<p>キースイッチには、赤軸、青軸、茶軸、スピードシルバー軸などなどさまざまな種類があり、それぞれによって打鍵したときの感触や打鍵音が変わってきます。HHKBやKeychronなどを購入する際にも比較するような情報ですね。</p>
<p>今回のキーボードでは、軽めの打鍵感と感触の好みからスピードシルバー軸のものを購入しました。</p>
<h3 id="2-キーキャップの試行錯誤">2. キーキャップの試行錯誤</h3><p>キーキャップの素材やデザインも打鍵感に意外なほど大きな影響を与えてきます。</p>
<p>このキーキャップの形状についてはProfile(プロファイル)と呼ばれており、高さやくぼみなどの形状が異なってきます。市販のメカニカルキーボードの多くはOEM Profileと呼ばれるものに該当するそうです。</p>
<img src="/images/2024/20240502a/image.png" alt="image.png" width="1200" height="352" loading="lazy">

<p>(https://www.keycaps.info で作成した画像を引用)</p>
<p>上記の画像で見比べて見ると、多様性があることがわかるのではないでしょうか？</p>
<p>試行錯誤の末、私の個人的な好みとしては高めのProfileであるSA Profileが打ちやすいことがわかりました。</p>
<h3 id="3-キーボード角度の試行錯誤">3. キーボード角度の試行錯誤</h3><p>キーボードの水平方向の傾きもキー入力のしやすさや肩こりの解消に大きな影響を与えてきます。自分の手を前水平に伸ばしてみると感じることができますが、人間の手首は微妙に外向きに回転した状態が自然な除隊となることが多いです。</p>
<p>そのため、机と平行なフラットなキーボードで長時間入力することで、手首や肘への疲労が蓄積していまいます。</p>
<p>対策として、キーボードの角度を変える、「テンティング」という単語で呼ばれる試行錯誤があります。</p>
<p>結論として、水平方向ではこのくらいの角度が自分にはフィットしました。(人によっては60度くらいの角度を付けている人も見かけますね)</p>
<img src="/images/2024/20240502a/PXL_20240501_222151250.MP_(1).jpg" alt="PXL_20240501_222151250.MP_(1).jpg" width="1200" height="377" loading="lazy">

<h3 id="4-キーマップの試行錯誤">4. キーマップの試行錯誤</h3><p>こちらは40％キーボードや60％キーボード特有の話となってきますが、キーマップの試行錯誤についても探求の余地があります。</p>
<p>そもそも、数字キーボードや一部記号キーがない40％キーボードでは、これらのキーをどうやって入力するのでしょうか？</p>
<p>ここで、「レイヤー」という概念が出てきます。通常のキーボードでもFnキーを押している間、他のキー入力の効果が変わるといったことがあると思います。これを自由にカスタマイズできるのが特徴です。</p>
<p>下画像での例としては、「G」キーを単体で入力したときにはGがそのまま入力され、右下の青い上矢印キー+「G」キーを入力したときには6が入力される、といった挙動としています。</p>
<img src="/images/2024/20240502a/PXL_20240501_204920074.MP_2.jpg" alt="PXL_20240501_204920074.MP.jpg" width="1200" height="675" loading="lazy">

<p>自作キーボードの多くは、以下のRemapのサイト上などでキー配置を自由に組み替えることができます。</p>
<p>https://remap-keys.app/configure</p>
<p>私のキーマップ例を示しますが、以下のようになっています。自分の入力しやすいようにカスタマイズできるのはもちろん、レイヤーキーとの組み合わせで入力できるキー数が3倍となっています。</p>
<p>40％キーボードでありながら、過不足なく、ホームポジションからほとんど手を動かさずに入力できるのは素晴らしい利点です。</p>
<img src="/images/2024/20240502a/image_2.png" alt="image.png" width="1076" height="1172" loading="lazy">

<hr>
<p>これら以外にも、3Dプリンタでの筐体作成、バックライト、トラックボールの組み込みなどまだまだこだわれる要素はたくさんあります。</p>
<p>あまりに極めすぎると2度と通常のキーボードで入力できなくなる懸念はありますが、楽しみが多いのは良いことですね。</p>
<h2 id="まとめ">まとめ</h2><p>これまでの記事で、自作40％キーボードに挑戦する道のり、そのさきの沼の深さの一端をご紹介しました。</p>
<p>肩こりを動機として挑んだ自作40％キーボードですが、試行錯誤の末かなり満足できる環境とできたと個人的には感じています。</p>
<p>ひとつひとつ自分の体の特徴やクセを見極め、相棒となる道具をアップデートして成長していける経験は人生でもそう多くないものと考えています。</p>
<p>個人的には、そのような機会に大人になってから挑戦できるという意味でも良い経験となりました。</p>
<p>肩こりに悩む方、キーボード沼にハマってみたい方、あるいは自分の相棒となるキーボードを探したい方、それ以外の全ての方にも、自作キーボードを探して・作って・試行錯誤して、といった経験はとても楽しいものですのでぜひ一度はやってみることをオススメしたいです。思ったより敷居は高くなかったです。</p>
]]></content>
    <summary type="html">以前から自作キーボード、分割キーボード、XX％キーボードといった単語には興味を惹かれていたのですが、電子工作初心者の自分としてはなかなか敷居を高く感じていました。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="リモートワーク" scheme="https://future-architect.github.io/tags/%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF/"/>
    <category term="自作キーボード" scheme="https://future-architect.github.io/tags/%E8%87%AA%E4%BD%9C%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>ESP32とGoogleSpreadSheetで始めるIoT入門</title>
    <link href="https://future-architect.github.io/articles/20240423a/"/>
    <id>https://future-architect.github.io/articles/20240423a/</id>
    <published>2024-04-22T15:00:00.000Z</published>
    <updated>2024-04-22T15:00:00.000Z</updated>
    <author><name>高世駿</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>こんにちは！ 流通・製造サービス事業部所属の高世です。</p>
<p>こちらは春の入門連載2024の10記事目です。</p>
<p>IoTという言葉が話題になり始めてから、かなりの時間が経ちました。私自身も身近なIoTに関心を持っていろいろ取り組んできましたが、ソフトウェアだけでなくハードウェアの知識も必要になるなど、初めて挑戦する方にとっては敷居が高いと感じることがしばしばありました。<br>特にインターネットに接続するあたりのインフラ周辺は、選択肢が多様で、その分複雑であったり費用がかかったりする場合もあります。</p>
<p>この記事では、そうしたIoTの問題点をGoogleSpreadSheetを利用することで、手軽に実装できた経験を共有したいと思います。</p>
<h2 id="今回やること">今回やること</h2><img fetchpriority="high" src="/images/2024/20240423a/image.png" alt="" width="1200" height="607">

<p>今回はESP32というマイコンを利用して温度センサから定期的に温度を計測し、その情報をGoogleSpreadSheet上に保持するシステムを構築します。</p>
<p>温度センサにはDS18B20という安価で入手性の良いセンサを使用し、冷蔵庫内と冷蔵庫の外（室内）の2つのセンサを設置します。</p>
<h3 id="ESP32とは">ESP32とは</h3><img src="/images/2024/20240423a/115673.jpg" alt="" width="640" height="480" loading="lazy">

<p>https://akizukidenshi.com/catalog/g/g115673/</p>
<p>今回使用するESP32は、いわゆるマイコン（マイクロコントローラー）と呼ばれるもので、PCほど高尚な処理はできませんが、プログラムを書き込むことで決められたInputから何かしらのOutputを出すことができます。</p>
<p>その中でもESP32はWi-FiやBluetoothなどの無線通信機能を搭載し、IoTデバイスの開発などによく用いられます。</p>
<p>またArduino IDEと互換性があるのも強みです。Arduino IDEで利用できる様々なライブラリを用いることでセンサーやデバイスとの接続を容易にします。</p>
<h3 id="DS18B20とは">DS18B20とは</h3><img src="/images/2024/20240423a/image_2.png" alt="" width="438" height="371" loading="lazy">

<p>今回使用する温度センサです。<br>Amazonにて5本で1000円というかなり安価に手に入りますし、1-wireインターフェースといって1本の信号線でデータのやりとりができたり、複数のDS18B20を1本の信号線で接続して制御できたりなど使い勝手がかなり良かったです。</p>
<h3 id="前提">前提</h3><p>今回のシステムを構築するにあたっての前提条件としてはArduino IDEでESP32を用いたDS18B20の開発環境が整えられていることです。</p>
<p>検索すると多くの記事がでてきますが、私は以下の記事を参考にしました。</p>
<ul>
<li>ESP32開発ボード Arduino IDE開発環境の構築</li>
<li>温度センサー DS18B20をESP32で動かすメモ</li>
</ul>
<h2 id="温度センサ-DS18B20-で温度を測ってみる">温度センサ(DS18B20)で温度を測ってみる</h2><p>温度センサ(DS18B20)を使って温度を測ってみます。</p>
<p>DS18B20から3本の線が出ており、電源線(赤)・信号線(黄)・GND線(黒)となっております。</p>
<p>ブレッドボートに挿せるようにピンヘッダをハンダ付けしてあげて、ESP32と接続します。</p>
<p>余談ですがハンダ付けしたあとの保護のためにホットボンドを利用しています（左下画像）。</p>
<img src="/images/2024/20240423a/image_3.png" alt="" width="704" height="316" loading="lazy">

<p>シンプルな1センサに対しての温度計測と表示を行ってみます。</p>
<figure class="highlight c++"><figcaption><span>温度計測テストプログラム</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;OneWire.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;DallasTemperature.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1-Wire設定</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ONE_WIRE_BUS 4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1-Wire接続インスタンスの作成</span></span><br><span class="line"><span class="function">OneWire <span class="title">oneWire</span><span class="params">(ONE_WIRE_BUS)</span></span>;</span><br><span class="line"><span class="function">DallasTemperature <span class="title">sensors</span><span class="params">(&amp;oneWire)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setup</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="comment">// シリアル通信の設定</span></span><br><span class="line">  Serial.<span class="built_in">begin</span>(<span class="number">115200</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 1-Wire接続スタート</span></span><br><span class="line">  <span class="built_in">pinMode</span>(ONE_WIRE_BUS, INPUT_PULLUP);</span><br><span class="line">  sensors.<span class="built_in">begin</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">(<span class="type">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123; </span><br><span class="line">  <span class="built_in">delay</span>(<span class="number">500</span>);</span><br><span class="line">  Serial.<span class="built_in">print</span>(<span class="string">&quot;温度測定中...&quot;</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// デバイスに対して温度計測をリクエスト</span></span><br><span class="line">  sensors.<span class="built_in">requestTemperatures</span>();</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 計測結果をプリント</span></span><br><span class="line">  Serial.<span class="built_in">println</span>(sensors.<span class="built_in">getTempCByIndex</span>(<span class="number">0</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>ソースはこちらのサンプルコードを参考にしました。<br>ちょっと変更を入れている点としては、<code>pinMode(ONE_WIRE_BUS, INPUT_PULLUP)</code>によって1-Wireの信号線に対して、ESP32の内蔵プルアップを設定しています。</p>
<p>実際に書き込みを行い、Arduino IDEのシリアルモニタで見てみます。</p>
<img src="/images/2024/20240423a/sokutei2.gif" alt="sokutei2.gif" width="645" height="321" loading="lazy">

<p>温度の変化がわかるようにセンサ部を指で温めています。<br>最初<code>26.44度</code>からスタートし、指で触り始めると温度が上がっていくことを確認しました。</p>
<h2 id="GoogleSpreadSheetでWebアプリをデプロイする">GoogleSpreadSheetでWebアプリをデプロイする</h2><p>GoogleSpreadSheetでPOSTリクエストを受け付けるWebアプリを作っていきます。</p>
<p>まずスプシを作成し、拡張機能&gt;Apps ScriptからGASを作成します。</p>
<img src="/images/2024/20240423a/image_4.png" alt="" width="783" height="249" loading="lazy">

<p>今回はESP32から計測した温度データをJSONに詰めて、POSTリクエストを飛ばします。</p>
<p>送信するJSONをあらかじめ以下のように定義しておきます。</p>
<p>今回は2つのセンサからの温度情報を送るため、温度が問題なく取得できたことを確認する属性<code>success</code>と温度情報<code>temp</code>を持つオブジェクトをリストで持つように設計しました。</p>
<figure class="highlight json"><figcaption><span>ESP32から送信するJSONデータ</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">	<span class="attr">&quot;data&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">		<span class="punctuation">&#123;</span></span><br><span class="line">			<span class="attr">&quot;success&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;temp&quot;</span><span class="punctuation">:</span> <span class="number">1.23</span></span><br><span class="line">		<span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">		<span class="punctuation">&#123;</span></span><br><span class="line">			<span class="attr">&quot;success&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">			<span class="attr">&quot;temp&quot;</span><span class="punctuation">:</span> <span class="number">2.34</span></span><br><span class="line">		<span class="punctuation">&#125;</span></span><br><span class="line">	<span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>上記のJSONを受け取ってスプシにデータを挿入するスクリプトを書きます。</p>
<p>空行を挿入して、そこに受信した温度データを書き込む仕様としました。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">doPost</span>(<span class="params">e</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> params = <span class="title class_">JSON</span>.<span class="title function_">parse</span>(e.<span class="property">postData</span>.<span class="title function_">getDataAsString</span>()); </span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> sheet = <span class="title class_">SpreadsheetApp</span>.<span class="title function_">getActiveSpreadsheet</span>().<span class="title function_">getSheets</span>()[<span class="number">0</span>];</span><br><span class="line">  sheet.<span class="title function_">insertRowBefore</span>(<span class="number">2</span>);</span><br><span class="line">  sheet.<span class="title function_">getRange</span>(<span class="number">2</span>, <span class="number">1</span>).<span class="title function_">setValue</span>((<span class="keyword">new</span> <span class="title class_">Date</span>).<span class="title function_">toLocaleString</span>(<span class="string">&#x27;ja-JP&#x27;</span>));</span><br><span class="line">  sheet.<span class="title function_">getRange</span>(<span class="number">2</span>, <span class="number">2</span>).<span class="title function_">setValue</span>(params.<span class="property">data</span>[<span class="number">0</span>].<span class="property">success</span> ? params.<span class="property">data</span>[<span class="number">0</span>].<span class="property">temp</span> : <span class="string">&quot;err&quot;</span>);</span><br><span class="line">  sheet.<span class="title function_">getRange</span>(<span class="number">2</span>, <span class="number">3</span>).<span class="title function_">setValue</span>(params.<span class="property">data</span>[<span class="number">1</span>].<span class="property">success</span> ? params.<span class="property">data</span>[<span class="number">1</span>].<span class="property">temp</span> : <span class="string">&quot;err&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">ContentService</span>.<span class="title function_">createTextOutput</span>(<span class="string">&quot;OK&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>それではこちらのPOSTリクエストを受け付けるWebアプリをデプロイします。</p>
<p>デプロイ方法は簡単で以下手順でボタン押下するだけでURLが発行されます。</p>
<img src="/images/2024/20240423a/image_5.png" alt="" width="1110" height="443" loading="lazy">

<img src="/images/2024/20240423a/image_6.png" alt="" width="1200" height="631" loading="lazy">

<img src="/images/2024/20240423a/image_7.png" alt="" width="1200" height="631" loading="lazy">

<p>このURLにPOSTリクエストを投げれば、先程のスクリプトが実行されます。<br>注意点としては、このURLはデプロイするたびに変わるということと、認証などの仕組みはないため、URLが漏洩すると誰でもアクセスできるので取り扱いには注意してください。</p>
<h2 id="ESP32からデータをアップロードする">ESP32からデータをアップロードする</h2><p>プログラムを作る前に温度センサを冷蔵庫に設置します。</p>
<p>冷蔵庫内部にセンサをマスキングテープで付けて、ケーブルを冷蔵庫の外に出してESP32と接続しました。</p>
<img src="/images/2024/20240423a/image_8.png" alt="" width="507" height="377" loading="lazy">

<p>それではESP32から先程デプロイしたWebアプリに対してPOSTリクエストを送信するプログラムを作成していきます。<br>ちょっと長くなってしまいましたが、やってることとしてはWi-Fiの接続設定と1分間隔で温度計測とAPIリクエストの実行となっています。</p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;OneWire.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;DallasTemperature.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WiFi.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;HTTPClient.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1-Wire設定</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ONE_WIRE_BUS 4</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> TEMPERATURE_PRECISION 12</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1-Wire接続インスタンスの作成</span></span><br><span class="line"><span class="function">OneWire <span class="title">oneWire</span><span class="params">(ONE_WIRE_BUS)</span></span>;</span><br><span class="line"><span class="function">DallasTemperature <span class="title">sensors</span><span class="params">(&amp;oneWire)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接続するデバイスのアドレスを指定（以下サンプルソースを利用して取得）</span></span><br><span class="line"><span class="comment">// https://github.com/milesburton/Arduino-Temperature-Control-Library/blob/master/examples/oneWireSearch/oneWireSearch.ino</span></span><br><span class="line">DeviceAddress refrigeratorDevice = &#123; <span class="number">0x28</span>, <span class="number">0xC3</span>, <span class="number">0x9F</span>, <span class="number">0x46</span>, <span class="number">0xD4</span>, <span class="number">0x09</span>, <span class="number">0x76</span>, <span class="number">0xF9</span> &#125;;</span><br><span class="line">DeviceAddress outsideDevice = &#123; <span class="number">0x28</span>, <span class="number">0x75</span>, <span class="number">0x4B</span>, <span class="number">0x46</span>, <span class="number">0xD4</span>, <span class="number">0x4B</span>, <span class="number">0x21</span>, <span class="number">0x5F</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Wi-Fi接続設定</span></span><br><span class="line"><span class="type">char</span> ssid[] = <span class="string">&quot;xxx&quot;</span>;</span><br><span class="line"><span class="type">char</span> password[] = <span class="string">&quot;yyy&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// リクエストURL設定</span></span><br><span class="line"><span class="type">char</span> requestURL[] = <span class="string">&quot;GASでデプロイしたWEBアプリのURL&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">unsigned</span> <span class="type">long</span> timer;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setup</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="comment">// シリアル通信設定</span></span><br><span class="line">  Serial.<span class="built_in">begin</span>(<span class="number">115200</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// WiFi接続スタート.</span></span><br><span class="line">  Serial.<span class="built_in">print</span>(<span class="string">&quot;Connecting to &quot;</span>);</span><br><span class="line">  Serial.<span class="built_in">println</span>(ssid);</span><br><span class="line">  WiFi.<span class="built_in">begin</span>(ssid, password);</span><br><span class="line">  WiFi.<span class="built_in">setAutoConnect</span>(<span class="literal">true</span>);</span><br><span class="line">  WiFi.<span class="built_in">setAutoReconnect</span>(<span class="literal">true</span>);</span><br><span class="line">  <span class="comment">// WiFi接続完了まで待機.</span></span><br><span class="line">  <span class="keyword">while</span> (WiFi.<span class="built_in">status</span>() != WL_CONNECTED)</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">500</span>);</span><br><span class="line">    Serial.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// WiFi接続完了.</span></span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;WiFi connected&quot;</span>);</span><br><span class="line">  Serial.<span class="built_in">println</span>(<span class="string">&quot;IP address: &quot;</span>);</span><br><span class="line">  Serial.<span class="built_in">println</span>(WiFi.<span class="built_in">localIP</span>());</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 1-Wire接続スタート</span></span><br><span class="line">  <span class="built_in">pinMode</span>(ONE_WIRE_BUS, INPUT_PULLUP);</span><br><span class="line">  sensors.<span class="built_in">begin</span>();</span><br><span class="line">  sensors.<span class="built_in">setResolution</span>(refrigeratorDevice, TEMPERATURE_PRECISION);</span><br><span class="line">  sensors.<span class="built_in">setResolution</span>(outsideDevice, TEMPERATURE_PRECISION);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// タイマ初期化</span></span><br><span class="line">  timer = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 1分間隔で実行</span></span><br><span class="line">  <span class="keyword">if</span> (timer &lt; <span class="built_in">millis</span>()) &#123;</span><br><span class="line">    timer = <span class="built_in">millis</span>() + <span class="number">60</span> * <span class="number">1000</span>;</span><br><span class="line">    <span class="built_in">measureAndRequestSend</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">measureAndRequestSend</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="comment">// デバイスに対して温度計測をリクエスト</span></span><br><span class="line">  Serial.<span class="built_in">print</span>(<span class="string">&quot;温度測定中...&quot;</span>);</span><br><span class="line">  sensors.<span class="built_in">requestTemperatures</span>();</span><br><span class="line"></span><br><span class="line">  <span class="type">float</span> refrigeratorTemp = sensors.<span class="built_in">getTempC</span>(refrigeratorDevice);</span><br><span class="line">  <span class="type">float</span> outsideTemp = sensors.<span class="built_in">getTempC</span>(outsideDevice);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 計測結果をプリント</span></span><br><span class="line">  Serial.<span class="built_in">print</span>(refrigeratorTemp);</span><br><span class="line">  Serial.<span class="built_in">print</span>(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">  Serial.<span class="built_in">println</span>(outsideTemp);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 送信するJSON文字列生成</span></span><br><span class="line">  String requestStr = <span class="built_in">String</span>(<span class="string">&quot;&#123;\&quot;data\&quot;:[&quot;</span>) +</span><br><span class="line">                      <span class="string">&quot;&#123;\&quot;success\&quot;:&quot;</span> + <span class="built_in">String</span>(refrigeratorTemp != DEVICE_DISCONNECTED_C) + <span class="string">&quot;,\&quot;temp\&quot;:&quot;</span> + <span class="built_in">String</span>(refrigeratorTemp, <span class="number">2</span>) + <span class="string">&quot;&#125;,&quot;</span> +</span><br><span class="line">                      <span class="string">&quot;&#123;\&quot;success\&quot;:&quot;</span> + <span class="built_in">String</span>(outsideTemp != DEVICE_DISCONNECTED_C) + <span class="string">&quot;,\&quot;temp\&quot;:&quot;</span> + <span class="built_in">String</span>(outsideTemp, <span class="number">2</span>) + <span class="string">&quot;&#125;]&#125;&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// データ送信</span></span><br><span class="line">  <span class="built_in">apiRequest</span>(requestStr);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">apiRequest</span><span class="params">(String body)</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 指定されたデータを送信する</span></span><br><span class="line">  <span class="keyword">if</span> (WiFi.<span class="built_in">status</span>() == WL_CONNECTED) &#123;</span><br><span class="line">    HTTPClient http;</span><br><span class="line">    http.<span class="built_in">setFollowRedirects</span>(HTTPC_STRICT_FOLLOW_REDIRECTS);</span><br><span class="line">    http.<span class="built_in">begin</span>(requestURL);</span><br><span class="line">    http.<span class="built_in">addHeader</span>(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>);</span><br><span class="line">    <span class="type">int</span> httpResponseCode = http.<span class="built_in">POST</span>(body);</span><br><span class="line">    <span class="keyword">if</span> (httpResponseCode &lt; <span class="number">0</span>) &#123;</span><br><span class="line">      Serial.<span class="built_in">print</span>(<span class="string">&quot;通信に失敗しました。&quot;</span>);</span><br><span class="line">      Serial.<span class="built_in">println</span>(httpResponseCode);</span><br><span class="line">    &#125;</span><br><span class="line">    http.<span class="built_in">end</span>();</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    Serial.<span class="built_in">println</span>(<span class="string">&quot;Wi-Fi接続に失敗しました。&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>こちらのプログラムをESP32に書き込み、実行してみます。</p>
<p>するとスプシ側に送信したデータが書き込まれていることを確認できました。</p>
<img src="/images/2024/20240423a/image_9.png" alt="" width="405" height="348" loading="lazy">

<p>試しに1日動かして結果をプロットしてみました。<br>室内温度は昼間になるにつれて温度が高くなっていき、15時過ぎごろにピークを迎えます。<br>冷蔵庫温度は細かな上がり下がりが確認できますが、1日中一定を保っています。おそらく基準の温度があってそこを超えたら冷却をON、下回ったらOFFにするような制御が入っているのではと予想できます。</p>
<img src="/images/2024/20240423a/image_10.png" alt="" width="590" height="363" loading="lazy">

<h2 id="まとめ">まとめ</h2><p>IoTで取得したデータをクラウド上にストアすることを考えると、AWSだったりのクラウドサービスを使ったりやHerokuなどのPaaSを利用する方法が考えられますが、インフラの構築コストだったり、利用料金がかかってしまいます。</p>
<p>ですが今回紹介したGoogleSpreadSheetとGASを利用する方法では、無料ですし、たった数行のコードを書くだけでWebアプリをデプロイできるのが強みだと思います。</p>
<p>またGASのトリガーを使えば定期的にスクリプトを実行できるので、例えば温度が規定値を上回った場合にメール通知を送ることなども実装できるので、応用すれば色々できそうです。</p>
<h2 id="参考URL">参考URL</h2><ul>
<li>ESP32開発ボード Arduino IDE開発環境の構築</li>
<li>温度センサー DS18B20をESP32で動かすメモ</li>
<li>DS18B20 Programmable Resolution 1-Wire Digital Thermometer</li>
<li>外部からJSONをPOSTするだけでGoogleSpreadSheetにデータを書き込む</li>
</ul>
]]></content>
    <summary type="html">IoTという言葉が話題になり始めてから、かなりの時間が経ちました。私自身も身近なIoTに関心を持っていろいろ取り組んできましたが、ソフトウェアだけでなくハードウェアの知識も必要になるなど、初めて挑戦する方にとっては敷居が高いと感じることがしばしばありました。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="ESP32" scheme="https://future-architect.github.io/tags/ESP32/"/>
    <category term="GAS" scheme="https://future-architect.github.io/tags/GAS/"/>
    <category term="スプレッドシート" scheme="https://future-architect.github.io/tags/%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>【合格体験記】（ドローン）二等無人航空機操縦士の振り返り</title>
    <link href="https://future-architect.github.io/articles/20240109a/"/>
    <id>https://future-architect.github.io/articles/20240109a/</id>
    <published>2024-01-08T15:00:00.000Z</published>
    <updated>2024-01-08T15:00:00.000Z</updated>
    <author><name>村上一彦</name></author>
    <content type="html"><![CDATA[<p>こんにちは。公共サービス事業部の村上一彦です。2020年フューチャーに中途入社し、これまで金融業・公共業にわたりシステム開発に従事してきました。</p>
<p>そんな私が、無人航空機操縦士試験（二等）に合格するまでの体験記をご紹介します。</p>
<p>↓著者の相棒 DJI社製 Mini3<br><img fetchpriority="high" src="/images/2024/20240109a/IMG_6263.jpg" alt="IMG_6263.jpg" width="562" height="323"></p>
<h2 id="1-無人航空機操縦士試験（二等）とは">1. 無人航空機操縦士試験（二等）とは</h2><p>無人航空機操縦士試験（二等）とは、ドローン等（無人航空機）に関する唯一の国家資格になります。</p>
<p>2022年12月5日から技能証明制度が施行されており、ドローン等を飛行させるのに必要な技能（知識及び能力）を有することを証明する資格制度です。</p>
<p>資格の取得には、以下の３つの試験をクリアする必要があります。</p>
<p>仮免試験がない車の免許をイメージして頂くと近いと思います。</p>
<ul>
<li>学科試験</li>
<li>実地試験</li>
<li>身体検査</li>
</ul>
<p>また資格には、以下の限定(車で言うとAT限定のようなもの)があり、限定解除する場合は、通常の資格に追加して講習や試験が必要になります。</p>
<dl>
<dt>無人航空機の種類（機体の種類）</dt>
<dd> 回転翼航空機（マルチローター）</dd>
<dd> 回転翼航空機（ヘリコプター）</dd>
<dd> 飛行機（固定翼）</dd>

<dt>無人航空機の種類（機体の重量）</dt>
<dd>上記の機体の種類それぞれに対して最大離陸重量25kg未満</dd>

<dt>飛行の方法</dt>
<dd>目視内飛行(モニターを見ず、機体を目視しながら飛行させること)</dd>
<dd>昼間飛行</dd>
</dl>

<p>著者は、回転翼航空機（マルチローター）で資格を取得し、他の限定解除は行っておりません。</p>
<h2 id="2-無人航空機操縦士のメリットと有用性">2. 無人航空機操縦士のメリットと有用性</h2><p>この資格は以下のメリットがあります。</p>
<ol>
<li>認証済みの機体と合わせて申請することで、（ルールの範囲内で）自由に飛ばすことができる。</li>
<li>国土交通省への飛行許可申請の際に、一部手続きを省略できる。</li>
<li>建設業等で入札条件に含まれている。</li>
</ol>
<p>著者も飛行許可申請を出していますが、実際に手続きを一部省略して申請しています。</p>
<p>しかしドローンを取り巻く環境は、まだ成長途中です。国家資格を利用して、（ルールの範囲内で）自由に飛ばすことができるようになるには、以下の２つをクリアすることが必要です。</p>
<ul>
<li>操縦者技能証明（無人航空機操縦士の資格）</li>
<li>型式認証済みの機体 or 機体認証済みの機体</li>
</ul>
<p>現在、個人で買えるような価格帯で、型式認証をクリアしている機体が少なく、この資格が効力を発揮できない状況となっております。</p>
<p>今後、メーカーが基準を満たす機体を幅広く製造してくれることを願っています。</p>
<p>※なお、上記認証済みでない機体でも、国土交通省への飛行許可申請を実施すれば、（ルールの範囲内で）ドローン自体は飛ばせます。</p>
<p>著者がドローンに興味を持った経緯として、物流を担うシステムを担当したことがきっかけとなっています。物流業界は2024年問題をはじめ、あらゆる課題がある業界です。そして、ドローンはその解決策の1つであると同時に、複雑な法制度や制約に縛られているということを知り、自分で勉強してその壁を超えたいと思うようになりました。ちょうど国家資格が始まったこともあり、国家資格を取りながら体系的に学びたいと思い、今回チャレンジすることにしました。</p>
<h2 id="3-資格取得までの道のり">3. 資格取得までの道のり</h2><p>資格取得までの道のりは、以下の通りです。全体で2か月程かかりました。</p>
<img src="/images/2024/20240109a/資格取得フロー.png" alt="資格取得フロー.png" width="570" height="523" loading="lazy">

<p>こちらの順で詳細を紹介させて頂きます。</p>
<ol>
<li>ドローンスクールに入会</li>
<li>学科試験の講習→受験</li>
<li>実地試験の講習→受験</li>
<li>身体検査</li>
<li>証明書交付</li>
</ol>
<h3 id="1-ドローンスクールに入会">1.ドローンスクールに入会</h3><p>まずは、無人航空機操縦士試験に対応しているドローンスクールを選びました。</p>
<p>著者は、株式会社モビリティテクノのドローンマスターズスクール(通称DMS)を選びました。</p>
<p>公式サイト：https://drone-school.mobility-techno.jp</p>
<p>選んだ理由は、「都内にある」「価格が安い」「練習場が利用できる」という理由でした。当時の基準で選んでいるため、選ぶ際は最新の情報で確認をお願いします。2023年12月22日時点では、二等資格の初学者コースは25万円(税込)に設定されておりました。</p>
<h3 id="2-学科試験の講習→受験">2.学科試験の講習→受験</h3><p>スクールに入校後は、e-learningで学科試験の勉強が始まります。</p>
<p>全体で約8時間＋確認テストの内容になっています。</p>
<ol>
<li>はじめに</li>
<li>無人航空機操縦者の心得</li>
<li>無人航空機に関する規則</li>
<li>無人航空機のシステム</li>
<li>無人航空機の操縦者及び運航体制</li>
<li>運行上のリスク管理</li>
</ol>
<p>受験はテストセンターで受験しました。</p>
<p>無事、一発合格できました。</p>
<h3 id="3-実地試験の講習→受験">3.実地試験の講習→受験</h3><p>実地試験は以下のようなスケジュールです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="center">日数</th>
<th align="center">時間</th>
<th align="center">内容</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1日目</td>
<td align="center">10:00～16:30</td>
<td align="center">机上試験対策＋実技対策</td>
</tr>
<tr>
<td align="center">2日目</td>
<td align="center">10:00～16:30</td>
<td align="center">机上試験対策＋実技対策</td>
</tr>
<tr>
<td align="center">3日目</td>
<td align="center">約1時間</td>
<td align="center">実地試験</td>
</tr>
</tbody></table></div>
<p>1,2日目は東京足立校で実施し、3日目の試験は埼玉浦和校で実施しました。</p>
<p>実施講習の中で、操縦時間が目一杯あるわけではなく、技能に不安を感じていたので、モバイルアプリで300円ほど課金して、練習もしました。</p>
<p>実地試験では、満点合格で試験管の人に技能を褒めて頂けました。</p>
<p>試験会場の様子はこちらです。</p>
<h3 id="4-身体検査">4.身体検査</h3><p>著者は、普通自動車運転免許を持っていたため、システム上の手続きで完了しました。</p>
<p>検査内容は、以下のように定められています。</p>
<p>https://ua-remote-pilot-exam.com/</p>
<blockquote>
<p>身体検査は、①有効な公的証明書の提出、②-1医療機関の診断書の提出（一等25㎏未満限定及び二等）、②-2医療機関の診断書の提出（一等25kg以上）、③指定試験機関の身体検査受検（一等25㎏未満限定及び二等）のいずれかの方法で受検ができます。</p>
</blockquote>
<div class="scroll"><table>
<thead>
<tr>
<th align="center">項目</th>
<th align="left">身体検査基準</th>
</tr>
</thead>
<tbody><tr>
<td align="center">視力</td>
<td align="left">視力が両眼で0.7以上、かつ、一眼でそれぞれ0.3以上であること、または一眼の視力が0.3に満たない者若しくは一眼が見えない者については、他眼の視野が左右150度以上で、視力が0.7以上であること。</td>
</tr>
<tr>
<td align="center">色覚</td>
<td align="left">赤色、青色及び黄色の識別ができること。</td>
</tr>
<tr>
<td align="center">聴力</td>
<td align="left">後方2メートルの距離から発せられた通常の強さの会話の音声が正しく聞き取れること。</td>
</tr>
<tr>
<td align="center">一般</td>
<td align="left">（1）施行規則第236条の62第4項第1号または第2号にあげる身体の障害が無いこと。<br>（2）（1）に定めるもののほか、無人航空機の安全な飛行に必要な認知または操作のいずれかに係る能力を欠くこととなる四股又は体幹の障害があるが、法第132条の44の規定による条件を付すことにより、無人航空機の安全な飛行に支障を及ぼす恐れがないと認められること。</td>
</tr>
</tbody></table></div>
<h3 id="5-技能証明書交付">5.技能証明書交付</h3><p>ここまでで、技能証明書交付までの要件が完了したため、システム上で発行手続きを行い、2週間ほどで手元に証明書が届きました。</p>
<h2 id="4-ドローンを飛ばしてみて">4. ドローンを飛ばしてみて</h2><p>ドローンの飛行には、国土交通省への飛行許可申請や、関係各所への許可取り等も行う必要があります。警察署に連絡したり、土木管理事務所に使用許可申請を提出したりすることもあります。</p>
<p>また、著者は新たにドローンを購入したため、機体登録、リモートID（車のナンバーのようなもの）の登録など、様々な手続きを行っています。</p>
<p>それらを完了させて、実際に撮影した写真はこちらになります（実際は映像ですがここでは静止画で紹介となります）。</p>
<img src="/images/2024/20240109a/image.png" alt="image.png" width="889" height="504" loading="lazy">
<img src="/images/2024/20240109a/image_2.png" alt="image.png" width="1200" height="554" loading="lazy">

<h2 id="5-まとめ">5.まとめ</h2><p>ドローンを飛ばすのはとても楽しく、やってみたら多くの人がはまると思います。しかし、危険性も高く、法律も複雑です。何も理解しないまま飛行させると、知らぬ間に違法な飛行として、警察のお世話になる可能性や、重大な事故に発展する可能性もあります。</p>
<p>そのため、しっかり勉強したうえで取り組む必要があると思います。また、ドローンを飛ばしているだけではなかなか価値が生まれませんが、武器の1つになると思っています。これからも、楽しみながら、よりよいドローンの活用を考え実践していきたいと思います。</p>
]]></content>
    <summary type="html">無人航空機操縦士試験（二等）に合格するまでの体験記をご紹介させて頂きます</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="ドローン" scheme="https://future-architect.github.io/tags/%E3%83%89%E3%83%AD%E3%83%BC%E3%83%B3/"/>
    <category term="合格記" scheme="https://future-architect.github.io/tags/%E5%90%88%E6%A0%BC%E8%A8%98/"/>
  </entry>
  <entry>
    <title>Raspberry PiからArduinoに接続したLCDに文字を表示する</title>
    <link href="https://future-architect.github.io/articles/20230905a/"/>
    <id>https://future-architect.github.io/articles/20230905a/</id>
    <published>2023-09-04T15:00:00.000Z</published>
    <updated>2023-09-04T15:00:00.000Z</updated>
    <author><name>水田祐介</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>初めまして。製造（PLMソリューション）所属&#x2F;九州在住　の水田です。</p>
<p>夏の自由研究連載2023の第3弾の方を執筆させていただきます。</p>
<p>今回が技術ブログ初投稿になります。不慣れな部分もありますが、なるべく分かりやすく書くように心がけていますので、最後までお付き合いください。</p>
<h2 id="研究テーマについて">研究テーマについて</h2><p>漠然と</p>
<ul>
<li>ArduinoやRaspberry Piを触ってみたい</li>
<li>ArduinoにWebサーバを構築してみたい</li>
</ul>
<p>…と思ったので<strong>Raspberry Piに立てたWebサーバのブラウザから好きな文字列を入力してArduinoに接続したLCDに表示する</strong> までをやっていきます。</p>
<p>最終的には、在宅勤務している部屋のドアに設置して、スマホ経由でブラウザにアクセスして会議中であることを表示できればいいなとか考えています。</p>
<p>あと、色々とハマったのでその辺りも書いておこうかと思います。</p>
<p>※長くなるのでRaspberry PiやArduinoの基本的な部分の話は、割愛しています。</p>
<h2 id="構成-予定">構成(予定)</h2><p>最終的に以下の図のような構成を予定しています。</p>
<img fetchpriority="high" src="/images/2023/20230905a/image.png" alt="" width="987" height="197">

<h2 id="Arduino→LCDへの表示">Arduino→LCDへの表示</h2><p>まずは、LCDに任意の文字列を表示してみます。</p>
<h3 id="使用するArduinoについて">使用するArduinoについて</h3><p>家にあるArduinoを使おうとしたのですが、どのLCDを買えばよいのかがよく分からなかったので、新しくスターターキットを購入しました。値段の割に基本的なセンサー類からLCDやコントローラーまでついている上にサンプルコードまでついていて分かりやすいです。</p>
<p>ELEGOO Arduino用UNO R3スターターキット レベルアップ チュートリアル付 mega2560 r3 nanoと互換 [並行輸入品]</p>
<p>以下、実際につないで動かしてみた画像と使用したソースコードです。</p>
<img src="/images/2023/20230905a/lcd.jpg" alt="" width="1200" height="900" loading="lazy">

<figure class="highlight cpp"><figcaption><span>Arduino</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">// LCDを使用するためのライブラリをインクルード</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;LiquidCrystal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Arduinoから出力するPin番号とLCDのPin番号との対応を定義</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> rs = <span class="number">7</span>, en = <span class="number">8</span>, d4 = <span class="number">9</span>, d5 = <span class="number">10</span>, d6 = <span class="number">11</span>, d7 = <span class="number">12</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// LCDの初期化（使用するPin番号の指定）</span></span><br><span class="line"><span class="function">LiquidCrystal <span class="title">lcd</span><span class="params">(rs, en, d4, d5, d6, d7)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setup</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  lcd.<span class="built_in">begin</span>(<span class="number">16</span>, <span class="number">2</span>);             <span class="comment">// 桁数と行数の指定</span></span><br><span class="line">  lcd.<span class="built_in">clear</span>();                  <span class="comment">// 画面のクリア</span></span><br><span class="line">  lcd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">0</span>);          <span class="comment">// カーソル位置の指定（1行目の1文字目に設定）</span></span><br><span class="line">  lcd.<span class="built_in">print</span>(<span class="string">&quot;Hello, World!&quot;</span>);   <span class="comment">// 文字出力</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  lcd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">1</span>);          <span class="comment">// カーソル位置の指定（2行目の1文字目に設定）</span></span><br><span class="line">  lcd.<span class="built_in">print</span>(<span class="built_in">millis</span>() / <span class="number">1000</span>);   <span class="comment">// 文字出力</span></span><br><span class="line">  lcd.<span class="built_in">print</span>(<span class="string">&quot; [sec]&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<div class="note-container info"><span class="fa-check-circle"></span><div><p>以下の理由で少しハマりました。ハードウェアは原因の特定が難しい、、、<br></p>
<ul>
<li>可変抵抗の初期不良なのかブレッドボードにうまく刺さらない為、LCDのコントラストがうまく調整できず文字が表示できなかった。→可変にする必要もないので固定抵抗で対応<br></li>
<li>LCDもブレッドボードにちゃんと刺さらないので、出力されなかったり文字化けが発生したりした</li>
</ul></div></div>

<h2 id="Raspberry-Pi-の準備">Raspberry Pi の準備</h2><p>Raspberry Piについては、昔購入したModel Bを使用。OSの状態とか覚えていないので、まずは付属のSDカードよりRaspian（wheezy）を再インストール＆以下の作業を実施</p>
<ul>
<li>packageの更新</li>
<li>python3とpipの更新</li>
<li>pyserial（＝シリアル通信で使用するPythonモジュール）のインストール</li>
</ul>
<div class="note-container info"><span class="fa-check-circle"></span><div><p>wheezyはサポートが切れているので、新しいバージョン（BusterとかBullseeye）にしたかったのですがRaspberry Pi Imagerを使用して書き込むとOSが全然立ち上がってくれない為、諦めてwheezyでやっています。</p></div></div>

<h3 id="packageの更新">packageの更新</h3><p>apt-getがうまく動かなかったので苦労しました。</p>
<p>色々と調べてみるとapt-getでパッケージを更新する際に使用するミラーサイトのURLが古いのが原因だったみたいです。以下に記載があるURLを最新のURLに書き換えて対応しました。</p>
<ul>
<li>&#x2F;etc&#x2F;apt&#x2F;sources.list</li>
<li>&#x2F;etc&#x2F;apt&#x2F;sources.list.d&#x2F;collabora.list</li>
</ul>
<h3 id="python3とpipの更新">python3とpipの更新</h3><p>apt-getでインストールしようとするとエラーになるのでこっちも苦労しました。色々と調べた結果、pyenvを使用してインストールするとうまくいきました。</p>
<h3 id="pyserial（＝シリアル通信で使用するPythonモジュール）のインストール">pyserial（＝シリアル通信で使用するPythonモジュール）のインストール</h3><p>pipでインストールしたらすぐに終わりました。</p>
<h2 id="Raspberry-Pi-→Arduinoへシリアル通信">Raspberry Pi →Arduinoへシリアル通信</h2><p>Raspberry PiとArduinoの準備が大体整ったので試しにシリアル通信を行ってみます。<br>以下のような文字列送信用のPythonを用意します。</p>
<div class="note-container info"><span class="fa-check-circle"></span><div><p>ser.open()時にPermissionエラーが出た場合は、portに指定しているポートに対して権限を付与すれば大丈夫です。<br>例）sudo chmod 666 &#x2F;dev&#x2F;ttyACM0</p></div></div>

<figure class="highlight python"><figcaption><span>Raspberry Pi（送信側）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> serial</span><br><span class="line"></span><br><span class="line">ser = serial.Serial()</span><br><span class="line">ser.port = <span class="string">&#x27;/dev/ttyACM0&#x27;</span></span><br><span class="line">ser.baundrate = <span class="number">9600</span></span><br><span class="line">ser.<span class="built_in">open</span>()</span><br><span class="line"></span><br><span class="line">data = <span class="string">&#x27;NOW MEETING...  16:30 - 17:30&#x27;</span></span><br><span class="line">ser.write(data.encode())</span><br><span class="line">ser.close()</span><br></pre></td></tr></table></figure>

<p>Arduino側は、上記コードを以下のように書き換えます。なお、送られてくる文字列は1文字目～16文字目までを1行目に、17文字目～32文字目までを2行目に出す想定にしています</p>
<figure class="highlight cpp"><figcaption><span>Arduino（受信側）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">// LCDを使用するためのライブラリをインクルード</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;LiquidCrystal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Arduinoから出力するPin番号とLCDのPin番号との対応を定義</span></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> rs = <span class="number">7</span>, en = <span class="number">8</span>, d4 = <span class="number">9</span>, d5 = <span class="number">10</span>, d6 = <span class="number">11</span>, d7 = <span class="number">12</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// LCDの初期化（使用するPin番号の指定）</span></span><br><span class="line"><span class="function">LiquidCrystal <span class="title">lcd</span><span class="params">(rs, en, d4, d5, d6, d7)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setup</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  lcd.<span class="built_in">begin</span>(<span class="number">16</span>, <span class="number">2</span>);             <span class="comment">// 桁数と行数の指定</span></span><br><span class="line">  lcd.<span class="built_in">clear</span>();                  <span class="comment">// 画面のクリア</span></span><br><span class="line">  lcd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">0</span>);          <span class="comment">// カーソル位置の指定（1行目の1文字目に設定）</span></span><br><span class="line">  lcd.<span class="built_in">print</span>(<span class="string">&quot;INPUT...OK&quot;</span>);      <span class="comment">// 文字出力</span></span><br><span class="line">  Serial.<span class="built_in">begin</span>(<span class="number">9600</span>);           <span class="comment">// ボーレートの設定</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (Serial.<span class="built_in">available</span>())                         <span class="comment">// シリアル通信が有効かどうかの確認</span></span><br><span class="line">  &#123;</span><br><span class="line">    String input = Serial.<span class="built_in">readString</span>();           <span class="comment">// 文字列取得</span></span><br><span class="line">    lcd.<span class="built_in">clear</span>();                                  <span class="comment">// 画面のクリア</span></span><br><span class="line">    lcd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">0</span>);                          <span class="comment">// カーソル位置の指定（1行目の1文字目に設定）</span></span><br><span class="line">    lcd.<span class="built_in">print</span>(input);                             <span class="comment">// 文字出力</span></span><br><span class="line"></span><br><span class="line">    String input2 = <span class="built_in">String</span>(input.<span class="built_in">substring</span>(<span class="number">16</span>));  <span class="comment">// 文字列の後半部分を抜き出し</span></span><br><span class="line">    lcd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">1</span>);                          <span class="comment">// カーソル位置の指定（2行目の1文字目に設定）</span></span><br><span class="line">    lcd.<span class="built_in">print</span>(input2);                            <span class="comment">// 文字出力</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>以下、結果です。<br><img src="/images/2023/20230905a/2.jpg" alt="" width="1200" height="1600" loading="lazy"></p>
<h3 id="うまくいっているように見えますが">うまくいっているように見えますが</h3><p>実は、うまくいっていないんです。。。笑</p>
<p>具体的に書くと</p>
<ul>
<li>上記Pythonファイルを叩いても、LCDが点滅するだけ</li>
<li>Pythonファイルの内容をPython3のコマンドラインに張り付けて実行<br>→LCDに表示される（上記は、この時の写真となります）</li>
</ul>
<p>調べてみると、Arduino Unoには「USB経由でシリアル通信した場合にDTR端子の状態がLOWであればリセットがかかる」仕様があるらしくこれが原因でLCDがリセットされているようです。DTR端子をHIGHにすればよいのでは？ とは思うので設定してみてはいるのですがなぜかうまくいかない。</p>
<h3 id="仮説">仮説</h3><p>open()後にsleepを入れるとうまくいくのでは？ という仮説が出てきました。<br>ファイル実行では、open→writeまでの処理に間が無いので、open処理完了→DTRリセット処理　のリセット中に書き込みを行っていてうまくいっていないような気がしてきました。</p>
<p>コマンドライン実行では、ser.open()→数秒後にser.writeを実行という流れなので、数秒の間にリセット処理が完了→LCDへの書き込みがうまくいくという流れのような気がしています。</p>
<p>コマンドライン実行時のopen処理直後のLCDを見ていると、open直後のタイミングでLCDにリセットがかかっているように見えます。試す価値がありそうです。</p>
<p>（open＝シリアル通信開始なので当たり前と言ったら当たり前ですが）、、、</p>
<p>以下のようにsleepを2秒入れて実行してみました。</p>
<figure class="highlight python"><figcaption><span>Raspberry Pi（送信側）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> serial</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"></span><br><span class="line">ser = serial.Serial()</span><br><span class="line">ser.port = <span class="string">&#x27;/dev/ttyACM0&#x27;</span></span><br><span class="line">ser.baundrate = <span class="number">9600</span></span><br><span class="line">ser.<span class="built_in">open</span>()</span><br><span class="line">time.sleep(<span class="number">2</span>)</span><br><span class="line"></span><br><span class="line">data = <span class="string">&#x27;NOW MEETING...  16:30 - 17:30&#x27;</span></span><br><span class="line">ser.write(data.encode())</span><br><span class="line">ser.close()</span><br></pre></td></tr></table></figure>

<p>結果としてはうまくいきました。仮説が当たっていたみたいです。</p>
<p>個人的には、以下の理由で上記方法は気に入らないのですがひとまず、これで進めます。</p>
<ul>
<li>本来であればDTR端子を何とかする（＝設定やらなんやらで何とかする）のが筋</li>
<li>sleepで解決するのが力技っぽくて、あまり好みではない</li>
</ul>
<h3 id="よくよく考えると、、、">よくよく考えると、、、</h3><p>sleepを入れない方向でもいいのでは？ と考えています。</p>
<p>まず、Webサーバの方に必要なモジュールをブラウザに表示する内容から考えると以下のようになると考えています。</p>
<p><strong>ブラウザ表示（仮）</strong><br><img src="/images/2023/20230905a/image_2.png" alt="" width="597" height="301" loading="lazy"></p>
<ul>
<li>シリアル通信開始（接続ボタン押下）</li>
<li>シリアル通信終了（切断ボタン押下）</li>
<li>文字列送信（送信ボタン押下）</li>
</ul>
<p>上記のようなUIで考えると、シリアル通信開始～送信ボタン押下までには、タイムラグがあると思います。</p>
<p>仮に接続ボタンを無くしたようなUIであっても、文字列送信部分に入力するのに数秒はかかるはずなので、sleepは無くて良いと想像しています。<br><span style="font-size: 70%;">（接続ボタン押下からコンマ数秒で入力する超人または、RTA挑戦者は考えていないです）</span></p>
<p>そのため、この後の方向としては、以下のようになると考えています。</p>
<ul>
<li>文字列送信については、sleepを入れない形でひとまず実装を進める</li>
<li>上記実装で問題が発生した場合、改めてsleepを入れることを検討する</li>
</ul>
<h2 id="おわりに">おわりに</h2><h3 id="Webサーバは？">Webサーバは？</h3><p>という声が聞こえてきそうですが、これ以上自由研究に作業時間が取れそうにないので今回はここまでとさせていただきます。今後執筆する機会があれば、続きを書きます。</p>
<p>メモ：残作業</p>
<ul>
<li>Webサーバ構築（Apache?nginx?マシンスペックが弱いので軽いのがいい）</li>
<li>マイコンへの書き込み及び、基盤へのはんだ付け作業</li>
<li>マイコンとRaspberry Piを設置するようの箱を用意（木？ 近所に3Dプリンターが使える店があるのでプラスチック？ )</li>
<li>ドアへの設置</li>
</ul>
<h3 id="あとがき">あとがき</h3><p>初めてArduinoやRaspberry Piに触ってみましたが色々とトラブルが続いて思うように進まなかったです。ハードウェア周りは、未経験だったので、良い経験にはなったかなと考えています。</p>
<p>あと、今回のような誰かに見せるようなものを書いたりすると、アウトプットの仕方を考えたりする練習にもなると思うので、書いたことが無くて二の足を踏んでいるような方がいれば、挑戦してみると良い経験になるかと思います。</p>
<p>では、また執筆する機会があればお会いしましょう</p>
]]></content>
    <summary type="html">Raspberry Piに立てたWebサーバのブラウザから好きな文字列を入力してArduinoに接続したLCDに表示するまでをやっていきます</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Arduino" scheme="https://future-architect.github.io/tags/Arduino/"/>
    <category term="RaspberryPi" scheme="https://future-architect.github.io/tags/RaspberryPi/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>音声合成サービス比較検証</title>
    <link href="https://future-architect.github.io/articles/20230620a/"/>
    <id>https://future-architect.github.io/articles/20230620a/</id>
    <published>2023-06-19T15:00:00.000Z</published>
    <updated>2023-06-19T15:00:00.000Z</updated>
    <author><name>寺迫太吾</name></author>
    <content type="html"><![CDATA[<h2 id="1-はじめに">1. はじめに</h2><p>フューチャーアーキテクトでアルバイトをしている寺迫です。</p>
<p>代表的な音声合成サービスの使用方法や性能の違いについて調査しました。</p>
<p><strong>音声合成(text-to-speech)の概要</strong></p>
<ul>
<li>コンピュータを用いて音声を生成する技術(主に人間の発話音声)</li>
<li>深層学習・強化学習における音声合成モデルの性能向上により、自然な音声を生成することが出来る</li>
<li>応用分野<br>  - 音声案内システム：券売機や電話案内、ナビゲーション等に利用される<br>  - バーチャルキャラクター:キャラクターのセリフの作成に利用される<br>  - 失声症の方への支援</li>
</ul>
<p><strong>想定読者</strong></p>
<ul>
<li>音声合成サービスを試してみたい人</li>
<li>品質の良い音声合成を行いたい人</li>
<li>音声合成サービスの差異を確認したい人</li>
</ul>
<p><strong>記事に含まれない内容</strong></p>
<ul>
<li>音声合成モデルの説明</li>
<li>音声合成時の具体的な読み間違い・イントネーションなどの調整方法</li>
<li>英語言語の音声合成</li>
</ul>
<div class="note-container info"><span class="fa-check-circle"></span><div><p>付録として、本記事で比較を行っていない音声合成サービスのリンク集(サンプル音声、音声合成デモ)を表にまとめています。</p></div></div>

<div class="note-container warn"><span class="fa-check-circle"></span><div><p>本記事の調査は2023年5月に行ったものです。最新の声色の種類や音声合成モデルの変更などのアップデート情報は各サービスドキュメント等の参照を推奨します。</p></div></div>

<h2 id="2-音声合成サービス比較">2. 音声合成サービス比較</h2><h2 id="2-1-比較方法">2.1 比較方法</h2><h3 id="比較サービス一覧">比較サービス一覧</h3><p>音声合成サービスのうち、（1）商用利用可能 （2）API提供あり（3）公式ドキュメントの充実（4）発音調整が可能なものを比較しました。</p>
<ul>
<li>Google Cloud Platform text-to-speech(以下、GCP TTS)</li>
<li>Amazon Polly(以下、Polly)</li>
<li>Azure text-to-speech(以下、Azure TTS)</li>
<li>VOICEVOX</li>
</ul>
<h3 id="比較観点">比較観点</h3><p>8項目を比較観点としました。</p>
<ul>
<li>声色の種類</li>
<li>音声合成の品質<ul>
<li>音質</li>
<li>ノイズ</li>
<li>読み間違い</li>
<li>なまり(アクセント、イントネーション)</li>
</ul>
</li>
<li>辞書機能</li>
<li>音声調整の容易さ</li>
<li>実装負荷(Software Development Kitの有無)</li>
<li>利用料金</li>
<li>クォータリクエスト上限</li>
<li>入力文字制限</li>
</ul>
<h3 id="比較に使用したテキストデータ">比較に使用したテキストデータ</h3><p>平均100文字の日本語テキスト約35文で品質の評価を行いました。</p>
<h2 id="2-2-比較結果詳細">2.2 比較結果詳細</h2><h3 id="声色の種類">声色の種類</h3><div class="note-container warn"><span class="fa-check-circle"></span><div><p>声色の種類は日本語に限定しています。その他の言語については、公式ドキュメントの参照を推奨します。</p></div></div>

<p>声色の種類はVOICEVOXが多いです。アニメからアナウンスまで多彩な状況にマッチする声色が揃っています。</p>
<p>GCP TTS、Polly、Azure TTSはアナウンス、会話音声に向いている声色の種類があります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>声色の種類</th>
<th>参照元</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>・20種類のキャラクターVOICEと複数の発話タイプ合計46種類から選択が可能である。 <br>・声色によってクレジット表記や契約が必要なものがある。</td>
<td>VOICEVOXホームページ</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>・「BASIC<sup id="fnref:1">1</sup>」,「WaveNet<sup id="fnref:2">2</sup>」,「Neural2<sup id="fnref:3">3</sup>」 各自４パターンの声色が設定可能である。 <br> ・BASIC,WaveNetの音声はロボットが発話しているような音声が生成される。　<br>・Neural2音声は人間のように自然である。</td>
<td>Google Cloud ドキュメント</td>
</tr>
<tr>
<td align="left">Polly</td>
<td>・「standard<sup id="fnref:4">4</sup>」2種類、「neural<sup id="fnref:5">5</sup>」3種類　<br>・neuralの音声は人間のように自然である。</td>
<td>Amazon Polly ドキュメント</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>・「neural<sup id="fnref:5">5</sup>」7種類　<br>・音声は人間のように自然である。</td>
<td>Azure Cognitive Services ドキュメント</td>
</tr>
</tbody></table></div>
<h3 id="品質">品質</h3><div class="note-container warn"><span class="fa-check-circle"></span><div><p>音声合成の品質は定性的な評価であり、実際に使用するテキストを用いて品質確認を推奨します。</p></div></div>

<div class="note-container warn"><span class="fa-check-circle"></span><div><p>品質の評価は以下の声色で行いました。VOICEVOX(波音リツ)、GCP TTS(ja-JP-Neural2-B)、Polly(neural Takumi)、Azure TTS(neural 七海)</p></div></div>

<p>Azure TTS &gt; Polly &gt; GCP TTS &gt; VOICEVOXの順番で合成音声の品質が良いです。VOICEVOXはノイズが発生したり、イントネーションが不自然な時があります。Azure TTS、Polly、GCP TTSに関して、無調整だと少し不自然なところがあります。しかし、アクセント、イントネーションの調整によって、自然な音声を生成できます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>サービス名</th>
<th align="center">音質</th>
<th align="center">ノイズ</th>
<th align="center">読み間違い</th>
<th align="center">なまり</th>
<th>備考</th>
</tr>
</thead>
<tbody><tr>
<td>VOICEVOX</td>
<td align="center">△</td>
<td align="center">ｘ</td>
<td align="center">△</td>
<td align="center">△</td>
<td>ノイズが発生する確率が高く、イントネーションが不自然である。</td>
</tr>
<tr>
<td>GCP TTS</td>
<td align="center">〇</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td>ロボットのような平坦な発声になる場合がある。</td>
</tr>
<tr>
<td>Polly</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td>不自然な間(無音)が挿入される場合がある。</td>
</tr>
<tr>
<td>Azure TTS</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td align="center">◎</td>
<td>品質が良く、違和感を感じるところは少ない。</td>
</tr>
</tbody></table></div>
<h3 id="辞書機能">辞書機能</h3><p>GCP TTS以外辞書機能を備えています。登録できる単語数の上限が決められていることや、辞書ファイルをpublicなURIに公開が必要など扱いづらいところがあります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>辞書機能</th>
<th>参照元</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>・読み間違い、アクセント、エイリアス<sup id="fnref:6">6</sup>の登録が可能<br></td>
<td>VOICEVOXホームページ</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>・ドキュメントに記載なし</td>
<td></td>
</tr>
<tr>
<td align="left">Polly TTS</td>
<td>・XML形式のカスタム辞書ファイルを登録可能<br>・読み間違い、アクセント、エイリアス<sup id="fnref:6">6</sup>の登録が可能<br> ・100辞書の登録が可能<br>・１辞書当たり4000文字登録可能<br>・音声生成時、5辞書指定可能</td>
<td>レキシコンの管理</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>・XML構造化カスタム辞書ファイルを登録可能　<br>・辞書機能を使用するためにpublicなURIにファイルを配置する必要がある。 <br>・読み間違い、アクセント、エイリアス<sup id="fnref:6">6</sup>の登録が可能<br>・辞書の最大ファイルサイズは100KB(日本語約51,200文字)に制限</td>
<td>Azure Cognitive Services ドュメント</td>
</tr>
</tbody></table></div>
<h3 id="利用料金">利用料金</h3><p>VOICEVOXは一部の声色の商用利用が有料であるが基本無料です。</p>
<p>GCP TTS、Polly、Azure TTSは無料枠を除き、同一料金の従量課金です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>無料枠</th>
<th>超過分</th>
<th>参照元</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>・基本無料<br></td>
<td>・基本無料</td>
<td>VOICEVOXホームページ</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>・BASIC<sup id="fnref:1">1</sup> 1か月あたり 400文字無料<br>・WaveNet<sup id="fnref:2">2</sup> 1か月あたり 100文字無料<br>・Neural2<sup id="fnref:3">3</sup> 1か月あたり 100文字無料</td>
<td>・BASIC 100文字当たり4ドル <br>・WaveNet 100文字当たり16ドル <br>・Neural2 100文字当たり16ドル</td>
<td>Text-to-Speech の料金</td>
</tr>
<tr>
<td align="left">Polly</td>
<td>・standard<sup id="fnref:4">4</sup> 最初のリクエストから12か月間は1か月あたり 500万文字無料<br>・neural<sup id="fnref:5">5</sup> 最初のリクエストから 12 か月間は 1 か月あたり 100 万文字無料<br></td>
<td>・standard 100文字当たり4ドル<br>・neural 100文字当たり16ドル</td>
<td>Amazon Polly 料金</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>・1か月あたり 50万文字無料<br></td>
<td>・100文字当たり16ドル</td>
<td>Speech Services の価格</td>
</tr>
</tbody></table></div>
<h3 id="実装負荷">実装負荷</h3><p>GCP TTS、Polly、Azure TTSは、Software Development Kit(SDK)が準備されているため、容易に実装することが可能です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>実装負荷(SDK有無)</th>
<th align="center">認証必要&#x2F;不要</th>
<th>参照元</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>SDKはない。</td>
<td align="center">不要</td>
<td>VOICEVOX　APIリクエスト実装例</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>C#、Go、Java、Node.js、PHP、Python、RubyでのSDKが用意されている。</td>
<td align="center">必要</td>
<td>Google Cloud ドキュメント</td>
</tr>
<tr>
<td align="left">Polly</td>
<td>Android、JavaScript、iOS、Java、.NET、Node.js、Python、PHP、RubyでのSDKが用意されている。</td>
<td align="center">必要</td>
<td>開発者用リソース</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>C#、Go、Java、Node.js、PHP、Python、RubyでのSDKが用意されている。</td>
<td align="center">必要</td>
<td>Azure Speech Services</td>
</tr>
</tbody></table></div>
<h3 id="音声調整の容易さ">音声調整の容易さ</h3><p>Polly、AzureTTSで使用できる「カナ」を用いた、音声調整はとても直観的で優れています。GCP TTSでの音声調整は国際音素記号を使用が必要なためで学習コストと調整コストが高いです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>音素・アクセント・イントネーションの調整</th>
<th align="center">GUI対応</th>
<th align="center">SSML対応<sup id="fnref:7">7</sup></th>
<th align="center">調整コスト</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>・GUI操作で調整可能である。</td>
<td align="center">〇</td>
<td align="center">x</td>
<td align="center">中</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>・日本語の音素記号がサポートされていないため、&lt;phoneme&gt;タグ内で国際音素記号<sup id="fnref:8">8</sup>を用いて調整を行う。</td>
<td align="center">x</td>
<td align="center">〇</td>
<td align="center">高</td>
</tr>
<tr>
<td align="left">Polly</td>
<td>・&lt;phoneme&gt;タグ内で「’」記号を用いて、直観的に調整可能である。</td>
<td align="center">x</td>
<td align="center">〇</td>
<td align="center">低</td>
</tr>
<tr>
<td align="left">Azure  TTS</td>
<td>・&lt;phoneme&gt;タグ内で「’」記号を用いて、直観的に調整可能である。<br>・ピッチ曲線でイントネーションを視覚的に調整できる。<br>・Audio Content Creation(音声生成GUI)を使用できる。</td>
<td align="center">〇</td>
<td align="center">〇</td>
<td align="center">低</td>
</tr>
</tbody></table></div>
<p>カナを用いた発音調整(読み間違い+アクセント)</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">飴と雨のアクセント例</span><br><span class="line">「雨」は「ア」が高く「メ」が低い、反対に「飴」は「ア」が低く「メ」が高く発音される。</span><br><span class="line"></span><br><span class="line"><span class="comment"># 雨が降る</span></span><br><span class="line">&lt;phoneme alphabet=<span class="string">&quot;x-amazon-pron-kana&quot;</span> ph=<span class="string">&quot;アメ&quot;</span>&gt;雨&lt;/phoneme&gt;が降る</span><br><span class="line"></span><br><span class="line"><span class="comment"># 飴を食べる</span></span><br><span class="line">&lt;phoneme alphabet=<span class="string">&quot;x-amazon-pron-kana&quot;</span> ph=<span class="string">&quot;アメ&#x27;&quot;</span>&gt;飴&lt;/phoneme&gt;を食べる</span><br><span class="line"></span><br><span class="line">橋と箸のアクセント例</span><br><span class="line">「橋」 の「ハ」は低く、「シ」は高く、 「箸」 の「ハ」は高く、「シ」は低く発音される。</span><br><span class="line"></span><br><span class="line"><span class="comment"># 橋を渡る</span></span><br><span class="line">&lt;phoneme alphabet=<span class="string">&quot;x-amazon-pron-kana&quot;</span> ph=<span class="string">&quot;ハシ&quot;</span>&gt;橋&lt;/phoneme&gt;を渡る</span><br><span class="line"></span><br><span class="line"><span class="comment"># 箸を使う</span></span><br><span class="line">&lt;phoneme alphabet=<span class="string">&quot;x-amazon-pron-kana&quot;</span> ph=<span class="string">&quot;ハシ&#x27;&quot;</span>&gt;箸&lt;/phoneme&gt;を使う</span><br></pre></td></tr></table></figure>

<p>VOICEVOX、GUI上での音声調整</p>
<img fetchpriority="high" src="/images/2023/20230620a/VOICEVOX.PNG" alt="VOICEVOX.PNG" width="1200" height="625">

<p>Azure、GUI上でのイントネーション修正</p>
<img src="/images/2023/20230620a/キャプチャ.PNG" alt="キャプチャ.PNG" width="1200" height="706" loading="lazy">

<h3 id="APIクォータ">APIクォータ</h3><p>GCP TTS、Polly、Azure TTSは申請することで上限を引き上げることが出来ます。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>リクエスト上限</th>
<th>参照元</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>記載なし</td>
<td>VOICEVOX　APIリクエスト実装例</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>standard、neuralともに 16tps<sup id="fnref:9">9</sup><br>※申請により上限を上げることができる。</td>
<td>割り当てと上限</td>
</tr>
<tr>
<td align="left">Polly(SynthesizeSpeech API)</td>
<td>standard: 80tps、バースト制限100tps<br>neural: 8tps、バースト制限10tps<br>※申請により上限を上げることができる。</td>
<td>Amazon Polly のクォータ</td>
</tr>
<tr>
<td align="left">Polly(StartSpeechSynthesisTask API)</td>
<td>standard: 10tps、バースト制限12tps<br>neural: 1tps<br>※申請により上限を上げることができる。</td>
<td>Amazon Polly のクォータ</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>200tps<br>申請により上限を上げることができる(MAX1000tps)。</td>
<td>Azure Speech Services</td>
</tr>
</tbody></table></div>
<p>SpeechSynthesisTaskとStartSpeechSynthesisTaskの違い</p>
<div class="scroll"><table>
<thead>
<tr>
<th>API名</th>
<th>レスポンス</th>
<th>データ保存</th>
<th>入力テキスト制限</th>
</tr>
</thead>
<tbody><tr>
<td>SpeechSynthesisTask</td>
<td>合成された音声のバイトストリーム</td>
<td>レスポンス結果を各自でストレージに保存</td>
<td>最大3,000文字</td>
</tr>
<tr>
<td>StartSpeechSynthesisTask</td>
<td>非同期合成タスクオブジェクト</td>
<td>Amazon S3 バケットに保存</td>
<td>最大100,000文字</td>
</tr>
</tbody></table></div>
<h3 id="合成音声の最大長">合成音声の最大長</h3><p>万単位文字の音声合成を行う場合Polly、Azure TTSを使用する必要があります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>生成可能な音声の長さ</th>
<th>入力文字数制限</th>
<th>参照元</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>記述なし</td>
<td>記述なし</td>
<td></td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>記述なし</td>
<td>5,000B<br>(日本語約2,560文字)</td>
<td>割り当てと上限</td>
</tr>
<tr>
<td align="left">Polly(SynthesizeSpeech API)</td>
<td>10分</td>
<td>3,000文字<br>※SSMLタグを除く</td>
<td>Quotas in Amazon Polly</td>
</tr>
<tr>
<td align="left">Polly(StartSpeechSynthesisTask API)</td>
<td>記載なし</td>
<td>100,000文字<br>※SSMLタグを除く</td>
<td>Quotas in Amazon Polly</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>10分</td>
<td>64KB<br>(日本語約33,000文字)</td>
<td>Azure Speech Services</td>
</tr>
</tbody></table></div>
<h2 id="2-3-比較結果まとめ">2.3 比較結果まとめ</h2><p>音声合成サービスを比較した結果の長所、短所をまとめました。</p>
<p>IaaSに依存しない場合、Azure TTS &gt; Polly &gt; GCP TTS &gt; VOICEVOXの順でお勧めです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">サービス名</th>
<th>長所</th>
<th>短所</th>
</tr>
</thead>
<tbody><tr>
<td align="left">VOICEVOX</td>
<td>・基本的に無料(一部音声の商用利用が有料)で使用することができる。<br>・アニメキャラクターのよう声色から朗読まで広範囲の場面に対応できる。<br> ・ソフトウェアのインストールのみで利用できる。<br>・GUIでの直観的な合成の調整が可能である。</td>
<td>・合成音声の品質が他サービスに劣る。<br>・音声調整の癖があり、コツをつかむまで時間がかかってしまう。<br>・調整しても消すことができないノイズが音声する場合がある。</td>
</tr>
<tr>
<td align="left">GCP TTS</td>
<td>SDKが充実している。</td>
<td>・ドキュメントに辞書機能の記述がない。<br>・アクセント、イントネーション調整に「カナ」が使用できないため「国際音素記号<sup id="fnref:8">8</sup>」を使用する必要がある。</td>
</tr>
<tr>
<td align="left">Polly</td>
<td>・SDKが充実している。<br>・neuralで作成した音声は、自然な印象を受ける。<br>・アクセント、イントネーションを「カナ」で直観的に調整可能である。</td>
<td>・自動的に挿入される間(音声の空白時間)が不自然な場合がある。</td>
</tr>
<tr>
<td align="left">Azure TTS</td>
<td>・SDKが充実している。<br>・neuralで作成した音声は、自然な印象を受ける。<br>・アクセント、イントネーションを「カナ」で直観的に調整可能である。<br>・GUIでの直観的な合成の調整が可能である。</td>
<td>・短所に感じる箇所はなかった。</td>
</tr>
</tbody></table></div>
<h2 id="3-おわりに">3. おわりに</h2><p>本記事ではアナウンス、朗読の仕様用途を想定した比較検証の結果をまとめました。</p>
<p>生成モデルは日進月歩で性能が向上し、SOTA(Steate of the art)が続々と塗り替えられています。deep modelの音声合成は特段修正せずとも違和感が少なく驚きました。しかし、イントネーションについては調整を行わなければいけない場合が多少ありました。今後の音声合成モデル改善に期待したいです。</p>
<p>付録に記載した、リンク集は他の記事で見当たりませんでした。サンプル音声の再生や音声合成のデモを簡単に触ることができるのでぜひ試してほしいです。</p>
<p>本記事が音声合成を用いてサービスを作成する人の助けになると幸いです。</p>
<h2 id="4-付録">4. 付録</h2><h3 id="4-1-各サービス-SAMPLE・DEMO一覧">4.1 各サービス　SAMPLE・DEMO一覧</h3><p>※以下のsample・demoは音声の品質確認用途以外の目的で使用できないため注意が必要</p>
<div class="scroll"><table>
<thead>
<tr>
<th>サービス名</th>
<th align="center">登録必要&#x2F;不要</th>
<th align="center">音声サンプル</th>
<th>備考</th>
</tr>
</thead>
<tbody><tr>
<td>GCP TTS</td>
<td align="center">不要</td>
<td align="center">✕</td>
<td>登録せずとも持参のテキストで音声合成を試すことができる</td>
</tr>
<tr>
<td>Polly</td>
<td align="center">必要</td>
<td align="center">〇</td>
<td>awsの無料枠の範囲で試すことが可能</td>
</tr>
<tr>
<td>Azure TTS</td>
<td align="center">不要</td>
<td align="center">✕</td>
<td>登録せずとも持参のテキストで音声合成を試すことができる</td>
</tr>
<tr>
<td>CoeFont</td>
<td align="center">必要</td>
<td align="center">〇</td>
<td>持参のテキストで音声合成を行う場合は登録が必要<br>二か月間の無料枠がある(2023年5月現在)</td>
</tr>
<tr>
<td>ReadSpeaker</td>
<td align="center">不要</td>
<td align="center">〇</td>
<td>登録せずとも持参のテキストで音声合成を試すことができる</td>
</tr>
<tr>
<td>コエステーション</td>
<td align="center">不要</td>
<td align="center">〇</td>
<td>登録せずとも持参のテキストで音声合成を試すことができる</td>
</tr>
<tr>
<td>AITalk</td>
<td align="center">不要</td>
<td align="center">〇</td>
<td>登録せずとも持参のテキストで音声合成を試すことができる</td>
</tr>
<tr>
<td>RECAIUS</td>
<td align="center">必要</td>
<td align="center">✕</td>
<td>登録後、問い合わせが必要</td>
</tr>
<tr>
<td>VOICE VOX</td>
<td align="center">不要</td>
<td align="center">〇</td>
<td>登録せずとも持参のテキストで音声合成を試すことができる<br>dokcerでVOICEVOXコンテナを起動 or ソフトウェアのインストールが必要</td>
</tr>
</tbody></table></div>
<ul>
<li>XMLの一種</li>
<li>音声合成の制御を行うことができる</li>
<li>サービスによって独自のタグ(制御方法)がある</li>
<li>W3Cにおいて標準化されている</li>
<li>polly SSML タグ説明ドキュメント<br>  <figure class="highlight text"><table><tr><td class="code"><pre><span class="line"># polly SSMLの一例</span><br><span class="line">&lt;speak&gt;本日、&lt;phoneme alphabet=&quot;x-amazon-pron-kana&quot; ph=&quot;セカイホケンキカン&quot;&gt;世界保健機関&lt;/phoneme&gt;が記者会見を開いた。&lt;/speak&gt;</span><br></pre></td></tr></table></figure></li>
</ul>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;"><strong>BASIC</strong>
GCP TTSのVoice Typeの一種
信号処理アルゴリズムVocoderにより音声データを生成する。</span> ↩</li><li id="fn:2"><span style="vertical-align: top; padding-right: 10px;">2.</span><span style="vertical-align: top;"><strong>WaveNet</strong>
GCP TTSのVoice Typeの一種
DeepMind WaveNetのニューラルモデルにより音声データを生成する</span> ↩</li><li id="fn:3"><span style="vertical-align: top; padding-right: 10px;">3.</span><span style="vertical-align: top;"><strong>Neural2</strong>
GCP TTSのVoice Typeの一種
最先端の合成音声生成技術が使用されている。</span> ↩</li><li id="fn:4"><span style="vertical-align: top; padding-right: 10px;">4.</span><span style="vertical-align: top;"><strong>standard</strong>
PollyのVoice Typeの一種
録音された音声の音素をまとめ (連結)、自然な合成音声を生成する。</span> ↩</li><li id="fn:5"><span style="vertical-align: top; padding-right: 10px;">5.</span><span style="vertical-align: top;"><strong>neural</strong>
最新のdeepmodelにより合成音声を生成する。</span> ↩</li><li id="fn:6"><span style="vertical-align: top; padding-right: 10px;">6.</span><span style="vertical-align: top;"><strong>エイリアス</strong>
略語などの読み方を別称として登録できる例1)WHO → World Health Organization <br>例2)WHO → 世界保健機関</span> ↩</li><li id="fn:7"><span style="vertical-align: top; padding-right: 10px;">7.</span><span style="vertical-align: top;"><strong>SSML(Speech Synthesis Markup Language)</strong></span> ↩</li><li id="fn:8"><span style="vertical-align: top; padding-right: 10px;">8.</span><span style="vertical-align: top;"><strong>国際音素記号(IPA)</strong>
様々な言語の音声を文字で表記を行うために国際音声学会が定めた音声記号である。
大辞林 日本語の音</span> ↩</li><li id="fn:9"><span style="vertical-align: top; padding-right: 10px;">9.</span><span style="vertical-align: top;"><strong>tps(transaction per second)</strong>
一秒間のトランザクション量</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">代表的な音声合成サービスの使用方法や性能の違いについて調査しました。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="Azure" scheme="https://future-architect.github.io/tags/Azure/"/>
    <category term="GoogleCloud" scheme="https://future-architect.github.io/tags/GoogleCloud/"/>
    <category term="TextToSpeech" scheme="https://future-architect.github.io/tags/TextToSpeech/"/>
    <category term="技術選定" scheme="https://future-architect.github.io/tags/%E6%8A%80%E8%A1%93%E9%81%B8%E5%AE%9A/"/>
  </entry>
  <entry>
    <title>お家で電子工作入門 ～上空のフライト情報を可視化する🛫～</title>
    <link href="https://future-architect.github.io/articles/20230418a/"/>
    <id>https://future-architect.github.io/articles/20230418a/</id>
    <published>2023-04-17T15:00:00.000Z</published>
    <updated>2023-04-17T15:00:00.000Z</updated>
    <author><name>岸下優介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2023/20230418a/j413_6_2_(1).png" alt="" width="500" height="281">

<h2 id="はじめに">はじめに</h2><p>本記事は春の入門祭り2日目の記事となります。<br>今日はM5Stackを使ったお家電子工作の沼へ誘いたいと思います。</p>
<p>過去にも電子工作連載があり、Futureでは活発にお家電子工作をやっております。</p>
<p>https://future-architect.github.io/articles/20220404a/</p>
<h2 id="何を作るか考える">何を作るか考える</h2><img src="/images/2023/20230418a/427ad433-320a-a7a1-a967-4122190b4765.png" alt="" width="632" height="750" loading="lazy">

<p>お家電子工作はワクワクが原動力なので、何かワクワクするものを作りたいなーと考えてみました。</p>
<p>うーんと考えてみたところ、この3年間コロナもあり海外に全然行ってないなーと。</p>
<p>海外に行くときのワクワクはまず空港に到着してフライト情報を眺めることから始まります。</p>
<p>じゃあ、<strong>自分の住んでいる周辺を飛ぶ飛行機の情報を表示</strong>することで、その飛行機に思いを馳せてワクワクするんじゃないかと考えました。</p>
<h2 id="必要なもの">必要なもの</h2><ul>
<li>M5Stack Basic</li>
<li>SDカード（16GB以下）</li>
<li>Wi-Fi環境</li>
<li>VSCode</li>
</ul>
<h3 id="M5Stackはいいゾ">M5Stackはいいゾ</h3><p>とりあえずお家で電子工作やIoTをやってみたい方はぜひ1台持っておくとよいです。ほぼ何でもできます。</p>
<ul>
<li>ESP32ベース<ul>
<li>簡単にWi-Fiが使えるのでIoTにもってこいです。<ul>
<li>処理も早いです。</li>
</ul>
</li>
</ul>
</li>
<li>ノーコードでもいける<ul>
<li>GUIで簡単に作ることもできます。</li>
<li>https://flow.m5stack.com/</li>
</ul>
</li>
<li>公式が凄い（語彙力）<ul>
<li>とりあえず、公式がM5Stack売り始めからめっちゃ頑張ってます。</li>
<li>めっちゃ拡張機能出してます。<ul>
<li>https://www.switch-science.com/collections/m5stack</li>
</ul>
</li>
<li>自動水やりとかできます。<ul>
<li>https://www.switch-science.com/products/6913</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>学生時代にM5Stack拡張して、赤外線サバゲー作ってたりもしました。</p>
<p>https://www.youtube.com/watch?v=deW3GmdMe_M</p>
<h2 id="環境構築">環境構築</h2><p>VSCodeは公式ページからインストールして下さい。<br>VSCodeの拡張機能であるPlatformIO IDEを使って開発を行います。<br>PlatformIO IDEの導入はこちらの記事を参考にして下さい。</p>
<h3 id="プロジェクトの作成">プロジェクトの作成</h3><p>拡張機能のインストールが終わると、Welcome画面が出てきます。<br>「New Project」からプロジェクトを作成しましょう。</p>
<img src="/images/2023/20230418a/b5e37c37-c91d-85c8-da5a-5bc95ed16254.png" alt="" width="1200" height="511" loading="lazy">

<ul>
<li>Name: Future_Tech_Blog（なんでもよいです）。</li>
<li>Board: M5Stack Core ESP32</li>
<li>Framework: Arduino</li>
</ul>
<img src="/images/2023/20230418a/1467bebc-58ea-f16d-6211-13c5d0fd8134.png" alt="" width="1200" height="869" loading="lazy">

<p>以下のようなディレクトリ構成でプロジェクトが作成されます。</p>
<img src="/images/2023/20230418a/c53220d7-0840-3663-c777-dae6b227a8d6.png" alt="" width="491" height="460" loading="lazy">

<h3 id="ライブラリのインストール">ライブラリのインストール</h3><p>今回は以下のライブラリを使用します。</p>
<ul>
<li>M5Stack.h<ul>
<li>M5Stackの各種機能を使うためのライブラリ</li>
</ul>
</li>
<li>Wi-Fi.h<ul>
<li>Wi-Fiに接続するためのライブラリ</li>
</ul>
</li>
<li>HTTPClient.h<ul>
<li>GETリクエストを送るためのライブラリ</li>
</ul>
</li>
<li>ArduinoJson.h<ul>
<li>JSONを扱うためのライブラリ</li>
</ul>
</li>
<li>time.h<ul>
<li>時刻を扱ったり、変換したりするためのライブラリ</li>
</ul>
</li>
</ul>
<p>PlatformIOでのライブラリインストールは以下の手順で行っていきます。</p>
<p>PlatformIO Home画面から「Libraries」を選択します。</p>
<img src="/images/2023/20230418a/c217b591-431a-d565-6747-f4361dfd5f88.png" alt="" width="1200" height="938" loading="lazy">

<p>M5Stack.hを例にライブラリを検索します。</p>
<img src="/images/2023/20230418a/f61e15fa-deac-9968-eab7-0ed288999b39.png" alt="" width="1200" height="409" loading="lazy">

<p>該当のライブラリをクリックします。</p>
<img src="/images/2023/20230418a/e6ea510f-ad6f-d4df-bb3f-e0451de648cd.png" alt="" width="1175" height="330" loading="lazy">

<p>「Add to Project」からプロジェクトへ追加します。<br><img src="/images/2023/20230418a/cea2ae68-5753-78b3-49e1-0d13e4365174.png" alt="" width="1188" height="526" loading="lazy"></p>
<p>「Select a project」から作成したプロジェクトを選択し、「Add」を押すことで追加できます。</p>
<img src="/images/2023/20230418a/0cb18834-4f55-7a54-c889-6a76e4ff5fc2.png" alt="" width="1200" height="748" loading="lazy">

<p>これをあとArduinoJsonとTimeに対して行うことで準備完了です。<br>HTTPClientやWi-Fiに対しては不要です。</p>
<h2 id="Flightrader24を理解する">Flightrader24を理解する</h2><img src="/images/2023/20230418a/f53f1045-f749-5372-1bbc-f23471d484a9.png" alt="" width="1200" height="568" loading="lazy">

<p>Flightrader24は世界中のフライト情報を取得できます。</p>
<p>iOSやAndroidのアプリもあり、触ったことがある人も居るのではないでしょうか？</p>
<p>Flightrader24ではREST APIも公開していて、以下のURLにGETリクエストするとJSONでデータを取得できます。</p>
<p><code>http://data-live.flightradar24.com/zones/fcgi/feed.js?adsb=1&amp;mlat=1&amp;faa=1&amp;flarm=1&amp;estimated=1&amp;air=1&amp;gnd=1&amp;vehicles=1&amp;gliders=1&amp;array=1</code></p>
<p>各クエリパラメータの説明は以下に詳しく書いてあります。<br>JSON - Flightradar24 から飛行中の航空機情報を取得！</p>
<p>ここで<code>bounds=</code>というクエリパラメータを追加し、緯度（北）・緯度（南）・経度（西）・経度（東）の順で値を入れていくとフライト情報を取得するエリアを指定できます。</p>
<p>今回は大崎がある品川区の端から端を指定してみます。</p>
<p>どうやって緯度・経度を取得するかって？ それはChat-GPTにお任せです。</p>
<img src="/images/2023/20230418a/4d540cca-b35a-ee19-1e69-b3138553e2df.png" alt="" width="958" height="612" loading="lazy">

<p>こういう調べても出てきそうにない情報は初手Chat-GPTがおススメです。<br>緯度・経度が得られたのでクエリパラメータを指定したURLが以下になります。</p>
<p><code>http://data-live.flightradar24.com/zones/fcgi/feed.js?bounds=35.63,35.59,139.71,139.76&amp;adsb=1&amp;mlat=1&amp;faa=1&amp;flarm=1&amp;estimated=1&amp;air=1&amp;gnd=1&amp;vehicles=1&amp;gliders=1&amp;array=1</code></p>
<p>これでGETしてみたところ、取得できませんでした。<br>さすがに範囲が狭すぎたようなので小数点第二位を四捨五入して取得したところ、以下のようなフライト情報が取得できました。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">[<span class="string">&#x27;2fe70675&#x27;</span>, <span class="string">&#x27;4D23DB&#x27;</span>, 35.5631, 139.7589, 329, 0, 1, <span class="string">&#x27;&#x27;</span>, <span class="string">&#x27;T-RJTT172&#x27;</span>, <span class="string">&#x27;GL7T&#x27;</span>, <span class="string">&#x27;9H-VIL&#x27;</span>, 1681552816, <span class="string">&#x27;XSP&#x27;</span>, <span class="string">&#x27;HND&#x27;</span>, <span class="string">&#x27;VJT735&#x27;</span>, 1, 0, <span class="string">&#x27;VJT735&#x27;</span>, 0]</span><br></pre></td></tr></table></figure>

<p>各項目の意味は先ほどのリンクを参考にしてください。<br>JSON - Flightradar24 から飛行中の航空機情報を取得！</p>
<p>とりあえず、10: 便名、11: 時刻、12: 出発空港, 13: 到着空港がわかれば楽しめそうです。</p>
<h2 id="M5Stack側のコードを作成">M5Stack側のコードを作成</h2><p>M5Stackは<code>setup()</code>と<code>loop()</code>の2つの大きな関数による構造となっております。<br><code>setup()</code>は最初の1度しか呼ばれず、残りは<code>loop()</code>が名前の通り永遠に呼ばれるようになっています。</p>
<p>コード全体はこちらのGitHubに置いてあります。</p>
<p>https://github.com/bigface0202/flightM5rader</p>
<h3 id="setup側">setup側</h3><p>setup側は起動後の1度しか呼ばれないため、主に接続を確立したり、画面の初期設定を行うなどの処理を記述します。</p>
<h4 id="M5Stackのディスプレイ設定">M5Stackのディスプレイ設定</h4><p>M5Stackのディスプレイには文字や画像を表示できます。<br>その初期設定を以下のように記述します。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// M5Stackの初期化</span></span><br><span class="line">M<span class="number">5.</span><span class="built_in">begin</span>();</span><br><span class="line"><span class="comment">// フォントサイズ</span></span><br><span class="line">M<span class="number">5.L</span>cd.<span class="built_in">setTextFont</span>(<span class="number">4</span>);</span><br><span class="line"><span class="comment">// フォントカラー</span></span><br><span class="line">M<span class="number">5.L</span>cd.<span class="built_in">setTextColor</span>(WHITE);</span><br><span class="line"><span class="comment">// 文字の初期位置</span></span><br><span class="line">M<span class="number">5.L</span>cd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">0</span>);</span><br></pre></td></tr></table></figure>

<h4 id="Wi-Fiへ接続">Wi-Fiへ接続</h4><p>今回はHTTPリクエストを投げる必要があるため、Wi-Fiへの接続は必須となります。<br><code>WIFI_SSID</code>と<code>WIFI_PASSWORD</code>を自宅のWi-Fiの情報に書き換えることで接続できるようになります。<br>また、5GHz帯は接続不可能なため2.4GHz帯のWi-Fiを選んで下さい。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// WiFiに接続</span></span><br><span class="line">WiFi.<span class="built_in">begin</span>(WIFI_SSID, WIFI_PASSWORD);</span><br><span class="line"><span class="type">int</span> _cursorX = <span class="number">0</span>;</span><br><span class="line">M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;Connecting to Wi-Fi&quot;</span>);</span><br><span class="line"><span class="comment">// 接続を試みている状態を画面に表示</span></span><br><span class="line"><span class="keyword">while</span> (WiFi.<span class="built_in">status</span>() != WL_CONNECTED) &#123;</span><br><span class="line">    Serial.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">setCursor</span>(<span class="number">0</span> + <span class="number">5</span> * _cursorX, <span class="number">30</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">300</span>);</span><br><span class="line">    _cursorX++;</span><br><span class="line">    <span class="keyword">if</span> (_cursorX &gt; <span class="number">320</span>) &#123;</span><br><span class="line">      _cursorX = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="loop側">loop側</h3><p>loop側は主に処理をさせたい内容を記述していきます。<br>今回の場合だと、フライト情報の取得とその情報の変換・表示になります。</p>
<h4 id="リクエストを投げる">リクエストを投げる</h4><p>GETリクエストは以下のように投げることができます。<br>毎回、取得できるとは限らないので<code>HTTP_CODE_OK</code>以外の時はエラーコードを表示するにようにしています。<br>また、GETリクエストを送る際は<code>delay</code>を最後に挟むようにしてください。<br><code>delay</code>を入れないとマシンガンリクエストを送ることになり、APIサーバーに対して負荷をかけてしまうため大変よろしくないです。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line">HTTPClient http;</span><br><span class="line">String apiAddress = <span class="string">&quot;http://data-live.flightradar24.com/zones/fcgi/feed.js?bounds=35.6,35.5,139.7,139.8&amp;adsb=1&amp;mlat=1&amp;faa=1&amp;flarm=1&amp;estimated=1&amp;air=1&amp;gnd=1&amp;vehicles=1&amp;gliders=1&amp;array=1&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    String payload;</span><br><span class="line">    http.<span class="built_in">begin</span>(apiAddress);</span><br><span class="line">    <span class="type">int</span> httpCode = http.<span class="built_in">GET</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (httpCode &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (httpCode == HTTP_CODE_OK) &#123;</span><br><span class="line">            <span class="comment">// 以下、取得したデータの処理</span></span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        M<span class="number">5.L</span>cd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;HTTP GET ERROR: &quot;</span>);</span><br><span class="line">        M<span class="number">5.L</span>cd.<span class="built_in">println</span>(httpCode);</span><br><span class="line">        <span class="built_in">delay</span>(<span class="number">5000</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// delayを入れ忘れるとマシンガンリクエストになるので、忘れずに</span></span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">5000</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="取得したJSONの処理と表示">取得したJSONの処理と表示</h4><p>Flightrader24にリクエストを送って取得した情報を表示していきます。<br>基本的に取得した配列に対して、ループで3秒ごとに表示していく処理になります。<br>1点だけ注意したいのが、リクエストを投げて得られる時刻がUNIX時間なので人間が読める時間に変換する必要があり、そちらの関数<code>unix2datetime</code>は後述します。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="comment">// JSON取得用の変数</span></span><br><span class="line"><span class="function">DynamicJsonDocument <span class="title">doc</span><span class="params">(<span class="number">4096</span>)</span></span>;</span><br><span class="line"><span class="comment">// リクエストで取得した情報を格納</span></span><br><span class="line">payload = http.<span class="built_in">getString</span>();</span><br><span class="line"><span class="comment">// String -&gt; JSON</span></span><br><span class="line"><span class="built_in">deserializeJson</span>(doc, payload);</span><br><span class="line"><span class="comment">// 配列の大きさを取得</span></span><br><span class="line"><span class="type">int</span> len = doc[<span class="string">&quot;aircraft&quot;</span>].<span class="built_in">size</span>();</span><br><span class="line"><span class="comment">// 配列の数だけフライト情報を表示</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; len; i++) &#123;</span><br><span class="line">    String flightName = doc[<span class="string">&quot;aircraft&quot;</span>][i][<span class="number">10</span>];</span><br><span class="line">    <span class="type">time_t</span> unixTimeUTC = doc[<span class="string">&quot;aircraft&quot;</span>][i][<span class="number">11</span>];</span><br><span class="line">    String departure = doc[<span class="string">&quot;aircraft&quot;</span>][i][<span class="number">12</span>];</span><br><span class="line">    String arrival = doc[<span class="string">&quot;aircraft&quot;</span>][i][<span class="number">13</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Unix timeから日本時間へ変換</span></span><br><span class="line">    String dateTimeString = <span class="built_in">unix2datetime</span>(unixTimeUTC);</span><br><span class="line">    <span class="comment">// 表示</span></span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">setCursor</span>(<span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;Flight Name: &quot;</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">println</span>(flightName);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;Time: &quot;</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">println</span>(dateTimeString);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;Departure: &quot;</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">println</span>(departure);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">print</span>(<span class="string">&quot;arrival: &quot;</span>);</span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">println</span>(arrival);</span><br><span class="line">    <span class="comment">// 3秒待つ</span></span><br><span class="line">    <span class="built_in">delay</span>(<span class="number">3000</span>);</span><br><span class="line">    <span class="comment">// 画面初期化</span></span><br><span class="line">    M<span class="number">5.L</span>cd.<span class="built_in">fillScreen</span>(BLACK);</span><br></pre></td></tr></table></figure>

<h4 id="UNIX時間を変換する">UNIX時間を変換する</h4><p>Chat-GPTマジ便利。<br>要所要所で詰まったらChat-GPT先生にお任せしたほうが早いです。</p>
<img src="/images/2023/20230418a/637f864f-c784-1683-86fe-56959601377c.png" alt="" width="1032" height="1138" loading="lazy">

<figure class="highlight cpp"><figcaption><span>unix2datetime</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="function">String <span class="title">unix2datetime</span><span class="params">(<span class="type">time_t</span> unixTime)</span> </span>&#123;</span><br><span class="line">  <span class="type">time_t</span> unixTimeJTC = unixTime + <span class="number">32400</span>;</span><br><span class="line">  <span class="keyword">struct</span> <span class="title class_">tm</span> *tmTime = <span class="built_in">localtime</span>(&amp;unixTimeJTC);</span><br><span class="line">  <span class="type">char</span> dateTimeString[<span class="number">20</span>];</span><br><span class="line">  <span class="built_in">strftime</span>(dateTimeString, <span class="built_in">sizeof</span>(dateTimeString), <span class="string">&quot;%Y-%m-%d %H:%M:%S&quot;</span>, tmTime);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> dateTimeString;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="画像を表示する（おまけ）">画像を表示する（おまけ）</h3><p>ここからは機能的にはあまり関係ないのでおまけになりますが、こうやって遊びを入れることで自分のプロダクトに愛着が沸きます。<br>現在の状態だと画面にフライト情報の文字だけが表示されて味気ないので、フライト情報のAPIをGETする際に飛行機の画像を表示します。</p>
<p>画像はこちらのフリー素材をサイズ変更（幅160px）してダウンロードし、SDカードにフォルダ（フォルダ名：img）を作成して取り込んで下さい。<br>取り込み終わったSDカードはM5Stackに挿しましょう。</p>
<p>https://jitanda.com/2014/02/07/j413_6/</p>
<p>また、LavyanGFXをライブラリインストールの手順でやったようにインストールして下さい。</p>
<img src="/images/2023/20230418a/0dabb105-0d88-ed15-a015-7e3aa6b39624.png" alt="" width="1200" height="231" loading="lazy">

<p>コードでは画像を<code>sprite.pushRotateZoom()</code>で徐々に拡大させることで、近づいてきているようなエフェクトを作ってみます。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> LGFX_AUTODETECT</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;LovyanGFX.hpp&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;LGFX_AUTODETECT.hpp&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> LGFX lcd;</span><br><span class="line"><span class="function"><span class="type">static</span> LGFX_Sprite <span class="title">sprite</span><span class="params">(&amp;lcd)</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 画像サイズが違うかもなので、ここはよしなに変えて下さい</span></span><br><span class="line"><span class="type">int</span> width = <span class="number">160</span>;</span><br><span class="line"><span class="type">int</span> height = <span class="number">120</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">setup</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 初期化処理</span></span><br><span class="line">    lcd.<span class="built_in">init</span>();</span><br><span class="line">    lcd.<span class="built_in">clear</span>(TFT_BLACK);</span><br><span class="line">    SD.<span class="built_in">begin</span>(TFCARD_CS_PIN, SPI, <span class="number">20000000</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 画像の読み込み</span></span><br><span class="line">    sprite.<span class="built_in">createSprite</span>(width, height);</span><br><span class="line">    sprite.<span class="built_in">drawPngFile</span>(SD, <span class="string">&quot;/img/airplane.png&quot;</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">loop</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// 画像を徐々にズーム</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i ++) &#123;</span><br><span class="line">        sprite.<span class="built_in">pushRotateZoom</span>(width, height, <span class="number">0</span>, i, i);</span><br><span class="line">        <span class="built_in">delay</span>(<span class="number">100</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="できあがったもの">できあがったもの</h2><img src="/images/2023/20230418a/m5stack_airplane3.gif" alt="m5stack_airplane3.gif" width="284" height="274" loading="lazy">

<h2 id="まとめ">まとめ</h2><p>こうやってハードウェア込みで自分がワクワクするものを開発できるのがお家電子工作の良いところですね。GPSモジュールなんかもあるので、位置情報のあたりも自動化するなど色々工夫もできそうです。</p>
<p>ぜひ皆さんもLet’s enjoy お家電子工作してください。</p>
<p>明日は井上さんで初めてのセキュリティ情報収集（mjckeck4)です。</p>
<p>アイキャッチは時短だの飛行機38を使わせていただきました。</p>
]]></content>
    <summary type="html">M5Stackを使ったお家電子工作の沼へ誘いたいと思います。この3年間コロナもあり海外に全然行ってないなーと。海外に行くときのワクワクはまず空港に到着してフライト情報を眺めることから始まります</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="M5Stack" scheme="https://future-architect.github.io/tags/M5Stack/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>Raspberry Piをかき集めてKubernetesを体感する</title>
    <link href="https://future-architect.github.io/articles/20220908a/"/>
    <id>https://future-architect.github.io/articles/20220908a/</id>
    <published>2022-09-07T15:00:00.000Z</published>
    <updated>2022-09-07T15:00:00.000Z</updated>
    <author><name>岸下優介</name></author>
    <content type="html"><![CDATA[<p>夏の自由研究ブログ連載2022 の10本目です。</p>
<h2 id="はじめに">はじめに</h2><p>TIG 岸下です。業務でGKE（Google Kubernetes Engine）を利用することがあるのですが、Kubernetesの挙動や仕組みなど如何せん理解が難しいです。</p>
<p>そこで今回は、自分の手でイチからKubernetesを構築することで勉強しようと思ったのが本記事のモチベーションです。</p>
<p>ちょうど自宅にRaspberry Piが3台あったのでRaspberry Piでクラスタを構築していこうと思います。基本的には以下の記事を参考に設定を行っていき、自分の理解を深めるために解説を挟みながら書いていこうと思います。</p>
<p>参考：RaspberryPi 4 にUbuntu20.04 をインストールして、Kubernetes を構築してコンテナを動かす</p>
<h2 id="今回Kubernetes構築するにあたって用意したもの">今回Kubernetes構築するにあたって用意したもの</h2><ul>
<li>Raspberry Pi3 Model B（メモリ1GB）</li>
<li>Raspberry Pi4 Model B（メモリ4GB）</li>
<li>Raspberry Pi4 Model B（メモリ8GB）</li>
<li>SDカード（64GB）x 3</li>
<li>5ポートスイッチングハブ（BUFFALO LSW6-GT-5EPL&#x2F;NBK）</li>
<li>LANケーブル x 4（CATはバラバラ）</li>
<li>キーボード</li>
<li>マウス</li>
</ul>
<p>各種Raspberry Piの電源はコンセントから取っています。</p>
<h2 id="ラズパイの設定">ラズパイの設定</h2><ul>
<li>3台共通のものと、3台それぞれで設定する内容があるので注意して下さい。</li>
</ul>
<h3 id="OS">OS</h3><ul>
<li>OS: Ubuntu 20.04LTS（3台共通）</li>
</ul>
<p>Raspberry Pi Imagerを使うと簡単にSDカードへOSを焼くことが出来ます。<br>SDカード3枚全てにUbuntu 20.04LTSを焼きます。</p>
<h3 id="Kubernetesのバージョン">Kubernetesのバージョン</h3><ul>
<li>v1.25.0</li>
</ul>
<h3 id="初期設定（3台共通）">初期設定（3台共通）</h3><p>ラズパイにOSをインストールしたSDカードを差し込み、パッケージを最新版にしておきます。</p>
<figure class="highlight bash"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt update</span><br><span class="line"><span class="built_in">sudo</span> apt upgrade -y</span><br></pre></td></tr></table></figure>

<h3 id="ネットワーク周り">ネットワーク周り</h3><h4 id="物理的な構成図">物理的な構成図</h4><img fetchpriority="high" src="/images/2022/20220908a/Screenshot_from_2022-09-04_20-54-30.png" alt="Screenshot_from_2022-09-04_20-54-30.png" width="1200" height="732">

<img src="/images/2022/20220908a/image.png" alt="image.png" width="939" height="730" loading="lazy">

<p><strong>※Desktop PCはラズパイ達とSSHするために繋いでいます。Kubernetesの構成には必要ありません。</strong></p>
<div class="scroll"><table>
<thead>
<tr>
<th>ラズパイ</th>
<th>役割</th>
<th>IPアドレス</th>
</tr>
</thead>
<tbody><tr>
<td>ラズパイ4B（8GB）</td>
<td>マスター</td>
<td>192.168.1.101</td>
</tr>
<tr>
<td>ラズパイ4B（4GB）</td>
<td>ワーカー（1）</td>
<td>192.168.1.102</td>
</tr>
<tr>
<td>ラズパイ3B</td>
<td>ワーカー（2）</td>
<td>192.168.1.103</td>
</tr>
</tbody></table></div>
<p>で構成しております。</p>
<h4 id="ネットワークの設定（ラズパイ3台それぞれで）">ネットワークの設定（ラズパイ3台それぞれで）</h4><p>上記のIPアドレスを各ラズパイに割り振って固定化します。<br>以下のファイルを作成します。</p>
<figure class="highlight bash"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vi /etc/netplan/99-network.yaml</span><br></pre></td></tr></table></figure>

<figure class="highlight yaml"><figcaption><span>99-network.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">network:</span></span><br><span class="line">  <span class="attr">version:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">renderer:</span> <span class="string">networkd</span></span><br><span class="line">  <span class="attr">ethernets:</span></span><br><span class="line">    <span class="attr">eth0:</span></span><br><span class="line">      <span class="attr">dhcp4:</span> <span class="literal">false</span></span><br><span class="line">      <span class="attr">dhcp6:</span> <span class="literal">false</span></span><br><span class="line">      <span class="attr">addresses:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.101</span><span class="string">/24</span> <span class="comment"># ラズパイ毎でIPを変更してください。（末尾がそれぞれ101, 102, 103になります。）</span></span><br><span class="line">      <span class="attr">gateway4:</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.1</span> <span class="comment"># 自宅のルーターからゲートウェイを調べて下さい。ここを間違えるとラズパイはネットに繋がらなくなります…</span></span><br><span class="line">      <span class="attr">nameservers:</span></span><br><span class="line">        <span class="attr">addresses:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.1</span></span><br></pre></td></tr></table></figure>

<p>作成したら、適用します。</p>
<figure class="highlight bash"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> netplan apply</span><br><span class="line">ifconfig</span><br><span class="line"><span class="comment"># eth0の部分が所望のIPアドレスに変わっていることを確認。</span></span><br><span class="line"><span class="comment"># ifconfigが無ければ</span></span><br><span class="line"><span class="comment"># sudo apt install ifconfig</span></span><br><span class="line"><span class="comment"># でインストール</span></span><br></pre></td></tr></table></figure>

<h4 id="SSHの設定（デスクトップPC）">SSHの設定（デスクトップPC）</h4><p>IPアドレスの固定化が完了したので、ここからはSSHで操作を行うようにします。</p>
<p><strong>※SSHは利用しなくても設定できますが、3台分のラズパイのディスプレイを切り替える作業のストレスが無くなります。</strong></p>
<h5 id="VSCodeのRemote-SSHを用意">VSCodeのRemote SSHを用意</h5><p>デスクトップPCにてVSCodeをインストールして、「拡張機能」からRemote - SSHをインストールしてください。</p>
<img src="/images/2022/20220908a/Screenshot_from_2022-09-03_15-19-08.png" alt="Screenshot_from_2022-09-03_15-19-08.png" width="1200" height="233" loading="lazy">

<h5 id="SSH構成ファイルを用意">SSH構成ファイルを用意</h5><p>VSCodeウィンドウ左下の「＞＜」をクリックして、「SSH構成ファイルを開く」から<code>config</code>を開いて以下のように設定して保存します。</p>
<figure class="highlight plaintext"><figcaption><span>config</span></figcaption><table><tr><td class="code"><pre><span class="line">Host rpi4_8</span><br><span class="line">    HostName 192.168.1.101</span><br><span class="line">    User ubuntu</span><br><span class="line"></span><br><span class="line">Host rpi4_4</span><br><span class="line">    HostName 192.168.1.102</span><br><span class="line">    User ubuntu</span><br><span class="line"></span><br><span class="line">Host rpi3</span><br><span class="line">    HostName 192.168.1.103</span><br><span class="line">    User ubuntu</span><br></pre></td></tr></table></figure>

<ul>
<li>Host名も好きに変更して頂いて構いません。</li>
<li>IPアドレスは自宅の環境に合わせて適宜変更して下さい。</li>
<li>User名を変更している場合も適宜変更して下さい。</li>
</ul>
<h5 id="各ラズパイに接続する">各ラズパイに接続する</h5><p>VSCodeウィンドウ左下の「＞＜」をクリックして、「ホストに接続する」からホスト名を選んで接続します。<br>初回は接続キーの登録が行われるため、キーが表示されたら「続行」を押し、あとはログイン用のパスワードを入力すると接続できます。<br>複数台同時に接続できるので、3画面分用意しておけばデスクトップPC側からラズパイの操作が可能となります。</p>
<h4 id="ホスト名の変更（ラズパイ3台それぞれ）">ホスト名の変更（ラズパイ3台それぞれ）</h4><p>ここからはまたラズパイの操作となります。<br>以下のホスト名を割り振っていきましょう。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>ラズパイ</th>
<th>役割</th>
<th>ホスト名</th>
</tr>
</thead>
<tbody><tr>
<td>ラズパイ4B（8GB）</td>
<td>マスター</td>
<td>mas01.example.com</td>
</tr>
<tr>
<td>ラズパイ4B（4GB）</td>
<td>ワーカー（1）</td>
<td>work01.example.com</td>
</tr>
<tr>
<td>ラズパイ3B</td>
<td>ワーカー（2）</td>
<td>work02.example.com</td>
</tr>
</tbody></table></div>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> hostnamectl set-hostname mas.example.com <span class="comment"># 各々のラズパイでhostnameは変える</span></span><br><span class="line">hostname <span class="comment"># 変更の確認作業</span></span><br></pre></td></tr></table></figure>

<h4 id="etc-hostsの設定（ラズパイ3台共通）">&#x2F;etc&#x2F;hostsの設定（ラズパイ3台共通）</h4><p>ホスト名とIPアドレスを対応させるために、<code>etc/hosts</code>に以下の内容を追記します。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vi /etc/hosts</span><br></pre></td></tr></table></figure>

<figure class="highlight plaintext"><figcaption><span>hosts</span></figcaption><table><tr><td class="code"><pre><span class="line"># ...</span><br><span class="line"></span><br><span class="line">192.168.1.101 mas01 mas01.example.com</span><br><span class="line">192.168.1.102 work01 work01.example.com</span><br><span class="line">192.168.1.103 work02 work02.example.com</span><br><span class="line"></span><br><span class="line"># ...</span><br></pre></td></tr></table></figure>

<p>これによって、ネットワークの名前解決ができるようになります。</p>
<h5 id="余談">余談</h5><p>以下はKubernetesの設定とは関係のない話なので余談ですが、ターミナル上で</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">ssh work01.example.com</span><br></pre></td></tr></table></figure>

<p>と打てば、<code>192.168.1.102</code>に接続できます。<br>これはDNS（Domain Name Service）でも同じことが行われています。<br>DNSについては、ぜひTIG西田さんのNW入門を読んでみてください。ハンズオン形式でわかりやすいと思います。</p>
<h4 id="IPv6の停止（ラズパイ3台共通）">IPv6の停止（ラズパイ3台共通）</h4><p>IPv6は今回利用しないので停止します。<br><code>sysctl.conf</code>をvimで開きます。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vi /etc/sysctl.conf</span><br></pre></td></tr></table></figure>

<p>下記設定を最後の方へ追記します。<br>設定を1にすることで停止となります。</p>
<figure class="highlight plaintext"><figcaption><span>sysctl.conf</span></figcaption><table><tr><td class="code"><pre><span class="line">#...</span><br><span class="line"></span><br><span class="line">net.ipv6.conf.all.disable_ipv6 = 1</span><br><span class="line">net.ipv6.conf.default.disable_ipv6 = 1</span><br><span class="line">net.ipv6.conf.eth0.disable_ipv6 = 1</span><br><span class="line">net.ipv6.conf.lo.disable_ipv6 = 1</span><br></pre></td></tr></table></figure>

<h5 id="余談-1">余談</h5><p>また余談ですが、最近はKubernetesでIPv4&#x2F;IPv6デュアルスタックが利用できるそうです。<br>参考：IPv4&#x2F;IPv6デュアルスタック</p>
<blockquote>
<p>IPv4&#x2F;IPv6デュアルスタックを利用するとIPv4とIPv6のアドレスの両方をPod及びServiceに指定できるようになります。</p>
</blockquote>
<p>なるほど、わからんという感じです。<br>IPv6自体はIPv4アドレスの枯渇問題を解決するためのプロトコルで、他にもIPv4に対する不満の多くを一挙に解消しようとしています（マスタリングTCP&#x2F;IP入門編 P.171）。<br>デュアルスタック機能によって、お互いは仕様の異なるプロトコルスタックですが共存させる仕組みで、やはりどちらのプロトコルも使えるのが美味しいポイントとなるのでしょうか。利用する機会があればまた調べて見ようと思います。</p>
<h4 id="timezone-keymapの変更（ラズパイ3台共通）">timezone, keymapの変更（ラズパイ3台共通）</h4><p>タイムゾーンを日本に、keymapを日本語にします。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># タイムゾーンの変更</span></span><br><span class="line"><span class="built_in">sudo</span> timedatectl set-timezone Asia/Tokyo</span><br><span class="line"></span><br><span class="line"><span class="comment"># keymapの変更</span></span><br><span class="line"><span class="built_in">sudo</span> localectl set-keymap jp106</span><br></pre></td></tr></table></figure>

<h3 id="Kubernetes周り">Kubernetes周り</h3><p>ここからKubernetes周りの設定を行っていきます。<br>基本はkubeadmの設定に沿っていきます。</p>
<p>参考：kubeadmのインストール</p>
<h4 id="iptablesがnftablesバックエンドを使用しないようにする（ラズパイ3台共通）">iptablesがnftablesバックエンドを使用しないようにする（ラズパイ3台共通）</h4><blockquote>
<p>nftablesバックエンドは現在のkubeadmパッケージと互換性がありません。(ファイアウォールルールが重複し、kube-proxyを破壊するためです。)</p>
</blockquote>
<p>だそうです。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># レガシーバイナリをインストール</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y iptables arptables ebtables</span><br><span class="line"></span><br><span class="line"><span class="comment"># レガシーバージョンに切り替える</span></span><br><span class="line"><span class="built_in">sudo</span> update-alternatives --<span class="built_in">set</span> iptables /usr/sbin/iptables-legacy</span><br><span class="line"><span class="built_in">sudo</span> update-alternatives --<span class="built_in">set</span> ip6tables /usr/sbin/ip6tables-legacy</span><br><span class="line"><span class="built_in">sudo</span> update-alternatives --<span class="built_in">set</span> arptables /usr/sbin/arptables-legacy</span><br><span class="line"><span class="built_in">sudo</span> update-alternatives --<span class="built_in">set</span> ebtables /usr/sbin/ebtables-legacy</span><br></pre></td></tr></table></figure>

<p>参考：kubernetes: iptablesがnftablesバックエンドを使用しないようにする</p>
<h4 id="Dockerのインストール（ラズパイ3台共通）">Dockerのインストール（ラズパイ3台共通）</h4><p>参考：Install Docker Engine on Ubuntu</p>
<p>ターミナルにて以下のコマンドでインストールします。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get -y install \</span><br><span class="line">    apt-transport-https \</span><br><span class="line">    ca-certificates \</span><br><span class="line">    curl \</span><br><span class="line">    gnupg-agent \</span><br><span class="line">    software-properties-common</span><br><span class="line">curl -fsSL https://download.docker.com/linux/ubuntu/gpg | <span class="built_in">sudo</span> apt-key add -</span><br><span class="line"><span class="built_in">sudo</span> add-apt-repository <span class="string">&quot;deb [arch=arm64] https://download.docker.com/linux/ubuntu <span class="subst">$(lsb_release -cs)</span> stable&quot;</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get -y install docker-ce docker-ce-cli containerd.io</span><br><span class="line"><span class="comment"># apt-mark holdで、バージョンを固定します。</span></span><br><span class="line"><span class="built_in">sudo</span> apt-mark hold docker-ce docker-ce-cli containerd.io</span><br></pre></td></tr></table></figure>

<p>dockerをroot権限無しで実行するためにdockerグループへユーザーを割り当てます。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> adduser ubuntu docker</span><br><span class="line"><span class="built_in">cat</span> /etc/group | grep docker</span><br><span class="line"><span class="comment"># 自分のユーザー名が追加されていることを確認</span></span><br></pre></td></tr></table></figure>

<p>グループ割当を適用するために、一度ログオフ or 再起動をしましょう。<br>Dockerのバージョンを確認します。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">docker version</span><br><span class="line"><span class="comment"># Dockerのバージョンが表示されればOK</span></span><br><span class="line"><span class="comment"># グループ割当が適用されていないと、Warningが出る。</span></span><br></pre></td></tr></table></figure>

<p>dockerの動作確認に<code>hello-world</code>コンテナを使います。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">docker run hello-world</span><br><span class="line"><span class="comment"># Hello from Docker!と表示されればOK</span></span><br></pre></td></tr></table></figure>

<h4 id="kubeadm、kubelet、kubectlのインストール（ラズパイ3台共通）">kubeadm、kubelet、kubectlのインストール（ラズパイ3台共通）</h4><p>参考：kubeadm、kubelet、kubectlのインストール</p>
<p>以下のコマンドでインストールします。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt-get update &amp;&amp; <span class="built_in">sudo</span> apt-get install -y apt-transport-https curl</span><br><span class="line">curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | <span class="built_in">sudo</span> apt-key add -</span><br><span class="line"><span class="built_in">cat</span> &lt;&lt;<span class="string">EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list</span></span><br><span class="line"><span class="string">deb https://apt.kubernetes.io/ kubernetes-xenial main</span></span><br><span class="line"><span class="string">EOF</span></span><br><span class="line"><span class="built_in">sudo</span> apt-get update</span><br><span class="line"><span class="built_in">sudo</span> apt-get install -y kubelet kubeadm kubectl</span><br><span class="line"><span class="built_in">sudo</span> apt-mark hold kubelet kubeadm kubectl</span><br></pre></td></tr></table></figure>

<p>そもそもこのkubeadm、kubelet、kubectlは何に使われるのでしょうか？</p>
<h5 id="kubeadm">kubeadm</h5><p>参考：Kubeadm</p>
<ul>
<li>Kubeadmは<code>kube init</code>や<code>kubeadm join</code>といったコマンドを提供するために作られたツールである。</li>
<li>最低限実行可能なKubernetesクラスタを立ち上げるために必要なアクションを実行する。</li>
<li>Kubeadmの設計上、クラスタの立ち上げのみにフォーカスを当てており、マシンへのプロビジョニングまでは考えていない。<ul>
<li>同様に、Kubernetesダッシュボードのような種々の便利なアドオンをインストールしたり、モニタリング、クラウド固有のアドオンもスコープ外である。</li>
</ul>
</li>
<li>理想的には全てのデプロイのベースとしてkubeadmを使うことで、適合するクラスタの作成が容易になる。</li>
</ul>
<p>クラスタの立ち上げまではkubeadmが面倒見てくれて、残りのツールとか設定は各々よしなにやりましょうという感じでしょうか。</p>
<h5 id="kubelet">kubelet</h5><p>参考：kubelet</p>
<p>kubeletを理解する前に、クラスターの全体像をまず理解する必要がありそうです。</p>
<img src="/images/2022/20220908a/9e8e89cf-751b-32cf-e060-c445acc0784c.png" alt="" width="1200" height="561" loading="lazy">

<p>画像引用先： Kubernetesのコンポーネント</p>
<p>グレーの箇所はクラスターになり、先程紹介したKubeadmによって提供されます。</p>
<ul>
<li>Control Plane（マスターノード）に対してNode（ワーカーノード）がぶら下がる。<ul>
<li>今回で言えば、マスターノードであるラズパイ<code>master01</code>にワーカーノードである<code>worker01</code>、<code>worker02</code>がぶら下がる。</li>
</ul>
</li>
<li>マスターノードはワーカーノードとPodを管理する。</li>
<li>ワーカーノードはアプリケーションのコンポーネントであるPodをホストする</li>
<li>Podは1つ以上のコンテナのグループを持ち、Kubernetesにデプロイできる最小単位になる<ul>
<li>種々のアプリケーションはPodの中のコンテナ上で動作</li>
</ul>
</li>
</ul>
<p>で、kubeletですが図を見ると各ワーカーノードの中にkubeletが存在し、ワーカーノードの中で使われることがわかります。どこで使われるかがわかったところでkubeletの機能についてまとめていきます。</p>
<ul>
<li>kubeletは、各ノード上で実行される主要な”ノードエージェント”<ul>
<li>”エージェント”なので各ノードの中での仲介者で、Podの起動・管理を行う</li>
</ul>
</li>
<li>kubeletは、PodSpecの観点から動作する<ul>
<li>PodSpecはPodに関する様々な情報（例えばコンテナの名前やimage）を載せたYAML or JSONファイル</li>
<li>PodSpec通りにコンテナが実行・動作されているかを確認することでPodを管理する</li>
</ul>
</li>
</ul>
<p>kubeletは各Pod内の管理・仲介者と考えておくとよさそうです。</p>
<h5 id="kubectl">kubectl</h5><p>参考：kubectlの概要</p>
<p>kubectlはKubernetesクラスターを制御するためのコマンドラインインターフェース（CLI）です。ターミナルからKubernetesクラスターを制御するのに使われます。</p>
<h4 id="cgroupでmemoryの有効化（ラズパイ3台共通）">cgroupでmemoryの有効化（ラズパイ3台共通）</h4><p>Kubernetesを利用する際に、cgroupのmemoryを有効化する必要があります。<br>はて、cgroupとは何なんでしょうか。</p>
<h5 id="Kubernetes内でのcgroup">Kubernetes内でのcgroup</h5><p>参考：Linuxカーネルのコンテナ機能［2］ ─cgroupとは？（その1）<br>参考：Kubernetes で cgroup がどう利用されているか</p>
<p>cgroupはControle Groupの略で、プロセスをグループ化して、そのグループ内に存在するプロセスに対して共通の管理を行うために使われます。例としては、ホストOSが持つCPUやメモリなどのリソースに対して、グループごとに制限をかけることができます。</p>
<p>kubeletの説明の中でPodSpecの話が出てきました。PodSpecのファイルではPod内のコンテナに関する情報を書くわけですが、この中でCPUやメモリの量も制限する（<code>resources</code>の<code>limits</code>）ことが可能です。正にここでcgroupが使われていて、ラズパイの計算リソースに対して、例えば計算リソースをそこまで必要としないPodに対しては制限をすることで、ラズパイのリソースを無駄に食い潰さないようにできます。</p>
<h5 id="有効化の設定">有効化の設定</h5><p>さて、cgroupのメモリーを有効化していきます。<br>以下のコマンドで確認すると、初期では無効化（末尾が0）されています。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> /proc/cgroups | grep memory</span><br><span class="line"><span class="comment"># 下記が表示される</span></span><br><span class="line">memory  0       105     0</span><br></pre></td></tr></table></figure>

<p><code>boot/firmware/cmdline.txt</code>を開き、<code>cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory</code>を追記します。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> vi /boot/firmware/cmdline.txt</span><br></pre></td></tr></table></figure>

<figure class="highlight txt"><figcaption><span>/boot/firmware/cmdline.txt</span></figcaption><table><tr><td class="code"><pre><span class="line">elevator=deadline net.ifnames=0 console=serial0,115200 dwc_otg.lpm_enable=0 console=tty1 root=LABEL=writable rootfstype=ext4 rootwait fixrtc quiet splash cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory</span><br></pre></td></tr></table></figure>

<p><strong>※改行はありません。既に書かれている文の末尾にスペースを空けて追記する形となります。</strong></p>
<p>変更を適用するために再起動します。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> reboot</span><br><span class="line"><span class="comment"># SSH接続が切れるので、ログインし直す</span></span><br></pre></td></tr></table></figure>

<p>memoryが有効化されていることを確認します。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">cat</span> /proc/cgroups | grep memory</span><br><span class="line"><span class="comment"># 下記が表示される</span></span><br><span class="line">memory  7       107     1</span><br></pre></td></tr></table></figure>

<h2 id="Kubernetesクラスターの作成">Kubernetesクラスターの作成</h2><p>参考：kubeadmを使用したクラスターの作成</p>
<h3 id="コントロールプレーンノードの初期化（マスターノードのラズパイのみ）">コントロールプレーンノードの初期化（マスターノードのラズパイのみ）</h3><p>先程も出てきましたが、コントロールプレーンノード＝マスターノードです。<br>マスターノードにて操作を行っていきます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> kubeadm init --apiserver-advertise-address=192.168.1.101 --pod-network-cidr=10.244.0.0/16</span><br></pre></td></tr></table></figure>

<ul>
<li><code>apiserver-advertise-address</code><ul>
<li>このオプションを利用して明示的にAPIサーバーのadvertise addressを設定します。</li>
<li>明示的に指定しない場合はデフォルトゲートウェイに関連付けられたネットワークインターフェースを使用して設定されます。</li>
</ul>
</li>
<li><code>pod-network-cidr</code><ul>
<li>Flannelを使用する場合、こちらを指定する必要があります。</li>
<li>Flannelはノード間をつなぐネットワークに仮想的なトンネルを構成することで、クラスター内のPod同士の通信を実現しています。</li>
<li><code>/16</code>と広めに設定します（GitHub - flannel-io&#x2F;flannel）。</li>
</ul>
</li>
</ul>
<p>初期化後、<code>kubeadm join 192.168.1.101:6443 --token ...</code>という出力が出たら、どこかのテキストエディタにコピーしておきます。<br>このコマンドはワーカーノードを追加する際に利用します。</p>
<h4 id="もしinit時にcontainer-runtime-is-not-runningというエラーが出た場合">もしinit時に<code>container runtime is not running</code>というエラーが出た場合</h4><p>参考：Kubeadm unknown service runtime.v1alpha2.RuntimeService #4581</p>
<p>上記ページにエラーについて載っており、内容を読むと以下のように書いております。</p>
<blockquote>
<p>In the config.toml file installed by package containerd.io there is the line disabled_plugins &#x3D; [“cri”] that am guessing creating the issue.</p>
</blockquote>
<p>パッケージ<code>containerd.io</code>から<code>config.toml</code>をインストールした際に今回のエラーを引き起こす行があるようです（確証ではないみたい？）。<br>解決方法はマスターノードにて以下のコマンドを実行し、configファイルを削除します。<br>そして、<code>containerd</code>を再実行します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># configファイルを削除</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">rm</span> /etc/containerd/config.toml</span><br><span class="line"><span class="comment"># containerdを再実行</span></span><br><span class="line">systemctl restart containerd</span><br></pre></td></tr></table></figure>

<h3 id="環境変数と入力補完の設定（マスターノードのラズパイのみ）">環境変数と入力補完の設定（マスターノードのラズパイのみ）</h3><p>kubectlをroot以外のユーザーでも実行できるようにするために、以下の設定を行っていきます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># ホームディレクトリに.kubeディレクトリを作成</span></span><br><span class="line"><span class="built_in">mkdir</span> -p ~/.kube</span><br><span class="line"><span class="comment"># Kubernetesのadmin.confを.kubeディレクトリのconfigファイルへコピー</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">cp</span> -i /etc/kubernetes/admin.conf ~/.kube/config</span><br><span class="line"><span class="comment"># configファイルの所有者がrootになっているのでk8suserへ変更</span></span><br><span class="line"><span class="built_in">sudo</span> <span class="built_in">chown</span> $(<span class="built_in">id</span> -u):$(<span class="built_in">id</span> -g) ~/.kube/config</span><br><span class="line"><span class="comment"># .bashrcへ環境変数の追加</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&#x27;KUBECONFIG=$HOME/.kube/config&#x27;</span> &gt;&gt; ~/.bashrc</span><br><span class="line"><span class="comment"># コマンドの入力補完を設定</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;source &lt;(kubectl completion bash)&quot;</span> &gt;&gt; <span class="variable">$HOME</span>/.bashrc</span><br><span class="line"><span class="comment"># 変更を適用</span></span><br><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br></pre></td></tr></table></figure>

<h3 id="Flannelの設定（マスターノードのラズパイのみ）">Flannelの設定（マスターノードのラズパイのみ）</h3><p>先程もちょろっと説明しましたが、Flannelはノードを跨いでコンテナ同士が通信できるようにするPodネットワークアドオンになります。<br>コンテナにはIPアドレスが付与されるのですが、Internal IPなのでそのままだとノードを跨いでコンテナ間で通信できません。これを解決するために、Flannelによってノード間をつなぐネットワークに仮想的なトンネル（オーバーレイネットワーク）を構成することで、Kubernetesクラスター内のPod同士の通信（Podネットワーク）を実現しています。</p>
<p>参考：Kubernetes完全ガイド 3.3.3 Flannel</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml</span><br></pre></td></tr></table></figure>

<p>flannelが動作しているか確認します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">kubectl get pods -n kube-flannel</span><br><span class="line"><span class="comment"># 以下が出力される</span></span><br><span class="line">NAME                    READY   STATUS    RESTARTS   AGE</span><br><span class="line">kube-flannel-ds-qcspv   1/1     Running   0          23m</span><br></pre></td></tr></table></figure>

<h3 id="ロードバランサーのインストール（マスターノードのラズパイのみ）">ロードバランサーのインストール（マスターノードのラズパイのみ）</h3><p>こちらを参考にして作っているので、同様にMetalLBをインストールします。<br>MetalLB, bare metal load-balancer for Kubernetesを参考にインストールします。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.5/config/manifests/metallb-native.yaml</span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line">kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey=<span class="string">&quot;<span class="subst">$(openssl rand -base64 128)</span>&quot;</span></span><br></pre></td></tr></table></figure>

<p>起動の確認を行います。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">kubectl get pod -n metallb-system</span><br><span class="line"><span class="comment"># 以下が表示される</span></span><br><span class="line">NAME                          READY   STATUS    RESTARTS   AGE</span><br><span class="line">controller-8689779bc5-txnbg   0/1     Pending   0          70s</span><br><span class="line">speaker-vcg4j                 1/1     Running   0          70s</span><br></pre></td></tr></table></figure>

<h3 id="ワーカーノードをクラスタにジョイン">ワーカーノードをクラスタにジョイン</h3><p>先程、テキストエディタにコピーしておいたコマンドを実行します。<br><strong>※<code>sudo</code>忘れに注意</strong></p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> kubeadm <span class="built_in">join</span> 192.168.1.101:6443 --token y2grpy.nbvcyr1em9o5aigj　--discovery-token-ca-cert-hash sha256:3e9ef8910b95e0a366041c1e156b7cbd6802df4c857cd53ad59bbba631749983</span><br></pre></td></tr></table></figure>

<p>ちゃんとワーカーノードがジョインされたか確認してみましょう。</p>
<figure class="highlight sh"><figcaption><span>terminal（マスターノード）</span></figcaption><table><tr><td class="code"><pre><span class="line">kubectl get nodes</span><br><span class="line"><span class="comment"># 以下が表示される</span></span><br><span class="line">NAME       STATUS     ROLES           AGE    VERSION</span><br><span class="line">master01   Ready   control-plane      6d     v1.25.0</span><br><span class="line">work01     Ready      &lt;none&gt;          122m   v1.25.0</span><br><span class="line">work02     Ready      &lt;none&gt;          121m   v1.25.0</span><br></pre></td></tr></table></figure>

<p><code>ROLES</code>がデフォルトになっているので変更します。</p>
<figure class="highlight sh"><figcaption><span>terminal（マスターノード）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># work01</span></span><br><span class="line">kubectl label node work01 node-role.kubernetes.io/worker=worker</span><br><span class="line"><span class="comment"># work02</span></span><br><span class="line">kubectl label node work02 node-role.kubernetes.io/worker=worker</span><br><span class="line"><span class="comment"># 確認</span></span><br><span class="line">kubectl get nodes</span><br><span class="line"><span class="comment"># 以下が表示される</span></span><br><span class="line">NAME       STATUS     ROLES           AGE    VERSION</span><br><span class="line">master01   Ready   control-plane      6d     v1.25.0</span><br><span class="line">work01     Ready      worker          122m   v1.25.0</span><br><span class="line">work02     Ready      worker          121m   v1.25.0</span><br></pre></td></tr></table></figure>

<p>これでクラスタの完成です。遂に我が家にKubernetesがやって来ました。</p>
<h4 id="もしコピー-ペーストを忘れた場合">もしコピー&amp;ペーストを忘れた場合</h4><p>以下のコマンドで発行したトークンを確認することが出来ます。</p>
<figure class="highlight sh"><figcaption><span>terminal（マスターノード）</span></figcaption><table><tr><td class="code"><pre><span class="line">kubeadm token list</span><br><span class="line"><span class="comment"># 以下が表示される</span></span><br><span class="line">TOKEN                     TTL         EXPIRES                USAGES                   DESCRIPTION                                                EXTRA GROUPS</span><br><span class="line">y2grpy.nbvcyr1em9o5aigj   22h         2022-09-04T08:03:11Z   authentication,signing   &lt;none&gt;                                                     system:bootstrappers:kubeadm:default-node-token</span><br></pre></td></tr></table></figure>

<p>有効期限が切れてしまった場合は以下のコマンドで再発行します。</p>
<figure class="highlight sh"><figcaption><span>terminal（マスターノード）</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> kubeadm token create</span><br></pre></td></tr></table></figure>

<p>CA証明書のhashも必要なので、以下のコマンドで出力させます。</p>
<figure class="highlight sh"><figcaption><span>terminal（マスターノード）</span></figcaption><table><tr><td class="code"><pre><span class="line">openssl x509 -pubkey -<span class="keyword">in</span> /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2&gt;/dev/null | openssl dgst -sha256 -hex | sed <span class="string">&#x27;s/^.* //&#x27;</span></span><br></pre></td></tr></table></figure>

<h2 id="Kubernetesでコンテナを動かす">Kubernetesでコンテナを動かす</h2><h3 id="yamlファイルの作成とapply">yamlファイルの作成とapply</h3><p>Dockerが動作しているホストのHostnameを表示するNginxコンテナをお借りして、Metal-LBでロードバランシングします。</p>
<p>yamlはこちらを参考にさせて頂いております。<br>MetalLB v0.13以降はConfigMapでの設定が廃止され、Custom Resource Definitions（CRD）での設定が推奨になったようで、MetalLBの部分だけv0.13に適合するように書き換えます。</p>
<figure class="highlight yaml"><figcaption><span>display-hostname.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Namespace</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-prod</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">metallb.io/v1beta1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">IPAddressPool</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pool-ips</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">metallb-system</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">addresses:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="number">192.168</span><span class="number">.1</span><span class="number">.210</span><span class="number">-192.168</span><span class="number">.1</span><span class="number">.215</span> <span class="comment"># 外部IPレンジ（この中から適当に外部IPが選ばれる）</span></span><br><span class="line">  <span class="attr">autoAssign:</span> <span class="literal">true</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">metallb.io/v1beta1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">L2Advertisement</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">pool-ips</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">metallb-system</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">ipAddressPools:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">pool-ips</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Service</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-service-lb</span> <span class="comment"># Service(LoadBalancer) の名前</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">nginx-prod</span></span><br><span class="line">  <span class="attr">annotations:</span></span><br><span class="line">    <span class="attr">metallb.universe.tf/address-pool:</span> <span class="string">pool-ips</span> <span class="comment"># MetallbのIPプール名</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">LoadBalancer</span></span><br><span class="line">  <span class="attr">ports:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-service-lb</span></span><br><span class="line">      <span class="attr">protocol:</span> <span class="string">TCP</span></span><br><span class="line">      <span class="attr">port:</span> <span class="number">8080</span> <span class="comment"># ServiceのIPでlistenするポート</span></span><br><span class="line">      <span class="attr">nodePort:</span> <span class="number">30080</span> <span class="comment"># nodeのIPでlistenするポート（30000-32767）</span></span><br><span class="line">      <span class="attr">targetPort:</span> <span class="number">80</span> <span class="comment"># 転送先(コンテナ)でlistenしているPort番号のポート</span></span><br><span class="line">  <span class="attr">selector:</span> <span class="comment"># service のselctorは、matchLabels 扱いになる</span></span><br><span class="line">    <span class="attr">app:</span> <span class="string">nginx-pod</span> <span class="comment"># 転送先の Pod のラベル</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="attr">apiVersion:</span> <span class="string">apps/v1</span></span><br><span class="line"><span class="attr">kind:</span> <span class="string">Deployment</span></span><br><span class="line"><span class="attr">metadata:</span></span><br><span class="line">  <span class="attr">name:</span> <span class="string">nginx-deployment</span> <span class="comment"># Deployment の名前(ReplicaSetの名前もこれになる)</span></span><br><span class="line">  <span class="attr">namespace:</span> <span class="string">nginx-prod</span></span><br><span class="line"><span class="attr">spec:</span></span><br><span class="line">  <span class="attr">selector:</span></span><br><span class="line">    <span class="attr">matchLabels:</span> <span class="comment"># ラベルがマッチしたPodを対象とするReplicaSetの作成</span></span><br><span class="line">      <span class="attr">app:</span> <span class="string">nginx-pod</span></span><br><span class="line">  <span class="attr">replicas:</span> <span class="number">2</span></span><br><span class="line">  <span class="attr">template:</span> <span class="comment"># Pod のテンプレート</span></span><br><span class="line">    <span class="attr">metadata:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">nginx-pod</span> <span class="comment"># Pod の名前</span></span><br><span class="line">      <span class="attr">namespace:</span> <span class="string">nginx-prod</span></span><br><span class="line">      <span class="attr">labels:</span> <span class="comment"># Pod のラベル</span></span><br><span class="line">        <span class="attr">app:</span> <span class="string">nginx-pod</span></span><br><span class="line">    <span class="attr">spec:</span></span><br><span class="line">      <span class="attr">containers:</span> <span class="comment"># コンテナの設定</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-container</span> <span class="comment"># コンテナの名前</span></span><br><span class="line">          <span class="attr">image:</span> <span class="string">yasthon/nginx-display-hostname</span> <span class="comment"># イメージの名前</span></span><br><span class="line">          <span class="attr">env:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">nginx-container</span></span><br><span class="line">          <span class="attr">ports:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">containerPort:</span> <span class="number">80</span> <span class="comment"># コンテナのポート</span></span><br><span class="line">          <span class="attr">volumeMounts:</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">file-hostname</span></span><br><span class="line">              <span class="attr">mountPath:</span> <span class="string">/usr/share/nginx/html/hostname</span></span><br><span class="line">      <span class="attr">volumes:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">file-hostname</span></span><br><span class="line">          <span class="attr">hostPath:</span></span><br><span class="line">            <span class="attr">path:</span> <span class="string">/etc/hostname</span></span><br></pre></td></tr></table></figure>

<p>参考：</p>
<ul>
<li>MetalLB v0.13以降はConfigmapでの設定ができない</li>
<li>Heads up: breaking changes in 0.13.x #1473</li>
</ul>
<p>以下のコマンドでリソースを作成します。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># リソースの作成</span></span><br><span class="line">kubectl apply -f display-hostname.yaml</span><br></pre></td></tr></table></figure>

<h3 id="動作確認">動作確認</h3><p>色々確認していきましょう。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># ポッドの確認</span></span><br><span class="line">kubectl get pods -n nginx-prod</span><br><span class="line"><span class="comment"># 以下が表示される</span></span><br><span class="line">NAME                                READY   STATUS    RESTARTS   AGE</span><br><span class="line">nginx-deployment-5bd979fdf9-dftnr   1/1     Running   0          92s</span><br><span class="line">nginx-deployment-5bd979fdf9-m9b58   1/1     Running   0          92s</span><br><span class="line"></span><br><span class="line"><span class="comment"># デプロイメントの確認</span></span><br><span class="line">kubectl get deployment -n nginx-prod</span><br><span class="line"><span class="comment">#</span></span><br><span class="line">NAME               READY   UP-TO-DATE   AVAILABLE   AGE</span><br><span class="line">nginx-deployment   2/2     2            2           23m</span><br><span class="line"></span><br><span class="line"><span class="comment"># ReplicaSetの確認</span></span><br><span class="line">kubectl get replicaset -n nginx-prod</span><br><span class="line"><span class="comment">#</span></span><br><span class="line">NAME                          DESIRED   CURRENT   READY   AGE</span><br><span class="line">nginx-deployment-5bd979fdf9   2         2         2       23m</span><br></pre></td></tr></table></figure>

<p>ちゃんと2つ立ち上がってます🙌</p>
<p>サービスを見ることでロードバランサーの外部IPを取得し、アクセスしてみましょう。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">kubectl get svc -n nginx-prod</span><br><span class="line"><span class="comment"># 以下が表示される</span></span><br><span class="line">NAME               TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)          AGE</span><br><span class="line">nginx-service-lb   LoadBalancer   10.101.40.46   192.168.1.210   8080:30080/TCP   3m26s</span><br><span class="line"><span class="comment"># 外部IPへアクセスしてみる</span></span><br><span class="line"><span class="comment"># ポート番号の入力を忘れないように注意する。</span></span><br><span class="line">curl 192.168.1.210:8080/index.sh</span><br><span class="line"><span class="comment"># work02が表示される。</span></span><br><span class="line">&lt;html&gt;&lt;<span class="built_in">head</span>&gt;</span><br><span class="line">&lt;title&gt;work02&lt;/title&gt;</span><br><span class="line">&lt;meta http-equiv=<span class="string">&quot;Content-type&quot;</span> content=<span class="string">&quot;text/html;charset=UTF-8&quot;</span>&gt;</span><br><span class="line">&lt;/head&gt;&lt;body&gt;</span><br><span class="line">HOSTNAME : work02</span><br><span class="line">&lt;/body&gt;&lt;/html&gt;</span><br><span class="line"><span class="comment"># もう一度アクセスしてみる</span></span><br><span class="line">curl 192.168.1.210:8080/index.sh</span><br><span class="line"><span class="comment"># 今度はwork01が表示される</span></span><br><span class="line">&lt;html&gt;&lt;<span class="built_in">head</span>&gt;</span><br><span class="line">&lt;title&gt;work01&lt;/title&gt;</span><br><span class="line">&lt;meta http-equiv=<span class="string">&quot;Content-type&quot;</span> content=<span class="string">&quot;text/html;charset=UTF-8&quot;</span>&gt;</span><br><span class="line">&lt;/head&gt;&lt;body&gt;</span><br><span class="line">HOSTNAME : work01</span><br><span class="line">&lt;/body&gt;&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>アクセスするたびに接続先が変わっていることから、ロードバランシングされていることが見受けられます。<br>同じネットワーク内につながっているPCであれば、ブラウザから上記アドレスへアクセスすることも可能です。</p>
<h3 id="可用性を体感する">可用性を体感する</h3><p>こちらの記事同様、ワーカーを物理的に落としても外部IPへアクセスできることを確認します。<br>ワーカーノード（1）に接続されたLANケーブルを抜いてアクセスしてみます。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">curl 192.168.1.210:8080/index.sh</span><br><span class="line"><span class="comment"># ワーカーノード②に繋がる</span></span><br><span class="line">&lt;html&gt;&lt;<span class="built_in">head</span>&gt;</span><br><span class="line">&lt;title&gt;work02&lt;/title&gt;</span><br><span class="line">&lt;meta http-equiv=<span class="string">&quot;Content-type&quot;</span> content=<span class="string">&quot;text/html;charset=UTF-8&quot;</span>&gt;</span><br><span class="line">&lt;/head&gt;&lt;body&gt;</span><br><span class="line">HOSTNAME : work02</span><br><span class="line">&lt;/body&gt;&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>次に、ワーカーノード（1）を繋ぎ直し、ワーカーノード（2）に接続されたLANケーブルを抜いてアクセスしてみます。<br>（ワーカーノード（1）を繋いでから接続できるようになるまで少し時間がかかります）。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">curl 192.168.1.210:8080/index.sh</span><br><span class="line"><span class="comment"># ワーカーノード①に繋がる</span></span><br><span class="line">&lt;html&gt;&lt;<span class="built_in">head</span>&gt;</span><br><span class="line">&lt;title&gt;work01&lt;/title&gt;</span><br><span class="line">&lt;meta http-equiv=<span class="string">&quot;Content-type&quot;</span> content=<span class="string">&quot;text/html;charset=UTF-8&quot;</span>&gt;</span><br><span class="line">&lt;/head&gt;&lt;body&gt;</span><br><span class="line">HOSTNAME : work01</span><br><span class="line">&lt;/body&gt;&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>このように、どちらかのワーカーが落ちても外部IPアドレスにアクセスでき、可用性を体感できました。<br>ロードバランサー自体はアクセス先を2つのワーカーに振り分けることで負荷を分散させる役割を持っていますが、このようにアクセス可能なワーカーのみに振り分けることも可能です。</p>
<p>最後に2つのワーカーノードの接続を外してみます。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line">curl 192.168.1.210:8080/index.sh</span><br><span class="line"><span class="comment"># 繋がらない</span></span><br><span class="line">curl: (7) Failed to connect to 192.168.1.210 port 8080: No route to host</span><br></pre></td></tr></table></figure>

<p>当然ですが、アクセスできないことが確認できます。</p>
<h3 id="再起動してみる">再起動してみる</h3><p>クラスターを止めずに全ノード再起動して、再度確認してみます。</p>
<figure class="highlight sh"><figcaption><span>terminal</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># nodeの状態を確認する</span></span><br><span class="line">kubectl get node -A</span><br><span class="line"><span class="comment"># STATUSがReadyなことが確認できる</span></span><br><span class="line">NAME                STATUS   ROLES           AGE    VERSION</span><br><span class="line">mas01.example.com   Ready    control-plane   107m   v1.25.0</span><br><span class="line">work01              Ready    worker          101m   v1.25.0</span><br><span class="line">work02              Ready    worker          101m   v1.25.0</span><br><span class="line"><span class="comment"># コンテナの情報を見てみる</span></span><br><span class="line">kubectl get all -n nginx-prod</span><br><span class="line"><span class="comment"># コンテナが動いていることが確認できる</span></span><br><span class="line">NAME                                    READY   STATUS    RESTARTS      AGE</span><br><span class="line">pod/nginx-deployment-5bd979fdf9-h89bh   1/1     Running   1 (13m ago)   38m</span><br><span class="line">pod/nginx-deployment-5bd979fdf9-xnltc   1/1     Running   1 (13m ago)   38m</span><br><span class="line"></span><br><span class="line">NAME                       TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)          AGE</span><br><span class="line">service/nginx-service-lb   LoadBalancer   10.101.40.46   192.168.1.210   8080:30080/TCP   84m</span><br><span class="line"></span><br><span class="line">NAME                               READY   UP-TO-DATE   AVAILABLE   AGE</span><br><span class="line">deployment.apps/nginx-deployment   2/2     2            2           84m</span><br><span class="line"></span><br><span class="line">NAME                                          DESIRED   CURRENT   READY   AGE</span><br><span class="line">replicaset.apps/nginx-deployment-5bd979fdf9   2         2         2       84m</span><br><span class="line"><span class="comment"># 外部IPにアクセスしてみる</span></span><br><span class="line">curl 192.168.1.210:8080/index.sh</span><br><span class="line"><span class="comment"># アクセス可能</span></span><br><span class="line">&lt;html&gt;&lt;<span class="built_in">head</span>&gt;</span><br><span class="line">&lt;title&gt;work01&lt;/title&gt;</span><br><span class="line">&lt;meta http-equiv=<span class="string">&quot;Content-type&quot;</span> content=<span class="string">&quot;text/html;charset=UTF-8&quot;</span>&gt;</span><br><span class="line">&lt;/head&gt;&lt;body&gt;</span><br><span class="line">HOSTNAME : work01</span><br><span class="line">&lt;/body&gt;&lt;/html&gt;</span><br></pre></td></tr></table></figure>

<p>このように、ラズパイ自体を再起動してもまたクラスタが立ち上がっていることが確認できます。</p>
<h2 id="まとめ">まとめ</h2><p>ラズパイを使ってKubernetesのクラスタを作成し、ロードバランシング・可用性を体感できました。クラウド上でゴニョゴニョ行われていることに対して、物理的な構成から作ってみることで解像度が上がった気がします。また、Kubernetes周りで使われている技術（cgroup, kubeadm, kubelet, flannel, …）について調べながら進めることで、Kubernetesに対して理解が進みました。<br>今回、コンテナの構成に関しては自分で作らなかったので、次はWebアプリでも作ってデプロイしてみたいと思います。</p>
]]></content>
    <summary type="html">業務でGKE（Google Kubernetes Engine）を利用することがあるのですが、Kubernetesの挙動や仕組みなど如何せん理解が難しいです。そこで今回は、自分の手でイチからKubernetesを構築することで勉強しようと思ったのが本記事のモチベーションです。ちょうど自宅にRaspberry Piが3台あったのでRaspberry Piでクラスタを構築していこうと思います。基本的には以下の記事を参考に設定を行っていき、自分の理解を深めるために解説を挟みながら書いていこうと思います。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Kubernetes" scheme="https://future-architect.github.io/tags/Kubernetes/"/>
    <category term="RaspberryPi" scheme="https://future-architect.github.io/tags/RaspberryPi/"/>
  </entry>
  <entry>
    <title>GoでADコンバータ読み出し～観葉植物監視bot構築～</title>
    <link href="https://future-architect.github.io/articles/20220905a/"/>
    <id>https://future-architect.github.io/articles/20220905a/</id>
    <published>2022-09-04T15:00:00.000Z</published>
    <updated>2022-09-04T15:00:00.000Z</updated>
    <author><name>宮永崇史</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2022/20220905a/eyecatch.png" alt="eyecatch.png" width="1080" height="762">

<p>夏休み自由研究ブログ連載の8本目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。TIG&#x2F;DXユニットの宮永です。</p>
<p>昨今の観葉植物ブームに便乗して先日ドラセナ・コンパクタという熱帯アフリカ原産の植物を購入しました。茎の縞模様とパイナップルを彷彿させるような葉っぱがとても可愛らしい植物です。ふとしたときに眺めるととても癒やされます。</p>
<p>観葉植物を購入してから気づいたのですが、水やりというのは結構大変です。植物を置いている場所の室温、日当たり、湿度などから適切な水分量を見極めるのが素人の私には難しいと感じています。</p>
<p>また、生来がズボラな性格をしているため、水をやるのを忘れてしまったりなども今後考えられます。</p>
<p>枯れてしまってから泣きをみることがないように、今回は土壌水分計とRaspberryPi、ADコンバータを使用して観葉植物の水分量を監視するSlackBotを作成します。</p>
<p>また、本記事のサンプルコードはorangekame3&#x2F;mcp3002にて公開していますので、参考にしてください。</p>
<h2 id="システム構成">システム構成</h2><p>構成はシンプルです。</p>
<img src="/images/2022/20220905a/image.png" alt="" width="1200" height="733" loading="lazy">

<p>RaspberryPiにこちらの土壌水分計を取り付け、ADコンバータMCP3002で読み出しを行います。読み出した電圧値に応じてSlackBot経由で通知を行います。</p>
<p>Pythonで同様のことを行っている記事はたくさんありますが、Go言語での実装例は殆どなかったのでその点で参考になれば幸いです。</p>
<h2 id="使用機器">使用機器</h2><ul>
<li>RaspberryPi 3</li>
<li>土壌水分計</li>
<li>ADコンバータ(MCP3002)</li>
<li>ジャンパーワイヤ（適量）</li>
<li>ブレッドボード</li>
</ul>
<h2 id="配線">配線</h2><p>余談ですが、RaspberryPiには<code>pinout</code>というコマンドが標準で備わっています。配線するときに便利ですのでおすすめです。</p>
<img src="/images/2022/20220905a/image_2.png" alt="image.png" width="1200" height="1106" loading="lazy">

<p>せっかくですので、<code>pinout</code>で出力されたピン番号に沿って今回の配線を説明します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">J8:</span><br><span class="line">   3V3  (1) (2)  5V</span><br><span class="line"> GPIO2  (3) (4)  5V</span><br><span class="line"> GPIO3  (5) (6)  GND</span><br><span class="line"> GPIO4  (7) (8)  GPIO14</span><br><span class="line">   GND  (9) (10) GPIO15</span><br><span class="line">GPIO17 (11) (12) GPIO18</span><br><span class="line">GPIO27 (13) (14) GND</span><br><span class="line">GPIO22 (15) (16) GPIO23</span><br><span class="line">   3V3 (17) (18) GPIO24</span><br><span class="line">GPIO10 (19) (20) GND</span><br><span class="line"> GPIO9 (21) (22) GPIO25</span><br><span class="line">GPIO11 (23) (24) GPIO8</span><br><span class="line">   GND (25) (26) GPIO7</span><br><span class="line"> GPIO0 (27) (28) GPIO1</span><br><span class="line"> GPIO5 (29) (30) GND</span><br><span class="line"> GPIO6 (31) (32) GPIO12</span><br><span class="line">GPIO13 (33) (34) GND</span><br><span class="line">GPIO19 (35) (36) GPIO16</span><br><span class="line">GPIO26 (37) (38) GPIO20</span><br><span class="line">   GND (39) (40) GPIO21</span><br></pre></td></tr></table></figure>

<p>ADコンバータは秋月商店で購入しました。</p>
<img src="/images/2022/20220905a/I-02584.jpg" alt="I-02584.jpg" width="320" height="240" loading="lazy">

<p>商品詳細ページにデータシートがあったため、こちらを使用して配線の説明をします。</p>
<p>MCP3002には前後があります。写真にあるように前方には半円のマークがついています。<br>下図のように半円の印がついたものを上向きにした場合、左上から1,2,3…と番号が振られます。</p>
<img src="/images/2022/20220905a/image_3.png" alt="image.png" width="853" height="479" loading="lazy">

<p>以下、MCP3002とRaspberryPiそして土壌水分計との接続関係です。<br>今回はMCP3002のCH0のみ使用したため、CH1にはなにも接続していません。</p>
<p>▼配線一覧</p>
<div class="scroll"><table>
<thead>
<tr>
<th>MCP3002ピン番号</th>
<th>RaspberryPiピン番号</th>
<th>土壌水分計</th>
</tr>
</thead>
<tbody><tr>
<td>1(CS&#x2F;SHDN)</td>
<td>24(GPIO8)</td>
<td>Vcc</td>
</tr>
<tr>
<td>2(CH0)</td>
<td>-</td>
<td>Aout</td>
</tr>
<tr>
<td>3(CH1)</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>4(Vss)</td>
<td>6(GND)</td>
<td>GND</td>
</tr>
<tr>
<td>5(Din)</td>
<td>19(GPIO10)</td>
<td>-</td>
</tr>
<tr>
<td>6(Dout)</td>
<td>21(GPIO9)</td>
<td>-</td>
</tr>
<tr>
<td>7(CLK)</td>
<td>23(GPIO11)</td>
<td>-</td>
</tr>
<tr>
<td>8(Vdd&#x2F;Vref)</td>
<td>1(3V3)</td>
<td>-</td>
</tr>
</tbody></table></div>
<h2 id="実装">実装</h2><p>今回実装するにあたってこちらの<code>golang.org/x/exp/io/spi</code>パッケージを使用しました。</p>
<p>また、MCP3002からの読み出しの実装についてはこちらのリポジトリでMCP3004、MCP3008、MCP3204、MCP3208の実装がされていたため、MCP3002のデータシートと比較しながら実装の参考とさせていただきました。</p>
<h3 id="デバイスの読み込み">デバイスの読み込み</h3><p>まずは<code>golang.org/x/exp/io/spi</code>を使用して、デバイスを読み込みます。</p>
<figure class="highlight go"><figcaption><span>main.go</span></figcaption><table><tr><td class="code"><pre><span class="line">dev, err := spi.Open(&amp;spi.Devfs&#123;</span><br><span class="line">	Dev:      <span class="string">&quot;/dev/spidev0.0&quot;</span>,</span><br><span class="line">	Mode:     spi.Mode0,</span><br><span class="line">	MaxSpeed: <span class="number">3600000</span>,</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">	fmt.Println(err)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">defer</span> dev.Close()</span><br></pre></td></tr></table></figure>

<h3 id="MCP3002のドライバ作成">MCP3002のドライバ作成</h3><p>デバイスを読み込んだらMCP3002の仕様に合わせてドライバを作成します。</p>
<figure class="highlight go"><figcaption><span>mcp3002.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> mcp3002</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line"></span><br><span class="line">	<span class="string">&quot;golang.org/x/exp/io/spi&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> MCP3002 <span class="keyword">struct</span> &#123;</span><br><span class="line">	Dev     *spi.Device</span><br><span class="line">	Vref    <span class="type">float64</span></span><br><span class="line">	Channel <span class="type">int</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(m MCP3002)</span></span> Read() (<span class="type">float64</span>, <span class="type">error</span>) &#123;</span><br><span class="line">	<span class="keyword">if</span> m.Channel &lt; <span class="number">0</span> || m.Channel &gt; <span class="number">1</span> &#123; <span class="comment">//    ---------------- (1)</span></span><br><span class="line">		<span class="keyword">return</span> <span class="number">0</span>, fmt.Errorf(<span class="string">&quot;channel is only selected 0 or 1&quot;</span>)</span><br><span class="line">	&#125;</span><br><span class="line">	cmd := <span class="type">byte</span>(<span class="number">0x68</span>) <span class="comment">// ------------------------------------- (2)</span></span><br><span class="line">	<span class="keyword">if</span> m.Channel == <span class="number">1</span> &#123;</span><br><span class="line">		cmd = <span class="number">0x78</span></span><br><span class="line">	&#125;</span><br><span class="line">	in := []<span class="type">byte</span>&#123;cmd, <span class="number">0x00</span>&#125;</span><br><span class="line">	out := <span class="built_in">make</span>([]<span class="type">byte</span>, <span class="number">2</span>)</span><br><span class="line">	<span class="keyword">if</span> err := m.Dev.Tx(in, out); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> <span class="number">0</span>, fmt.Errorf(<span class="string">&quot;failed to read channel %d,%w&quot;</span>, m.Channel, err)</span><br><span class="line">	&#125;</span><br><span class="line">	data := <span class="type">int</span>(out[<span class="number">0</span>]&amp;<span class="number">3</span>)&lt;&lt;<span class="number">8</span> | <span class="type">int</span>(out[<span class="number">1</span>]) <span class="comment">//    ------------- (3)</span></span><br><span class="line">	<span class="keyword">return</span> (m.Vref / <span class="number">1024</span>) * <span class="type">float64</span>(data), <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上記の実装で初見でよくわからんと思うポイントにナンバリングしました。順に説明します。</p>
<h3 id="1-MCP3002のチャネル数">(1)MCP3002のチャネル数</h3><p>(1)の実装はMCP3002のチャネル数に対するバインディングです。冒頭で説明したとおり、MCP3002はチャネルが2つあります。使用するチャネルによってMCP3002に渡すバイナリも変わるため、ここのハンドリングは大事です。</p>
<h3 id="2-MCP3002に渡すバイナリ">(2)MCP3002に渡すバイナリ</h3><p>MCP3002のデータシートを参照すると以下のようなシーケンス図が記載されています。DinがRaspberryPi→MCP3002にわたすバイナリ、DoutがMCP3002→Raspberryで受け取るバイナリです。</p>
<img src="/images/2022/20220905a/image_4.png" alt="image.png" width="1025" height="774" loading="lazy">

<p>データシートにはMCP3002に送るべきバイナリと読みとるべきバイナリについて記載がありました。<br>このデータシートによれば、MCP3002に向けて2バイト渡せば良いことがわかります。そのうち、1バイト目には条件に沿ったバイナリを渡してあげる必要があります。</p>
<img src="/images/2022/20220905a/image_5.png" alt="image.png" width="1030" height="483" loading="lazy">

<p>StartBitの次に記載されているSGL&#x2F;DIFF、ODD&#x2F;SIGN、について0&#x2F;1のどちらを渡すかは使用するチャネルによります。<br>以下の図に従えばCH0を使用する場合は10を渡せば良いことがわかります。MSBFについてですが、Figure5-1に従う読み出しを行う場合は1にします。よってRaspberryPi→MCP3002にむけて送信するべきバイナリは</p>
<p><strong>01101000 00000000</strong></p>
<p>であることがわかります。これは16進数表記で<code>0x68</code>です。同様にして使用しているチャネルが1である場合SGL&#x2F;DIFF、ODD&#x2F;SIGNが11となるためRaspberryPi→MCP3002にむけて送信するべきバイナリは</p>
<p><strong>01111000 00000000</strong></p>
<p>となります。これは16進数表記で<code>0x78</code>です。これで(2)の謎の16進数の役割がおわかりいただけたかと思います。</p>
<img src="/images/2022/20220905a/image_6.png" alt="image.png" width="1013" height="402" loading="lazy">

<h3 id="3-10ビット読みだし">(3)10ビット読みだし</h3><p>データシートのFIGURE6-1に記載の通り、末尾10ビットに読み出すべき情報が含まれています。RaspberryPi→MCP3002で2バイト渡したように、MCP3002→RaspberryPiでも2バイト受け取ります。</p>
<p>1バイト目と3で論理積を取ることで1バイト目の下方2ビットの値を特定できます。</p>
<blockquote>
<p>例）<br>10110101と00000011(3)の論理積 は00000001です。</p>
</blockquote>
<p>上記で算出したバイナリを8ビット左シフトし後方1バイトと論理和を取ることで、読み出すべき10ビットを取得できます。</p>
<blockquote>
<p>例)<br>00000001を8ビット左シフトすると00000001 00000000になります。</p>
</blockquote>
<p>MCP3002→RaspberryPiで受け取った2バイト目のバイナリが<code>01011010</code>だとすると、上記<code>00000001 00000000</code>と<code>01011010</code>の論理和は</p>
<p><strong>00000001 01011010</strong></p>
<p>であることがわかります。これで(3)の謎の演算がわかったかと思います。<br>あとはこの読み出した値に対して閾値を設定してやれば、観葉植物監視botの要件を達成できます。</p>
<h3 id="呼び出し元の実装">呼び出し元の実装</h3><p>1つのADコンバータに2つのチャネルがついているので監視する観葉植物も2つ登録できます。<br>Plant構造体を作成してADコンバータを登録するようにします。<br>閾値0.4を境にbotが通知する状態を変えます。</p>
<figure class="highlight go"><figcaption><span>main.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/mcp3002&quot;</span></span><br><span class="line">	<span class="string">&quot;golang.org/x/exp/io/spi&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">	Tirsty = <span class="string">&quot;喉乾いたよ〜&quot;</span></span><br><span class="line">	Moist  = <span class="string">&quot;お水はもう十分だよ&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Plant <span class="keyword">struct</span> &#123;</span><br><span class="line">	ADC    mcp3002.MCP3002</span><br><span class="line">	Status <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Plant)</span></span> addState(v <span class="type">float64</span>) Plant &#123;</span><br><span class="line">	resp := p</span><br><span class="line">	<span class="keyword">if</span> v &gt; <span class="number">0.4</span> &#123;</span><br><span class="line">		resp.Status = Tirsty</span><br><span class="line">		<span class="keyword">return</span> resp</span><br><span class="line">	&#125;</span><br><span class="line">	resp.Status = Moist</span><br><span class="line">	<span class="keyword">return</span> resp</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Plant)</span></span> addChannel(ch <span class="type">int</span>) Plant &#123;</span><br><span class="line">	resp := p</span><br><span class="line">	resp.ADC.Channel = <span class="number">0</span></span><br><span class="line">	<span class="keyword">if</span> ch == <span class="number">1</span> &#123;</span><br><span class="line">		resp.ADC.Channel = <span class="number">1</span></span><br><span class="line">		<span class="keyword">return</span> resp</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> resp</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Plant)</span></span> addADC(mcp mcp3002.MCP3002) Plant &#123;</span><br><span class="line">	resp := p</span><br><span class="line">	resp.ADC = mcp</span><br><span class="line">	<span class="keyword">return</span> resp</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上記実装にSlack通知機能を実装します。cronで定期的に土壌水分を監視してPlant.StatusがThirstyである場合はSlackで通知するようにします。Slack通知にはslack-goをcronにはrobfig&#x2F;cronを使用しました。</p>
<figure class="highlight go"><figcaption><span>main.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;os&quot;</span></span><br><span class="line">	<span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">	<span class="string">&quot;github.com/joho/godotenv&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/mcp3002&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/robfig/cron/v3&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/slack-go/slack&quot;</span></span><br><span class="line">	<span class="string">&quot;golang.org/x/exp/io/spi&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	dev, err := spi.Open(&amp;spi.Devfs&#123;</span><br><span class="line">		Dev:      <span class="string">&quot;/dev/spidev0.0&quot;</span>,</span><br><span class="line">		Mode:     spi.Mode0,</span><br><span class="line">		MaxSpeed: <span class="number">3600000</span>,</span><br><span class="line">	&#125;)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		fmt.Println(err)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">defer</span> dev.Close()</span><br><span class="line"></span><br><span class="line">	<span class="comment">// AD converの設定</span></span><br><span class="line">	mcp := mcp3002.MCP3002&#123;</span><br><span class="line">		Dev:  dev,</span><br><span class="line">		Vref: <span class="number">3.3</span>,</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// slackの設定ファイル読み込み</span></span><br><span class="line">	err = godotenv.Load(<span class="string">&quot;../.env&quot;</span>)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatalf(<span class="string">&quot;read slack env&quot;</span>, err)</span><br><span class="line">	&#125;</span><br><span class="line">	tkn := os.Getenv(<span class="string">&quot;TOKEN&quot;</span>)</span><br><span class="line">	client := slack.New(tkn)</span><br><span class="line"></span><br><span class="line">	<span class="keyword">var</span> plantA Plant</span><br><span class="line">	plantA = plantA.addADC(mcp)</span><br><span class="line">	plantA = plantA.addChannel(<span class="number">0</span>)</span><br><span class="line"></span><br><span class="line">	c := cron.New()</span><br><span class="line">	c.AddFunc(<span class="string">&quot;@every 1s&quot;</span>, <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		<span class="keyword">if</span> err := worker(plantA, client); err != <span class="literal">nil</span> &#123;</span><br><span class="line">			log.Fatal(err)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;)</span><br><span class="line">	c.Start()</span><br><span class="line">	<span class="keyword">for</span> &#123;</span><br><span class="line">		time.Sleep(time.Second)</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">worker</span><span class="params">(p Plant, client *slack.Client)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">	v, _ := p.ADC.Read()</span><br><span class="line">	p = p.addState(v)</span><br><span class="line">	<span class="keyword">if</span> p.Status == Tirsty &#123;</span><br><span class="line">		_, _, err := client.PostMessage(<span class="string">&quot;#home&quot;</span>, slack.MsgOptionText(p.Status, <span class="literal">true</span>))</span><br><span class="line">		<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">			<span class="keyword">return</span> fmt.Errorf(<span class="string">&quot;failed to post message  %w&quot;</span>, err)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line">	Tirsty = <span class="string">&quot;喉乾いたよ〜&quot;</span></span><br><span class="line">	Moist  = <span class="string">&quot;お水はもう十分だよ&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Plant <span class="keyword">struct</span> &#123;</span><br><span class="line">	ADC    mcp3002.MCP3002</span><br><span class="line">	Status <span class="type">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Plant)</span></span> addState(v <span class="type">float64</span>) Plant &#123;</span><br><span class="line">	resp := p</span><br><span class="line">	<span class="keyword">if</span> v &gt; <span class="number">0.4</span> &#123;</span><br><span class="line">		resp.Status = Tirsty</span><br><span class="line">		<span class="keyword">return</span> resp</span><br><span class="line">	&#125;</span><br><span class="line">	resp.Status = Moist</span><br><span class="line">	<span class="keyword">return</span> resp</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Plant)</span></span> addChannel(ch <span class="type">int</span>) Plant &#123;</span><br><span class="line">	resp := p</span><br><span class="line">	resp.ADC.Channel = <span class="number">0</span></span><br><span class="line">	<span class="keyword">if</span> ch == <span class="number">1</span> &#123;</span><br><span class="line">		resp.ADC.Channel = <span class="number">1</span></span><br><span class="line">		<span class="keyword">return</span> resp</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> resp</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(p Plant)</span></span> addADC(mcp mcp3002.MCP3002) Plant &#123;</span><br><span class="line">	resp := p</span><br><span class="line">	resp.ADC = mcp</span><br><span class="line">	<span class="keyword">return</span> resp</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>それでは最後に上記ジョブを実行してみます。</p>
<img src="/images/2022/20220905a/eyecatch_2.png" alt="eyecatch.png" width="1080" height="762" loading="lazy">

<p>ドラセナが喉の乾きを訴えかけてきました…</p>
<h2 id="さいごに">さいごに</h2><p>今回は土壌水分計とADコンバータを用いて観葉植物監視botを作成しました。<br>実は<code>golang.org/x/exp/io/spi</code>パッケージはすでにメンテがされていないようで、https://github.com/periphの利用が現在は推奨されているようです。</p>
<p>記事をほぼ書き終えてから気づいたので少し反省です。今後GoでRaspberryPi周りを触る際はhttps://github.com/periphを利用してみようとおもいます。Gobotもこちらをベースに実装を行っているようです。</p>
<p>最後まで読んでいただきありがとうございました。</p>
]]></content>
    <summary type="html">昨今の観葉植物ブームに便乗して先日ドラセナ・コンパクタという熱帯アフリカ原産の植物を購入しました。茎の縞模様とパイナップルを彷彿させるような葉っぱがとても可愛らしい植物です。ふとしたときに眺めるととても癒やされます。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="BOT" scheme="https://future-architect.github.io/tags/BOT/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="センサー" scheme="https://future-architect.github.io/tags/%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC/"/>
  </entry>
  <entry>
    <title>IoTの力でワインの品質を管理する</title>
    <link href="https://future-architect.github.io/articles/20220901a/"/>
    <id>https://future-architect.github.io/articles/20220901a/</id>
    <published>2022-08-31T15:00:00.000Z</published>
    <updated>2022-08-31T15:00:00.000Z</updated>
    <author><name>今泉智義</name></author>
    <content type="html"><![CDATA[<p>夏休み自由研究ブログ連載の7本目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。日本ソムリエ協会認定ワインエキスパートの今泉です。<br>間違えました。TIG&#x2F;DXユニットの今泉です。</p>
<p>先日、古い年式のワインセラーを我が家に迎えました。新品といえど型落ち品のため、怖いのが故障です。</p>
<p>ワインはとてもデリケートな飲み物で真夏の気温などでは直ぐにダメになってしまいます。また熟成に適した温度もあり、一般的には年間を通じて12~15℃程度で一日の温度変化が少ないほうが良いとされています。<sup id="fnref:1">1</sup><br>もし長期間家を空けたタイミングでセラーが壊れてしまったら……………</p>
<p>機械なので故障は仕方ありませんが、なんとかトラブルをすぐに検知する仕組みが欲しいところです。<br>そこでセラーに温度データロガーを設置することにしましたが、私の要望を満たす市販の製品は非常に高価かつ私にとっては不要な機能も含まれており、選定は難航しました。</p>
<p>ちょうどいい製品がないのであれば作ればよいのです。</p>
<p>ということでアラート機能付きの温湿度データロガーを自作することにしました。</p>
<h2 id="システム構成">システム構成</h2><img fetchpriority="high" src="/images/2022/20220901a/無題.png" alt="API Gateway + Lambda + CloudWatch + DynamoDB" width="922" height="534">

<p>実現したいこととしては以下の通りです。</p>
<ol>
<li>セラー内部の温湿度を取得</li>
<li>API Gateway経由でLambdaにHTTP POSTし、DynamoDBにデータを蓄積</li>
<li>CloudWatchをトリガーにDBへのデータ更新が行われているか、セラー内部の温度が基準を上回っていないか監視</li>
<li>3で問題があった場合、LINE messaging APIで自分のLINEアカウントに通知</li>
</ol>
<p>MQTT &amp; AWSIoTで実現することも考えましたが、すでに宮永さんが素敵な記事を書いているので今回は見送りました。<br>GoでMQTT!!　～温湿度マイスターbotの作成～(前編)</p>
<h2 id="まずはAPI">まずはAPI</h2><p>まずはクラウド環境の構築ですが、今回使用するLambda、DynamoDBについては特段本記事では取り上げません。<br>フューチャー技術ブログでは数多く記事が投稿されていますので是非読んでみて下さい。</p>
<p>Lambdaから実行するコードは以下の通りです。<br>Node.jsでもよかったのですが、最近使い始めたGoに少しでも慣れるためにもGoで書くことにしました。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;encoding/json&quot;</span></span><br><span class="line">	<span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-lambda-go/events&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-lambda-go/lambda&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/aws&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/aws/session&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/service/dynamodb&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> tokyo, _ = time.LoadLocation(<span class="string">&quot;Asia/Tokyo&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CellarData <span class="keyword">struct</span> &#123;</span><br><span class="line">	Id          <span class="type">int</span>    <span class="string">`dynamodbav:&quot;id&quot;`</span></span><br><span class="line">	TimeStamp   <span class="type">string</span> <span class="string">`dynamodbav:&quot;timestamp&quot;`</span></span><br><span class="line">	Temperature <span class="type">string</span> <span class="string">`dynamodbav:&quot;temperature&quot;`</span></span><br><span class="line">	Humidity    <span class="type">string</span> <span class="string">`dynamodbav:&quot;humidity&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> RequestBody <span class="keyword">struct</span> &#123;</span><br><span class="line">	Temperature <span class="type">string</span> <span class="string">`json:&quot;temperature&quot;`</span></span><br><span class="line">	Humidity    <span class="type">string</span> <span class="string">`json:&quot;humidity&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Response <span class="keyword">struct</span> &#123;</span><br><span class="line">	RequestMethod <span class="type">string</span> <span class="string">`json:&quot;RequestMethod&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handler</span><span class="params">(request RequestBody)</span></span> (events.APIGatewayProxyResponse, <span class="type">error</span>) &#123;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// connect DB</span></span><br><span class="line">	db := dynamodb.New(session.Must(session.NewSession()))</span><br><span class="line"></span><br><span class="line">	<span class="comment">//set Data</span></span><br><span class="line">	cellarData := CellarData&#123;</span><br><span class="line">		Id:          <span class="number">1</span>,</span><br><span class="line">		TimeStamp:   time.Now().In(tokyo).Format(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>),</span><br><span class="line">		Temperature: request.Temperature,</span><br><span class="line">		Humidity:    request.Humidity,</span><br><span class="line">	&#125;</span><br><span class="line">	item, err := dynamodbattribute.MarshalMap(cellarData)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> events.APIGatewayProxyResponse&#123;</span><br><span class="line">			Body:       err.Error(),</span><br><span class="line">			StatusCode: <span class="number">500</span>,</span><br><span class="line">		&#125;, err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// execute</span></span><br><span class="line">	_, err = db.PutItem(&amp;dynamodb.PutItemInput&#123;</span><br><span class="line">		TableName: aws.String(<span class="string">&quot;CELLAR&quot;</span>),</span><br><span class="line">		Item:      item,</span><br><span class="line">	&#125;)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> events.APIGatewayProxyResponse&#123;</span><br><span class="line">			Body:       err.Error(),</span><br><span class="line">			StatusCode: <span class="number">500</span>,</span><br><span class="line">		&#125;, err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// set response</span></span><br><span class="line">	jsonBytes, _ := json.Marshal(Response&#123;</span><br><span class="line">		RequestMethod: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">	&#125;)</span><br><span class="line">	<span class="keyword">return</span> events.APIGatewayProxyResponse&#123;</span><br><span class="line">		Body:       <span class="type">string</span>(jsonBytes),</span><br><span class="line">		StatusCode: <span class="number">200</span>,</span><br><span class="line">	&#125;, <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	lambda.Start(handler)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>ここで話せるようなポイントはあまりないのですが、POSTのリクエストボディはAPI Gatewayのマッピングテンプレートを活用しました。</p>
<h2 id="ESP-WROOM02-ESP8266-を使った温湿度センサの作成">ESP-WROOM02(ESP8266)を使った温湿度センサの作成</h2><p>今回はじめて電子工作にチャレンジしたのですが、どのマイコンボードを選べばいいのか、どのような機材をそろえればいいのか、全く当たりがつきません。<br>Arduinoが入門者にはお勧めらしいのですが、無線でのインターネット接続を行うには別途Wifi通信モジュールが必要で、その通信モジュール自体もプログラムの読み書きが可能なマイコンとして動作が可能らしく、じゃあもうそれでいいじゃんとなりました。</p>
<p>回路の構築やはんだ付にやや不安があったのである程度構築済みのものを探した結果、以下の製品を利用することにしました。</p>
<ul>
<li>ESPr® Developer（ピンソケット実装済）</li>
<li>ESPr® Developer用環境センサシールド</li>
</ul>
<p>ESPr® DeveloperはWifiモジュールであるESP-WROOM-02(RSP8266)の開発ボードで、こちらの製品はすでにピンソケットが実装されています。温湿度センサーとしてはBME280とフォトトランジスタを搭載したESPr® Developer用環境センサシールドを利用したのですがこちらもピンヘッダがすでに付いているので手間が大幅に削減できます。</p>
<p>ESP-WROOM02はArduinoと同様、C&#x2F;C++をベースとしたArduino言語を使用できるため、キャッチアップなどは不要でプログラミングに取り掛かることができました。<br>開発環境構築に当たっては以下記事を参考にさせていただきました。</p>
<p>ESP-WROOM-02開発ボードをArduino IDEで開発する方法</p>
<p>ソースコードは次の通りです。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;ESP8266HTTPClient.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;ESP8266WiFi.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WiFiClientSecure.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;BME280I2C.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;Wire.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;ArduinoJson.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SERIAL_BAUD 115200</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> API_KEY <span class="string">&quot;your api_key&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WIFI_SSID <span class="string">&quot;your ssid&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PWD <span class="string">&quot;your password&quot;</span></span></span><br><span class="line"></span><br><span class="line">BME280I2C bme;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">setup</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">    Serial.begin(SERIAL_BAUD);</span><br><span class="line">    delay(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">    Serial.println(<span class="string">&quot;Connecting to Wi-Fi&quot;</span>);</span><br><span class="line">    WiFi.begin(WIFI_SSID, PWD);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (WiFi.status() != WL_CONNECTED) &#123;</span><br><span class="line">      delay(<span class="number">500</span>);</span><br><span class="line">      Serial.println(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    Serial.println(<span class="string">&quot;&quot;</span>);</span><br><span class="line">    Serial.println(<span class="string">&quot;Wi-Fi connected&quot;</span>);</span><br><span class="line">    Serial.println(WiFi.localIP());</span><br><span class="line"></span><br><span class="line">    Wire.begin();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!bme.begin())&#123;</span><br><span class="line">        Serial.println(<span class="string">&quot;BME280 is not available&quot;</span>);</span><br><span class="line">        delay(<span class="number">1000</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">loop</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">float</span> <span class="title function_">temp</span><span class="params">(NAN)</span>, <span class="title function_">hum</span><span class="params">(NAN)</span>, <span class="title function_">pres</span><span class="params">(NAN)</span>;</span><br><span class="line"></span><br><span class="line">    BME280::TempUnit <span class="title function_">tempUnit</span><span class="params">(BME280::TempUnit_Celsius)</span>;</span><br><span class="line">    BME280::PresUnit <span class="title function_">presUnit</span><span class="params">(BME280::PresUnit_Pa)</span>;</span><br><span class="line"></span><br><span class="line">    bme.read(pres, temp, hum, tempUnit, presUnit);</span><br><span class="line"></span><br><span class="line">    Serial.print(<span class="string">&quot;temp: &quot;</span>);</span><br><span class="line">    Serial.print(temp);</span><br><span class="line">    Serial.print(<span class="string">&quot;, humid: &quot;</span>);</span><br><span class="line">    Serial.print(hum);</span><br><span class="line">    Serial.print(<span class="string">&quot;, pressure: &quot;</span>);</span><br><span class="line">    Serial.println(pres);</span><br><span class="line"></span><br><span class="line">    registerSensor(temp,hum);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//30min</span></span><br><span class="line">    ESP.deepSleep(<span class="number">30</span> * <span class="number">60</span> * <span class="number">1000000</span> );</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">registerSensor</span><span class="params">(<span class="type">float</span> temp,<span class="type">float</span> hum)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (WiFi.status() == WL_CONNECTED) &#123;</span><br><span class="line"></span><br><span class="line">        HTTPClient http;</span><br><span class="line">        WiFiClientSecure client;</span><br><span class="line">        <span class="type">const</span> <span class="type">char</span>* path = <span class="string">&quot;your endpoint&quot;</span>;</span><br><span class="line">        http.setTimeout(<span class="number">500</span>); <span class="comment">// 500ms</span></span><br><span class="line">        client.setInsecure();</span><br><span class="line"></span><br><span class="line">        http.begin(client,path);</span><br><span class="line">        <span class="comment">//set header</span></span><br><span class="line">        http.addHeader(<span class="string">&quot;x-api-key&quot;</span>,API_KEY);</span><br><span class="line">        http.addHeader(<span class="string">&quot;Content-Type&quot;</span>, <span class="string">&quot;application/json&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//set requestbody</span></span><br><span class="line">        StaticJsonDocument&lt;JSON_OBJECT_SIZE(<span class="number">20</span>)&gt;  jsonArray;</span><br><span class="line">        <span class="type">char</span> tempstr[<span class="number">10</span>];</span><br><span class="line">        <span class="type">char</span> humstr[<span class="number">10</span>];</span><br><span class="line">        dtostrf(temp, <span class="number">6</span>, <span class="number">2</span>, tempstr);</span><br><span class="line">        dtostrf(hum, <span class="number">6</span>, <span class="number">2</span>, humstr);</span><br><span class="line"></span><br><span class="line">        jsonArray[<span class="string">&quot;temperature&quot;</span>] = tempstr;</span><br><span class="line">        jsonArray[<span class="string">&quot;humidity&quot;</span>] = humstr;</span><br><span class="line"></span><br><span class="line">        <span class="type">char</span> jsonString[<span class="number">255</span>];</span><br><span class="line">        serializeJson(jsonArray,jsonString, <span class="keyword">sizeof</span>(jsonString));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// post</span></span><br><span class="line">        <span class="type">int</span> httpCode = http.POST((<span class="type">uint8_t</span> *)jsonString, <span class="built_in">strlen</span>(jsonString));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (httpCode &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            Serial.<span class="built_in">printf</span>(<span class="string">&quot;[HTTPS] POST... code: %d\n&quot;</span>, httpCode);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            Serial.println(<span class="string">&quot;[HTTPS] no connection or no HTTP server.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        http.end();</span><br><span class="line">     &#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>環境センサシールドはI2C通信のみ対応しているため、温湿度の取得はBME280I2Cをimportしました。</p>
<p>コードはBME280ライブラリのサンプルを参考にしています。<br>組み込み系ではJSON形式にするのは面倒なのでは…？ と思っていましたが、ArduinoJsonを利用することで手間なくJson化できました。</p>
<h3 id="給電">給電</h3><p>今回、セラー内に設置する必要があるためPCやコンセント以外での電源確保が必要です。</p>
<p>たまたまスマートフォン用の電池交換式充填機があったため使ってみましたが、問題なく稼働しました。<br><img src="/images/2022/20220901a/IMG_4153.jpg" alt="IMG_4153.jpg" width="727" height="968" loading="lazy"></p>
<p>長時間駆動させることを考えるとセンサを常に稼働させておくのは望ましくありません。<br>ESP-WROOM02にはDeepSleepモードがあるため、これを活用して30分間隔で温湿度を取得するようにしました。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">//30min</span></span><br><span class="line">ESP.deepSleep(<span class="number">30</span> * <span class="number">60</span> * <span class="number">1000000</span> );</span><br></pre></td></tr></table></figure>

<p>sleepの単位はμ秒のため秒を1000000倍する必要があります。</p>
<p>なお、以下記事によると最大で71.5分ほどスリープが可能なようです。</p>
<p>ESP-WROOM-02のDeep-sleepはどれだけ寝ていられるのか<br>運用から4日程度経ちますが、今のところ駆動に問題はありません。あまりにも電池の持ちが短かったら別の給電方法を考えたいと思います。</p>
<p>さて、プログラムが組み終わったのでセラーに設置してみます。<br><img src="/images/2022/20220901a/IMG_4160.jpeg" alt="ワインセラー" width="807" height="605" loading="lazy"></p>
<p>なんだか電子工作感がまるでありませんが気にしないことにします。</p>
<h2 id="通知機能をつける">通知機能をつける</h2><p>Lambdaに実装した関数は次の通りです。</p>
<figure class="highlight go"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;strconv&quot;</span></span><br><span class="line">	<span class="string">&quot;time&quot;</span></span><br><span class="line"></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-lambda-go/lambda&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/aws&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/aws/session&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/service/dynamodb&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute&quot;</span></span><br><span class="line">	<span class="string">&quot;github.com/line/line-bot-sdk-go/linebot&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> CellarData <span class="keyword">struct</span> &#123;</span><br><span class="line">	Id          <span class="type">int</span>    <span class="string">`dynamodbav:&quot;id&quot;`</span></span><br><span class="line">	TimeStamp   <span class="type">string</span> <span class="string">`dynamodbav:&quot;timestamp&quot;`</span></span><br><span class="line">	Temperature <span class="type">string</span> <span class="string">`dynamodbav:&quot;temperature&quot;`</span></span><br><span class="line">	Humidity    <span class="type">string</span> <span class="string">`dynamodbav:&quot;humidity&quot;`</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handler</span><span class="params">()</span></span> <span class="type">error</span> &#123;</span><br><span class="line">	<span class="keyword">const</span> threshold = <span class="number">25.00</span></span><br><span class="line"></span><br><span class="line">	<span class="comment">// connect DB</span></span><br><span class="line">	db := dynamodb.New(session.Must(session.NewSession()))</span><br><span class="line"></span><br><span class="line">	result, err := db.Query(&amp;dynamodb.QueryInput&#123;</span><br><span class="line">		TableName:        aws.String(<span class="string">&quot;CELLAR&quot;</span>),</span><br><span class="line">		Limit:            aws.Int(<span class="number">1</span>),</span><br><span class="line">		ScanIndexForward: aws.Bool(<span class="literal">false</span>),</span><br><span class="line">		ExpressionAttributeNames: <span class="keyword">map</span>[<span class="type">string</span>]*<span class="type">string</span>&#123;</span><br><span class="line">			<span class="string">&quot;#id&quot;</span>: aws.String(<span class="string">&quot;id&quot;</span>),</span><br><span class="line">		&#125;,</span><br><span class="line">		ExpressionAttributeValues: <span class="keyword">map</span>[<span class="type">string</span>]*dynamodb.AttributeValue&#123;</span><br><span class="line">			<span class="string">&quot;:id&quot;</span>: &#123;</span><br><span class="line">				N: aws.String(<span class="string">&quot;1&quot;</span>),</span><br><span class="line">			&#125;,</span><br><span class="line">		&#125;,</span><br><span class="line">		KeyConditionExpression: aws.String(<span class="string">&quot;#id = :id&quot;</span>),</span><br><span class="line">	&#125;)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">var</span> cellarData []CellarData</span><br><span class="line">	err = dynamodbattribute.UnmarshalListOfMaps(result.Items, &amp;cellarData)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	t1, _ := time.Parse(<span class="string">&quot;2006-01-02 15:04:05&quot;</span>, cellarData[<span class="number">0</span>].TimeStamp)</span><br><span class="line">	t2 := time.Now().Add(time.Hour + <span class="number">7</span>)</span><br><span class="line"></span><br><span class="line">	f, _ := strconv.ParseFloat(cellarData[<span class="number">0</span>].Temperature, <span class="number">64</span>)</span><br><span class="line">	<span class="keyword">if</span> f &gt; threshold &#123;</span><br><span class="line">		<span class="keyword">return</span> toNotify(<span class="string">&quot;セラーの現在の温度は&quot;</span> + cellarData[<span class="number">0</span>].Temperature + <span class="string">&quot;度です&quot;</span>)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">if</span> t2.After(t1) &#123;</span><br><span class="line">		<span class="keyword">return</span> toNotify(cellarData[<span class="number">0</span>].TimeStamp + <span class="string">&quot;以降更新がありません&quot;</span>)</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">toNotify</span><span class="params">(str <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">	<span class="keyword">const</span> LINE_BOT_CHANNEL_SECRET = <span class="string">&quot;your channel&quot;</span></span><br><span class="line">	<span class="keyword">const</span> LINE_CHANNEL_ACCESS_TOKEN = <span class="string">&quot;your token&quot;</span></span><br><span class="line"></span><br><span class="line">	bot, err := linebot.New(LINE_BOT_CHANNEL_SECRET, LINE_CHANNEL_ACCESS_TOKEN)</span><br><span class="line">	<span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> err</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	message := linebot.NewTextMessage(str)</span><br><span class="line">	<span class="keyword">if</span> _, err := bot.BroadcastMessage(message).Do(); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> err</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	lambda.Start(handler)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>データの取得はQueryInputで最新のレコードを1件のみ取得しています。<br>2時間以上、更新がなければ通知するようにしたかったのですが、DBにはJSTで時刻が登録されているもののtimestamp型にparseする際、時刻はそのままにUTCに戻ってしまってしまうため、今回は 9(時差) -2 &#x3D;7で現在時刻をずらして無理やり比較しました。(なにをしてるのか意味不明かと思い補足)</p>
<h3 id="LINE-messaging-API">LINE messaging API</h3><p>今回初めて利用しましたが、非常に簡単に実装できました。</p>
<p>LINE DevelopersでMessaging APIのチャネルを作成し、払い出されたLINEシークレットとトークンを設定するのみでBotが作成できます。<br>通知には友達登録をしている人全員にメッセージを送信するBroadcastMessageを使用します。</p>
<p>実際にテストしてみた結果です。<br><img src="/images/2022/20220901a/IMG_4163.jpg" alt="LINEからの通知" width="351" height="760" loading="lazy"></p>
<p>これで夏場でも安心して外出できるようになりました。</p>
<h2 id="おわりに">おわりに</h2><p>今回の仕組みを導入し終えてから1本ワインを開けましたが、いつもより美味しく感じました。</p>
<p>これは新しいワインの楽しみ方を見出してしまったかもしれません……。</p>
<p>さて、実際に通知が来てしまった場合ですが、外出先であればとんぼ返りして、セラー内のワインを冷蔵庫に退避させるか、セラー内に氷を置いて温度を下げるなどの対応が必要になります。自分で実装した手前、通知が来てほしい気持ちがありつつも活躍する場面がないと嬉しいですね。複雑です。</p>
<p>案外手軽に実装できたので、次はデータだけは蓄積できている湿度<sup id="fnref:2">2</sup>も活用して、より厳格な品質管理にチャレンジしてみたいと思います。</p>
<div id="footnotes"><hr><div id="footnotelist"><ol style="list-style:none; padding-left: 0;"><li id="fn:1"><span style="vertical-align: top; padding-right: 10px;">1.</span><span style="vertical-align: top;">より厳格にはブルゴーニュ地方のもので11～12℃、ボルドー地方のもので13～15℃、カルフォルニアで14～18℃程度が望ましく、どの産地のものを保管するかによって温度をコントロールするのが良いです。</span> ↩</li><li id="fn:2"><span style="vertical-align: top; padding-right: 10px;">2.</span><span style="vertical-align: top;">湿度は70～75％程度が望ましいとされています。</span> ↩</li></ol></div></div>]]></content>
    <summary type="html">日本ソムリエ協会認定ワインエキスパートの今泉です。ワインはとてもデリケートな飲み物で真夏の気温などでは直ぐにダメになってしまいます。また熟成に適した温度もあり、一般的には年間を通じて12~15℃程度で一日の温度変化が少ないほうが良いとされています。ということで、アラート機能付きの温湿度データロガーを自作することにしました</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Arduino" scheme="https://future-architect.github.io/tags/Arduino/"/>
    <category term="DynamoDB" scheme="https://future-architect.github.io/tags/DynamoDB/"/>
    <category term="Go" scheme="https://future-architect.github.io/tags/Go/"/>
    <category term="センサー" scheme="https://future-architect.github.io/tags/%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC/"/>
  </entry>
  <entry>
    <title>Bluetoothで位置推定</title>
    <link href="https://future-architect.github.io/articles/20220725a/"/>
    <id>https://future-architect.github.io/articles/20220725a/</id>
    <published>2022-07-24T15:00:00.000Z</published>
    <updated>2022-07-24T15:00:00.000Z</updated>
    <author><name>岸下優介</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>地図・GIS・位置特定に関する連載の5日目のエントリーです。<br>マイコンとか電子工作などIoT関連が好きで、Bluetooth信号を使った位置推定手法について調査したことがあったので、紹介します。</p>
<h2 id="Bluetoothとは">Bluetoothとは</h2><p>数メートルから長いものでは数十メートル程度の距離の情報機器間で、電波を使いながら簡易な情報のやりとりを行うのに使用されます。<br>身近な例だと、</p>
<ul>
<li>イヤホン</li>
<li>マウス</li>
<li>キーボード</li>
<li>…etc.</li>
</ul>
<p>などはBluetooth信号で音の情報やキー・マウスが押された情報を伝えることで操作が行えるようになります。<br>近年ではBLE（Bluetooth Low Energy）が主流になってきています。</p>
<h3 id="RSSI">RSSI</h3><p>信号の強さを表す値として、RSSI（Received Signal Strength Indicator）が使われます。<br>大きいほど信号強度が強く、小さいほど信号強度が弱い状態を表します。<br>そして、発信源から距離が近いほど信号強度が強く、離れるにつれて段々と強度が弱くなっていきます。<br>つまり、このRSSIの値を使えばざっくりと位置推定ができるわけです。</p>
<h2 id="位置推定">位置推定</h2><p>Bluetoothを使った位置推定では、主にビーコンを配置することで行います。<br>ビーコンはただひたすら、Bluetooth信号を発信し続けます。電池持ちの良いものだと数年持つそうです。</p>
<h3 id="ビーコン1つだけを使った位置推定">ビーコン1つだけを使った位置推定</h3><p>まずは、ビーコンを1つだけ使った場合の位置推定手法です。<br>こちらの記事で詳しく紹介されていますが、<code>ビーコン自体が発するBluetoothの強度TxPower</code>と<code>RSSI</code>の値の関係から位置を計算できます。<br>ここでは、自由空間では受信信号強度は距離の二乗に反比例するという関係が使われます。<br>ただ記事内でも仰られている通り、実空間では床から反射したり、機器間に人間が居たり、完全な自由空間とは程遠いもので正確な距離を出すことはできません。<br>また、ビーコン1つだけだと、同心円上では図のように等しく<code>距離d</code>なので方向まではわかりません。<br><img fetchpriority="high" src="/images/2022/20220725a/8094da77-f209-e092-608f-42bcd0f26b49.png" alt="" width="483" height="543"></p>
<p>こんなの使えないじゃん！ ってなるところですが、実際は近くにいるかどうかわかれば良いという用途もあります。<br>例えば、ある商品棚の近くに来たときにプッシュ通知したり、ある店の前に通りかかったときにプッシュ通知してお得な情報をお知らせできます。<br><img src="/images/2022/20220725a/9038109f-b428-09a6-1af6-36dd63e300fe.png" alt="" width="711" height="644" loading="lazy"></p>
<p>これを利用したものとして、LINEビーコンがあります。<br>LINEビーコンで近くの人を検知、LINEでプッシュ通知という組み合わせです。<br>ちなみにこちらの記事を参考に、ESP32を使ってLINEビーコンを作る事ができます。<br>また、LINE DevelopersからLINEbotと組み合わせて、プッシュ通知を簡単に試すことが可能です。</p>
<h3 id="ビーコンを3つ以上使った位置推定">ビーコンを3つ以上使った位置推定</h3><p>次に、ビーコンを複数使った位置推定手法になります。<br>ビーコン1つの場合でも距離はざっくりとわかりましたが、方向がわかりませんでした。これを解決するために3つ以上のビーコンを使う事で方向まで推定します。<br><img src="/images/2022/20220725a/b5cf85ae-e7b1-4586-d814-126511066f71.png" alt="" width="640" height="672" loading="lazy"></p>
<p>図のように各ビーコンの位置は固定で、それぞれのビーコンには個別の識別番号を振っておくことで各ビーコンからの距離がわかります。<br>この時どこか<strong>基準点</strong>を決めて、その位置から各ビーコンの位置がどれだけ<code>x</code>方向と<code>y</code>方向に離れているか記録しておく必要があります。<br>それぞれの<code>x</code>、<code>y</code>座標を使って円の方程式を立て（距離<code>d1</code>、<code>d2</code>、<code>d3</code>が円の半径）、その連立方程式を解くことで交点を計算し、<strong>基準点</strong>に対する相対的な位置を推測できます。<br>これで距離も位置も決まった！ と思いきや、Bluetoothは様々な干渉を受けるため<code>距離d</code>がざっくりとした値になり、連立方程式が解けずに位置が求まらないことが多発します。</p>
<h3 id="ビーコンを複数と機械学習を使った位置推定">ビーコンを複数と機械学習を使った位置推定</h3><p>最後に、ビーコンを複数と機械学習を使った位置推定手法を紹介します。<br>簡潔に手法言うと、クラス分類モデルで位置を推定していきます。<strong>但し、このやり方だと正確な距離は推定できません。</strong></p>
<h4 id="（1）推定したい場所をグリッドで区切る">（1）推定したい場所をグリッドで区切る</h4><img src="/images/2022/20220725a/34ef68e6-ac85-f74f-84e3-f12a3c048475.png" alt="" width="451" height="487" loading="lazy">

<p>図のように部屋をグリッドで区切って番号を振ります。グリッド番号が位置を示します。<br>また、このグリッド番号をクラスとして分類モデルに学習させていきます。</p>
<h4 id="（2）ビーコンを配置する">（2）ビーコンを配置する</h4><img src="/images/2022/20220725a/6e20b812-7b5c-51df-6e1f-e008db678691.png" alt="" width="454" height="484" loading="lazy">

<p>これはどの手法でも同じですが、Bluetooth信号を発するビーコンに固定の番号を振ってそれぞれ配置します。<br>配置位置は特に決まっていませんが、部屋に満遍なく配置したほうがいいでしょう。<br>またこの例だと各部屋1つの計4つですが、グリッドが大きい場合はもっとたくさんビーコンを置いて、部屋のどの位置にいてもBluetooth信号を捕まえられるようにしたほうがより正確な推定ができます。</p>
<h4 id="（3）各グリッドでBluetoothのRSSIを収集し、機械学習モデルを学習させる">（3）各グリッドでBluetoothのRSSIを収集し、機械学習モデルを学習させる</h4><img src="/images/2022/20220725a/d1acd8bc-3ac5-4d85-d865-9c4366dae2b4.png" alt="" width="805" height="564" loading="lazy">

<p><strong>※図の吹き出しに記載している値はRSSIの大きさ</strong><br>各グリッドの位置でそれぞれのビーコンから送られてくるRSSIの値を収集します。<br>収集したRSSIの値をグリッド番号推定用の機械学習モデルの学習データとして使うので、なるべく多めに収集しておいたほうが良いです。</p>
<p>そして、収集したRSSIの値に対するグリッド番号（位置）をモデルに学習させます。<br>機械学習モデルでは収集したRSSIの値を元にグリッド番号をクラス分類するように学習していきます。</p>
<h4 id="（4）位置推定する">（4）位置推定する</h4><img src="/images/2022/20220725a/b11df6af-4e01-f552-67d1-1c6e15327790.png" alt="" width="808" height="593" loading="lazy">

<p>学習させたモデルを使って現在のRSSI値からグリッド番号を推定することで位置がわかります。<br>推定された位置はDBに格納するなりして、アプリケーションにて可視化することでユーザーの現在位置が把握できます。</p>
<p>ただ、新しいグリッドができた場合やビーコンを新たに追加した場合、学習データを収集し直す必要があります。<br>また、グリッドが多いと学習データの収集が非常に大変です。</p>
<h2 id="おまけ">おまけ</h2><h3 id="v-s-GPS">v.s. GPS</h3><p>無線通信を使った位置推定手法にはGPS（Global Positioning System）、全地球測位システムも存在します。<br>GPSは上空にある複数の衛星（3個以上は必須）から信号を受け取り、現在位置を知ることができるシステムです。こちらも身近な位置推定手法で、</p>
<ul>
<li>スマホ</li>
<li>カーナビ</li>
</ul>
<p>などに搭載されています。<br>GPS自体の入手は比較的容易で、Switch Sicenseさんでも受信機自体を多数見つけることができます。<br>結構簡単に使うことができ、井上さんの過去記事プロトタイピングの勧めの中でもGPSユニットが使われています。</p>
<p>Bluetoothと比較した利点・欠点としては、</p>
<ul>
<li>広域で見るとかなり精度が良く、位置情報が安定している</li>
<li>信号の発信機を設置する必要がない</li>
<li>遮蔽物に弱い</li>
</ul>
<p>が挙げられます。屋外で使う分には力を発揮しますが、3個以上の信号を受信する必要があるため、遮蔽物があると信号を受信できずに位置を測位できません。</p>
<p>そのため、ある商品棚の近くや、ある部屋に居るなどの狭い範囲の位置推定や屋内だとBluetoothの方に分があります。</p>
<h3 id="v-s-タグ">v.s. タグ</h3><p>こちらは無線ではありませんが、カメラでバーコードのようなタグを認識することで位置を認識する手法があります。<br>（もちろんタグだけでは移動できないので、LiDARなどを用いてSLAM（自己位置推定）を併用しながら移動します）。<br>近年では飲食店でのロボットの導入が進んでおり、狭い範囲で決まった位置への移動を繰り返すロボットにとってはタグは非常に有効な手法になります。</p>
<p>また、タグを天井に配置できるためタグを隠す障害物の心配もなく、さらに飲食店の景観を損ねずに済みます。<br>余談ですが、羽田空港にあるAI_SCAPEではシェフもウェイターもロボットらしく、今回紹介したタグによる位置認識も導入されているようなので今度行ってみようと思います。</p>
<p>用途が違うので比較するのもおかしい話ですが、Bluetoothと比較した利点・欠点としては、</p>
<ul>
<li>位置推定ではなく、マーカーによって位置を認識しているので正確</li>
<li>マーカーに電源が要らない</li>
<li>人間の位置推定には向かない</li>
</ul>
<p>が挙げられます。</p>
<p>カメラ取り付けたヘルメット被ってもらって、天井にマーカー配置すれば人間も位置認識できなくはなさそうですね。</p>
<h2 id="まとめ">まとめ</h2><p>以上、3種類のBluetoothを用いた位置推定手法を紹介させて頂きました。<br>個人的には、ビーコンを1つのみ使って近くにいるかどうかのみを判断する手法の方が手間がかからず、コスト的にも安く抑えられるのかなと思います。</p>
<p>次は真野さんのPlus Codeについて調べた です。</p>
]]></content>
    <summary type="html">マイコンとか電子工作などIoT関連が好きで、Bluetooth信号を使った位置推定手法について調査したことがあったので、紹介します。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Bluetooth" scheme="https://future-architect.github.io/tags/Bluetooth/"/>
    <category term="位置特定" scheme="https://future-architect.github.io/tags/%E4%BD%8D%E7%BD%AE%E7%89%B9%E5%AE%9A/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>LT大会#13 2022 Spring</title>
    <link href="https://future-architect.github.io/articles/20220704a/"/>
    <id>https://future-architect.github.io/articles/20220704a/</id>
    <published>2022-07-03T15:00:00.000Z</published>
    <updated>2022-07-03T15:00:00.000Z</updated>
    <author><name>ダワージャルガルオチラル</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2022/20220704a/lt.png" alt="" width="800" height="340">

<h2 id="はじめに">はじめに</h2><p>みなさまどうもこんにちは、TIGのオチラルです。<br>2022年4月22日(金）に13回目の社内LT大会を開催しましたので、報告記事を書いていきます！</p>
<p>2020年10月に入社してから前回開催となる、#11「冬のぽかぽかテックトーク」、#12「今だから言えるゴメンナサイ話」を視聴し、とてもハイクォリティなLTに感銘を受けていた経緯もあり今回はLT大会の運営に携われて嬉しく思います！</p>
<ul>
<li>過去のLT大会の開催記事を読みたい場合はこちらの記事タグをご覧下さい。</li>
</ul>
<h2 id="LT大会とは">LT大会とは</h2><p>LT(Lightning Talk)とは5分程度の短いプレゼンテーションと言われています。ライトニングトークの略で直訳で「稲妻話」になりますね。「稲妻⚡のような短い時間で行うプレゼン」です（さらに「稲妻⚡を受けたような衝撃を受けるプレゼン」（※個人の解釈）です）。</p>
<p>主にIT業界で広まっている文化で、自分の知識を周りに短い時間で濃く伝えるというとても良い文化だなと思います。<br>外国でどれぐらい広まっている文化か分かりませんが、少なくとも僕の国🇲🇳ではあまり聞かない文化なので日本🇯🇵のIT業界の良き文化だなと思います。</p>
<p>フューチャーのLT大会は毎回テーマを設けており、今回のLT大会は春開催ということもあり、「春のウキウキテックトーク」というテーマで募集しました！</p>
<h2 id="発表内容">発表内容</h2><p>公開されている資料たちです。今回のOPトークにはFutureGlobalDesignの浜田さんが、シンガポールでの新規案件獲得に関する話をしてくれました！<br>グローバルプロジェクトの挑戦という「ウキウキ」とどこか心躍るトークでLT大会が始まりました。</p>
<p>井上さん、清水さんお二人の資料は社外公開がOKということで、共有します！</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/162525942aba41eba15ae3609726d98f" title="M5Stackで バイク用データロガーを作って ウキウキした話" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 420px;" data-ratio="1.3333333333333333"></iframe>

<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/af763d83b41540268253683e3b0afc4a" title="GAS+AppSheetで部活動のスタンプカードアプリを作った話" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 560px; height: 315px;" data-ratio="1.7777777777777777"></iframe>

<p>以下LT大会の発表内容です！ どれもレベルが高く聞いていてとても楽しかったです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>No</th>
<th>Title</th>
<th>Category</th>
<th>Name</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>ゼロからアジアビジネスはじめた話</td>
<td>オープニングトーク</td>
<td>浜田博隆さん</td>
</tr>
<tr>
<td>2</td>
<td>店舗配送を改善したい話</td>
<td>LT</td>
<td>尾関光徳さん</td>
</tr>
<tr>
<td>3</td>
<td>M5Stackでバイク用データロガーを作ってウキウキした話</td>
<td>LT</td>
<td>井上圭さん</td>
</tr>
<tr>
<td>4</td>
<td>GAS+AppSheetでFACTSのスタンプカードアプリを作った話</td>
<td>LT</td>
<td>清水雄一郎さん</td>
</tr>
<tr>
<td>5</td>
<td>船のデータを集めている話</td>
<td>LT</td>
<td>矢野颯太さん</td>
</tr>
<tr>
<td>6</td>
<td>昔ながらのおもちゃをハイテク化してみた</td>
<td>LT</td>
<td>石井純平さん</td>
</tr>
</tbody></table></div>
<p>また、今回見事優勝に輝いたのは「昔ながらのおもちゃをハイテク化してみた」で発表した石井純平さんでした！<br>外部非公開なので詳細は話せませんがとても「ウキウキ」とするLTでした！</p>
<h2 id="LT大会振り返り">LT大会振り返り</h2><p>合計107名の方が視聴してくださり、「気負うことなく、面白い話が聞けた」「とてもウキウキした」「テーマが良かった」などポジティブな感想を頂きました！</p>
<p>今回のLT大会の特徴としては：</p>
<ul>
<li>従来の運営メンバーが忙しく、新しい運営メンバーを募集しました。</li>
<li>新運営メンバーの<strong>2人で司会</strong>を行いました。<ul>
<li>自分で言うのもなんですが、2020年10月入社の僕と、2021年7月入社の合田さんという、年度の若い2人で司会を行い、「春らしいフレッシュさ」を感じさせられたかなと思っています</li>
</ul>
</li>
<li>参加者が少なかったこともあり、司会者トークだったり、LT時間が伸びても止めなかったり、質疑応答の時間を多く取りました。<ul>
<li>結果として、ゆったりとした雰囲気で進められたかなと思っています。</li>
<li>ただし、「もう少しLTの時間設定を守らせた方が大会としては良いと思います」という厳しい意見も頂きました。</li>
<li>「合田さんとオチラルさんの司会とコメントが良かった」と言った意見も頂きました！（嬉しいです😊)</li>
</ul>
</li>
<li>従来と同じ内定者の方々の参加もありました！ また、4月開催だったこともあり、4月新人の方々も沢山参加して頂きました！</li>
<li>従来と同様、コメントがとても盛り上がりました！</li>
</ul>
<h2 id="まとめ">まとめ</h2><p>今回2022年4月にLT大会 Springの開催報告を致しました。</p>
<p>次回開催もお楽しみに！</p>
]]></content>
    <summary type="html">みなさまどうもこんにちは、TIGのオチラルです。2022年4月22日(金）に13回目の社内LT大会を開催しましたので、報告記事を書いていきます！2020年10月に入社してから前回開催となる、#11「冬のぽかぽかテックトーク」を視聴し、とてもハイクォリティなLTに感銘を受けていた経緯もあり今回はLT大会の運営に携われて嬉しく思います！</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="LT" scheme="https://future-architect.github.io/tags/LT/"/>
    <category term="登壇レポート" scheme="https://future-architect.github.io/tags/%E7%99%BB%E5%A3%87%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>AWS Partner Summit Japan 2022に登壇してみた</title>
    <link href="https://future-architect.github.io/articles/20220415b/"/>
    <id>https://future-architect.github.io/articles/20220415b/</id>
    <published>2022-04-14T15:00:01.000Z</published>
    <updated>2022-04-14T15:00:01.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2022/20220415b/top.png" alt="" width="1200" height="447">

<h2 id="はじめに">はじめに</h2><p>2022&#x2F;3&#x2F;16（水）に開催されたAWS Partner Summit Japan 2022 に縁あって登壇の機会をいただきました。オンラインでしたが、撮影は目黒セントラルスクエア。TV局かなと思うほどの撮影機材や、明らかにプロフェッショナルなスタッフの皆さまに囲まれての発表だったので非常によい刺激をいただけました。終わった後の達成感が凄く良かったです！</p>
<h2 id="AWS-Partner-Summitとは">AWS Partner Summitとは</h2><p>2022年は3月16日～17日の2日開催で、AWSパートナーネットワーク(APN)に参加しているかつ、NDA締結済み企業が参加できる、年に1度の大型イベントです。完全なオープンでないはずですが、参加者は数千人オーダーとのこと。人気の秘密はセッションのタイムテーブルを読むと分かる気がします。興味をひかれる気合の入ったセッションが多数発表されていました。</p>
<p>フューチャーとしてはAWS Partner Summitに登壇したのは私が最初だったようで、ちょっと会社に爪痕を残せたようで嬉しいです。来年以降も良い事例などを作っていきたいなと思いました。</p>
<h2 id="登壇内容">登壇内容</h2><p>3&#x2F;16のなんと基調講演での枠にて登壇時間は7分。AWS 導入事例：日本瓦斯株式会社 | AWS で触れている内容について説明しました。</p>
<p>登壇の様子を同僚に撮影してもらいました。当日は視聴者多数で接続優先のため画質が落とされていたなどの裏事情があり静止画で見ると少し粗いです。<br>スライドですが会社説明はともかく、AWSパートナーシップについても触れるのはこのイベントならではだなと思いました。笑</p>
<img src="/images/2022/20220415b/会社紹介.png" alt="会社紹介" width="1200" height="635" loading="lazy">

<p>目的・ミッション・採用技術などはかなり面白く話せることが無限にあるのですが、この場ではスライドの紹介だけにさせてください。30分くらいは余裕で話せると思うのですが…、時間が短い！</p>
<img src="/images/2022/20220415b/プロジェクトミッション.png" alt="プロジェクトミッション" width="1200" height="653" loading="lazy">

<img src="/images/2022/20220415b/アーキテクチャ.png" alt="アーキテクチャ" width="1200" height="510" loading="lazy">

<p>短い時間とはいえ、企業のデジタルトランスフォーメーション化の文脈で、かなり野心的なプロジェクトであったことは伝えられたのはないでしょうか。</p>
<h2 id="さいごに">さいごに</h2><p>AWSの中橋さんが丁寧に色々サポートしてくださって助かりました。社内だと吉田さんとか富山さんに頼り切りました。会場の様子ですが、こんな感じでした。登壇時のみノーマスクで、待機時間などはディスタンスを保ちつつアクリル板とかも用意されていたので安心でした。ページ送りとかは左手に持っているスイッチみたいなので行い、写真にはちょっとしか写ってないですが、前面のディスプレイを見ながら話せるのでとても楽でした。</p>
<img src="/images/2022/20220415b/会場の様子.jpeg" alt="会場の様子" width="1200" height="900" loading="lazy">

<p>繰り返しになりますが、貴重な機会をいただけて感謝しています。自分自身としてもこうしたDX案件が大好きなので、楽しみながらさらにステージを上げていきたいと思ってます。最後まで読んでいただき、ありがとうございました！</p>
]]></content>
    <summary type="html">2022/3/16（水）に開催されたAWS Partner Summit Japan 2022に縁あって登壇の機会をいただきました。オンラインでしたが、撮影は目黒セントラルスクエア。TV局かなと思うほどの撮影機材や、明らかにプロフェッショナルなスタッフの皆さまに囲まれての発表だったので非常によい刺激をいただけました。終わった後の達成感が凄く良かったです！</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="登壇レポート" scheme="https://future-architect.github.io/tags/%E7%99%BB%E5%A3%87%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>元日に92%引きで叩き売りされていた変形ホームロボットを買ったのでScalaから叩いてみた</title>
    <link href="https://future-architect.github.io/articles/20220408a/"/>
    <id>https://future-architect.github.io/articles/20220408a/</id>
    <published>2022-04-07T15:00:00.000Z</published>
    <updated>2022-04-07T15:00:00.000Z</updated>
    <author><name>山本力世</name></author>
    <content type="html"><![CDATA[<p>電子工作&#x2F;IoT連載 の5本目です</p>
<h2 id="リベンジ">リベンジ</h2><p>2019年に某ロボットが85％引きで叩き売りされましたが、その時はウェーブに乗り遅れてしまい、入手できず悔しい思いをした経験があったのですが、今年の正月、それを超える値引率である92%引きで叩き売りされていた変形ホームロボットを購入できました。今回はウェーブに乗ることができたのでとりあえずそれだけで非常に満足でした。</p>
<h2 id="今回入手した変形ホームロボットについて">今回入手した変形ホームロボットについて</h2><p>以前叩き売りされていた某ロボットは25軸でしたが、今回の変形ホームロボットは3軸+2輪となります。<br>ロボットの名前はCerevo社の「Tipron」と言い、6年前のCESでもお披露目されていたロボです。<br>ルンバはお掃除ロボットですが、このTipronはプロジェクターロボットになります。わざわざ、部屋の真ん中にプロジェクターを手で運ばなくても、プロジェクター自身が自分で部屋の真ん中まで自動で移動して映画などを映してくれるというものです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>変形前</th>
<th>変形後</th>
</tr>
</thead>
<tbody><tr>
<td><img fetchpriority="high" src="/images/2022/20220408a/fold.png" alt="fold" width="998" height="1331"></td>
<td><img src="/images/2022/20220408a/extend.png" alt="extend" width="990" height="1320" loading="lazy"></td>
</tr>
</tbody></table></div>
<h2 id="準備">準備</h2><p>Scalaを今使うならやっぱりフレッシュな3を使いたいですよね。<br>ただし、現状のsbt(Scalaのビルドツール）はJava 18だとエラーが出るので、Java 17で実行したいと思います。</p>
<figure class="highlight bash"><figcaption><span>macOS/Linuxなどでの手順例</span></figcaption><table><tr><td class="code"><pre><span class="line">&gt;curl -s <span class="string">&quot;https://get.sdkman.io&quot;</span> | bash</span><br><span class="line">&gt;<span class="built_in">source</span> <span class="string">&quot;<span class="variable">$HOME</span>/.sdkman/bin/sdkman-init.sh&quot;</span></span><br><span class="line">&gt;sdk install java 17-open</span><br><span class="line">&gt;sdk install sbt 1.6.2</span><br></pre></td></tr></table></figure>

<p>次の各ファイル、フォルダを作成します。</p>
<figure class="highlight scala"><figcaption><span>project/build.properties</span></figcaption><table><tr><td class="code"><pre><span class="line">sbt.version=<span class="number">1.6</span><span class="number">.2</span></span><br></pre></td></tr></table></figure>

<figure class="highlight scala"><figcaption><span>project.sbt</span></figcaption><table><tr><td class="code"><pre><span class="line">scalaVersion := <span class="string">&quot;3.1.1&quot;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight scala"><figcaption><span>tipron.scala</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> jp.co.future.example.tipron</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.net._</span><br><span class="line"><span class="keyword">import</span> java.nio._</span><br><span class="line"><span class="keyword">import</span> java.nio.channels._</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">object</span> <span class="title">Tipron</span> <span class="keyword">extends</span> <span class="title">App</span></span>:</span><br><span class="line">  <span class="keyword">val</span> <span class="type">TIPRON_IP</span> = <span class="string">&quot;Tipron本体のIPアドレス&quot;</span></span><br><span class="line">  <span class="keyword">val</span> <span class="type">SENDER_ADDR</span> = <span class="keyword">new</span> <span class="type">InetSocketAddress</span>(<span class="number">9999</span>)</span><br><span class="line">  <span class="keyword">val</span> <span class="type">RECIEVER_ADDR</span> = <span class="keyword">new</span> <span class="type">InetSocketAddress</span>(<span class="type">TIPRON_IP</span>, <span class="number">9999</span>)</span><br><span class="line">  <span class="keyword">val</span> channel = <span class="type">DatagramChannel</span>.open()</span><br><span class="line">  channel.socket().bind(<span class="type">SENDER_ADDR</span>)</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">def</span> <span class="title">sendMsg</span></span>(msg: <span class="type">String</span>) =</span><br><span class="line">    <span class="keyword">val</span> buff = <span class="type">ByteBuffer</span>.allocate(msg.length)</span><br><span class="line">    buff.clear()</span><br><span class="line">    buff.put(msg.getBytes)</span><br><span class="line">    buff.flip()</span><br><span class="line">    <span class="keyword">val</span> result = channel.send(buff, <span class="type">RECIEVER_ADDR</span>)</span><br><span class="line">    result</span><br><span class="line"></span><br><span class="line">  sendMsg(<span class="string">&quot;&quot;&quot;&#123;&quot;clearQueue&quot;:false,&quot;commandId&quot;:1,&quot;commandName&quot;:&quot;turnLeft&quot;,&quot;commandValue&quot;:&quot;-90&quot;&#125;&quot;&quot;&quot;</span>)</span><br><span class="line">  <span class="type">Thread</span>.sleep(<span class="number">5000</span>)</span><br><span class="line">  sendMsg(<span class="string">&quot;&quot;&quot;&#123;&quot;clearQueue&quot;:false,&quot;commandId&quot;:1,&quot;commandName&quot;:&quot;turnLeft&quot;,&quot;commandValue&quot;:&quot;90&quot;&#125;&quot;&quot;&quot;</span>)</span><br></pre></td></tr></table></figure>

<h2 id="叩いてみる">叩いてみる</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">&gt;sbt run</span><br></pre></td></tr></table></figure>

<p>実行すると、右旋回を少し行なった後、同じ角度分、左旋回を行なって元の向きに戻ります。<br><img src="/images/2022/20220408a/run.gif" alt="run" width="270" height="480" loading="lazy"></p>
<h2 id="今後の展望">今後の展望</h2><p>Tipronの標準バッテリーだとプロジェクターの連続稼働時間が心許ないので、手元のお小遣いに余裕ができてきたら、追加のバッテリーを買って空いているバッテリー格納スペースに入れたいと思っています。</p>
<h2 id="参考">参考</h2><p>本記事は次のサイトを参考しました。</p>
<ul>
<li>GitHub Gist: yoneken&#x2F;cam_stream.py</li>
<li>Cerevo TechBlog: 変形するロボットプロジェクターをスクリプトで自由自在に動かす（絶賛決算セール中！）</li>
</ul>
]]></content>
    <summary type="html">2019年に某ロボットを購入することができました。今回はウェーブに乗ることができたのでとりあえずそれだけで非常に満足でした。以前叩き売りされていた某ロボットは25軸でしたが、今回の変形ホームロボットは3軸+2輪となります。ロボットの名前はCerevo社ロボです。ルンバはお掃除ロボットですが、このTipronはプロジェクターロボットになります。わざわざ、部屋の真ん中にプロジェクターを手で運ばなくても、プロジェクター自身が自分で部屋の真ん中まで自動で移動して映画などを映してくれるというものです。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Scala" scheme="https://future-architect.github.io/tags/Scala/"/>
    <category term="ロボット" scheme="https://future-architect.github.io/tags/%E3%83%AD%E3%83%9C%E3%83%83%E3%83%88/"/>
  </entry>
  <entry>
    <title>TinkerBoard 2S：AWS Greengrass v1をインストールする</title>
    <link href="https://future-architect.github.io/articles/20220407a/"/>
    <id>https://future-architect.github.io/articles/20220407a/</id>
    <published>2022-04-06T15:00:00.000Z</published>
    <updated>2022-04-06T15:00:00.000Z</updated>
    <author><name>宮永崇史</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2022/20220407a/tinker.png" alt="tinker.png" width="945" height="287">

<p>電子工作&#x2F;IoT連載 の4本目です</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは、TIG所属の宮永です。</p>
<p>ASUSが販売しているシングルボードコンピュータTinekr Board2SにAWS Greengrassの環境を構築したので記事にまとめました。</p>
<p>シングルボードコンピュータと聞くと真っ先に思い浮かべるのはRaspberryPiでしょうか。私も初めて触ったシングルボードコンピュータはRaspberryPiでした。</p>
<p>マザーボードなどで有名なASUSが販売しているシングルボードコンピュータの性能はとても気になります。</p>
<p>ネット上でもTinker Board2Sの記事はRaspberryPiに比べると非常に少ないため、本記事の構成も前半はTinker Boardの初期セットアップ、後半をAWS Greengrassの環境構築との章立てとしています。</p>
<p>本記事がTinker Board2S購入検討をしている方の役にたてば幸いです。</p>
<h2 id="Tinker-Board2Sとは">Tinker Board2Sとは</h2><p>ASUSが販売するArmベースのシングルボードコンピュータです。<br>商品についての公式ページはこちらです。<br>まずは外観から観察します。</p>
<h3 id="外観">外観</h3><p>一際目を惹くのはでっかいヒートシンクですね。😳<br>このヒートシンクは付属品です。</p>
<img src="/images/2022/20220407a/tinker.JPG" alt="外観" width="1200" height="676" loading="lazy">

<p>手前にピンヘッダーが40個確認できます。着色してあるため非常に便利です。<br>RaspberryPiだと上から1つずつ数えていかなければ行けないのでこの仕様はとてもありがたいですね。</p>
<p>外部インターフェースを見ていきます。<br><img src="/images/2022/20220407a/usb.JPG" alt="外部インターフェース" width="1200" height="676" loading="lazy"></p>
<p>3.2USB Gen1 Type-Aが3つ、3.2USB Gen1 Type-Cが１つ付属しています。有線LANも接続できるようになっています。</p>
<img src="/images/2022/20220407a/DSC_0559.JPG" alt="USB周り" width="1200" height="676" loading="lazy">
側面です。一番左にあるのは電源端子その隣がHDMI端子です。
HDMIの横にMIDI DSI(Mobile Industry Processor Interface Display Serial Interface)も確認できます。
こちらの端子はディスプレイモジュールを扱う際に使用します。

<img src="/images/2022/20220407a/DSC_0560.JPG" alt="HDMI" width="1200" height="676" loading="lazy">

<p>最後に前面(背面？ )です。<br>手前に見えている端子はMIPI CSI(Mobile Industry Processor Interfa Camera Serial Interface)です。こちらの端子はカメラモジュールを取り付ける際に使用します。</p>
<p>その右側には小さいですがLEDが3つついています。<br>左から電源用LEDランプ、ディスクアクティビティLEDランプ、プログラマブルLEDです。<br>外観の観察もほどほどにOSのインストールをします。</p>
<h3 id="OSインストール">OSインストール</h3><p>Tinker Boardは専用のOSが公式ページで配布されています。<br>まずはOSをデスクトップPCにダウンロードします。<br>筆者のデスクトップ環境はUbuntu22.04ですのでセットアップもそれに準じたものとなっています。<br>以下Ubuntu22.04がインストールされた母艦PCをデスクトップPCと呼称しています。</p>
<p>デバイスをUSB経由でPCに接続しデスクトップPCでの正常に認識されているかを確認します。<br><code>lsusb</code>で接続デバイスを確認できます。</p>
<p><strong>Tinker Board接続前</strong></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">❯❯❯ lsusb</span><br><span class="line">Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub</span><br><span class="line">Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub</span><br><span class="line">Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub</span><br><span class="line">Bus 001 Device 031: ID 08bb:27c4 Texas Instruments PCM2704C stereo audio DAC</span><br><span class="line">Bus 001 Device 005: ID 05e3:0608 Genesys Logic, Inc. Hub</span><br><span class="line">Bus 001 Device 003: ID 0b05:1939 ASUSTek Computer, Inc. AURA LED Controller</span><br><span class="line">Bus 001 Device 043: ID 0d8c:016c C-Media Electronics, Inc.</span><br><span class="line">Bus 001 Device 042: ID 2be8:0001  USB 2.0 Hub [Safe]</span><br><span class="line">Bus 001 Device 041: ID 046d:c52b Logitech, Inc. Unifying Receiver</span><br><span class="line">Bus 001 Device 040: ID 1a40:0101 Terminus Technology Inc. Hub</span><br><span class="line">Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub</span><br></pre></td></tr></table></figure>

<p><strong>Tinker Board接続後</strong></p>
<figure class="highlight diff"><table><tr><td class="code"><pre><span class="line">❯❯❯ lsusb</span><br><span class="line">Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub</span><br><span class="line">Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub</span><br><span class="line">Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub</span><br><span class="line">Bus 001 Device 031: ID 08bb:27c4 Texas Instruments PCM2704C stereo audio DAC</span><br><span class="line">Bus 001 Device 005: ID 05e3:0608 Genesys Logic, Inc. Hub</span><br><span class="line">Bus 001 Device 003: ID 0b05:1939 ASUSTek Computer, Inc. AURA LED Controller</span><br><span class="line"><span class="addition">+Bus 001 Device 047: ID 0b05:7820 ASUSTek Computer, Inc. USB download gadget</span></span><br><span class="line">Bus 001 Device 043: ID 0d8c:016c C-Media Electronics, Inc. USB download gadget</span><br><span class="line">Bus 001 Device 042: ID 2be8:0001  USB 2.0 Hub [Safe]</span><br><span class="line">Bus 001 Device 041: ID 046d:c52b Logitech, Inc. Unifying Receiver</span><br><span class="line">Bus 001 Device 040: ID 1a40:0101 Terminus Technology Inc. Hub</span><br><span class="line">Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub</span><br></pre></td></tr></table></figure>

<p>デバイスが1つ増えていますね。</p>
<p>書き込みはEthcerを使用します。<br>デバイスを認識した状態でOSイメージをTinkerOSで選択します<br>私の場合はこのタイミングで書き込み先も自動的にTinkerBoardが登録されました。</p>
<img src="/images/2022/20220407a/image.png" alt="Ethcer" width="890" height="579" loading="lazy">

<p>書き込まれるのを待つだけです。</p>
<img src="/images/2022/20220407a/image_2.png" alt="Ethcer書き込み中" width="801" height="503" loading="lazy">

<p>イメージの書き込みが完了したら再度電源を入れます。<br>起動するとリブートが始まり、以下の画面が立ち上がります。<br><img src="/images/2022/20220407a/Screenshot_from_2022-02-04_22-29-32.png" alt="起動画面" width="1200" height="676" loading="lazy"></p>
<p>有線LANを接続した状態で引き続きセットアップを行います。</p>
<p>SSH接続します。<br>ログイン名linaroとなっているので</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ssh linaro@&lt;IPaddress&gt;</span><br></pre></td></tr></table></figure>

<p>で接続ができます。Passwordを要求されるのでlinaroと入力すると接続ができます。<br>AWS Greengrassの環境構築はssh接続できれば問題ありませんので、Tinker Boardの初期セットアップは以上で完了とします。</p>
<h2 id="AWS-Greengrassのインストール">AWS Greengrassのインストール</h2><p>AWS Greengrassは2022年4月現在v1とv2の２つあります。<br>今回はCPU　ARM64 【AArch64】環境でdockerを使用したかったためv1の環境構築を行います。<br>v2では　ARM64 【AArch64】のdocker環境はサポートされていないようです。</p>
<p>なお、以下の環境構築ではTinker Board上にdocker及び、docker-composeがインストールされていることを想定しています。</p>
<p>環境構築は以下の公式の開発者ガイドに沿って行います。<br>https://docs.aws.amazon.com/ja_jp/greengrass/v1/developerguide/what-is-gg.html</p>
<h3 id="Greengrassのグループ作成">Greengrassのグループ作成</h3><p>まずはAWS Greengrassのグループを作成します。<br>デフォルトの設定でグループを作成します。<br>AWS IoTコンソール画面左のタブから「クラシック＞グループ」を選択します.<br>画面遷移後「グループを作成」を選択すると下図のようになるので「デフォルト作成を使用」を選択します。</p>
<img src="/images/2022/20220407a/image_3.png" alt="Greengrassのグループ作成" width="241" height="107" loading="lazy">

<img src="/images/2022/20220407a/image_4.png" alt="Greengrassのグループ作成のグループ名" width="879" height="620" loading="lazy">
適当にグループ名をつけて「次へ」を選択します。

<img src="/images/2022/20220407a/image_5.png" alt="Greengrassのグループ作成のグループ名" width="878" height="481" loading="lazy">
こちらも同様にして「次へ」を選択します。
<img src="/images/2022/20220407a/image_6.png" alt="次へ" width="867" height="477" loading="lazy">

<p>グループの作成が完了すると証明書が発行されます。<br>この証明書は後ほど使用するためデスクトップPCにダウンロードしておきます。</p>
<img src="/images/2022/20220407a/image_7.png" alt="証明書の発行" width="910" height="859" loading="lazy">

<p>次に先程発行した証明書を使用してGreengrassコンテナを起動します。<br>必要なDockerfileなどはこちらからダウンロードできます。</p>
<p>私はバージョン1.10を使用しました。<br><img src="/images/2022/20220407a/image_8.png" alt="バージョン1.10" width="1197" height="181" loading="lazy"></p>
<p>ダウンロードした圧縮ファイルはデスクトップPCからTinker Boardに転送します。<br>以下のコマンドTinker Boardのhomeに転送できます。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">scp -r aws-greengrass-docker-1.11.0.tar.gz linaro@&lt;IPaddress&gt;:~/</span><br></pre></td></tr></table></figure>

<p>先程ダウンロードした証明書も転送してしまいましょう。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">scp -r xxxxxx-setup.tar.gz linaro@&lt;IPaddress&gt;:~/</span><br></pre></td></tr></table></figure>

<p>それではsshでTinker Boardに接続してコンテナを起動します。</p>
<p>sshで接続します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">ssh linaro@&lt;IPaddress&gt;</span><br></pre></td></tr></table></figure>

<p>ディレクトリ構成ですが私はhome直下にgreengrassというディレクトリを作成し、そこに先程の2つのファイルを格納しました。<br>下記のような構成です。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">linaro@linaro-alip:~/greengrass$ tree</span><br><span class="line">.</span><br><span class="line">├── xxxxxxx-setup.tar.gz</span><br><span class="line">└── aws-greengrass-docker-1.11.0.tar.gz</span><br></pre></td></tr></table></figure>

<p>まずはaws-greengrass-docker-1.11.0.tar.gzを同一ディレクトリに解凍します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">tar -zxvf aws-greengrass-docker-1.11.0.tar.gz</span><br></pre></td></tr></table></figure>

<p>次に証明書の圧縮ファイルを解凍したフォルダの中に解凍します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">tar -xzvf xxxxxxx-setup.tar.gz -C aws-greengrass-docker-1.11.0/</span><br></pre></td></tr></table></figure>

<p>この状態でディレクトリ構成は以下のようになります。<br>certsとconfigは証明書の圧縮ファイルを解凍して生成されたものです。<br>本記事で解説している方法は解凍ファイル中のREADME.mdに全て記載されています。<br>RaspberryPiを使用される方はarmv7l についての環境構築方法も記載されているため、ぜひご覧になってください。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">linaro@linaro-alip:~/greengrass/aws-greengrass-docker-1.11.0$ <span class="built_in">ls</span></span><br><span class="line">Dockerfile                          config</span><br><span class="line">Dockerfile.alpine-aarch64           deployment</span><br><span class="line">Dockerfile.alpine-aarch64.template  docker-compose.alpine-aarch64.yml</span><br><span class="line">Dockerfile.alpine-armv7l            docker-compose.alpine-armv7l.yml</span><br><span class="line">Dockerfile.alpine-armv7l.template   docker-compose.alpine-x86-64.yml</span><br><span class="line">Dockerfile.alpine-x86_64            docker-compose.yml</span><br><span class="line">Dockerfile.alpine-x86_64.template   greengrass-entrypoint.sh</span><br><span class="line">README.md                           greengrass-license-v1.pdf</span><br><span class="line">certs</span><br></pre></td></tr></table></figure>

<h3 id="コンテナの起動">コンテナの起動</h3><p>開発ガイドにはLinuxをコアデバイスとするときに以下のコマンドを入力するように記載されていますので、それに従います。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> 1 &gt; /proc/sys/fs/protected_hardlinks</span><br><span class="line"><span class="built_in">echo</span> 1 &gt; /proc/sys/fs/protected_symlinks</span><br></pre></td></tr></table></figure>

<p>また、<code>/etc/sysctl.conf</code>に以下の記載をした後に<code>sudo sysctl -p</code>を端末に入力します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">net.ipv4.ip_forward = 1</span><br></pre></td></tr></table></figure>

<p>それでは用意されているdocekr-composeからイメージをビルドします。<br>docker-compose.ymlはCPUアーキテクチャ毎に用意されているので使用するプラットフォームに合わせてファイルを指定します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> docker-compose -f docker-compose.alpine-aarch64.yml build</span><br></pre></td></tr></table></figure>

<p>ビルドが終了したら、コンテナを起動します。<br>コンテナの起動の前にルート証明書が必要なのでcertsディレクトに移動して以下のコマンドを入力します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> wget -O root.ca.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem</span><br></pre></td></tr></table></figure>

<p>certs配下にroot.ca.pemがダウンロードされていることを確認してください。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">linaro@linaro-alip:~/greengrass/aws-greengrass-docker-1.11.0/certs$ tree</span><br><span class="line">.</span><br><span class="line">├── xxxxxxxxxx.cert.pem</span><br><span class="line">├── xxxxxxxxxx.private.key</span><br><span class="line">├── xxxxxxxxxx.public.key</span><br><span class="line">└── root.ca.pem</span><br></pre></td></tr></table></figure>

<p>ここまでできたらコンテナを起動します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> docker-compose -f docker-compose.alpine-aarch64.yml up</span><br></pre></td></tr></table></figure>

<p>起動しました🎉</p>
<img src="/images/2022/20220407a/image_9.png" alt="起動画面" width="1167" height="167" loading="lazy">

<h3 id="Lambda関数の準備">Lambda関数の準備</h3><p>次にマネジメントコンソールからLambda関数をコンテナに向けてデプロイします。</p>
<p>Lambda関数を作成するのに必要なGreengrassのPython SDKはここからダウンロードできます。</p>
<p>開発者ガイドに記載の通り<br>Helloフォルダにgreengrasssdkをコピーしてzipファイルに圧縮します。<br>ここからダウンロードしたファイルのexamplesにHelloフォルダがあるので以下の構成で<br>圧縮ファイルを作成します。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line">~/Downloads/aws-greengrass-core-sdk-python-master/examples/HelloWorld</span><br><span class="line">❯❯❯ tree</span><br><span class="line">.</span><br><span class="line">├── greengrasssdk</span><br><span class="line">│   ├── IoTDataPlane.py</span><br><span class="line">│   ├── Lambda.py</span><br><span class="line">│   ├── SecretsManager.py</span><br><span class="line">│   ├── __init__.py</span><br><span class="line">│   ├── client.py</span><br><span class="line">│   ├── stream_manager</span><br><span class="line">│   │   ├── __init__.py</span><br><span class="line">│   │   ├── data</span><br><span class="line">│   │   │   └── __init__.py</span><br><span class="line">│   │   ├── exceptions.py</span><br><span class="line">│   │   ├── streammanagerclient.py</span><br><span class="line">│   │   ├── util.py</span><br><span class="line">│   │   └── utilinternal.py</span><br><span class="line">│   └── utils</span><br><span class="line">│       ├── __init__.py</span><br><span class="line">│       └── testing.py</span><br><span class="line">└── greengrassHelloWorld.py</span><br></pre></td></tr></table></figure>

<p>圧縮コマンドは以下の通りです。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">zip -r hello_world_python_lambda.zip greengrasssdk greengrassHelloWorld.py</span><br></pre></td></tr></table></figure>

<p>それでは、AWSクラウド上にLambdda関数をデプロイします。<br>マネジメントコンソールで適当な名前をつけてLambda関数を作成します。</p>
<img src="/images/2022/20220407a/image_10.png" alt="Lambdaデプロイ" width="1200" height="450" loading="lazy">

<p>先程のzipファイルを直接アップロードしてハンドラの名前を編集します。</p>
<img src="/images/2022/20220407a/image_11.png" alt="Lambdaのハンドラ設定" width="774" height="639" loading="lazy">

<p>開発者ガイドに沿ってバージョンとエイリアスも設定します。</p>
<img src="/images/2022/20220407a/image_12.png" alt="Lambdaのエイリアス" width="813" height="369" loading="lazy">

<p>クラウド上へのLambda関数のデプロイが完了したらコンテナに向けてコードの更新を行います。<br>Greengrassのグループから「Lambdaの追加」を選択します。</p>
<img src="/images/2022/20220407a/image_13.png" alt="Lambda追加" width="930" height="806" loading="lazy">

<p>すでにLambda関数はデプロイしていますので「既存のLambdaの使用」を選択します。</p>
<img src="/images/2022/20220407a/image_14.png" alt="既存のLambdaの使用ボタン" width="906" height="456" loading="lazy">

<p>関数を追加したらLambdaの設定画面に移動して<br>「タイムアウト」とLambdaの「ライフサイクル」を下記のように設定します。<br>他の設定はデフォルトのままで「更新」を選択します。</p>
<img src="/images/2022/20220407a/image_15.png" alt="Lambdaの実行時の設定画面" width="923" height="741" loading="lazy">

<p>グループの設定画面に戻り「最初のサブスクリプションの追加」を選択します。</p>
<img src="/images/2022/20220407a/image_16.png" alt="Greengrassの最初のサブスクリプションの追加" width="920" height="605" loading="lazy">

<p>「サブスクリプションの作成」画面ではメッセージの送信元(ソース)と受信先（ターゲット）を選択します。<br>AWS IoTではMQTTをPub&#x2F;Subできます。<br>以前RaspberryPiとAWS IoT CoreでPub&#x2F;Subした記事をいかにまとめていますのでよろしければご覧になってください。</p>
<p>GoでMQTT!!　～温湿度マイスターbotの作成～(前編)<br>GoでMQTT!!　～温湿度マイスターbotの作成～(後編)</p>
<p>トピックのフィルターに「hello&#x2F;world」と入力して「次へ」を選択します。</p>
<img src="/images/2022/20220407a/image_17.png" alt="image.png" width="936" height="567" loading="lazy">

<p>それではコアデバイスにLambda関数をデプロイします。<br>作成したHelloWorldグループ画面で「アクション＞デプロイ」を選択します。<br>デプロイする前にコアデバイスでGreengrassが起動していることを再度確認しましょう。</p>
<img src="/images/2022/20220407a/image_18.png" alt="トピックのフィルタリング" width="931" height="476" loading="lazy">

<p>「自動検出」を選択するとデプロイが開始します。</p>
<img src="/images/2022/20220407a/image_19.png" alt="自動検出" width="924" height="476" loading="lazy">

<p>デプロイが正常に終了するとステータスが緑色になります。</p>
<img src="/images/2022/20220407a/image_20.png" alt="ステータス" width="904" height="480" loading="lazy">

<h2 id="動作確認">動作確認</h2><p>AWS IoTのマネジメントコンソールの「テスト＞MQTTテストクライアント」からトピックをサブスクライブします。</p>
<img src="/images/2022/20220407a/image_21.png" alt="動作確認" width="244" height="103" loading="lazy">

<p>トピックのフィルターはhello&#x2F;worldとして、各種設定を以下のようにします。</p>
<img src="/images/2022/20220407a/image_22.png" alt="トピックの設定" width="899" height="548" loading="lazy">

<p>サブスクライブを選択すると、MQTTの受信が始まります。</p>
<p>コアデバイスが正常に動作していると下図のようにコアデバイスから設定したメッセージが届きます🎉</p>
<img src="/images/2022/20220407a/image_23.png" alt="MQTT受信" width="1200" height="556" loading="lazy">

<p>コアデバイスのLambda関数はクラウド上から関数を更新、グループの設定から再デプロイを行うことでいつでも更新できます。とても便利ですね。</p>
<p>今回はTinker BoardにGreengrassの環境を構築して終わりましたが、次回はエッジデバイスでの加工処理なども行いたいと思います。<br>最後までお付き合いいただきありがとうございました。</p>
]]></content>
    <summary type="html">ASUSが販売しているシングルボードコンピュータTinekr Board2SにAWS Greengrassの環境を構築したので記事にまとめました。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="シングルボード" scheme="https://future-architect.github.io/tags/%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%9C%E3%83%BC%E3%83%89/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>プロトタイピングの勧め</title>
    <link href="https://future-architect.github.io/articles/20220406a/"/>
    <id>https://future-architect.github.io/articles/20220406a/</id>
    <published>2022-04-05T15:00:00.000Z</published>
    <updated>2022-04-05T15:00:00.000Z</updated>
    <author><name>井上圭</name></author>
    <content type="html"><![CDATA[<p>電子工作&#x2F;IoT連載 の3本目です。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。フューチャーの井上です。<br>過去にこんな記事を書いています。</p>
<ul>
<li>自作キーボードを組み立てた話</li>
<li>キーボードを、基盤から自作してみた</li>
</ul>
<p>今回は、M5Stackというマイコンモジュールを使って、「バイクの各種情報を簡単に表示できたらいいな」という”思い付きを形にしていく過程”を記事にしようと思います。</p>
<h2 id="概要">概要</h2><p>結果として、車両データロガーのようなものを作りました。</p>
<img fetchpriority="high" src="/images/2022/20220406a/image.png" alt="車両データを表示したM5Stack" width="600" height="322">

<p>車でレースをする人のにはなじみ深い、いわゆるデータロガーで取るようなデータの一部を表示します。モックアップとしてM5Stackで実装し、実際に実装する際に考慮すべき点などをこれで確認しました。</p>
<p>以下のような流れで制作しています。これは、仕事で何かを作る際にやることと一緒だと思います。</p>
<h3 id="やりたいことを決める">やりたいことを決める</h3><p>現在、私はバイクを3台所有しています（HONDA GROM&#x2F;HONDA V-Twin Magna&#x2F;HONDA CB150R）。以前私は車でレースをしていたのですが、バイクは市販のままだと車両データがほとんど見れないのです。ある程度同じような情報が見たいな、と思っていました。</p>
<p>四輪のレース時は、以下のような情報を取っていました。</p>
<ul>
<li>加速度（旋回&#x2F;加速の強度を見れる）</li>
<li>傾き（地形の影響を見れる、操作の滑らかさを確認できる）</li>
<li>エンジン状態（回転数、燃料消費量、スロットル開度、etc…）</li>
</ul>
<p>四輪の車においてはOBD2という自動車診断規格があり、そこから車内ネットワークであるCAN Busにアクセスして情報を取得できます。</p>
<blockquote>
<p>CAN（Controller Area Network）とは、（ざっくりと説明すると）車載機器に使われるネットワークのことです。CANプロトコルを使い、車載機器間の通信を行います。例えばエンジンからは回転数情報を取得することができ、メーターがこの値を表示することで例えば「運転席のメーターにエンジン回転数が表示される」ようになります。逆に言うと、偽のデータを送り付けることで車両に異常を起こすことが可能です（セキュリティの領域）。</p>
</blockquote>
<blockquote>
<p>OBD2は、自動車診断企画です。上記CANはOBD2で使用されるプロトコルの一つで、故障診断用端末を接続しデータを吸い出すことを主な目的としています。日本では、四輪車量は2008年以降の新車に義務付け、二輪車両は2020年12月以降の新車に義務付けがされました。最近車両を買った方がいれば、ダッシュボードや運転席前部にコネクタが隠されているはずです。尚、適当に触ってしまうと機器故障と判断され、ディーラー持ち込み修理になる可能性があるのでお勧めはしません。</p>
</blockquote>
<p>私が最近買ったバイク（GROM）にはOBD2が装備されているらしいことを知ったので、そこから情報を取得して、記事にしようと思います。<br>四輪でのOBD2経由でデータ取得&#x2F;書き換えの記事は世の中にあふれていますが、二輪のものはあまりありません。何ができるのか、辺りを調べることは良いアウトプットになりそうです。</p>
<p>以下をゴールにしようと思います。</p>
<ul>
<li>バイクに設置する、データロガーを作ろう<ul>
<li>以下の機能を実装する<ul>
<li>傾斜を表示する（ロール&#x2F;ピッチ）</li>
<li>加速度を表示する（X&#x2F;Y軸の加速度）</li>
<li>エンジン状態を表示する（OBD2経由で取得・四輪なら取得できる）<ul>
<li>燃料消費量<ul>
<li>100kmでの燃料消費量（L&#x2F;100km）</li>
<li>1時間での消費量（L&#x2F;h）</li>
<li>燃料圧力（kPa）</li>
</ul>
</li>
<li>車速（km&#x2F;h）</li>
<li>ブースト圧（bar）</li>
<li>回転数（rpm）</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="最初の躓き">最初の躓き</h3><p>データが取得できることをまずは確かめたいので、このようなプロジェクトで使いやすいM5Stackというマイコンモジュールを使います。</p>
<blockquote>
<p>M5Stackとは、WiFiやBlutoothの機能を備えたCPUに、液晶ディスプレイやボタンや入出力ピンなどを一通り搭載した、小型のマイコンモジュールです。”Stack”と名前がついているように、専用モジュールを積み重ねて機能拡張ができます。</p>
</blockquote>
<p>https://m5stack.com/</p>
<p>M5Stackを使ってバイクのCAN-Bus情報を見ようと思ったのですが、なんと、<br>M5Stack用の <strong>CAN Busユニットが売り切れ</strong> でした。</p>
<p>本企画の根幹を揺る問題ですが、この記事は書かないといけません。<br>故に、OBD2を使わずに車両状況を見るものを作ってみます。</p>
<p>M5Stackを使うと以下の情報が単体で取得できます。また、追加のセンサーがUnitとして販売されているので、今回はGPS Unitを使ってみようと思います。</p>
<ul>
<li>M5Stack本体<ul>
<li>M5StickC Plus</li>
<li>内蔵センサ<ul>
<li>6軸IMU<ul>
<li>加速度（X&#x2F;Y&#x2F;Z軸）</li>
<li>傾斜角度（ピッチ&#x2F;ロール）</li>
</ul>
</li>
</ul>
</li>
<li>表示機能<ul>
<li>液晶（135 x 240）</li>
<li>LED x1</li>
</ul>
</li>
</ul>
</li>
<li>追加のUnit<ul>
<li>GPS Unit<ul>
<li>経度緯度</li>
<li>速度、コース</li>
<li>時刻</li>
</ul>
</li>
</ul>
</li>
</ul>
<img src="/images/2022/20220406a/image_2.png" alt="M5Stack" width="600" height="382" loading="lazy">

<p>当初の実装目標を変更し、以下を実現できるものにしようと思います。</p>
<ul>
<li>取得する&#x2F;表示するもの<ul>
<li>加速度(X&#x2F;Y軸)<ul>
<li>急な加減速は安全運転ではない、レース時は同じ場所で同じような加減速をすることでタイムが安定する）</li>
</ul>
</li>
<li>傾斜角度（ピッチ&#x2F;ロール）<ul>
<li>ロール不足は車両の能力を生かし切れていない、ピッチが大きい場合はサスペンションセッティングの変更が必要</li>
<li>山道などでどの程度の坂なのか、が判別できる（燃費に影響）</li>
</ul>
</li>
<li>現在時刻<ul>
<li>時刻は見えた方が良いよね（バイクのメーターにも表示されているけど）</li>
</ul>
</li>
<li>車速<ul>
<li>今は使わないけど、サーキットでは位置情報などと合わせて安定した周回ができているかの判断になる</li>
</ul>
</li>
<li>GPS情報<ul>
<li>補足している衛星数とGPS受信ステータスは見えたほうが良い</li>
</ul>
</li>
</ul>
</li>
<li>取らないもの<ul>
<li>加速度（Z軸）<ul>
<li>Z軸（上下動）はジャンプでもしない限り不要なので実装しない</li>
</ul>
</li>
<li>緯度経度情報<ul>
<li>M5Stackの制約を考えると、プロトタイプなので、まだ要らない<ul>
<li>利用するM5StickC PlusにはSDカードなどの記録媒体が無いので、ローカル記録ができない</li>
<li>画面表示領域が狭い、経度緯度を見ても人間には分かりづらい（要PCでのデータ加工）</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<img src="/images/2022/20220406a/image_3.png" alt="加速度、ピッチのイメージ" width="801" height="429" loading="lazy">

<h3 id="どのように「見せるか」を決める">どのように「見せるか」を決める</h3><p>M5StackC Plusの液晶（125 x 240）は小さいので、表示方法を考えないといけません。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">項目</th>
<th align="left">数値必須</th>
<th align="left">見やすくする必要性</th>
</tr>
</thead>
<tbody><tr>
<td align="left">加速度</td>
<td align="left">None</td>
<td align="left">数値自体より、推移を見たい</td>
</tr>
<tr>
<td align="left">傾斜度</td>
<td align="left">None</td>
<td align="left">数値自体より、推移を見たい</td>
</tr>
<tr>
<td align="left">現在時刻</td>
<td align="left">Yes</td>
<td align="left">なくてもいいけど…</td>
</tr>
<tr>
<td align="left">車速</td>
<td align="left">Yes</td>
<td align="left">汎用的に言えば、数字出しておけばいいのでは</td>
</tr>
<tr>
<td align="left">GPS:補足衛星数</td>
<td align="left">Yes</td>
<td align="left">デバッグ的に見れれば良さそう</td>
</tr>
<tr>
<td align="left">GPS:ステータス</td>
<td align="left">None</td>
<td align="left">デバッグ的に見れれば良さそう</td>
</tr>
</tbody></table></div>
<p>グラフと数値の組み合わせで表現をすれば良さそうなので、一旦イメージを書き出してみます。</p>
<img src="/images/2022/20220406a/image_4.png" alt="M5Stackに表示するレイアウトイメージ" width="801" height="536" loading="lazy">

<p>運転しながら見えるサイズなのかは少し疑問ですが、とりあえずは実装してみましょう。</p>
<h3 id="実装する">実装する</h3><p>M5Stackは、UIFlow&#x2F;microPythonかArudino IDEで開発する必要があります。<br>今回はプログラミング知識が少なくてもできるように、UIFlowを使います。UIFlowは各ブロックを組み合わせてプログラミングできるツールです。ブラウザでのオンライン利用も可能なので、実機が無くても一部試してみることは可能です。</p>
<p>https://flow.m5stack.com/</p>
<img src="/images/2022/20220406a/image_5.png" alt="UIFlow" width="600" height="259" loading="lazy">

<p>まずは、画面を設計します。</p>
<ul>
<li>左側のハードウェア風の画像で、テキストボックス（Label）や線などを配置していきます。<ul>
<li>バーグラフは、線を位置を指定して再描画することで表現します。</li>
<li>水平儀の位置は、赤い色の円のオブジェクトを再描画して表現します。</li>
</ul>
</li>
</ul>
<p>次に、データを取得できるようにし、表示位置のラベルに記載するようにします。</p>
<ul>
<li>定期的に更新されるように Timer オブジェクトを利用します。</li>
<li>電源を切りやすいように、AボタンにPowerOffを割り当てます。</li>
</ul>
<p>できたものが以下のです。<br>ブロック構成で、日本語でアクションが書かれているので、プログラミングの知識が無くても理解できそう&#x2F;作れそう ですね。</p>
<img src="/images/2022/20220406a/image_6.png" alt="ブロック構成" width="1200" height="762" loading="lazy">

<p>UIFlowで作ったものを、microPythonに変換することもできます。<br>但し、UIFlowとmicroPythonは交互に行き来はできない為、microPythonで修正を行うとUIFlowに反映することはできず、以降はmicroPythonで構築することになります。<br><img src="/images/2022/20220406a/image_7.png" alt="microPython" width="1200" height="762" loading="lazy"></p>
<h3 id="試用する">試用する</h3><p>センサやプログラムの設計はできたので、さっそく実車で使用してみます。</p>
<p>まずはバイク（V-TwinMagna）のETCカードホルダに乗せてみましたが、ちょっと水平が取れてないので修正が必要ですね。<br>あと、絶望的に見辛いです。表示面を運転者側に傾けても、結構距離があるので文字が小さいので見えません。<br><img src="/images/2022/20220406a/image_8.png" alt="バイクに取り付けたM5Stack" width="500" height="667" loading="lazy"></p>
<p>次に別のバイク（GROM）に乗せてみます。ETCアンテナベースをミラーステーに付け、そこに養生用のテープで巻きつけます。<br>これもやっぱり、見辛いですね。高さの関係で見えはするのですが、文字サイズ的に読み取るのは難しいですね。<br>（画像を拡大すると、表示されている部分が見えます）。。<br><img src="/images/2022/20220406a/image_9.png" alt="別のバイクに取り付けたM5Stack" width="600" height="800" loading="lazy"></p>
<p>視認性が悪いですね。ですが、動作は問題なく行われました（運転中に撮影はできないので画像はありません）。</p>
<h3 id="改善案">改善案</h3><p>実際に屋外で使ってみたことで、（分かってはいたけど）以下の問題が明確に判明しました</p>
<ul>
<li>視認性の問題<ul>
<li>設置角度が運転者方向に向いていないので、のぞき込む必要がある。<ul>
<li>表示器を分離する必要がある。</li>
<li>M5Stack自体を傾ける場合、内蔵センサの補正が必要で、制度が下がる。外付けの加速度センサで水平を出す必要がある。</li>
</ul>
</li>
<li>輝度が足りず、日照下では見えない。<ul>
<li>7Segment LEDなどを使うか、屋外用のディスプレイを使う必要がある &#x3D; 表示器とセンサは分離する。</li>
<li>電子ペーパーなら視認性は良いが、紫外線耐久性や表示更新頻度で不安がある。</li>
</ul>
</li>
</ul>
</li>
<li>データ誤差があると推定される<ul>
<li>センサーを水平に保てていない場合、加速度等低く見積もられる &#x3D; センサー設置時の水平確認</li>
</ul>
</li>
<li>データが記録できない<ul>
<li>MicroSD等が使えるものを使う</li>
</ul>
</li>
<li>エンジン情報が取れない<ul>
<li>CAN Busへのアクセスが必要 &#x3D; 部品を買おう（売り切れ）</li>
</ul>
</li>
</ul>
<p>以上から、今後は以下のように更新していく予定です。</p>
<ul>
<li>表示機能を別に用意する<ul>
<li>LEDテープや7Segment LEDの方が見やすいものもある</li>
</ul>
</li>
<li>センサーは内臓ではなく、別部品で用意する<ul>
<li>設置自由度も上がる</li>
</ul>
</li>
<li>記録媒体が使えるデバイスに置き換える<ul>
<li>M5StickC Plusではなく、M5Stack（Basic等）などのMicroSDが使える筐体を利用する</li>
<li>Raspberry Piに乗り換える</li>
</ul>
</li>
<li>CAN Busに接続する<ul>
<li>バイクの診断ポートをOBD2変換し、OBD2経由でアクセスする</li>
<li><strong>部品を買う</strong></li>
</ul>
</li>
</ul>
<h2 id="さいごに">さいごに</h2><p>こんな感じでプロトタイプ作って改善していくのは楽しいですね。<br>思い付きを形にすることが最近は簡単にできるので、やってみましょう！</p>
<ul>
<li>M5Stackは簡単にプログラムできるし、いろいろなセンサ（UNIT）がそろっているので、思い付きを形にしやすいですね！</li>
</ul>
<p>弊社ではデバイスの開発をするプロジェクトもあり、おそらくこのように「やりたい事」「プロトタイプ」「改善」を回して開発をしていると思われます（私はそのようなプロジェクトにまだ参加していませんのですが…）。<br>ここまで読んだ方なら興味があると思いますので、ぜひ採用担当者等にご相談ください。関連する方を紹介してもらえるかもしれません。</p>
<h2 id="参考">参考</h2><ul>
<li>M5Stack<ul>
<li>https://m5stack.com/<ul>
<li>用途に合わせて色々なシリーズがあります。</li>
<li>基本的にGPIOと表示系とボタンがあり、単体で活用しやすいモジュール構成になっています。</li>
<li>プログラミングは、UIFlow&#x2F;MicroPythonかArudinoのものを利用します。</li>
</ul>
</li>
<li>同様なものに micro:bit - https://microbit.org/ja/ があります。</li>
</ul>
</li>
<li>UIFlow<ul>
<li>https://m5stack.com/uiflow<ul>
<li><code>Get Started FREE</code>　ボタンから試すことができます。<code>APIキー</code> は指定せず、<code>Device:</code> は適当なものを指定すれば使えます。</li>
</ul>
</li>
</ul>
</li>
<li>CAN Bus<ul>
<li>O’REILLYの「カーハッカーズ・ハンドブック –車載システムの仕組み・分析・セキュリティ–」辺りが参考になります。<ul>
<li>https://www.oreilly.co.jp/books/9784873118239/</li>
</ul>
</li>
</ul>
</li>
<li>売っている店<ul>
<li>マルツオンライン や スイッチサイエンス 辺りで通販することが多いですね。</li>
<li>マルツ秋葉原本店 で実物を見ながら検討するのもよいでしょう</li>
</ul>
</li>
<li>ホンダ<ul>
<li>GROM</li>
<li>V-TwinMagna</li>
<li>CB150R</li>
</ul>
</li>
</ul>
]]></content>
    <summary type="html">M5Stackというマイコンモジュールを使って、「バイクの各種情報を簡単に表示できたらいいな」という思い付きを形にしていく過程を記事にしようと思います。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="M5Stack" scheme="https://future-architect.github.io/tags/M5Stack/"/>
    <category term="MicroPython" scheme="https://future-architect.github.io/tags/MicroPython/"/>
    <category term="シリアル通信" scheme="https://future-architect.github.io/tags/%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E9%80%9A%E4%BF%A1/"/>
    <category term="マイコン" scheme="https://future-architect.github.io/tags/%E3%83%9E%E3%82%A4%E3%82%B3%E3%83%B3/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>雀を見守るカメラを作ってみた</title>
    <link href="https://future-architect.github.io/articles/20220405a/"/>
    <id>https://future-architect.github.io/articles/20220405a/</id>
    <published>2022-04-04T15:00:00.000Z</published>
    <updated>2022-04-04T15:00:00.000Z</updated>
    <author><name>矢野颯太</name></author>
    <content type="html"><![CDATA[<p>電子工作&#x2F;IoT連載 の2本目です。</p>
<h2 id="背景、はじめに。">背景、はじめに。</h2><p>みなさんこんにちは。フューチャーの矢野です。</p>
<p>もう春ですね。この季節になると実家の壁に雀が巣を作ることがあります。</p>
<p>今年も雀が巣を作ったなら、それを見守れたら楽しいなと思います。</p>
<p>そこで、雀を見守るためのカメラを試作してみました。</p>
<h2 id="概要">概要</h2><p>作ったのがこれです。(デモ)<br><img fetchpriority="high" src="/images/2022/20220405a/image.png" alt="デモ" width="1200" height="717"></p>
<p>今回実現したのは下記です。</p>
<ol>
<li>LAN内ならカメラの映像が確認できる</li>
<li>雀がいない間はカメラの電源を落とすことで節電</li>
<li>カメラが起動したら通知が届く</li>
<li>かわいい雀の写真がLINEに通知される</li>
<li>カメラの設定はスマホからできる</li>
</ol>
<h3 id="フロー図">フロー図</h3><img src="/images/2022/20220405a/TF_Blog2022-ページ2.drawio.png" alt="フロー図" width="697" height="843" loading="lazy">

<p>人感センサーに反応があると、カメラに電源が入ります。<br>カメラは起動するとまずWi-Fiに接続を試みて、失敗したらAPモードになり、APに接続することでWi-Fi設定などができるようになります。設定が終了したら、再起動して、またWi-Fiの接続を試みます。Wi-Fiの接続に成功したらば、まずLINEに通知を飛ばします。飛ばせない場合は、黙ってカメラのサーバーを立ち上げます。３０秒以上センサーに反応がない場合は、カメラの電源が落ちます。ここのロジックはATTiny202のマイコン側でやっていますが、ESP32-CAMだけで実現できると思います。</p>
<h3 id="使用の想定">使用の想定</h3><img src="/images/2022/20220405a/TF_Blog2022-ページ3.drawio.png" alt="使用の想定" width="854" height="399" loading="lazy">

<p>カメラがアクセスするWi-Fiなどの設定はスマホからできるようにしました。カメラへのアクセスはLAN内に限定しました。インターネット越しに雀を見守ることはできません。</p>
<p>今回の想定は、ユーザー（私）がLINEの通知をみたあと、同じWi-Fi内にいるにスマホでカメラに接続する想定です。</p>
<h3 id="回路図">回路図</h3><img src="/images/2022/20220405a/image_2.png" alt="回路図" width="1200" height="513" loading="lazy">

<p>常時人感センサーだけ給電されていて、人感センサーに反応があると、ほかのモジュールにも電源がいくようになっています。NchMOSFETとPchMOSFETを組み合わせて電源を入れる処理は実現しました。電源が入ったATTiny202がQ2のゲートを開けます。</p>
<p>これによって電源が切れないようなります。電源を切るときはQ2のゲートを閉じます。</p>
<p>ブレッドボードだとこうなります。<br><img src="/images/2022/20220405a/FTBlog_ブレッドボード.png" alt="ブレッドボード" width="1200" height="845" loading="lazy"><br>一枚のブレッドボードでサクッと作れるのは気持ちがいいですね。</p>
<h3 id="パーツリスト">パーツリスト</h3><p>今回の工作で使ったパーツと、購入できるページを載せておきます。<br>ブレッドボードやジャンパーピンは省略します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>購入先例</th>
</tr>
</thead>
<tbody><tr>
<td>U1</td>
<td>焦電型赤外線センサーモジュール（焦電人感センサ）</td>
<td>秋月電子</td>
</tr>
<tr>
<td>U2</td>
<td>ＡＶＲマイコン　ＡＴＴＩＮＹ２０２－ＳＳＮＲ</td>
<td>秋月電子</td>
</tr>
<tr>
<td>U3</td>
<td>ESPカメラモジュール [ESP32-CAM-K]</td>
<td>www.aitendo.com</td>
</tr>
<tr>
<td>Q1,Q2</td>
<td>ＮｃｈＭＯＳＦＥＴ　３０Ｖ５Ａ　ＩＲＬＭＬ６３４４ＴＲＰＢＦＴＲ</td>
<td>秋月電子</td>
</tr>
<tr>
<td>Q3</td>
<td>ＰｃｈＭＯＳＦＥＴ　ＩＲＬＭＬ２２４６ＴＲＰＢＦ</td>
<td>秋月電子</td>
</tr>
<tr>
<td>R1,R4,R5</td>
<td>100kΩ抵抗</td>
<td>秋月電子等</td>
</tr>
<tr>
<td>R2</td>
<td>39kΩ抵抗</td>
<td>秋月電子等</td>
</tr>
<tr>
<td>R3</td>
<td>100Ω抵抗</td>
<td>秋月電子等</td>
</tr>
<tr>
<td>LED1</td>
<td>何かしらのLED</td>
<td>秋月電子等</td>
</tr>
</tbody></table></div>
<p>MOSFETやATTiny202などの表面実装パッケージをブレッドボードに差し込むために下記の基板を使いました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th></th>
<th>名称</th>
<th>購入先例</th>
</tr>
</thead>
<tbody><tr>
<td>ATTiny202用</td>
<td>ＳＯＴ－２３－３　ＤＩＰ化基板　（５枚入）</td>
<td>秋月電子</td>
</tr>
<tr>
<td>MOSFET用</td>
<td>ＳＯＰ８（１．２７ｍｍ）ＤＩＰ変換基板　金フラッシュ　（９枚入）</td>
<td>秋月電子</td>
</tr>
</tbody></table></div>
<h3 id="ソースコード">ソースコード</h3><p>ソースコードは下記です。<br>ESP32のスケッチ例、CameraWebServerに下記のような変更を加えたものです。</p>
<ol>
<li>Wi-Fi接続時にWiFiManagerを用いることで、Wi-Fi設定をスマホからできるようにした。</li>
<li>HTTPClientを用いて、LINE NotifyのAPIを叩くようにした。</li>
</ol>
<p>画像をHTTPClientを用いてLINEにPOSTするのですが、この記事が大変参考になりました。</p>
<figure class="highlight c"><figcaption><span>camera_server.ino</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;esp_camera.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WiFi.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;DNSServer.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WebServer.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;WiFiManager.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;HTTPClient.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> CAMERA_MODEL_AI_THINKER</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;camera_pins.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">startCameraServer</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> LINE_ACCESS_TOKEN <span class="string">&quot;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> BOUNDARY <span class="string">&quot;CHUNCHUNNOTICE20220327&quot;</span></span></span><br><span class="line">HTTPClient http;</span><br><span class="line"><span class="type">int32_t</span> <span class="title function_">linePost</span><span class="params">( <span class="type">camera_fb_t</span> *fb )</span> &#123;</span><br><span class="line">  String RequestURL=<span class="string">&quot;https://notify-api.line.me/api/notify&quot;</span>;</span><br><span class="line">  http.begin(RequestURL);</span><br><span class="line">  <span class="keyword">if</span> (fb)</span><br><span class="line">  &#123;</span><br><span class="line">    String stConType =<span class="string">&quot;multipart/form-data; boundary=&quot;</span>;</span><br><span class="line">    stConType+=BOUNDARY;</span><br><span class="line">    http.addHeader(<span class="string">&quot;Content-Type&quot;</span>, stConType);</span><br><span class="line">    String authorization = <span class="string">&quot;Bearer &quot;</span>;</span><br><span class="line">    authorization += LINE_ACCESS_TOKEN;</span><br><span class="line">    http.addHeader(<span class="string">&quot;Authorization&quot;</span>, authorization);</span><br><span class="line"></span><br><span class="line">    String stMHead=<span class="string">&quot;--&quot;</span>;</span><br><span class="line">    stMHead += BOUNDARY;</span><br><span class="line">    stMHead += <span class="string">&quot;\r\n&quot;</span>;</span><br><span class="line"></span><br><span class="line">    stMHead += <span class="string">&quot;Content-Disposition: form-data; name=\&quot;message\&quot;;\r\n\r\n&quot;</span>;</span><br><span class="line">    stMHead += <span class="string">&quot;Camera: http://&quot;</span>;</span><br><span class="line">    stMHead += WiFi.localIP().toString();</span><br><span class="line">    stMHead += <span class="string">&quot;\r\n&quot;</span>;</span><br><span class="line"></span><br><span class="line">    stMHead += <span class="string">&quot;--&quot;</span>;</span><br><span class="line">    stMHead += BOUNDARY;</span><br><span class="line">    stMHead += <span class="string">&quot;\r\n&quot;</span>;</span><br><span class="line">    stMHead += <span class="string">&quot;Content-Disposition: form-data; name=\&quot;imageFile\&quot;; filename=\&quot;./a.jpg\&quot; \r\n&quot;</span>;</span><br><span class="line">    stMHead += <span class="string">&quot;Content-Type: image/jpeg \r\n&quot;</span>;</span><br><span class="line"></span><br><span class="line">    stMHead += <span class="string">&quot;\r\n&quot;</span>;</span><br><span class="line">    <span class="type">uint32_t</span> iNumMHead = stMHead.length();</span><br><span class="line"></span><br><span class="line">    String stMTail=<span class="string">&quot;\r\n--&quot;</span>;</span><br><span class="line">    stMTail += BOUNDARY;</span><br><span class="line">    stMTail += <span class="string">&quot;--\r\n\r\n&quot;</span>;</span><br><span class="line">    <span class="type">uint32_t</span> iNumMTail = stMTail.length();</span><br><span class="line"></span><br><span class="line">    <span class="type">uint32_t</span> iNumTotalLen = iNumMHead + iNumMTail + fb-&gt;len;</span><br><span class="line"></span><br><span class="line">    <span class="type">uint8_t</span> *uiB = (<span class="type">uint8_t</span> *)<span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="type">uint8_t</span>)*iNumTotalLen);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> uilp=<span class="number">0</span>;uilp&lt;iNumMHead;uilp++)</span><br><span class="line">      uiB[<span class="number">0</span>+uilp]=stMHead[uilp];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> uilp=<span class="number">0</span>;uilp&lt;fb-&gt;len;uilp++)</span><br><span class="line">      uiB[iNumMHead+uilp]=(fb-&gt;buf)[uilp];</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> uilp=<span class="number">0</span>;uilp&lt;iNumMTail;uilp++)</span><br><span class="line">      uiB[iNumMHead+fb-&gt;len+uilp]=stMTail[uilp];</span><br><span class="line"></span><br><span class="line">    <span class="type">int32_t</span> httpResponseCode = (<span class="type">int32_t</span>)http.POST(uiB,iNumTotalLen);</span><br><span class="line">    http.end();</span><br><span class="line">    <span class="built_in">free</span>(uiB);</span><br><span class="line">    Serial.print(<span class="string">&quot;Response Code:&quot;</span>);</span><br><span class="line">    Serial.println(httpResponseCode);</span><br><span class="line">    Serial.print(<span class="string">&quot;Response Body:&quot;</span>);</span><br><span class="line">    Serial.println(http.getString());</span><br><span class="line">    <span class="keyword">return</span> (httpResponseCode);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">takePhoto</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">camera_fb_t</span> * fb = <span class="literal">NULL</span>;</span><br><span class="line">    fb = esp_camera_fb_get();</span><br><span class="line">    <span class="keyword">if</span>(!fb) Serial.println(<span class="string">&quot;Camera capture failed&quot;</span>);</span><br><span class="line">    linePost(fb);</span><br><span class="line">    esp_camera_fb_return(fb);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">setup</span><span class="params">()</span> &#123;</span><br><span class="line">  Serial.begin(<span class="number">115200</span>);</span><br><span class="line">  Serial.setDebugOutput(<span class="literal">true</span>);</span><br><span class="line">  Serial.println();</span><br><span class="line"></span><br><span class="line">  <span class="type">camera_config_t</span> config;</span><br><span class="line">  config.ledc_channel = LEDC_CHANNEL_0;</span><br><span class="line">  config.ledc_timer = LEDC_TIMER_0;</span><br><span class="line">  config.pin_d0 = Y2_GPIO_NUM;</span><br><span class="line">  config.pin_d1 = Y3_GPIO_NUM;</span><br><span class="line">  config.pin_d2 = Y4_GPIO_NUM;</span><br><span class="line">  config.pin_d3 = Y5_GPIO_NUM;</span><br><span class="line">  config.pin_d4 = Y6_GPIO_NUM;</span><br><span class="line">  config.pin_d5 = Y7_GPIO_NUM;</span><br><span class="line">  config.pin_d6 = Y8_GPIO_NUM;</span><br><span class="line">  config.pin_d7 = Y9_GPIO_NUM;</span><br><span class="line">  config.pin_xclk = XCLK_GPIO_NUM;</span><br><span class="line">  config.pin_pclk = PCLK_GPIO_NUM;</span><br><span class="line">  config.pin_vsync = VSYNC_GPIO_NUM;</span><br><span class="line">  config.pin_href = HREF_GPIO_NUM;</span><br><span class="line">  config.pin_sscb_sda = SIOD_GPIO_NUM;</span><br><span class="line">  config.pin_sscb_scl = SIOC_GPIO_NUM;</span><br><span class="line">  config.pin_pwdn = PWDN_GPIO_NUM;</span><br><span class="line">  config.pin_reset = RESET_GPIO_NUM;</span><br><span class="line">  config.xclk_freq_hz = <span class="number">20000000</span>;</span><br><span class="line">  config.pixel_format = PIXFORMAT_JPEG;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// if PSRAM IC present, init with UXGA resolution and higher JPEG quality</span></span><br><span class="line">  <span class="comment">//                      for larger pre-allocated frame buffer.</span></span><br><span class="line">  <span class="keyword">if</span>(psramFound())&#123;</span><br><span class="line">    config.frame_size = FRAMESIZE_UXGA;</span><br><span class="line">    config.jpeg_quality = <span class="number">10</span>;</span><br><span class="line">    config.fb_count = <span class="number">2</span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    config.frame_size = FRAMESIZE_SVGA;</span><br><span class="line">    config.jpeg_quality = <span class="number">12</span>;</span><br><span class="line">    config.fb_count = <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> defined(CAMERA_MODEL_ESP_EYE)</span></span><br><span class="line">  pinMode(<span class="number">13</span>, INPUT_PULLUP);</span><br><span class="line">  pinMode(<span class="number">14</span>, INPUT_PULLUP);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// camera init</span></span><br><span class="line">  <span class="type">esp_err_t</span> err = esp_camera_init(&amp;config);</span><br><span class="line">  <span class="keyword">if</span> (err != ESP_OK) &#123;</span><br><span class="line">    Serial.<span class="built_in">printf</span>(<span class="string">&quot;Camera init failed with error 0x%x&quot;</span>, err);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="type">sensor_t</span> * s = esp_camera_sensor_get();</span><br><span class="line">  <span class="comment">// initial sensors are flipped vertically and colors are a bit saturated</span></span><br><span class="line">  <span class="keyword">if</span> (s-&gt;id.PID == OV3660_PID) &#123;</span><br><span class="line">    s-&gt;set_vflip(s, <span class="number">1</span>); <span class="comment">// flip it back</span></span><br><span class="line">    s-&gt;set_brightness(s, <span class="number">1</span>); <span class="comment">// up the brightness just a bit</span></span><br><span class="line">    s-&gt;set_saturation(s, <span class="number">-2</span>); <span class="comment">// lower the saturation</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// drop down frame size for higher initial frame rate</span></span><br><span class="line">  s-&gt;set_framesize(s, FRAMESIZE_QVGA);</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)</span></span><br><span class="line">  s-&gt;set_vflip(s, <span class="number">1</span>);</span><br><span class="line">  s-&gt;set_hmirror(s, <span class="number">1</span>);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">  WiFiManager wifiManager;</span><br><span class="line">  wifiManager.setConfigPortalTimeout(<span class="number">30</span>);</span><br><span class="line">  <span class="keyword">if</span> (!wifiManager.autoConnect(<span class="string">&quot;BirdWatcher&quot;</span>,<span class="string">&quot;hogehoge&quot;</span>)) &#123;</span><br><span class="line">    Serial.println(<span class="string">&quot;failed to connect and hit timeout&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  takePhoto();</span><br><span class="line">  startCameraServer();</span><br><span class="line"></span><br><span class="line">  Serial.print(<span class="string">&quot;Camera Ready! Use &#x27;http://&quot;</span>);</span><br><span class="line">  Serial.print(WiFi.localIP());</span><br><span class="line">  Serial.println(<span class="string">&quot;&#x27; to connect&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">loop</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="comment">// put your main code here, to run repeatedly:</span></span><br><span class="line">  delay(<span class="number">10000</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>ATTINY202側のコードです。人感センサーが反応してから30秒間電源が落ちないように0pin(NchMOSFETに繋がっている)をオンにします。こちらもArduinoのブートローダーを書き込んでArduinoIDEで開発してます。</p>
<figure class="highlight c"><figcaption><span>attiny202.ino</span></figcaption><table><tr><td class="code"><pre><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment">  PowerController for tiny 202</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="type">bool</span> <span class="title function_">keepPassive</span><span class="params">(<span class="type">int</span> digiin,<span class="type">int</span> interval)</span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span>; i&lt;interval; i++)</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">if</span> (digitalRead(digiin) == HIGH) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    delay(<span class="number">1000</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">setup</span><span class="params">()</span> &#123;</span><br><span class="line">  pinMode(<span class="number">0</span>, OUTPUT); <span class="comment">// POWER FLAG</span></span><br><span class="line">  pinMode(<span class="number">1</span>, INPUT); <span class="comment">// POWER BUTTON</span></span><br><span class="line">  digitalWrite(<span class="number">0</span>, HIGH);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">loop</span><span class="params">()</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> ( digitalRead(<span class="number">1</span>)==LOW &amp;&amp; keepPassive(<span class="number">1</span>, <span class="number">30</span>) ) digitalWrite(<span class="number">0</span>, LOW);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="デモ">デモ</h2><img src="/images/2022/20220405a/demo.gif" alt="demo.gif" width="200" height="433" loading="lazy">
消費電力はテスターで測れる範囲で測った結果が下記です。電源の電圧は5Vです。

<div class="scroll"><table>
<thead>
<tr>
<th>状態</th>
<th>電流</th>
</tr>
</thead>
<tbody><tr>
<td>人感センサのみ</td>
<td>0.0mA以下（※0.0mA以下をテスターで計測することができませんでした）</td>
</tr>
<tr>
<td>人感センサ反応後、Wi-Fi起動時</td>
<td>190mA前後</td>
</tr>
<tr>
<td>サーバー起動、待機中</td>
<td>120mA前後</td>
</tr>
<tr>
<td>カメラ起動、配信開始</td>
<td>200mA以上（※テスターでは200mA以上の測定ができませんでした）</td>
</tr>
</tbody></table></div>
<p>上記の通り、カメラを常時起動させているよりは電気を節約できていそうですね。</p>
<h2 id="まとめ">まとめ</h2><p>これでかわいい雀が巣を作っても見守れますね。今回はカメラ部分だけ作りました。</p>
<p>実際に雀を見守るとなると、巣箱と、カメラを動かす電源が必要になります。</p>
<p>電源にはソーラーパネルと鉛蓄電池を使おうかなと思っています。</p>
<p>ありがとうございました。</p>
<h2 id="利用ツール・参考">利用ツール・参考</h2><ol>
<li>diagrams.net(フロー図、利用想定図作成)</li>
<li>KiCad(回路図作成)</li>
<li>fritzing(ブレッドボードの図作成)</li>
<li>ffmpeg(デモ動画変換)</li>
<li>QuickTime Player + iPhone(デモ動画撮影)</li>
<li>Arduino HTTPClientでファイルのバイナリ送信</li>
<li>LINE Notify API Document([POST] &#x2F;api&#x2F;notify)</li>
</ol>
]]></content>
    <summary type="html">みなさんこんにちは。フューチャーの矢野です。もう春ですね。この季節になると実家の壁に雀が巣を作ることがあります。今年も雀が巣を作ったなら、それを見守れたら楽しいなと思います。そこで、雀を見守るためのカメラを試作してみました。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Arduino" scheme="https://future-architect.github.io/tags/Arduino/"/>
    <category term="ESP32" scheme="https://future-architect.github.io/tags/ESP32/"/>
    <category term="センサー" scheme="https://future-architect.github.io/tags/%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>電子工作/IoT連載をはじめます</title>
    <link href="https://future-architect.github.io/articles/20220404a/"/>
    <id>https://future-architect.github.io/articles/20220404a/</id>
    <published>2022-04-03T15:00:00.000Z</published>
    <updated>2022-04-03T15:00:00.000Z</updated>
    <author><name>admin</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2022/20220404a/bulb-gffed9c0de_640.png" alt=""  width="600" height="640">

<h2 id="はじめに">はじめに</h2><p>フューチャーには会社公認のクラブ活動（FACTS）があり、野球部、テニス部、ゴルフ部など、趣味を通じて和気あいあいと活動しています。この記事ではその中でもやや異色な存在である電子工作部＋αの有志で、ブログ連載を行います。</p>
<p>電子工作部自体は #Future電子工作部 で発信しようと2019年ごろからラインレーサーを企画したり起動に乗ろうとしていたところ、あいにくの情勢下で活動が難しくなり、主にチャット上で購入したデバイスについて自慢したり、良質な教材をもとにみんなで勉強したり、おもしろ動画を共有するといった緩い繋がりのコミュニティでした。</p>
<p>とはいえさすがに休止期間が長いので、何か技術ブログで発信していこうという活動を取ることにしました。</p>
<h2 id="スケジューリング">スケジューリング</h2><p>電子工作やIoTのテーマで、6名のメンバーで開催します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>Author</th>
</tr>
</thead>
<tbody><tr>
<td>4月4日</td>
<td>スマートLED照明に接続にしてみた</td>
<td>真野隼記</td>
</tr>
<tr>
<td>4月5日</td>
<td>雀を見守るカメラを作ってみた</td>
<td>矢野颯太</td>
</tr>
<tr>
<td>4月6日</td>
<td>プロトタイピングの勧め</td>
<td>井上圭</td>
</tr>
<tr>
<td>4月7日</td>
<td>TinkerBoard 2S：AWS Greengrass v1をインストールする</td>
<td>宮永崇史</td>
</tr>
<tr>
<td>4月8日</td>
<td>元日に92%引きで叩き売りされていた変形ホームロボットを買ったのでScalaから叩いてみた</td>
<td>山本力世</td>
</tr>
</tbody></table></div>
<p>実はメンバーが集まらず、企画が流れるかなと思いましたが、矢野さんや井上さんが急遽参画いただけて助かりました。</p>
<h2 id="さいごに">さいごに</h2><p>この情勢下で中々外出が難しい、リモートワークになり在宅率が高い状況下ですので、電子工作・IoTの力で少しでも快適に過ごせるヒントに慣ればと思います。このブログを読んだ社員の人で電子工作&#x2F;IoTに興味がある方はお気軽にメンバーまで連絡をいただければです。</p>
]]></content>
    <summary type="html">フューチャーには会社公認のクラブ活動（FACTS）があり、野球部、テニス部、ゴルフ部など、趣味を通じて和気あいあいと活動しています。この記事ではその中でもやや異色な存在である電子工作部＋αの有志で、ブログ連載を行います。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>Project Haystackに関する備忘録</title>
    <link href="https://future-architect.github.io/articles/20220308a/"/>
    <id>https://future-architect.github.io/articles/20220308a/</id>
    <published>2022-03-07T15:00:00.000Z</published>
    <updated>2022-03-07T15:00:00.000Z</updated>
    <author><name>大西翔太</name></author>
    <content type="html"><![CDATA[<p>最近、IoTに関するデータモデル標準化に用いられる Project Haystack について調べる機会があり、日本語でまとめられている文献が少なくて理解するのに若干苦労したため、備忘録も兼ねて調査した内容を簡単にまとめておきます。</p>
<p>Haystack の公式ドキュメントを読んだけどいまいち分からなかったという方がいれば参考にしていただけると幸いです。</p>
<h2 id="Project-Haystack-概要">Project Haystack 概要</h2><p>Project Haystack は、ファシリティ・機器などの IoT に関するデータモデルを標準化する取り組みです。<br>ビルシステムとそれに関連するデバイスのモデルを様々なツールで解釈できるようにするため、メタデータ定義の方法論と共通語彙を提供することをミッションとしています。</p>
<blockquote>
<p>We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities.<br>Applications include automation, control, energy, HVAC, lighting, and other environmental systems.</p>
</blockquote>
<p>(Project Haystack 公式ページから抜粋)</p>
<h2 id="技術概要">技術概要</h2><p>Haystackの技術概要をざっくり箇条書でまとめてみました。<br>エンティティとタグを使ってデータやデータ間の関係性を表現することがキモなようです。</p>
<ul>
<li>各機器や建物ごとに、それぞれ適した「エンティティ」（※1）と「タグ」（※2）を用いてデータモデルを標準化する。</li>
<li>建物は「site」、機器は「equip」、運転データは「point」エンティティを使用する。</li>
<li>エンティティ同士の関係性（どの機器がどの建物に設置されているかなど）も、タグによって表現する。</li>
<li>データモデルを標準化する際、site と equip エンティティではタグのみ付与されるが、point エンティティには実際の観測データが格納される。</li>
</ul>
<p>※1 エンティティ…実世界の物理オブジェクトを抽象化したもの（例: 建物、ヒーター、センサーなど）。<br>※2 タグ…エンティティに付与する情報。名前と値によって構成されており、タグによって値の型が異なる（例: 文字列型、参照型、真偽値型など）。</p>
<h2 id="エンティティごとの主なタグ">エンティティごとの主なタグ</h2><p>エンティティごとのお主なタグを表にまとめました。<br>全てのエンティティは一意な id と dis(表示名) のタグを持っており、エンティティごとに固有のタグも存在するようです。<br>また、カスタムタグとして Haystack が標準で用意していないタグを作成することも可能なようです。</p>
<img fetchpriority="high" src="/images/2022/20220308a/タグ.png" alt="タグ" width="1200" height="565">

<h2 id="エンティティ間の関係性の表現">エンティティ間の関係性の表現</h2><p>データモデルの例を図に示します。<br>Haystack ではエンティティ内に作成したタグによって、自身のエンティティの特性と他のエンティティとの関係性を示します。<br>例）Sample Electricity Meter は、equip, elec, meter タグにより 自身が equip の派生である elec-meterであることを示し、 site タグの中に Sample Site の id を記載することで、 Sample Site の中に設置されていることを示す。</p>
<img src="/images/2022/20220308a/要素間の関連.png" alt="要素間の関連" width="1200" height="506" loading="lazy">

<h2 id="Point-へのデータの格納">Point へのデータの格納</h2><p>エンティティのうち、Pointだけは実際の観測データを格納するなど、仕組みが複雑に感じたため Point へのデータの格納方法について箇条書きでまとめました。</p>
<ul>
<li>point には派生として現在状態を表す cur-point と過去状態を表す his-point が存在する。</li>
<li>cur-point は、スカラー値が入るcurValタグを持つ。</li>
<li>his-point は、timestamp と curVal のセットをローカルストレージに記録することで履歴を管理できる。</li>
<li>equip と point は1対多の関係であり、1つの point が1つの値（温度、速度など）を表す。</li>
<li>point は他のエンティティと結合し、用途に応じた働きをするための新たなエンティティを形成する。<br>例）point に air, temp タグをつけることで、 point と air-temp が結合し、温度計が計測した温度を表すためのエンティティとなる。</li>
</ul>
<h2 id="さいごに">さいごに</h2><p>本記事を読んでくださった方の Haystack への理解向上に少しでも役立っていれば幸いです。</p>
<h2 id="参考文献">参考文献</h2><ul>
<li>Project Haystack 公式ドキュメント<br>https://project-haystack.org/</li>
<li>Project Haystack オープンソースデータモデル化標準に基づくビルシステムおよび機器のデータモデル化ガイド仕様<br>https://marketing.project-haystack.org/images/documents/Project-Haystack-Guide-Specification-Japanese.pdf</li>
<li>Simplifying Data Collection and Analysis through Haystack<br>https://assets.new.siemens.com/siemens/assets/api/uuid:60cc0b1f-c9df-4a44-8116-a834c994c7ff/us-si-pss-bp-sra-projecthaystack-whitepaper-sie-brand-redesign-0.pdf</li>
</ul>
]]></content>
    <summary type="html">IoTに関するデータモデル標準化に用いられるProject Haystackについて調べる機会があり、日本語でまとめられている文献が少なくて理解するのに若干苦労したため、備忘録も兼ねて調査した内容を簡単にまとめておきます。Project Haystack は、ファシリティ・機器などの IoT に関するデータモデルを標準化する取り組みです。ビルシステムとそれに関連するデバイスのモデルを様々なツールで解釈できるようにするため...</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="データモデル" scheme="https://future-architect.github.io/tags/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%A2%E3%83%87%E3%83%AB/"/>
    <category term="設計" scheme="https://future-architect.github.io/tags/%E8%A8%AD%E8%A8%88/"/>
  </entry>
  <entry>
    <title>CHAdeMO vs コンボ EV充電規格を比較する</title>
    <link href="https://future-architect.github.io/articles/20220127a/"/>
    <id>https://future-architect.github.io/articles/20220127a/</id>
    <published>2022-01-26T15:00:00.000Z</published>
    <updated>2022-01-26T15:00:00.000Z</updated>
    <author><name>村上知優</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>こんにちは。TIG DXユニットの村上です。</p>
<p>大手自動車メーカーが続々とEVを発表したり、EUでは2035年にガソリン車の新車販売が禁止になったりと、近年電気自動車業界が盛り上がりを見せています。</p>
<p>EVといえば充電しなければならないのが特徴です。充電ステーションにはスマホのLightningやUSBなどのように、充電するための対応規格が存在します。今回はEVの充電規格について解説したいと思います。</p>
<h2 id="充電ステーションの通信規格">充電ステーションの通信規格</h2><p>充電ステーションとEVの通信規格は多数存在し、シェア争いが過熱しています。主要な規格をまとめると、以下のようになります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>CHAdeMO</th>
<th>GB&#x2F;T</th>
<th>テスラ</th>
<th>CCS1</th>
<th>CCS2</th>
</tr>
</thead>
<tbody><tr>
<td>規格制定国</td>
<td>日本</td>
<td>中国</td>
<td>アメリカ</td>
<td>アメリカ</td>
<td>ドイツ</td>
</tr>
<tr>
<td>デジタル通信規格</td>
<td>CAN</td>
<td>CAN</td>
<td>CAN</td>
<td>PLC</td>
<td>PLC</td>
</tr>
</tbody></table></div>
<p>CCS1とCCS2は通称<strong>コンボ</strong>と呼ばれています。<br>テスラは独自規格を採用していますが、専用の変換アダプタを使うことによってCHAdeMOで充電できます。</p>
<h3 id="CHAdeMO">CHAdeMO</h3><p>上記で示したように、充電規格は多数存在しますが、現在はCHAdeMOへの統一化の動きがあります。</p>
<p>実際に2020年にCHAdeMO、GB&#x2F;T、コンボに互換性のあるCHAdeMO3.0が完成しました。</p>
<img fetchpriority="high" src="/images/2022/20220127a/image.png" alt="チャデモ3.0" width="1200" height="620">

<p>参考：https://www.chademo.com/ja/chademo3-0/</p>
<p>現在CHAdeMOの充電ステーションは欧州を中心に全世界で35600台以上普及しており、世界最大のカバレッジを誇っています。規格上の最大出力は900kWです。</p>
<h4 id="CHAdeMOの充電シーケンス">CHAdeMOの充電シーケンス</h4><p>CHAdeMOではEVとCANによってデジタル通信を行いながら充電を進めます。</p>
<p>充電の手続きをざっくりとまとめると以下のようになります。</p>
<ol>
<li>EVと充電ステーションを接続</li>
<li>EVと充電ステーションの互換性を確認し、問題なければEVが準備完了状態に移行</li>
<li>車両コネクタロック</li>
<li>充電ステーションから給電開始</li>
<li>EVから充電停止命令を送ることで充電終了</li>
<li>車両コネクタロック解除</li>
<li>充電ステーションで支払い手続きを行う</li>
</ol>
<p>車両コネクタロックとは、充電コネクタがEVのプラグから離れないようにするための物理的なロックです。</p>
<p>安全面でのロックであることはもちろんですが、充電自体にしばらく時間がかかるため、充電を開始してからその場を離れることがあります。その際に風などで自然に外れたり、他人が勝手に外したりすることを防ぐ目的もあります。</p>
<p>また、このロックが解除された瞬間にEVとのデジタル通信が切断されます。</p>
<p>さらに給電が行われている最中は充電ステーションとEV間で以下のデータがやりとりされています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>充電ステーションから送信</th>
<th>EVから送信</th>
</tr>
</thead>
<tbody><tr>
<td>充電シーケンス管理番号</td>
<td>充電シーケンス管理番号</td>
</tr>
<tr>
<td>出力可能電圧値</td>
<td>電池総容量</td>
</tr>
<tr>
<td>出力可能電流値</td>
<td>電池耐久上限値</td>
</tr>
<tr>
<td>電池不適合</td>
<td>最大充電時間</td>
</tr>
<tr>
<td>車両コネクタロック</td>
<td>充電電圧上限値</td>
</tr>
<tr>
<td>充電システム異常</td>
<td>車両充電可能</td>
</tr>
<tr>
<td>充電器状態</td>
<td>充電電流指令値</td>
</tr>
<tr>
<td>現在出力電圧値</td>
<td>充電システム異常</td>
</tr>
<tr>
<td>現在充電電流値</td>
<td>車両シフト位置</td>
</tr>
<tr>
<td>残り充電時間</td>
<td></td>
</tr>
<tr>
<td>充電器異常</td>
<td></td>
</tr>
<tr>
<td>充電システム故障</td>
<td></td>
</tr>
</tbody></table></div>
<p>参考：https://kikakurui.com/d6/D61851-24-2014-01.html</p>
<p>これらのデータを用いてリアルタイムで充電状態を監視し、異常が発生した場合は安全にかつ素早く停止するようになっています。</p>
<p>CHAdeMOは物理的な安全面への配慮が徹底している規格です。</p>
<h3 id="コンボ">コンボ</h3><p>コンボは欧州で生まれた規格で、デジタル通信はPLCを用いて行われます。</p>
<p>Wikipediaによると規格上の最大出力は350kWになることがアナウンスされています。</p>
<p>また、コンボは料金の支払い方法に大きな違いがあります。</p>
<h3 id="コンボの充電シーケンス">コンボの充電シーケンス</h3><p>コンボでは支払方法を予め設定しておくことができます。</p>
<p>支払方法によって充電手順が若干変わってきますが、以下で一例を示します。</p>
<ol>
<li>EVに支払方法や契約情報をセットしておく<ul>
<li>充電ステーションへの直接支払い</li>
<li>EVへの自動課金</li>
</ul>
</li>
<li>EVと充電ステーションを接続</li>
<li>EVから指定の支払方法を送信（ここではEVへの自動課金を選択したと仮定）</li>
<li>充電ステーションで支払い契約情報の承認</li>
<li>EVから充電許可信号を送信</li>
<li>充電ステーションから給電開始</li>
<li>充電終了</li>
<li>充電ステーションから課金額をEVに送信</li>
</ol>
<p>このように、コンボではユーザの認証情報が充電コネクタを通して行われるケースがあるため、情報セキュリティ的な観点で安全性が高い設計となっています。</p>
<h3 id="CHAdeMOとコンボの比較">CHAdeMOとコンボの比較</h3><p>CHAdeMOとコンボを比較した表が以下になります。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>CHAdeMO</th>
<th>コンボ</th>
</tr>
</thead>
<tbody><tr>
<td>普及地域</td>
<td>世界中</td>
<td>欧州</td>
</tr>
<tr>
<td>最大出力</td>
<td>900kW</td>
<td>350kW</td>
</tr>
<tr>
<td>認証情報の通信</td>
<td>なし</td>
<td>あり</td>
</tr>
<tr>
<td>規格の設計思想</td>
<td>物理的な安全性重視</td>
<td>データのセキュリティ重視</td>
</tr>
</tbody></table></div>
<p>スペックを比較すると現状はCHAdeMOが優勢と言えそうです。<br>しかしEV市場は今後さらに盛り上がっていくと思われるので、今後どうなっていくか注目していきたいですね。</p>
<h2 id="おわりに">おわりに</h2><p>最後まで読んでいただいてありがとうございました！</p>
<p>2021年末にはトヨタがEVを大量に発表するなど、国内でもEVに対する盛り上がりが本格的になってきました。将来的には日本でもガソリン車に対する課税や販売制限などが設けられたりするのでしょうか？</p>
<p>EV市場の今後の動向には目が離せませんね。</p>
<h2 id="参考">参考</h2><ul>
<li>https://monoist.itmedia.co.jp/mn/articles/1409/03/news003_2.html</li>
<li>https://car.watch.impress.co.jp/docs/series/tech/554577.html</li>
<li>https://www.chademo.com/wp/pdf/QCtebikiRev32.pdf</li>
<li>https://monoist.itmedia.co.jp/mn/articles/1407/02/news014_2.html</li>
<li>https://www.chademo.com/ja/</li>
<li>https://www.chademo.com/ja/chademo3-0/</li>
<li>https://en.wikipedia.org/wiki/Combined_Charging_System</li>
<li>https://kikakurui.com/d6/D61851-23-2014-01.html</li>
<li>https://kikakurui.com/d6/D61851-24-2014-01.html</li>
</ul>
]]></content>
    <summary type="html">大手自動車メーカーが続々とEVを発表したり、EUでは2035年にガソリン車の新車販売が禁止になったりと、近年電気自動車業界が盛り上がりを見せています。EVといえば充電しなければならないのが特徴です。充電ステーションにはスマホのLightningやUSBなどのように、充電するための対応規格が存在します。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="エネルギー業界" scheme="https://future-architect.github.io/tags/%E3%82%A8%E3%83%8D%E3%83%AB%E3%82%AE%E3%83%BC%E6%A5%AD%E7%95%8C/"/>
    <category term="業界ドメイン" scheme="https://future-architect.github.io/tags/%E6%A5%AD%E7%95%8C%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3/"/>
  </entry>
  <entry>
    <title>AB Open社と提携してRISC-V PCを開発しました</title>
    <link href="https://future-architect.github.io/articles/20211202a/"/>
    <id>https://future-architect.github.io/articles/20211202a/</id>
    <published>2021-12-01T15:00:00.000Z</published>
    <updated>2021-12-01T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>この度、フューチャーはAB Open社と協力し、無料のオープンソースであるRISC-Vアーキテクチャを活用した独自PCを開発しましたのでご報告します。</p>
<p>ITコンサルティング企業のフューチャーがなぜRISC-VベースのPC開発に携わっているのか疑問をお持ちの方も多いかと思いますので、経緯にも触れます。</p>
<h2 id="RISC-Vとは">RISC-Vとは</h2><p>RISC-VはCPU命令セットアーキテクチャ（Instruction Set Architecture: ISA）の一種で、日本語では「リスクファイブ」と呼ぶこと多いです。Intelのx86&#x2F;x64系やArm系と異なるのは、使用料がかからないOSSライセンスで提供されていることでしょう。近年RISC-V準拠CPUコアや開発環境などが活発に開発され広がりを見せており、今後普及が進むと予想されているアーキテクチャの1つです。</p>
<ul>
<li>参考: RISC-V - Wikipedia</li>
</ul>
<h2 id="開発した-RISC-V-パーソナルコンピュータについて">開発した RISC-V パーソナルコンピュータについて</h2><img fetchpriority="high" src="/images/2021/20211202a/future_rvpc_promo_1.jpg" alt="future_rvpc_promo_1.jpg" width="1200" height="802">

<img src="/images/2021/20211202a/future_rvpc_promo_4.jpg" alt="future_rvpc_promo_4.jpg" width="1200" height="802" loading="lazy">

<img src="/images/2021/20211202a/future_rvpc_promo_3.jpg" alt="future_rvpc_promo_3.jpg" width="1200" height="802" loading="lazy">

<p>開発したPCは、RISC-VのパイオニアであるSiFive社のHiFive Unmatchedを使用しています。CPUはSiFive U740 SoC、メモリは16GBという構成です。最初の写真を見てわかるように、フロントパネルを持っていることが大きな特徴で、ど真ん中に「フューチャー」のロゴが印字されていています！</p>
<ul>
<li>HiFive Unmatched</li>
<li>SiFive U740 SoC（64bit 4コア+1コア）</li>
<li>DDR4 16GB</li>
<li>NVIDIA GeForceGT710</li>
<li>NVMe 1TB</li>
<li>GNU&#x2F;Linuxベース</li>
<li>Wi-Fi接続機能</li>
<li>カスタム設計されたOLEDディスプレイ</li>
</ul>
<p>フロントパネルには、Pythonアプリケーションを介しての表示が可能です。CPUとメモリの使用状況、コンポーネントの温度などを含むシステム統計が一目でわかるので、PCの状態を常に監視できます。</p>
<img src="/images/2021/20211202a/future_rvpc_promo_5.jpg" alt="future_rvpc_promo_5.jpg" width="1200" height="802" loading="lazy">

<p>このOLEDディスプレイのハードウェア設計とPythonを含むコードはGitHubでも公開しています。</p>
<ul>
<li>https://github.com/abopen/hifive-unmatched-front-panel</li>
</ul>
<p>今回の共同開発で、RISC-Vアーキテクチャをゲームからハイエンドな組み込みシステムまで多様なユースケースへの導入の道筋を作るキッカケにできたと考えています。</p>
<h2 id="AB-Open社さんとの出会い">AB Open社さんとの出会い</h2><p>AB Openさんは英国ハリファックスを拠点とする、オープンハードウェアおよび技術コミュニティのコンサルティングを得意とする企業です。</p>
<p>出会いのキッカケですが、完全なRISC-V PCを世界で最初に組み立てた企業の1つとして有名だったAB Open社に、以前からRISC-Vに高い関心をもっていた当社副社長CSOの石橋さんが声をかけたことから始まり、意気投合してプロジェクトが始まりました。それぞれのモノづくりへの強い思いとこだわりを示しながら開発が進められたそうです。</p>
<p>AB Open社のマネージングディレクターであるAndrew Backさんは「RISC-Vアーキテクチャへの関心は日々高まっているため、このプロジェクトはRISC-Vのポテンシャルを示すのに役に立つと思います。最新テクノロジーを採用するフューチャーと一緒にできたことを光栄に思います」と話しており、良い協力関係が築けたかなと感じています。</p>
<h2 id="なぜ開発にしようと考えたか">なぜ開発にしようと考えたか</h2><p>フューチャーには、「ないものはつくる」というカルチャーがあります。ソフトウェアだけではなく、必要ならばハードウェアも開発するという踏み込んだITコンサルティングを、1989年の創業から一貫して行ってきました。</p>
<p>今回RISC-Vに着目したのは、数多くのアプリケーション領域において、RISC-Vアーキテクチャによりイノベーションのスピードが加速されるのではないかと考えたからです。こうした領域でも業界をリードすべく、最初のステップとして高い性能を持ったフロントパネル付きのPCを開発しました。プロダクトのコンセプト・仕様面は当社が中心となって設計しました。コロナ禍、部品の収集にはとても苦労しましたが、こうして形になり、非常に嬉しく思っています。</p>
<h2 id="さいごに">さいごに</h2><p>フューチャーは、AIやロボット工学などの最新技術の知識と実行能力を強みにイノベーションを促進し、クライアントに新しい価値を創造することで社会に貢献することを目指しています。引き続きABOpen社と協力しRISC-Vを活用していこうと考えており、最終ゴールは、RISC-Vの国産マイクロプロセッサを目指しています！</p>
<p>また、RISC-Vサミットが2021年の12&#x2F;6~12&#x2F;8にサンフランシスコで開催されます。盛り上げていきましょう！<br>https://events.linuxfoundation.org/riscv-summit/</p>
<h2 id="参考">参考</h2><p>https://abopen.com/news/future-corporation-partners-with-ab-open-for-unique-risc-v-pc-project/</p>
]]></content>
    <summary type="html">フューチャーはAB Open社と協力し、無料のオープンソースであるRISC-Vアーキテクチャを活用した独自PCを開発しましたのでご報告します。ITコンサルティング企業のフューチャーがなぜRISC-VベースのPC開発に携わっているのか疑問をお持ちの方も多いかと思いますので、経緯にも触れます。</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="RISC-V" scheme="https://future-architect.github.io/tags/RISC-V/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
  <entry>
    <title>極小LinuxマシンでSwiftを動かそうとしてみた</title>
    <link href="https://future-architect.github.io/articles/20211105a/"/>
    <id>https://future-architect.github.io/articles/20211105a/</id>
    <published>2021-11-04T15:00:00.000Z</published>
    <updated>2021-11-04T15:00:00.000Z</updated>
    <author><name>山本力世</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2021/20211105a/Image_20211102_160635.jpeg" alt="" width="1108" height="663">

<p>秋のブログ週間連載の7本目です。</p>
<h2 id="はじめに">はじめに</h2><p>今回ターゲットとした極小Linuxマシンは、M5Stack UnitV2になります。</p>
<p>こちらのマシンは、ARM Cortex-A7デュアルコア1.2GHz、128MBメモリ、512MB内蔵Flash、microSDスロット、Full HDカメラ、Wi-Fi、冷却ファンを搭載し、OSはLinuxがプリインストールされていますが、非常にコンパクトで1万円弱で購入できます。</p>
<p>Python、Jupyter Notebookはプリインストールされているので、あえて、他の手段を試す必然性は全くないのですが、Swiftのインストールを試してみました。</p>
<h2 id="Swiftのインストール手順">Swiftのインストール手順</h2><p>まずは、buildSwiftOn ARMを見つけたのですが、swiftコマンドのサイズがほぼメインメモリの容量と同じ、かつ、導入時のサイズが約650MBのため、microSDをマウントしたり色々ファイルシステムをいじらないといけないので断念。</p>
<p>そこで、ターゲット上でビルド環境を整えるのは諦め、クロスコンパイル環境を探すことに。<br>Swift Cross Compilation Toolchainsを見つけるも、5.3.3からはARM32のサポートがなくなると。。</p>
<p>そもそも、Swiftは困難なようなので代わりにRustの環境を調べてみる方針へ変更。</p>
<p>手順は、まず、rustupを開発環境であるMacへ導入。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">brew install rustup</span><br><span class="line">brew install arm-linux-gnueabihf-binutils</span><br></pre></td></tr></table></figure>

<p>ビルド時にターゲットとしてarmv7-unknown-linux-gnueabihfを指定するも、エラーでうまくいかず、ここの記述を参考に、ターゲットをarmv7-unknown-linux-musleabihfへ変更することでようやくビルドがエラーなく実行できるようになりました。</p>
<figure class="highlight sh"><figcaption><span>.cargo/config</span></figcaption><table><tr><td class="code"><pre><span class="line">[target.armv7-unknown-linux-musleabihf]</span><br><span class="line">linker = <span class="string">&quot;arm-linux-gnueabihf-ld&quot;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">rustup target add armv7-unknown-linux-musleabihf</span><br><span class="line">cargo init</span><br><span class="line">cargo build --target armv7-unknown-linux-musleabihf</span><br></pre></td></tr></table></figure>

<p>環境は出来上がったので次回はRustでカメラを使ったアプリを組みたいと思います。</p>
<p>秋のブログ週間連載の7本目でした。</p>
]]></content>
    <summary type="html">今回ターゲットとした極小Linuxマシンは、M5Stack UnitV2になります。こちらのマシンは、ARM Cortex-A7デュアルコア1.2GHz、128MBメモリ、512MB内蔵フラッシュ、microSDスロット、Full HDカメラ、Wi-Fi、冷却ファンを搭載し、OSはLinuxがプリインストールされていますが...</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="Linux" scheme="https://future-architect.github.io/tags/Linux/"/>
    <category term="M5Stack" scheme="https://future-architect.github.io/tags/M5Stack/"/>
    <category term="Rust" scheme="https://future-architect.github.io/tags/Rust/"/>
    <category term="Swift" scheme="https://future-architect.github.io/tags/Swift/"/>
  </entry>
  <entry>
    <title>キーパッドを、基板から自作してみた</title>
    <link href="https://future-architect.github.io/articles/20211101a/"/>
    <id>https://future-architect.github.io/articles/20211101a/</id>
    <published>2021-10-31T15:00:00.000Z</published>
    <updated>2021-10-31T15:00:00.000Z</updated>
    <author><name>井上圭</name></author>
    <content type="html"><![CDATA[<p>こんにちは。CSIGの井上です。この記事は秋のブログ週間2021の4日目です。</p>
<p>本ブログでは何度か自作キーボードの記事を書いていますが、基本的には「誰かが設計したものを組み立てる」物でした。最近まではある程度満足して使っていたのですが、もう少しこうだと（私は）使いやすいかもしれない、という点が色々出てきたので、キーボードを基板から作ろうと思い始めました。</p>
<ul>
<li>市販の自作キーボードは、キーアサインの変更は可能です</li>
<li>しかしながら、物理的な配置等も変えたい場合は、自作するしかないです</li>
</ul>
<p>ですが、最初からキーボードを作るのは難度が高いので、まずは機能確認のキーパッドを作りました。これについて記事を書きました。</p>
<p>あと、<strong>普段はサイバーセキュリティの話をしていますが、今回は一切しません…</strong></p>
<h2 id="概要">概要</h2><h3 id="この記事について">この記事について</h3><p>この記事は、仕組みを知ることの楽しさを知ってもらう、という事を目的に書きました。</p>
<p>今回は、自作の第一歩として以下を含む基板を作ります。<br>（何かを始めてやるときは、実装テストを小規模で行う、のがセオリーですね）。</p>
<ul>
<li>4つのキー</li>
<li>1つのロータリーエンコーダー</li>
<li>1つのカラーシリアルLEDテープ</li>
</ul>
<img fetchpriority="high" src="/images/2021/20211101a/image.png" alt="image.png" width="694" height="501">

<h3 id="最終的に作りたいもの">最終的に作りたいもの</h3><p>本記事で書いたキーパッド設計により、設計プロセスや注意点を、おおよそ理解しました。<br>今後は、日本語キーボード（70キー程度）を作るつもりです。機会があればこちらに投稿するかもしれません。</p>
<ul>
<li>会社支給PCは日本語キーボードのノートPCであるが、キータッチは個人的にはあまり好きではない</li>
<li>リモートワーク環境であり、自宅では某自作日本語キーボードを利用している</li>
<li>出社または自宅外でリモートワークする時に上記のキーボードを持ち出すと、破損する恐れがある</li>
<li>持ち出しても破損しづらい、好みのキーボードが欲しい！ 作りたい！</li>
</ul>
<p>それでは、キーパッドを設計していきます。</p>
<h2 id="キーパッドを作る">キーパッドを作る</h2><h3 id="構成と利用ソフトウェア">構成と利用ソフトウェア</h3><h4 id="構成">構成</h4><p>自作キーボードは、一般的に以下の構成となっています。</p>
<ul>
<li>ProMicroと呼ばれるマイコン（Arduino互換機）に、各種スイッチ類やLEDなどを接続する</li>
<li>ProMicroのUSBポート経由でPCと接続し、キーボードとして使う</li>
<li>ProMicroのファームウェアは、QMK Firmwareというもので作成ができ、それをQMK ToolBoxで書き込むことが可能<ul>
<li>ファームウェアを書き換えることで、キーアサインなどを変更できる</li>
</ul>
</li>
</ul>
<img src="/images/2021/20211101a/image_2.png" alt="image.png" width="823" height="591" loading="lazy">

<h4 id="利用するソフトウェア">利用するソフトウェア</h4><p>今回は、以下のアプリケーションを使います。</p>
<ul>
<li>KiCAD<ul>
<li>https://www.kicad.org/</li>
<li><code>A Cross Platform and Open Source Electronics Design Automation Suite</code></li>
<li>回路の設計と、基板の設計ができるアプリケーションです。Ubuntuだとaptで導入もできました。</li>
<li>KiCADには、Eeschema（回路図エディター）&#x2F;Pcbnew（プリント基板用ソフトウェア）&#x2F;GrebView（ガーバー&#x2F;ドリル ファイルビューア）などが含まれています。</li>
</ul>
</li>
<li>Freerouting<ul>
<li>https://freerouting.mihosoft.eu/</li>
<li><code>PCB Autorouter</code></li>
<li>KiCADでは自動で配線はできないようなので、このアプリケーションにより配線をしてもらいます。</li>
</ul>
</li>
<li>QMK Firmware<ul>
<li>https://github.com/qmk/qmk_firmware</li>
<li>キーボードの制御でよく使われるProMicroに入れるファームウェアとして使われます。</li>
<li>githubプロジェクトに登録済みのキーボードであれば、QMK Configurator という、Web上でキーアサインを変更してファームウェアを作るものが利用できます。</li>
</ul>
</li>
<li>QMK ToolBox<ul>
<li>https://github.com/qmk/qmk_toolbox</li>
<li>QMK Firmwareで用意したファームウェアを、USB経由でキーボード上のProMicroに書き込みます。</li>
</ul>
</li>
</ul>
<h3 id="何を作りたいかを決める">何を作りたいかを決める</h3><p>自作プロセスの検証作業に近い内容なので、欲しい機能を盛り込みます。</p>
<ul>
<li>キーは必要<ul>
<li>最終的にはキーボードを作るつもりなので、複数のキーを扱う練習をしたい</li>
</ul>
</li>
<li>ロータリーエンコーダーも使ってみる<ul>
<li>マウス代わりとして使う可能性もあるので、実装してみたい</li>
</ul>
</li>
<li>シリアルカラーLEDテープ<ul>
<li>自作キーボード&#x3D;光る、これは定番</li>
</ul>
</li>
</ul>
<p>キーは内部的にマトリックスとして扱うので、最低限の 2×2 で 4キーとしました。<br>ロータリーエンコーダーとLEDテープは、実装テストなので数は不要として、1つとしました。</p>
<p>実装するものが決まったので、これ以外に必要なパーツなども含めて配置を考えます。</p>
<img src="/images/2021/20211101a/image_3.png" alt="image.png" width="518" height="427" loading="lazy">

<h3 id="回路を設計する">回路を設計する</h3><p>KiCADは設計用の複数の画面を持っていますが、今回はKiCADのEeschemaで部品を配置、配線します。<br>詳細は割愛しますが、キーボードのコントローラーとなるProMicroを中心に、回路を検討します。</p>
<img src="/images/2021/20211101a/image_4.png" alt="image.png" width="1085" height="455" loading="lazy">

<ul>
<li>キーはマトリックス配置とします。<ul>
<li>ProMicroの2Pinを「行」、同2Pinを「列」に割り当て、ダイオードで逆流を防止すると、4キーを認識できる</li>
<li>ProMicroは最大18Pinをこの用途で利用できるので、9行x9列&#x3D;81キーを単体で判別できる。</li>
<li>キーのマトリックスを作り、各Pinにアサインします。</li>
</ul>
</li>
<li>ロータリーエンコーダーは、2Pinを消費します。<ul>
<li>現状のQMK Firmwawreは、ロータリーエンコーダーはマトリックスに組込めない仕様です。</li>
<li>そのため、ProMicroに直接Pinアサインします。</li>
</ul>
</li>
<li>ファームウェア書き換えの簡略化の為、リセットスイッチも付けます。<ul>
<li>RST PinをGNDに短絡するように、スイッチを配線します。</li>
</ul>
</li>
<li>分割キーボードとする際に必要なTRRSジャックも一応つけておきます。<ul>
<li>キーボードを左右に分割した場合、シリアル通信若しくはI2C通信で同期させる必要があります。今回はシリアル通信要として1Pinを設定します。</li>
<li>I2Cの場合は GND&#x2F;VCC&#x2F;SDA&#x2F;SCLの4線が必要で、市販のステレオジャック（3極）では無く、TRRSジャック（4極）を使います。</li>
<li>シリアル通信だと GND&#x2F;VCC&#x2F;Dataで済みますが、汎用化の為にTRRSジャックを使います。</li>
</ul>
</li>
<li>シリアルLEDテープは、1Pin消費します。<ul>
<li>GND&#x2F;VCC&#x2F;Din なので、制御用のDinをProMicroに接続します。</li>
</ul>
</li>
</ul>
<p>この段階で、論理的な部品と配線が完了しました。<br>次に進む前に、ネットリストの出力や、フットプリントを割り当てておきます。</p>
<h3 id="PCBを設計する">PCBを設計する</h3><p>基板自体の設計に進みます。これは、KiCADのpcbnewを使います。</p>
<p>まずは部品の実際の配置を考えて整理します。</p>
<ul>
<li>使い勝手により位置が決まるもの（TRRSジャックは基板の端オードは同じ向きで固めて配置したい）等で位置決めをします。</li>
<li>その後、基板上での配線を書いていくのですが、KiCADでは自動配線をしてくれません。手動で配線する必要があります。<ul>
<li>そこでFreeRoutingを使い、自動配線します。</li>
</ul>
</li>
</ul>
<p>配線後は、下図このようになります。</p>
<ul>
<li>3Dモデルを表示することもできるので、仕上がりを確認することもできます。</li>
</ul>
<img src="/images/2021/20211101a/image_5.png" alt="image.png" width="950" height="500" loading="lazy">

<p>これで基板は完成したので、発注を行います。</p>
<h3 id="PCBを発注する">PCBを発注する</h3><p>KiCADで作成したデータを基に、業者に発注します。<br>各業者により特徴はありますが、基本的には同じです。</p>
<ul>
<li>個人で利用する場合、条件により費用が変わりやすいです。<ul>
<li>最低5枚から、特定のサイズ内であれば安く仕上がる、などの条件があります。</li>
<li>これに合わせるように基板を設計したほうが良いかもしれません。</li>
</ul>
</li>
<li>中国の工場で作成され、郵送されることが多いです。<ul>
<li>私が頼んだ業者は、深圳から送ってきました。特定のサイズ以下であれば、5枚で2000円以下、程度でした。</li>
<li>納期は3週間で、2週間目まで製造は全くされていませんでした。3周目頭で製造が始まり、DHLを使い、予定通り3週間中に送られてきました。</li>
</ul>
</li>
</ul>
<p>発注後暫く待つと、基板が届くはずです。</p>
<img src="/images/2021/20211101a/image_6.png" alt="image.png" width="884" height="478" loading="lazy">

<h3 id="組み立てる">組み立てる</h3><p>基板が届いたら、まずは部品を組みつけてみます。</p>
<ul>
<li>パーツ自体は一般的な物なので、関東であれば秋葉原のパーツ屋や台東区にある自作キーボード専門店で入手できます。</li>
</ul>
<img src="/images/2021/20211101a/image_7.png" alt="image.png" width="958" height="487" loading="lazy">

<ul>
<li>場合により、「取付向きが逆になる（フットプリントを逆に配置してしまった）」とかが発生することがあります。<ul>
<li>無理やり空中配線で対応したり、表裏を変えたりして対処できれば良いですが、無理な時はあきらめるしかありません…</li>
</ul>
</li>
</ul>
<p>部品が付いたことで、「見た目」は完成品になりました。<br>まだファームウェアを書いていないので、PCに接続しても何も起きません。<br>ファームウェアを用意します。</p>
<img src="/images/2021/20211101a/image_8.png" alt="image.png" width="988" height="469" loading="lazy">

<h3 id="ファームウェアを作成する">ファームウェアを作成する</h3><p>QMK Firmwareを利用して作成します。</p>
<p>Ubuntuのパッケージを利用して導入しましたが、GitHubのものよりバージョンが古いかもしれません。しかしながら、特段問題は出ないのでそのまま使いました。<br>QMK Firmwareは開発が頻繁に行われており、1週間程度ごとにはバージョンが上がっているようです。</p>
<ul>
<li>KiCADも含めて、PCにそのまま入れると環境が汚れるので、仮想マシン上で動かすのが良いかもしれません。<ul>
<li>今回はKiCAD, QMK FirmwareのビルドなどはUbuntuで行っています。</li>
</ul>
</li>
<li>開発の速さに伴いインターネット上の情報が古くなりやすいです。やはり公式のドキュメントを参照するのが一番良いようですが、なかなか複雑です。</li>
<li>Ubuntuでapt導入した場合は、<code>~/qmk_firmware/</code>以下に環境が用意されます。</li>
</ul>
<p>詳細は省略しますが、新規でゼロから作る場合は以下のようにします。</p>
<ul>
<li>テンプレートを作る<ul>
<li><code>$ ./util/new_keyboard.sh</code><ul>
<li>キーボード名や作成者名を入れると、テンプレートが <code>./keyboards/&lt;キーボード名&gt;</code>に作られる</li>
</ul>
</li>
</ul>
</li>
<li>編集を行う<ul>
<li>キーの数やアサイン、LEDシートやロータリーエンコーダーなどの設定をする<ul>
<li><code>config.h</code>, <code>&lt;キーボード名&gt;.h</code>, <code>keymap.c</code>等を書き換える</li>
<li>何を書き換えるかは…ネットで調べる方が良いかもしれない<ul>
<li>機能を<code>enable = ture</code>にする、ProMicroの割当Pin番号を記載していく、等</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>コンパイルする<ul>
<li><code>$ qmk compile --keyboard &lt;キーボード名&gt; --keymap default</code></li>
</ul>
</li>
</ul>
<p>ファームウェアをビルドすると<code>~/.build</code>以下に<code>*.hex</code>ファイルとして配置されるので、これをQMK ToolBoxでProMicroに書き込みます。</p>
<p>先人のキーボード設計を流用する場合は、keymapだけ書き換えることになると思います。<br>新規の場合、流用をすると色々エラーが起こったりすることが多いので、<code>new_keyboard.sh</code>した方が早いように感じました。</p>
<h3 id="ファームウェアを適用する">ファームウェアを適用する</h3><p>ProMicroへの書き込みですが、Windowsだと<code>QMK Toolbox.exe</code>で、GUIで操作ができます。</p>
<ul>
<li>（VM上のファームウェアを、ホストのWindowsから見えるところに移動させる）</li>
<li><code>QMK Toolbox.exe</code>を起動し、準備をする<ul>
<li>ファームウェアファイルを指定、<code>Auto-Flash</code>の有効化のチェックボックスを有効化</li>
</ul>
</li>
<li>キーボードをUSBで接続する<ul>
<li>リセットスイッチを押すと自動でファームウェアが書き込まれる</li>
</ul>
</li>
</ul>
<img src="/images/2021/20211101a/image_9.png" alt="image.png" width="802" height="645" loading="lazy">

<p>これでキーボードとして動作が可能な状態になりました。完成です。</p>
<p>ここまでできれば、一通りの機能実装ができたことになります。おそらく、ロータリーエンコーダーやLEDテープなどが無ければ、簡単に実装できます。<br>最初の実装としては問題なくできたので、次は日本語キーボードを作ろうと思います。</p>
<h3 id="（大半をWebで済ます方法もある）">（大半をWebで済ます方法もある）</h3><p>最近は、各種Webサービスを駆使することで、ほぼWeb上でキーボードが作れそうです。</p>
<ul>
<li>キーボードをデザインする<ul>
<li>keyboard-layout-editor でキーボードをデザインする</li>
</ul>
</li>
<li>KiCADで基板を作る<ul>
<li>ここは流石にWebではできないか。</li>
</ul>
</li>
<li>PCB発注<ul>
<li>Webで事業者に発注。一応、Webサービスですね。</li>
</ul>
</li>
<li>ファームウェアの作成<ul>
<li>Keyboard Firmware Builderでファームウェアを作る</li>
<li>keyboard-layout-editorのJSONを張り付けることで、オリジナルのキーボードでも作れる</li>
<li>但し、ロータリーエンコーダーは書けない<ul>
<li>ファームウェアをダウンロードしてみるとQMK Firmwareのようだけれども、それを改造してcompileするのは難しいみたい。</li>
</ul>
</li>
</ul>
</li>
<li>ファームウェアの書き込み<ul>
<li>QMK Tool boxを使うので、Webサービスじゃないですね…</li>
</ul>
</li>
</ul>
<img src="/images/2021/20211101a/image_10.png" alt="image.png" width="756" height="458" loading="lazy">

<h2 id="まとめ">まとめ</h2><p>キーボード自作のおおよそのフローは理解いただけたと思います。<br>自作キーボードに興味のない方には雑学としての知識を、興味のある方には自作の後押しをできていれば幸いです。</p>
<p>秋のブログ週間2021の4日目でした。</p>
]]></content>
    <summary type="html">本ブログでは何度か自作キーボードの記事を書いていますが、基本的には「誰かが設計したものを組み立てる」物でした。最近まではある程度満足して使っていたのですが、もう少しこうだと（私は）使いやすいかもしれない、という点が色々出てきたので、キーボードを基板から作ろうと思い始めました。- 市販の自作キーボードは、キーアサインの変更は可能です- しかしながら、物理的な配置等も変えたい場合は、自作するしかないです</summary>
    <category term="IoT" scheme="https://future-architect.github.io/categories/IoT/"/>
    <category term="KiCAD" scheme="https://future-architect.github.io/tags/KiCAD/"/>
    <category term="自作キーボード" scheme="https://future-architect.github.io/tags/%E8%87%AA%E4%BD%9C%E3%82%AD%E3%83%BC%E3%83%9C%E3%83%BC%E3%83%89/"/>
    <category term="電子工作" scheme="https://future-architect.github.io/tags/%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C/"/>
  </entry>
</feed>
