M5StackのTimed outエラー対策

一目惚れのM5Stack、購入してから〇ヵ月経ってしまいましたが、年末年始に時間がとれたので動かしてみました。

見た目がカッコイイ、M5Stack

5cm角のコアモジュールに、さまざまな機能モジュールを積み上げて機能拡張できる、開発ツールです。

f:id:matsujirushix:20180107143951p:plain

液晶、スイッチ、USBコネクター、マイクロSDスロット、Groveコネクターがコンパクトで綺麗なケースに包まれています。見た目大事。当初、MPUはESP8266だったようですが、現在発売されているのはESP32で、工事設計認証も取得済みのようです。

AliexpressのM5Storeから購入できます。

f:id:matsujirushix:20180107152509p:plain

開発環境はArduino IDE

Getting Startedに沿って実施しました。

開発環境は、Arduino IDE + Espressif ESP32という、一般的な構成。そして、M5Stack用ライブラリを追加、利用する形です。

開発の仕方については、ドキュメントがほとんど見当たりませんでした。ただ、サンプルスケッチが多数用意されているので、動かしてみてコード読みなさいという感じなのかもしれません。

スケッチのコンパイル、書き込み

サンプルのMPU9250BasicAHRSをコンパイル、書き込みしてみました。

コンパイルは3分ほど。結構、遅いですね。ESP32はこれくらいかかるのが一般的なのだろうか。まぁ、問題はありません。ちゃんとコンパイルできました。

書き込みは... Timed outエラーが発生して書き込みできず。何度試してもダメ。悲しい。

esptool.py v2.1
Connecting........_____....._____....._____....._____....._____....._____....._____....._____....._____....._____

A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header

f:id:matsujirushix:20180107151229p:plain

Tips - コンデンサを繋ぐ

M5Stack Communityに同様の事例と対策が載っていたので、手元にあった4.7uFをRSTとGNDに接続してみたところ、きちんと書き込みできました。

f:id:matsujirushix:20180107151724p:plain

f:id:matsujirushix:20180107151812p:plain

コンデンサを内蔵

せっかく恰好いいケースなのにコンデンサがはみ出ているのは悲しいので、分解して基板上に4.7uFのチップコンデンサを実装しました。

f:id:matsujirushix:20180107152040p:plain

f:id:matsujirushix:20180107152308p:plain

Azure IoT HubとDeviceの通信

公式ドキュメントを読んで机上で整理したものなので、実際の動作と異なる可能性があります。

IoT HubのエンドポイントはDevice, ServiceとManagementに関するものがあります。

f:id:matsujirushix:20171230211711p:plain

このうちのAzure IoT HubとDeviceの通信に使用する、Deviceのエンドポイントを整理しました。(上図の左側)

Device to Cloud

DeviceからCloud(IoT Hub)に情報を送信する手段は、3つ用意されています。

メッセージ

  • 時系列データアラートの送信に使用します。
  • エンドポイントは/devices/{deviceId}/messages/events
  • IoT Hubに最大7日間、保存することができます。
  • 1つのメッセージは最大256KB

レポートプロパティ(報告プロパティ)

  • 動作状態の送信に使用します。
  • エンドポイントは$iothub/twin/PATCH/properties/reported/?$rid={request id}
  • IoT Hubのバイスツインに保存。
  • レポートプロパティは最大8KB

ファイルのアップロード

  • 大きなデータの送信に使用します。
  • IoT Hubにファイルのアップロード要求を送信してから、Azure Storage SDKでファイルをアップロードします。
  • Azure Blob Storageに保存。
  • ファイルの最大はAzure Blob Storageによって制限されます。

Cloud to Device

Cloud(IoT Hub)からDeviceに情報を受信する手段は、3つ用意されています。

メッセージ

  • IoT Hubに溜まっているメッセージを受信します。
  • エンドポイントはdevices/{device_id}/messages/devicebound/#
  • IoT Hubに最大48時間、保存することができます。
  • 1つのメッセージは最大64KB

デザイアープロパティ(必要プロパティ)

  • 動作指示設定値の受信に使用します。
  • エンドポイントは$iothub/twin/PATCH/properties/desired/?$version={new version}
  • IoT Hubのバイスツインに保存。
  • デザイナープロパティは最大8KB

ダイレクトメソッド

  • 即時指示の受信に使用します。
  • エンドポイントは$iothub/methods/POST/#
  • 最大8KB

azure-c-shared-utility

Azure C SDKの土台となる、azure-c-shared-utilityをビルドしてみました。

リポジトリをclone

Setupのとおり、--recursiveオプションを付けてgit cloneします。

C:\Users\takashi\Documents\github>git clone --recursive https://github.com/Azure/azure-c-shared-utility.git
Cloning into 'azure-c-shared-utility'...
remote: Counting objects: 15474, done.
...
Submodule path 'testtools/umock-c/deps/testrunner': checked out 'f73e0a91e295fb64b94d72aaf16fa54d3079be22'

C:\Users\takashi\Documents\github>

何事も無く成功。約23MB。

参照しているサブモジュールは5つでした。

Submodule 'testtools/ctest' (https://github.com/Azure/azure-ctest.git) registered for path 'testtools/ctest'
Submodule 'testtools/testrunner' (https://github.com/Azure/azure-c-testrunnerswitcher.git) registered for path 'testtools/testrunner'
Submodule 'testtools/umock-c' (https://github.com/Azure/umock-c.git) registered for path 'testtools/umock-c'
Submodule 'deps/azure-ctest' (https://github.com/Azure/azure-ctest.git) registered for path 'testtools/umock-c/deps/ctest'
Submodule 'deps/testrunner' (https://github.com/Azure/azure-c-testrunnerswitcher.git) registered for path 'testtools/umock-c/deps/testrunner'

ビルド準備

cmakeを実行します。

C:\Users\takashi\Documents\github>cd azure-c-shared-utility

C:\Users\takashi\Documents\github\azure-c-shared-utility>cmake ..
CMake Error: The source directory "C:/Users/takashi/Documents/github" does not appear to contain CMakeLists.txt.
Specify --help for usage, or press the help button on the CMake GUI.

C:\Users\takashi\Documents\github\azure-c-shared-utility>

エラーになりました。CMakeLists.txtが無いって。そりゃそうですよ、一つ上の階層はcloneしていませんから。ドキュメントが間違っているのかな。

気を取り直して、cloneしたフォルダでcmakeを実行します。

C:\Users\takashi\Documents\github\azure-c-shared-utility>cmake .
-- Building for: Visual Studio 15 2017
-- The C compiler identification is MSVC 19.12.25831.0
-- The CXX compiler identification is MSVC 19.12.25831.0
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.12.25827/bin/Hostx86/x86/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.12.25827/bin/Hostx86/x86/cl.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.12.25827/bin/Hostx86/x86/cl.exe
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.12.25827/bin/Hostx86/x86/cl.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for include file stdint.h
-- Looking for include file stdint.h - found
-- Looking for include file stdbool.h
-- Looking for include file stdbool.h - found
-- target architecture: x86
-- Performing Test CXX_FLAG_CXX11
-- Performing Test CXX_FLAG_CXX11 - Success
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/takashi/Documents/github/azure-c-shared-utility

C:\Users\takashi\Documents\github\azure-c-shared-utility>

Visual Studio 15 2017向けのものをMSVCでコンパイルするよう準備したようです。

どこに何が出来上がっているのか分からないのですが、トップフォルダにこれらのファイルが出来上がっていました。

f:id:matsujirushix:20171224120843p:plain

ビルド

cmake --buildを実行します。

C:\Users\takashi\Documents\github\azure-c-shared-utility>cmake --build .
.NET Framework 向け Microsoft (R) Build Engine バージョン 15.5.180.51428
Copyright (C) Microsoft Corporation.All rights reserved.

2017/12/24 12:09:57 にビルドを開始しました。
ノード 1 上のプロジェクト "C:\Users\takashi\Documents\github\azure-c-shared-utility\ALL_BUILD.vcxproj" (既定のターゲット)。
...
ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:13.54

C:\Users\takashi\Documents\github\azure-c-shared-utility>

Debugフォルダのaziotsharedutil.libがビルド結果のようです。

f:id:matsujirushix:20171224123111p:plain

Azure IoT HubのSASトークンについて

Azure IoT SDKsを使わずに、Azure IoT HubにMQTT, AMQP, HTTPSなどで接続するにはSASトークという文字列を渡さなければいけないのだが、このSASトークが一体何者か良く分からない。

何度、ドキュメントを読み返しても分からない。

さまざまな用語が出てきます。共有アクセスポリシー、セキュリティ資格情報、資格情報、トークン、対称キー、SASトークン、IoTHubセキュリティトークン、セキュリティトークン、共有アクセスキーで署名されたトークン...

あー、わけわからない。

わからないままでは前に進めないので、ちょっと時間をかけて自分なりに整理してみました。

前提

  • バイスからIoT Hub、サービスからIoT Hubに接続するときの、SASトークンを調べました。他のAzureサービスのSASトークンが同様かは不明です。
  • IoT Hubへのアクセスの制御の日本語と英語を何度も読み返して整理しました。また、Azureポータルの画面も確認しました。アプリを作って検証とかはしていないです。

バイスからIoT Hub

IoT Hubレベルで定義されている共有アクセスポリシー、もしくは、デバイス毎のセキュリティ資格情報でアクセス制御します。

バイス毎のセキュリティ資格情報は、対称キー、自己X509証明書、証明機関X509証明書の3つがあり、デバイスを作るときに指定します。(なお、デバイス毎のセキュリティ資格情報はIoT HubのIdentity Registryに格納されています。)

  • 共有アクセスポリシー(IoT Hubレベル)
  • セキュリティ資格情報(デバイス毎)
    • 対称キー
    • 自己X509証明書
    • 証明機関X509証明書

このうち、共有アクセスポリシーと対称キーでアクセス制御するときに、SASトークンを生成して、MQTTなどで接続するときに渡します。

サービスからIoT Hub

共有アクセスポリシーでアクセス制御します。おしまいw

共有アクセスポリシーからSASトークンを生成

リソースURIと共有アクセスポリシー名、共有アクセスキー、有効期限から、base64とかHMAC-SHA256計算とかして作り出します。

  • リソースURI ... 接続するIoT HubのリソースURI。"myhub.azure-devices.net/devices/device1"
  • 共有アクセスポリシー名 ... "device"
  • 共有アクセスキー ... "Rosx8/5Q7THau334G6WlwAR0lsZc3Yt1QdcWCmr7KYA="
  • 有効期限 ... 適当に適切に決める

共有アクセスポリシー名、共有アクセスキーは、Azureポータルの下図の箇所の値です。

計算の具体的なコードはドキュメントgenerateSasTokenで検索して探してください。

f:id:matsujirushix:20171223232322p:plain

対称キーからSASトークンを生成

リソースURIと対称キー、有効期限から、base64とかHMAC-SHA256計算とかして作り出します。

  • リソースURI ... 接続するIoT HubのリソースURI。"myhub.azure-devices.net/devices/device1"
  • 対称キー ... "HDBqV7U2dRosEh2Xxf//MYzA2T111rRkEmV2dJW0k0s="
  • 有効期限 ... 適当に適切に決める

対称キーは、Azureポータルの下図の箇所の値です。

計算の具体的なコードはドキュメントgenerateSasTokenで検索して探してください。

f:id:matsujirushix:20171223232843p:plain

気になったこと

  • トークンの期限が切れた時点で、IoT Hub はデバイスの接続を切断します。 ”という記述があります。なので、切断されたら自動的にSASトークンを再生成して、ネットワーク再接続するロジックが必要。
  • 最後の方にちょろっと書いてある、”カスタムデバイスの認証”モデルが気になる。デバイストークンサービスにSASトークンを要求すると、何らかの手段でバイスを認証してSASトークンを発行する仕組み。
  • 共有アクセスポリシーや対称キー(から生成したSASトークン)はアクセスの制御。認証ではない!ところがポイント。

参考

TinyCLR OS 1.0 ロードマップ

GHI ElectronicsのJohnから、TinyCLR OS 1.0リリースまでのロードマップが発表されました。

forums.ghielectronics.com

簡単に日本語訳しますと、

v0.7.0(2018年1月)

  • CANライブラリ追加
  • Interops改善
  • ライブラリの全ピン対応

v0.8.0(2018年2月)

v0.9.0(2018年3月)

v1.0.0RC(2018年4月)

  • ドキュメントの更新
  • オンラインギャラリー

です。

v0.6.0が2017年8月だったので、急にスピードアップする感じですね。

気になる点としてはInterops改善ソケットとHTTPのライブラリ

現在のInteropsは少し複雑なので、改善されるのは歓迎なのですが、ちょうど今から本格的に取り組もうかと考えていたので、どうしようかなと迷うところ。

通信関連のライブラリは、やっとという感じです。みんな待っていたのではないでしょうか。Low-level ST's WiFi driverっていったい何を示しているんだろう?STM+WiFiでググってみると、SPWF04が出てきた。これのことなんだろうか。

【メモ】Native Interops in TinyCLR その2

これの続きです。

matsujirushi.hatenablog.jp

Native stubを生成

前回最後にやった「Native stubを生成」で、

class MyNativeClass
{
    [MethodImpl(MethodImplOptions.InternalCall)]
    public extern int MyNativeFunc(int param1, int param2);
}

から生成されたファイルは3つでした。

  • TinyCLRApplication1.h
  • TinyCLRApplication1.cpp
  • TinyCLRApplication1_TinyCLRApplication1_MyNativeClass.cpp

そのうち、実コードを記入するのはTinyCLRApplication1_TinyCLRApplication1_MyNativeClass.cppにある、

TinyCLR_Result Interop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass::MyNativeFunc___I4__I4__I4(const TinyCLR_Interop_MethodData md)

です。

TinyCLR_ResultはTinyCLR.hに定義されていて、enum型。まぁ、これは分かりやすい。

TinyCLR_Interop_MethodDataApiProviderStackが含まれた構造体で、ApiProviderはAcquire,Release,Add,Removeなどのvtableっぽい。StackはApiProviderにあるAPIに渡す引数で使うのかな。

これらメンバーの使い方はココにありますが、ほとんど書かれておらず参考になりませんねw

ApiProviderのメンバーをじっくり見ると、下記のとおり分類できるようだ。

ApiProvider 引数と戻り値

GetArgument(const TinyCLR_Interop_Provider* self, const TinyCLR_Interop_StackFrame& stack, size_t index, TinyCLR_Interop_ManagedValue& value)
GetReturn(const TinyCLR_Interop_Provider* self, TinyCLR_Interop_StackFrame& stack, TinyCLR_Interop_ManagedValue& value)

ApiProvider 参照カウンタ操作?

Acquire(const TinyCLR_Interop_Provider* self)
Release(const TinyCLR_Interop_Provider* self)

ApiProvider Interop追加/削除

Add(const TinyCLR_Interop_Provider* self, const TinyCLR_Interop_Assembly* interop)
Remove(const TinyCLR_Interop_Provider* self, const TinyCLR_Interop_Assembly* interop)

ApiProvider 型情報の検索

FindType(const TinyCLR_Interop_Provider* self, const char* assemblyName, const char* namespaceName, const char* typeName, TinyCLR_Interop_ManagedObjectType& type)

ApiProvider マネージオブジェクトの生成/入れ替え?/自オブジェクトの取得

CreateObject(const TinyCLR_Interop_Provider* self, TinyCLR_Interop_StackFrame& stack, TinyCLR_Interop_ManagedValue& value)
ReplaceObject(const TinyCLR_Interop_Provider* self, TinyCLR_Interop_ManagedValue& source, TinyCLR_Interop_ManagedValue& destination)
GetThisObject(const TinyCLR_Interop_Provider* self, TinyCLR_Interop_StackFrame& stack, TinyCLR_Interop_ManagedValue& value)

ApiProvider フィールドとイベント

GetField(const TinyCLR_Interop_Provider* self, TinyCLR_Interop_ManagedValue managedObject, size_t index, TinyCLR_Interop_ManagedValue& value)
GetStaticField(const TinyCLR_Interop_Provider* self, const TinyCLR_Interop_Assembly& interop, size_t index, TinyCLR_Interop_ManagedValue& value)
RaiseEvent(const TinyCLR_Interop_Provider* self, const char* eventDispatcherName, const char* apiName, size_t implementationIndex, uint64_t data0, uint64_t data1, intptr_t data2)

Interopコードを実装

細かいところは置いておいて、サンプルコードを参考に、呼び出された回数を返すようTinyCLRApplication1_TinyCLRApplication1_MyNativeClass.cppを実装してみました。

#include "TinyCLRApplication1.h"

TinyCLR_Result Interop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass::MyNativeFunc___I4__I4__I4(const TinyCLR_Interop_MethodData md)
{
    static int counter = 0;

    TinyCLR_Interop_ManagedValue ret;
    ret.Type = TinyCLR_Interop_ManagedValueType::I4;
    ret.Data.Numeric->I4 = counter++;

    return TinyCLR_Result::Success;
}

Interopのビルド

makefileの作成

サンプルのmakefileをコピペして、OUTPUT_NAMEをMyInterop、INC_DIRSにTinyCLR.hがあるフォルダを追加しました。

OUTPUT_NAME = MyInterop
LINKERSCRIPT_NAME = scatterfile

MCU_FLAGS = -mcpu=cortex-m4 -mthumb
INC_DIRS = -I. -IC:\TinyCLR\TinyCLR-Ports\Core

CC = arm-none-eabi-g++.exe
LD = arm-none-eabi-g++.exe
OC = arm-none-eabi-objcopy.exe

CC_FLAGS = $(INC_DIRS) $(MCU_FLAGS) -Os -std=c++11 -xc++ -Wall -Wabi -w -mabi=aapcs -fPIC -fno-exceptions -fno-rtti -fno-use-cxa-atexit -fno-threadsafe-statics
LD_FLAGS = $(MCU_FLAGS) -nostartfiles -lc -lgcc -T $(LINKERSCRIPT_NAME) -Wl,-Map,$(OUTPUT_NAME).map -Wl,--oformat -Wl,elf32-littlearm
OC_FLAGS = -S -O binary

SRC_FILES = $(wildcard *.cpp)
OBJ_FILES = $(patsubst %.cpp, %.obj, $(SRC_FILES))
 
rebuild: clean build

clean:
    del $(OBJ_FILES) $(OUTPUT_NAME).bin $(OUTPUT_NAME).elf $(OUTPUT_NAME).map

build: $(OBJ_FILES)
    $(LD) $(LD_FLAGS) -o $(OUTPUT_NAME).elf $^
    $(OC) $(OC_FLAGS) $(OUTPUT_NAME).elf $(OUTPUT_NAME).bin

%.obj: %.cpp
    $(CC) -c $(CC_FLAGS) -o $@ $^

scatterfileの作成

サンプルのscatterfileをコピペして、RLI_BASEとRLI_LENGTHを、デバイスのScatterfile.gcc.ldfに書かれている(ER_RLP_BEGIN値)と(ER_RLP_END値-ER_RLP_BEGIN値)に書き換えます。

MEMORY {
    SDRAM (wx) : ORIGIN = 0x2001BC00, LENGTH = (0x2001C000 - 0x08) - 0x2001BC00
}

SECTIONS {
    . = ALIGN(4);

    .text : {
        *(.text)
    }

    .rodata ALIGN(4): {
        *(.rodata )
    }

    .data ALIGN(4): {
        *(.data)
    }

    .bss ALIGN(4): {
        *(.bss)
    }
}

make build

準備できた状態がこちら。

f:id:matsujirushix:20171202184749p:plain

make buildコマンドを実行して、ビルド(コンパイル&リンク)してみます。

f:id:matsujirushix:20171202184929p:plain

f:id:matsujirushix:20171202185117p:plain

おおお!なんかできてるっぽい。

f:id:matsujirushix:20171202185607p:plain

(つづく)

【メモ】Native Interops in TinyCLR

TinyCLR OSのInteropをウォークスルーしてみます。

TinyCLR Applicationを新規作成

Visual Studio 2017を起動して、TinyCLR Applicatonプロジェクトを新規作成します。

f:id:matsujirushix:20171126214548p:plain

資料のサンプルコードはフィールド、メソッド、プロパティを宣言していますが、メソッドさえ動けばとりあえずは良いので、メソッドだけ宣言しました。

using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace TinyCLRApplication1
{
    class Program
    {
        static void Main()
        {
            var instance = new MyNativeClass();
            Debug.WriteLine(instance.MyNativeFunc(10, 20).ToString());
        }
    }

    class MyNativeClass
    {
        [MethodImpl(MethodImplOptions.InternalCall)]
        public extern int MyNativeFunc(int param1, int param2);
    }
}

Native stubを生成

プロジェクトのプロパティにある、 * Generate native stubs for internal methods * Generate bare native stubs をチェックして、リビルドします。

f:id:matsujirushix:20171126215429p:plain

リビルドした後は、元に戻しておきます。(両方ともチェックを外す)

リビルドした結果、bin/Debug/pe/Interop配下に、 * TinyCLRApplication1.h * TinyCLRApplication1.cpp * TinyCLRApplication1_TinyCLRApplication1_MyNativeClass.cpp が生成されます。

f:id:matsujirushix:20171126220000p:plain

中身は次のとおり。

TinyCLRApplication1.h

#pragma once

#include <TinyCLR.h>

struct Interop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass {
    static TinyCLR_Result MyNativeFunc___I4__I4__I4(const TinyCLR_Interop_MethodData md);
};

extern const TinyCLR_Interop_Assembly Interop_TinyCLRApplication1;

TinyCLRApplication1.cpp

#include "TinyCLRApplication1.h"

static const TinyCLR_Interop_MethodHandler methods[] = {
    Interop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass::MyNativeFunc___I4__I4__I4,
    nullptr,
    nullptr,
    nullptr,
};

const TinyCLR_Interop_Assembly Interop_TinyCLRApplication1 = {
    "TinyCLRApplication1",
    0xAA047403,
    methods
};

TinyCLRApplication1_TinyCLRApplication1_MyNativeClass.cpp

#include "TinyCLRApplication1.h"

TinyCLR_Result Interop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass::MyNativeFunc___I4__I4__I4(const TinyCLR_Interop_MethodData md) {


    return TinyCLR_Result::NotImplemented;
}

Interop_TinyCLRApplication1 -> methods -> Interop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass::MyNativeFuncI4I4I4 と参照しているので、メソッドの実装はTinyCLRApplication1_TinyCLRApplication1_MyNativeClass.cppのInterop_TinyCLRApplication1_TinyCLRApplication1_MyNativeClass::MyNativeFuncI4I4I4 に書けば良いようです。

methods の配列が4なのが気になります。なにか意味があるのだろうか?

さらに、メソッドの引数の型、TinyCLR_Interop_MethodData も謎。

(つづく)