「weblog」カテゴリーアーカイブ

ESP32 IDFからTLS 1.3 (mbedTLS 3.1.0)を使う

最近、mbedTLSをちょっと調べていて、ふとみんな大好きESP32のデフォはmbedTLSだからどうなっているか?見てみた。
以下な感じで、https://www.google.com にTLS 1.3でアクセスできる。ESP32 IDFのgithubのcomponent/mbedTLSを見ると、mbedTLS 3.1になっている。mbedTLS 3.1にはTLS1.3(medtls_config.h: MBEDTLS_SSL_PROTO_TLS1_3)を利用することができる。

ちなみに、いま主流で使われているmbedTLS 2.x系はリリース情報にもある通り、2024年までしかサポートされない。2.16系はlong-time support 。恐らく、これからは3.x系がメインになってくるのを、ESP IDFでは早くも取り込んできているものと思われ。
なにはともあれ、動かしてみたのがこちら。

ばっちりTLS 1.3でwww.google.comにアクセスしてくれている。1-RTTで実現するはずが、クライアント側からChange Cipher Spec(No. = 13)が付いているのは、MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE の説明にある通り(RFC 8446 D.4. Middlebox Compatibility Mode)、ミドルボックスの下位互換性のために投げている空のChange Cipher Specっすね。
TCPと見えるように、QUICは未です。一応、忘れないように手順だけ残しておく。ちなみに、wolfsslも使おうと思えばいけると思われ(前にノリでwolfssl tls 1.3をespで動かした記憶がある)。もう、この手の端末でもTLS 1.3がやってくる、というかすでに来ている。

# https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html の手順に従って環境を準備する
# 必要なパッケージをインストールしたりとか。
# release バージョンではなく、最新のrepoから全部取得してくる。
git clone --recursive https://github.com/espressif/esp-idf.git

# あとは手順に従ってビルド環境を整備する。
# ここからが本番
cp -r examples/protocols/https_mbedtls/ . 
cd https_mbedtls
idf.py set-target esp32
idf.py menuconfig

menuconfigで、”Component config” -> “mbedTLS” -> “mbedTLS v3.x related” -> “Support TLS 1.3 protocol” を有効にする。mbedTLSのconfig.hを直接アタッチしなくても、menuconfigから制御できるのは良い感じ。

あとはWiFiの接続設定とかも済ませておく。wifiは”Example Connection Configuration”から。次にコードを修正する。面倒なので丸っと以下に残しておく。

/* HTTPS GET Example using plain mbedTLS sockets
 *
 * Contacts the howsmyssl.com API via TLS v1.2 and reads a JSON
 * response.
 *
 * Adapted from the ssl_client1 example in mbedtls.
 *
 * SPDX-FileCopyrightText: The Mbed TLS Contributors
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * SPDX-FileContributor: 2015-2021 Espressif Systems (Shanghai) CO LTD
 */
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_netif.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/esp_debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "esp_crt_bundle.h"


// 接続先はGoogle
#define WEB_SERVER "www.google.com"
#define WEB_PORT "443"
#define WEB_URL "/"

static const char *TAG = "example";

static const char *REQUEST = "GET " WEB_URL " HTTP/1.0\r\n"
    "Host: "WEB_SERVER"\r\n"
    "User-Agent: esp-idf/1.0 esp32\r\n"
    "\r\n";

// GTS Root R1 のPEMを取ってきて置いとく
#define GTSR1_CA_PEM                                              \
"-----BEGIN CERTIFICATE-----\r\n"  \
"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\r\n"  \
"bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\r\n"  \
"-----END CERTIFICATE-----"
const unsigned char gtsr1_pem[] = GTSR1_CA_PEM;


static void https_get_task(void *pvParameters)
{
    char buf[512];
    int ret, flags, len;

    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_ssl_context ssl;
    mbedtls_x509_crt cacert;
    mbedtls_ssl_config conf;
    mbedtls_net_context server_fd;

    mbedtls_ssl_init(&ssl);
    mbedtls_x509_crt_init(&cacert);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    ESP_LOGI(TAG, "Seeding the random number generator");

    mbedtls_ssl_config_init(&conf);

    mbedtls_entropy_init(&entropy);
    if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
                                    NULL, 0)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret);
        abort();
    }

    ESP_LOGI(TAG, "Attaching the certificate bundle...");

    ret = esp_crt_bundle_attach(&conf);

    if(ret < 0)
    {
        ESP_LOGE(TAG, "esp_crt_bundle_attach returned -0x%x\n\n", -ret);
        abort();
    }

    ESP_LOGI(TAG, "Setting hostname for TLS session...");

     /* Hostname set here should match CN in server certificate */
    if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
        abort();
    }

    ESP_LOGI(TAG, "Setting up the SSL/TLS structure...");

    if((ret = mbedtls_ssl_config_defaults(&conf,
                                          MBEDTLS_SSL_IS_CLIENT,
                                          MBEDTLS_SSL_TRANSPORT_STREAM,
                                          MBEDTLS_SSL_PRESET_DEFAULT)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret);
        goto exit;
    }

    /* MBEDTLS_SSL_VERIFY_OPTIONAL is bad for security, in this example it will print
       a warning if CA verification fails but it will continue to connect.

       You should consider using MBEDTLS_SSL_VERIFY_REQUIRED in your own code.
    */
    mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);

    // root caの読み込みとセッションチケットを有効にしておいた
    // あとはTLSの鍵交換モードの設定、re-negotiationはとりあえず無効に
    // TLSの最大・最小バージョンをTLS 1.3固定
    // 上記の記載のとおり、サンプルなので、本来はちゃんとVERIFYすること!!
    mbedtls_x509_crt_parse( &cacert, gtsr1_pem, sizeof(gtsr1_pem) );
    mbedtls_ssl_conf_session_tickets( &conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED );
    mbedtls_ssl_conf_tls13_key_exchange_modes( &conf, MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_ALL );
    mbedtls_ssl_conf_renegotiation( &conf, MBEDTLS_SSL_RENEGOTIATION_DISABLED );
    mbedtls_ssl_conf_min_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_4 );
    mbedtls_ssl_conf_max_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_4 );

    mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
#ifdef CONFIG_MBEDTLS_DEBUG
    mbedtls_esp_enable_debug_log(&conf, CONFIG_MBEDTLS_DEBUG_LEVEL);
#endif

    if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret);
        goto exit;
    }

    while(1) {
        mbedtls_net_init(&server_fd);

        ESP_LOGI(TAG, "Connecting to %s:%s...", WEB_SERVER, WEB_PORT);

        if ((ret = mbedtls_net_connect(&server_fd, WEB_SERVER,
                                      WEB_PORT, MBEDTLS_NET_PROTO_TCP)) != 0)
        {
            ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret);
            goto exit;
        }

        ESP_LOGI(TAG, "Connected.");

        mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);

        ESP_LOGI(TAG, "Performing the SSL/TLS handshake...");

        while ((ret = mbedtls_ssl_handshake(&ssl)) != 0)
        {
            if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
            {
                ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
                goto exit;
            }
        }

        ESP_LOGI(TAG, "Verifying peer X.509 certificate...");

        if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0)
        {
            /* In real life, we probably want to close connection if ret != 0 */
            ESP_LOGW(TAG, "Failed to verify peer certificate!");
            bzero(buf, sizeof(buf));
            mbedtls_x509_crt_verify_info(buf, sizeof(buf), "  ! ", flags);
            ESP_LOGW(TAG, "verification info: %s", buf);
        }
        else {
            ESP_LOGI(TAG, "Certificate verified.");
        }

        ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl));

        ESP_LOGI(TAG, "Writing HTTP request...");

        size_t written_bytes = 0;
        do {
            ret = mbedtls_ssl_write(&ssl,
                                    (const unsigned char *)REQUEST + written_bytes,
                                    strlen(REQUEST) - written_bytes);
            if (ret >= 0) {
                ESP_LOGI(TAG, "%d bytes written", ret);
                written_bytes += ret;
            } else if (ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_WANT_READ) {
                ESP_LOGE(TAG, "mbedtls_ssl_write returned -0x%x", -ret);
                goto exit;
            }
        } while(written_bytes < strlen(REQUEST));

        ESP_LOGI(TAG, "Reading HTTP response...");

        do
        {
            len = sizeof(buf) - 1;
            bzero(buf, sizeof(buf));
            ret = mbedtls_ssl_read(&ssl, (unsigned char *)buf, len);

            if(ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
                continue;

            if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
                ret = 0;
                break;
            }

            if(ret < 0)
            {
                ESP_LOGE(TAG, "mbedtls_ssl_read returned -0x%x", -ret);
                break;
            }

            if(ret == 0)
            {
                ESP_LOGI(TAG, "connection closed");
                break;
            }

            len = ret;
            ESP_LOGD(TAG, "%d bytes read", len);
            /* Print response directly to stdout as it is read */
            for(int i = 0; i < len; i++) {
                putchar(buf[i]);
            }
        } while(1);

        mbedtls_ssl_close_notify(&ssl);

    exit:
        mbedtls_ssl_session_reset(&ssl);
        mbedtls_net_free(&server_fd);

        if(ret != 0)
        {
            mbedtls_strerror(ret, buf, 100);
            ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf);
        }

        putchar('\n'); // JSON output doesn't have a newline at end

        static int request_count;
        ESP_LOGI(TAG, "Completed %d requests", ++request_count);
        printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());

        for(int countdown = 10; countdown >= 0; countdown--) {
            ESP_LOGI(TAG, "%d...", countdown);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
        ESP_LOGI(TAG, "Starting again!");
    }
}

void app_main(void)
{
    ESP_ERROR_CHECK( nvs_flash_init() );
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

    xTaskCreate(&https_get_task, "https_get_task", 8192, NULL, 5, NULL);
}

あとは idf.py build, flashすればOK

mbedTLS 3.1.0 TLS 1.3 simple sample

元のssl_client2がTLS12, 13, DTLSが混じって分りにくかったので、簡単なサンプル。セッションの保存は行わない。
Root CAは組込みでファイルシステムが無い場合があるので、PEMとしてdefineして使う。とりあえず、event driven&timer制御での非同期性はオフにしたシンプルな感じで。configでMBEDTLS_SSL_PROTO_TLS1_3を有効にしたり、TLS 1.3周りの設定をする必要あり。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/hmac_drbg.h"
#include "mbedtls/x509.h"
#include "mbedtls/error.h"
#include "mbedtls/debug.h"
#include "mbedtls/timing.h"
#include "mbedtls/base64.h"

#define BUFFER_SIZE   20000
#define SERVER_NAME    "www.google.com"
#define SERVER_PORT    "443"

#define GTSR1_CA_PEM                                              \
"-----BEGIN CERTIFICATE-----\r\n"  \
"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\r\n"  \
"bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\r\n"  \
"-----END CERTIFICATE-----"
const unsigned char gtsr1_pem[] = GTSR1_CA_PEM;

typedef struct {
    mbedtls_ssl_context *ssl;
    mbedtls_net_context *net;
} io_ctx_t;

typedef struct {
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context drbg;
} rng_context_t;

void debug( void *ctx, int level, const char *file, int line, const char *str ) {
    const char *p, *basename;
    for( p = basename = file; *p != '\0'; p++ )
        if( *p == '/' || *p == '\\' )
            basename = p + 1;

    mbedtls_fprintf( (FILE *) ctx, "%s:%04d: |%d| %s",
                     basename, line, level, str );
    fflush( (FILE *) ctx  );
}

int rng_get( void *p_rng, unsigned char *output, size_t output_len ) {
    rng_context_t *rng = p_rng;
    return( mbedtls_ctr_drbg_random( &rng->drbg, output, output_len ) );
}

int send_cb( void *ctx, unsigned char const *buf, size_t len ) {
    io_ctx_t *io_ctx = (io_ctx_t*) ctx;
    return( mbedtls_net_send( io_ctx->net, buf, len ) );
}

int recv_cb( void *ctx, unsigned char *buf, size_t len ) {
    io_ctx_t *io_ctx = (io_ctx_t*) ctx;
    size_t recv_len;
    int ret;

    ret = mbedtls_net_recv( io_ctx->net, buf, len );
    if( ret < 0 )
        return( ret );
    recv_len = (size_t) ret;

    return( (int) recv_len );
}

int main() {
    int ret = 0, len, written, frags;
    mbedtls_net_context server_fd;
    io_ctx_t io_ctx;
    unsigned char buf[BUFFER_SIZE + 1];
    const char *pers = "ssl_client";

    rng_context_t rng;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    uint32_t flags;
    mbedtls_x509_crt cacert;

    /*
     * Make sure memory references are valid.
     */
    mbedtls_net_init( &server_fd );
    mbedtls_ssl_init( &ssl );
    mbedtls_ssl_config_init( &conf );
    mbedtls_ctr_drbg_init( &rng.drbg );
    mbedtls_entropy_init( &rng.entropy );
    mbedtls_x509_crt_init( &cacert );

    mbedtls_debug_set_threshold( 3 );

    /*
     * 0. Initialize the RNG and the session data
     */
    mbedtls_printf( "\n  . Seeding the random number generator..." );
    fflush( stdout );
    ret = mbedtls_ctr_drbg_seed( &rng.drbg, mbedtls_entropy_func, &rng.entropy,
                                     (const unsigned char *) pers,
                                     strlen( pers ) );
    if( ret != 0 )
        mbedtls_exit( ret );
    mbedtls_printf( " ok\n" );

    /*
     * 1. Load the trusted CA
     */
    mbedtls_printf( "  . Loading the CA root certificate ..." );
    fflush( stdout );

    ret = mbedtls_x509_crt_parse( &cacert, gtsr1_pem, sizeof(gtsr1_pem) );
    if( ret < 0 ) {
        mbedtls_printf( " failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n",
                        (unsigned int) -ret );
        mbedtls_exit( ret );
    }

    /*
     * 2. Setup stuff
     */
    mbedtls_printf( "  . Setting up the SSL/TLS structure..." );
    fflush( stdout );

    if( ( ret = mbedtls_ssl_config_defaults( &conf,
                    MBEDTLS_SSL_IS_CLIENT,
                    MBEDTLS_SSL_TRANSPORT_STREAM,
                    MBEDTLS_SSL_PRESET_DEFAULT ) ) != 0 ) {
        mbedtls_printf( " failed\n  ! mbedtls_ssl_config_defaults returned -0x%x\n\n", (unsigned int) -ret );
        mbedtls_exit( ret );
    }

    if( ( ret = mbedtls_ssl_conf_max_frag_len( &conf, MBEDTLS_SSL_MAX_FRAG_LEN_NONE ) ) != 0 ) {
        mbedtls_printf( " failed\n  ! mbedtls_ssl_conf_max_frag_len returned %d\n\n",
                        ret );
        mbedtls_exit( ret );
    }

    mbedtls_ssl_conf_rng( &conf, rng_get, &rng );
    mbedtls_ssl_conf_dbg( &conf, debug, stdout );

    mbedtls_ssl_conf_read_timeout( &conf, 0 );
    mbedtls_ssl_conf_session_tickets( &conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED );
    mbedtls_ssl_conf_tls13_key_exchange_modes( &conf, MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_ALL );
    mbedtls_ssl_conf_renegotiation( &conf, MBEDTLS_SSL_RENEGOTIATION_DISABLED );
    mbedtls_ssl_conf_ca_chain( &conf, &cacert, NULL );

    mbedtls_ssl_conf_min_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_4 );
    mbedtls_ssl_conf_max_version( &conf, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_4 );

    if( ( ret = mbedtls_ssl_setup( &ssl, &conf ) ) != 0 ) {
        mbedtls_printf( " failed\n  ! mbedtls_ssl_setup returned -0x%x\n\n",
                        (unsigned int) -ret );
        mbedtls_exit( ret );
    }

    if( ( ret = mbedtls_ssl_set_hostname( &ssl, SERVER_NAME ) ) != 0 ) {
        mbedtls_printf( " failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret );
        mbedtls_exit( ret );
    }

    io_ctx.ssl = &ssl;
    io_ctx.net = &server_fd;
    mbedtls_ssl_set_bio( &ssl, &io_ctx, send_cb, recv_cb, NULL);

    mbedtls_printf( " ok\n" );

    /*
     * 3. Start the connection
     */
    if( ( ret = mbedtls_net_connect( &server_fd,
                       SERVER_NAME, SERVER_PORT, MBEDTLS_NET_PROTO_TCP) ) != 0 ) {
        mbedtls_printf( " failed\n  ! mbedtls_net_connect returned -0x%x\n\n", (unsigned int) -ret );
        mbedtls_exit( ret );
    }

    ret = mbedtls_net_set_block( &server_fd );
    if ( ret != 0 ) {
        mbedtls_printf( " failed\n  ! net_set_(non)block() returned -0x%x\n\n", (unsigned int) -ret );
        mbedtls_exit( ret );
    }
    mbedtls_printf( " ok\n" );

    /*
     * 4. Handshake
     */
    mbedtls_printf( "  . Performing the SSL/TLS handshake..." );
    fflush( stdout );

    while( ( ret = mbedtls_ssl_handshake( &ssl ) ) != 0 ) {
        if( ret != MBEDTLS_ERR_SSL_WANT_READ &&
            ret != MBEDTLS_ERR_SSL_WANT_WRITE &&
            ret != MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS ) {
            mbedtls_printf( " failed\n  ! mbedtls_ssl_handshake returned -0x%x\n", (unsigned int) -ret );
            mbedtls_exit( ret );
        }
    }

    mbedtls_printf( " ok\n    [ Protocol is %s ]\n    [ Ciphersuite is %s ]\n",
                    mbedtls_ssl_get_version( &ssl ),
                    mbedtls_ssl_get_ciphersuite( &ssl ) );
    /*
     * 5. Verify the server certificate
     */
    mbedtls_printf( "  . Verifying peer X.509 certificate..." );
    if( ( flags = mbedtls_ssl_get_verify_result( &ssl ) ) != 0 )
        mbedtls_printf( " server sertificate failed\n" );
    else
        mbedtls_printf( " ok\n" );
    
    /*
     * 6. Write the GET request
     */
    len = mbedtls_snprintf( (char *) buf, sizeof( buf ) - 1, "GET / HTTP/1.0\r\nExtra-header: \r\n\r\n" );
    written = 0;
    frags = 0;

    do {
        while( ( ret = mbedtls_ssl_write( &ssl, buf + written,
                                            len - written ) ) < 0 ) {
            if( ret != MBEDTLS_ERR_SSL_WANT_READ &&
                ret != MBEDTLS_ERR_SSL_WANT_WRITE &&
                ret != MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS ) {
                mbedtls_printf( " failed\n  ! mbedtls_ssl_write returned -0x%x\n\n",
                                (unsigned int) -ret );
                mbedtls_exit( ret );
            }
        }
        frags++;
        written += ret;
    } while( written < len );

    buf[written] = '\0';
    mbedtls_printf( " %d bytes written in %d fragments\n\n%s\n", written, frags, (char *) buf );

    /*
     * 7. Read the HTTP response
     */
    do {
        len = sizeof( buf ) - 1;
        memset( buf, 0, sizeof( buf ) );
        ret = mbedtls_ssl_read( &ssl, buf, len );

        if( ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE )
            continue;

        if( ret <= 0 ) {
            if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
                mbedtls_printf( " connection was closed gracefully\n" );
                ret = 0;
                break;
            } else {
                mbedtls_printf( " mbedtls_ssl_read returned -0x%x\n",
                                (unsigned int) -ret );
                mbedtls_exit( ret );
            }
        }

        len = ret;
        buf[len] = '\0';
        mbedtls_printf( " %d bytes read\n\n%s", len, (char *) buf );

        if( ret > 0 && buf[len-1] == '\n' ) {
            ret = 0;
            break;
        }
    } while( 1 );

    do ret = mbedtls_ssl_close_notify( &ssl );
    while( ret == MBEDTLS_ERR_SSL_WANT_WRITE );

    mbedtls_net_free( &server_fd );
    mbedtls_ssl_free( &ssl );
    mbedtls_ssl_config_free( &conf );
    mbedtls_x509_crt_free( &cacert );
    mbedtls_ctr_drbg_free( &rng.drbg );
    mbedtls_entropy_free( &rng.entropy );

    mbedtls_exit( ret );
}

LCD guitar

LCDをギターのネック内に埋め込んだ。

基本はドリルギターとして作っていたものをアップデートした。LCDにギターのコードや歌詞、TABを表示させたり、内蔵している6軸センサの動きに合わせて何か表示したり…色々と遊べるかなと。
ネックにLCDを埋め込みとか、あまりやらないとは思うけど、手順を記しておく。ざっくり工程としては…

  1. 指板剥がし
  2. ネック・指板削り
  3. LCD用アクリル合わせ
  4. タイトボンドで合わせ
  5. エキポシ樹脂で凹凸・溝消し
  6. 動作確認

■ 指板剥がし

まずは定番の指板剥がしから。フレットレスと前にLEDを埋め込んだ時にやっているから、今回もアイロンで熱を加えながら指板をゆっくり剥がしていく。

指板を剥がしてLEDも付け替え。とりあえずクリーンな状態になった。

■ ネック・指板削り

LCDを埋め込む場所を決めたら、ネック本体の方にLCDを埋め込むスペースを作る。指板はローズウッドでそれなりに硬いものの、薄いというのもあって、基本はネック側にLCDを固定することにした。イメージとしてはこんな感じで。指板の弦側はアクリルを埋め込むために、斜めカットする。

LCD部分の穴あけ・削り

指板側はLCDの面が見える空間、そして弦や指圧から保護するためにアクリルを埋め込む。そしてネック側はLCDの固定・配線をする。

ネック側を削ってLCDを埋め込み・配線はトラスロッドを通すようにする。LEDは指板側に付けて、トラスロッドに合わせて配線

ネック部分にLCDを固定出来るようになったら、今度は指板の方に思いきりよく穴を空けてひたすら削っていく。最初は彫刻刀で出来るかな?と思ったものの、ローズウッドは硬い方なので刃が全然入っていかない…というわけで、指板にドリルで穴をあけてから、それをサイズに合わせて削ってヤスることにした。

https://www.hirotakaster.com/wp-content/uploads/2021/08/IMG_1686.mov

指板側もLCD用の空間を用意して、加速度センサの値を表示させて動作確認もOKそう。

■ LCD用アクリル合わせ

LCDを入れるために指板に空けた空間を埋めるために、アクリルをLCD部(指板)に合わせていく。ここで注意したのが、

  • 押弦したときに、アクリル部分に圧がかかるから、LCD面とアクリルは↑の図の通り接していない。
  • アクリルと指板の接着はエキポシ樹脂を利用する。
  • 弦側アクリル部も上面はエキポシ樹脂を塗って、アクリル面自体も押弦からは直接的には保護する。(フレットレスギターなので、指板全体に薄く樹脂を塗って弦から保護するようにはしている)

アクリルは東急ハンズで1mmのをカットして利用した。1mmでも64×128のサイズなので、耐久性はメチャクチャ強い。

アクリルをカット、まずはマスキングテープで保護した後にエキポシ樹脂でコーティング
次にアクリルを埋め込んでエキポシ樹脂で接合

このまま1−2日、完全に硬化するまで放置しておく。そして樹脂の凹凸が指板側に出来るから、荒い〜細めの水ヤスリで指板の平面を形成してあげる。

■ タイトボンドで合わせ

指板、ネックともに出来たら、ドキドキの指板合わせに。タイトボンドを指板・ネック側に塗りたくってクランプで接合する。ここで接合したら後戻りは出来なくなるから、わりとドキドキする瞬間だったりする。

クランプしても本当に大丈夫か?動作確認をしながら行った。一応ちゃんと動いてはいたから、電源を落としてボンドが乾くまで1-2日放置した。

■ エキポシ樹脂で凹凸・溝消し

指板とネックが合わさったのを確認したら、最後に指板・ネックのタイトボンドがはみ出ていたり、溝が出来ていたり、指板保護と弾きやすさ(ネック側で指をスライドする時に、はみ出たボンドや溝があると弾きにくい)のために、全体を軽くエキポシ樹脂でコーティングして綺麗にヤスって完成した。

ネックが完成した

■ 動作確認

ボディと合わせて動作確認をすることに。電源はバックプレート内にLiPoもしくは電池で動作できるようにしている。LCDの表示も問題なし。

アンプにつなげて音の確認。問題なさげ。

6軸の値を表示させるようにしておいたのだけど、最終的な目的「コードを表示させて、誰でもLCDの表示を見ながら簡単に弾ける!!」というために、コードとTABを表示させるようにした。
コードの表記はNW経由で、クラウド側(インターネットを介して)から自由に表示することが出来る。

https://www.hirotakaster.com/wp-content/uploads/2021/08/IMG_2335.mov

なーんか…弦が邪魔でうまく見えないような気が…とりあえずチューニングしない弦を張ったままの状態で、実際に見ながら弾けるか?試してみた。

https://www.hirotakaster.com/wp-content/uploads/2021/08/trim_1369F109-32DB-4B4A-994D-AB9BD95B6AA0.mov

あれ?あぁ…全然だめだ…弾けねぇ。LCDあっても無くても関係ないし。というか、このギター….フレットレスだからポジションを表示しても意味なくね?というのを弾いてみてから気づいたw

主に使うコードは覚えていて、1ポジのネックをいちいち確認して弾かないっすね。そして、よくよく考えたら、ネックの指板側を見ながら弾くこと自体がムリゲーだったw(製作する前に気づけば良かった…orz)

ただ、弾いていて別の事も発見した。ドリル(モーター)駆動用のスイッチを使って、ドリル音スイッチング奏法が出来ることが分かった。

https://www.hirotakaster.com/wp-content/uploads/2021/08/IMG_1796.mov

フロントピックアップを潰してモーターを2個搭載(速弾き用)していて、セレクタがキルスイッチの状態になっている。だから、通常のスイッチング奏法+ドリル音も混ぜた鳴らし方が出来る。

とりあえずは当初の目的通り、LCDは埋め込んで弾けるギターは出来た。ただ、コードを表示させてもなぁ…って気がしてきて、6軸に合わせてなにか表示した方が良さげかもしれない。
今の所のスペックは以下の通り。

・端末:Particle Argon ESP32-D0WD 240MHz Xtensa Dual Core, Nordic nRF52830 ARM Cortex-M4F 64MHz
・NW:2.4GHz WiFi, Bluetooth 5
・センサ:MPU 6050(6軸)
・その他:Dual Motor Driver、64×128 LCD、SK6812(full color led)

技術を食い物にした話

そういえば、書いてなかったな…と思って、残しておくことにする。

よく開発・プロダクトにして売買するの凄い、という話があったりするけど、お金に還元されないと価値は無いのか?というと、もちろんそうでは無いですよね。お金にするという選択もあり、それ以外の方法もある(技術を食い物にする)という事を少しばかり。

■ モノと経緯

数年前にRealSenseの変換アダプタを制作した。
Intel RealSense USB 3.0 original board

TMCN USB 3.0変換ボードと書いてある通り、コミュニティと人の繋がりで出来た物だった。USB 3.0の導体をひっぱり出して、検証した内容はこちら。
TMCN Intel RealSense USB 3.0 original board

elecrowで作って動作確認・公開したところ、色んな国・人からのコメントや「欲しい」というリクエストがあった。どうやら中古PCをバラして、内臓されているRealSenseを使うというニーズや、単品で怪しく出回っていた(今はaliexpress で検索すると色々と出てくる。当時は無かったと思うけど…)。

欲しいと個別にコンタクトしてきて、BOM/Circuitを送ったり公開して、自分で作れるようにした。それでも欲しい、お金出すから送ってというリクエストもかなりあった。

■ 物々交換

確かにお金のやりとりで送るというのもアリなのだけど、採った選択は、
「お金はいらねー、モノは送るから、あなたのお国の美味しい物を送ってきて」
という事にした。お返しの物は何でもOK。お菓子でも特産品でもお酒でも何でもね。

モノとモノの交換。しかも会った事もない海外の人と
ぶっちゃけ欲しい人からしてみたら、お金で済むならそれが一番早いんだろうけど、「食い物が欲しい」とか何言っちゃってんだこいつ?と思っただろう。

■ 送付

欲しいと言われて、色んなお国・人に送りました。
US、Canada、French、Italy、England、Ukraine、Switchland、Chinaあたりかな。カナダに送った時はお返しにサーモンでも来ないかしら。。。と期待したり、送るときのテンションはかなり高くなった。

ちなみに国際便で送るとき、一番お手軽で安く済むのは国際eパケットライトだと思う。Webで発送先を書いて、持っていくだけ。お値段もめちゃくちゃ安いし。

こんな感じでJPのWeb上で送り状を書いて持っていけばOK。基板と接続用のケーブルを送ったりした。
ただ、ルーチンで同じ物を送るのも面白くないな。。。と思って「これ日本で超流行ってんだぜ!!」とダイソーのクラッピーを同梱したり。

安いESPを使っているとか言っていたから、手持ちで全然使わないESP 12F(aitendoのブツに接続)を同梱してみた。
他にもLEDを詰め込んだりもした記憶がある。深い意味は全くない。なんかあった物を一緒に詰めてみた..というだけ。送料も全然変わらないからね。

■ 結果は…

本当に送ってきます。まじで。

カナダからはサーモンを期待したけど、お菓子だった。

ウクライナからもお菓子だった。

なんとイギリスからはウイスキーのボトル1本(一人で全部飲みました)

他にも写真を撮り忘れたけど、パンに塗って食べると劇ウマだぜ!!と言われた発酵物(ほんと数年前の事だから忘れた。。。)とか、生物を送るのはヤバいからお菓子にするわ、とかで色々と送ってきてくれた。結果として、お金や売り物にしなくても、技術を食い物にできた。
思った事としては、

・ネット上だけで会ったことも無い人
・主に海外の人たちがお相手
・ネットから生まれるコミュニティや人との繋がりめちゃ大切
・モノ(技術)の価値はお金以外にも還元可能

blogに欲しいと書き込んだのをトリガーに、会ったことも無い海外の人とモノを送って物々交換は成立する。
いまはコロナで実際に会うというハードルが高くなってきている中で、ネット上で技術をベースにした交流やつながりは沢山ありますね。
そして、必ずしも何かを作ったらお金に還元しないとダメという事は全くない訳で、コミュニティや人とのつながりの大切さ・物々交換という方法だってあるんだよ、という事は強く思っていることでもありますね。

ネイルにNFCを搭載してみる

親指にNFCを仕込んでみた。自分で好きなデザイン・色で仕込んで、指にスマホを非接触通信でデータの読み書き・何らかの制御が出来たら面白いんじゃん、とか思いつつ。

とりあえず今はこんな感じ。右親指の先にNFCが仕込んである。

そして実際にNFCにWrite=>Readも問題なし。iPhoneのNFC Toolを使ってサクッとバッチリ。

https://www.hirotakaster.com/wp-content/uploads/2021/04/p2.mp4

通信する距離が気になるところで、1cm離れていても非接触で大丈夫だった。この位の距離で使えるのはいい感じ。

https://www.hirotakaster.com/wp-content/uploads/2021/04/p1.mp4

■ 使ったNFC

まずは利用したNFCはこちら。5x5mmのNFC。薄さは0.1mm程度。まぁ、普通のNFCですね。

普通サイズのNFCだと非常に単価が安くて、数円/枚で束で買っても全然安いんだけど、このサイズになると少し高くなる。150円/個だと既存の20倍近くの高値に。とりあえず10個ゲットしたのが届いた。これが、そのうち数十円程度になってくるような気もしている。このサイズ感になると、何かに仕込むときも便利な予感がしている。
こちらは束で買ってあったNFC。大量に貼り付け・位置情報(XY)を保持させて、座標が分かる物を作ったときに利用した。めちゃ安いから大量に買っても全然安い。

■ ネイル塗り

ネイルを塗るときは、ベースコート(凹凸補正・爪の保護)=>色塗り=>トップコート(保護)という順で行う。ここで順番として、ベースコート=>NFC=>ベースコート=>色塗り=>トップコートという順でやってみたのだけど、これは上手くいかなかった。というのも、

1. NFCタグをシールから剥がしたときの粘着力が強くて、ベースコートを剥がしてしまう。
2. NFCの厚さ(0.1mm)の凹凸補正がうまく出来なかった。

この2点がうまくクリアできなかった。そこで、NFC=>ベースコート=>色塗り=>トップコートという順にすることになった。
まず最初にやろうとしたのが、中指の爪の上に配置することだった。結果的にはこれは失敗した。というのも、爪はもちろん微妙にカーブ(湾曲)していて、5x5mmサイズといってもうまく制作できなかった。

そこで次は親指に付けてみることにした。何個か中指につけようと試行錯誤して、何個かNFCを犠牲にしてしまった。。。自分の場合は、親指のこの部分に張り付けて、ベースコートで凹凸補正したら下地はいい感じになった。

そして塗りとトップコート。この時点で、NFCを仕込んでいるのは分からなくなっている。ワンカラーならここでOK。

最後に軽くデコパーツを付けて、トップコートで補正と保護。ちょうど、親指の先のマークあたりにNFCが搭載されている。キラキラパーツを付けてもNFCのwirte/readは問題なし。

勢い的にやってみたけど、まぁ大丈夫そう。このまま過ごしてみて、NFCがダメになるまで試してみよう。なーんとなく、塗りとトップコートの保護のおかげで、問題なく使いつづけられるような気もするけど、やってみない事には分からないからね。

■ 遊んでみる・アプリ連携

NFCを搭載したら、指にデータを書き込んで遊びたくなるですね。使ったのはNFC ToolsというアプリでNFCに書き出し。iPhone側は特に何もしなくても、自動でNFCを認識・アプリを起動してくれる。

まずはGoogle Map連携。住所(ランドマーク名とかでも)をGoogleMapで起動するようにしておくと、親指をかざすとGoogle Mapが起動する。

https://www.hirotakaster.com/wp-content/uploads/2021/05/VID_20210505_132209_Trim.mp4

他にもURL(www.yahoo.co.jp)を書き込んで、指をかざしてブラウザを起動させる。

https://www.hirotakaster.com/wp-content/uploads/2021/05/180153315_232703191944625_3974814249969059158_n.mp4

他にもメール・電話・Facetime・SNS…etc とアプリをキックするトリガーになって遊べる感じ。1週間以上、ネイルに搭載したままでも問題なく使えていて(デコった柄を変えてみても問題なし)、このサイズのNFCは使える感じがする。

100V ACスイッチ

最近なんも書いて無かったから、ちょっと書いてみるか…みたいな感じで書いておく。
100V ACをマイコンからON/OFF制御する必要が出てきてキットで作ったもの。まぁ…SSRキットを接続してサクッと出来る。制御は3V-5Vでいける。こんな感じ。

秋月 SSRキット説明書から

■ パーツ一覧

ソリッド・ステート・リレー(SSR)キット 8Aタイプ
2P ACコード(2Pプラグ/ハンダ仕上げコード)
ガラス管ヒューズ MF51NR 250V10A 20mm
管ヒューズホルダー 10A 30mm(確かこれ。100円ちょいで千石で入手)
ACアウトレット(50円くらいで千石にあるはず)
放熱板(ヒートシンク、何かあったものをつけた)
格納しておくボックス(これも、なんかあった物を使った)
ビニール線(赤白青、あったものを使った)
熱収縮チューブ(これも、あった物を使った)

■ サクッと作る

まぁ…ゲットしてきて接続するだけ。

んで完成。作るのはキットの説明に従って説明するだけ。電源は接続したら熱収縮で保護してあげる。100V感電には注意してやるくらい。ケーブルは2P ACコードが1.8mで長いから切って流用する感じで。
んで完成。そっこー出来ると思われ。

■ 動作確認

ダイソーにあった100V ACでLEDが光るという物を実験体にして使ってみることに。

マイコンにはParticle Argonを使ってNW経由でHIGH/LOWで点灯させてばっちり。

https://www.hirotakaster.com/wp-content/uploads/2020/06/IMG_3364.mov

HTTP風なCoAP

そういやCoAPについて何も書いてなかったな…と思って書いておくことに。
CoAP(RFC7252)で定義されているUDPベースのプロトコルで、MQTTと同じくIoT/M2M向けの軽量版みたいな感じ。
ググればRFC/Wikipediaとかに詳しい説明があるから、そっちを見て貰った方が早いと思われ。MQTTとの比較はこんな感じ。

■ Simple Coap Library

このシンプルな実装を何年か前に作って公開している。ArduinoやPlatformIOで”CoAP”で検索すると出てくる、Simple Coap Libraryというもので。シンプルという名の通り、ライブラリのサイズ・実装も非常に軽量なものにしている。
一応動作するのは、Ardiuno物、ESP8266、ESP32、Particleといった環境での動作確認をしている。少し改造すれば、まぁ何でも動くと思われ。

Arduino libraryでCoAPで検索すると出てくる

このCoAPだけど、見事なほどまで人気が無いと個人的には思っている。MQTTとかはよく見るし、Google/AWS/AzureのIoT向けサービスもMQTTに対応している。ただ、CoAPに対応している物は数少ない。MQTT(TCP)、CoAP(UDP)とあるにも関わらず。CoAP over TCP/TLS/WebSocket(RFC 8323)という、TCP上に実装した物や、DTLS(TLSのUDP版みたいなもの)上に実装したりセキュリティには配慮していたりするのだけど。
個人的にCoAPの良いと思う点は、

・UDPで軽い
・HTTP Likeでとっつきやすい
・ノード間通信を意識している

このあたりかな。まぁ、こういう選択肢もあるよ、くらいな感じで使う感じだとは思っている。

■ サンプルコード

ESP32向けのサンプルコードはこんな感じ。githubにあるサンプルそのまんま、UDP サーバー・クライアントとして動作する。もちろん、クライアントだけで動作させてもOK。

#include <WiFi.h>
#include <WiFiUdp.h>
#include <coap-simple.h>

// WiFi接続用
const char* ssid     = "your-ssid";
const char* password = "your-password";

// デフォルトコールバック用関数
void callback_response(CoapPacket &packet, IPAddress ip, int port);

// endpoint(/light)に対するコールバック用関数
void callback_light(CoapPacket &packet, IPAddress ip, int port);

// UDPとCoAPクラスの定義
WiFiUDP udp;
Coap coap(udp);

// Lチカ用の状態
bool LEDSTATE;

// endpoint(/light)に対するコールバック用関数
// LEDのON/OFFに対応する
void callback_light(CoapPacket &packet, IPAddress ip, int port) {
  char p[packet.payloadlen + 1];
  memcpy(p, packet.payload, packet.payloadlen);
  p[packet.payloadlen] = NULL;
  
  String message(p);
  if (message.equals("0"))
    LEDSTATE = false;
  else if(message.equals("1"))
    LEDSTATE = true;

  // 状態によってLEDをON/OFFして、応答パケットを返送   
  if (LEDSTATE) {
    digitalWrite(9, HIGH) ; 
    coap.sendResponse(ip, port, packet.messageid, "1");
  } else { 
    digitalWrite(9, LOW) ; 
    coap.sendResponse(ip, port, packet.messageid, "0");
  }
}

// デフォルトのコールバック。CoAPのプロトコルをハンドリングする。
void callback_response(CoapPacket &packet, IPAddress ip, int port) {
  Serial.println("[Coap Response got]");
  
  char p[packet.payloadlen + 1];
  memcpy(p, packet.payload, packet.payloadlen);
  p[packet.payloadlen] = NULL;
  
  Serial.println(p);
}

void setup() {
  Serial.begin(9600);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }

  // Lチカ用の状態
  pinMode(9, OUTPUT);
  digitalWrite(9, HIGH);
  LEDSTATE = true;
  
  // エンドポイント(/light)に対応するコールバック登録
  coap.server(callback_light, "light");

  // デフォルトのコールバックを登録
  coap.response(callback_response);

  // CoAPの起動
  coap.start();
}

void loop() {
  delay(1000);
  coap.loop();
}

URIのコールバック用の関数を登録して動かすだけ。登録以外のURLにパケットが来た場合はデフォルトコールバックが動作する(コールバックを登録していないとパケットを無視する)。例えば…

coap.server(callback_light, "light");
coap.server(callback_light_state, "light/state");
coap.server(callback_light_hogehoge, "light/hogehoge");

こんな感じで複数登録して、そのURIに来たリクエストをハンドリングすることが出来る。クライアントだけで動作させたい場合は、次の関数を用意している。

coap.get(IPAddress(XXX, XXX, XXX, XXX), 5683, "time");
coap.put(IPAddress(XXX, XXX, XXX, XXX), 5683, "light", "1");

PUT/GETに対応した関数で、[IPアドレス、ポート番号、endpoint(time/)、value(PUTする時)] を投げることが出来る。CoAPサーバー(UDPサーバ)としての動作と、クライアント単体としての動作のどちらでも動かせる感じ。
Simple CoAP Libraryという名前の通り、シンプルな動作でProxyとしての動作、DELETEや他のHTTPメソッドについては実装していない。もしその手の動作をさせたい場合は、デフォルトコールバック関数で自分でやれば良いんじゃね?基本的なPUT/GETで十分でしょ、と思っていたりする。

MQTTをこの手のデバイスで使う時は、サーバーが必要になるけど、CoAPはクライアント&サーバーとしての役割があるから、単体で動作させることが出来るというのが大きな違いかな。

TensorFlow Lite 2.1 on STM32F

TensorFlow 2.1.0がリリースされていた。最近はなぜか組込み向けでDeep Learningをする話がやってくる事があって、TensorFlow Liteを使ったりしている。

TF Liteを使ってみたことがある人は分かるだろうけど、TensorFlowのHP・ドキュメントにある通りにやっても基本的にビルドできない、サンプルが動かない、ドキュメントのリンク先は404 not foundという「一体これは何だ?動くのか?」という思いが最初に来ると思う。さらにはLayerの制限で、このモデルを作ったはいいけど、TF Lite上で動かしたらエラーで動かん…とか罠が満載で快感すら覚えてしまう。
自分もバージョンが変わる事に起きるドラスティックな変更で、これはどうすれば動くんだ?というので頭を抱えてしまう。

サクサク動いてめちゃ便利なんだけど、動かすまでが大変な沼にハマる…ということで、自分用としてもTensorflow 2.1.0で動かす手順を残しておく。ターゲットとしては次のとおり。hello_worldのサンプルを動かす。
ちなみに、本家のドキュメントのとおりArduino MKRZERO向けのサンプルのビルドをやろうとしても現時点でビルドできないからねwww ちょっと色々と修正するとArduino IDEでもビルドできるけど、デフォのサンプルだと動かないw (Arduino IDE向けのサンプルコードはこちら、もしくはAdafruitのdiffを当てる)

■ 環境
Tensorflow 2.1.0
Nucleo STM32F446RE(手元にあったもの)
VScode (PlatformIO)

ソースコード一式はこちら。PlatformIO向けに持ってきてビルドすれば動く。学習モデルはGoogle Colabで公開されている物を使うとTensorFlow Lite向けの軽量・量子化されたデータをそのまま持ってきて使える。ソースコードはNucleo向けだけど、ソースコードはそのままでarm向けなら何でもOK。
この一式を使えば、TensorFlowで学習したモデルを持ってきて、arm向けにビルドしてすぐ組込める。もちろんarm Arduinoとかでもね。一旦作っちゃえばオールOKすね。

以下は、自分で頑張ってゼロから環境を作りたい人へのメモ。

■ TensorFlow 2.1.0 Liteのプロジェクトを作る

Tensorflow Liteでは組込で必要なプロジェクトを自動で生成してくれるmakeが用意されている。一旦、Ubuntu 18.04上で作る。

pip install six==1.12.0
wget https://github.com/tensorflow/tensorflow/archive/v2.1.0.tar.gz
tar -zxvf  v2.1.0.tar.gz
cd tensorflow-2.1.0
make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=arduino TAGS="" generate_hello_world_arduino_library_zip
# じっと待っているとtensorflow_lite.zipが出来る。これを持ってきて利用する。
cp tensorflow/lite/experimental/micro/tools/make/gen/arduino_x86_64/prj/hello_world/tensorflow_lite.zip  $HOME

tensorflow_lite.zipを展開するとArduino向けのプロジェクトとして使える一式が出来る。他にも画像・音声認識とかのプロジェクトを生成できたりもする。
このまま使えばOKじゃん!!と思うかも知れないけど、もちろん、このままArduion IDEで取り込んでもビルドできない(TF Liteマジでふざけんなよw)。ちょっと工夫するとArduino IDEでも動かすことが出来る。実際にトラ技で書いた内容では、TensorFlow & ARDUINO MKRZERO arm-cortex M0で自作モデルを動作させて性能も出せている。
ということで、tensorflow_lite.zipをWindows/Macどちらでも持ってきて、PlatformIO向けに展開していく。

■ PIOのプロジェクトを作る

FrameworkはArduinoで適当に作っておく。mbed向けになると、前述のTF Liteのスケルトンプロジェクトをmbedとして作り直し(がんば)。

■ TF LiteをPIOにぶち込む

tensorflow_lite.zipを展開すると次のような感じになる。

これをPlatformIOのフォルダに次のような感じで展開する。

■ 推論の性能どんなもん?

推論を実行しているのはmain.cppの次の部分。
TfLiteStatus invoke_status = interpreter->Invoke();
この単体性能を測定してみると、170回/sec でまぁ使える感じの速度ですな。モデルや設定次第で数千回/secは超える。sin波を推定しているだけだから軽めというのもあるけどね。動かしてみるとこんな感じ。

https://www.hirotakaster.com/wp-content/uploads/2020/01/movie.mp4

■ 自前のセンサー対応モデルを組込むには?

一旦動いてしまえばカンタンな訳だけど。Google Colab上で最後に量子化して生成されたコードをsine_model_data.cppの中の配列・配列長にコピペして終わり。推論の方はinputに値を渡してInvoke、結果はoutputのTfLiteTensorから取得できる。
問題は…DropoutとかFlattenとかー…使えないのよね。対応しているオペレータはこちら。これが地味にハマるというか辛いポイントだったりする。対応してあると書いてあるオペレーターが別の資料はで未実装と書いてあったり、動くと期待してモデルに組み込んだら見事にエラーとか、ビルドもだけどトラップがねw

Ardiuno Library Manager

Arduinoで自分用のlibraryやセンサーを扱うものを使った人はとても多いと思う。というか使ったことが無い人は居ないと思うほど。
既存のlibraryを使うのもだけど、自分で作ったライブラリを公開して広く多くの人に使って、フィードバックを貰うことが出来る。ZIPファイルとして用意して個別にダウンロードしてもらうというのもアリだけど、Arduinoのlibrary群に追加することも出来る。

メニューの[スケッチ]->[ライブラリをインクルード]->[ライブラリを管理]で自分で作ったライブラリを公開することが出来る。その方法を適当に書いておく。

これは自分が公開しているCoAPライブラリ

■ ライブラリを作る

公開するまでの大まかな流れは…

1. github上でライブラリを作る。
2. Arduinoのissueにあげる。

という非常に簡単な流れになる。ライブラリは開発してrelease tagを付けておくと自動で更新されて、Arduino IDE上でも更新された物をインストール出来るようになるという便利な感じ。

まず最初にライブラリを作る。自分のgithubのであれだけど、サンプルはこんな感じで。CoAP-simple-library このlibraryはCoAPというプロトコルの基本的なPUT/GET(その他)をコールバック形式のコード表現で使える。
内容はともかく、何かライブラリを作ったら次のファイルを含めておく。

library.properties
keybords.txt
license.txt

まぁ、ぶっちゃけ仕様はここに書いてあるArduino-IDE 1.5: Library specificationとおりに、自分のライブラリに合わせて記載しておく。あとreleaseも忘れずに適当にバージョンを付けてtagを付けておく。license.txt は必要じゃないけど、MIT/Apache/BSD…etc とかライセンスを記載しておいた方が良いっすね。

■ issueを送る

ライブラリを作ったらArduinoに対してissueを送る。自分の場合はこんな感じ。わりとすぐ返事が来る。指摘された所を修正&updateすると、Arduino libraryに登録されてIDEからも検索して見つけられる。
おまけでArduino IDE以外にも、なぜかPlatformIOでもlibraryが検出できるようになっていた。

運用は全然難しくなくて、コードを修正して新バージョンにしたくなったら、github上でreleaseをするとArduino/PIOともに反映される。リードタイム的には半日以下でIDEでも新しいバージョンを見ることが出来るようになる。

■ おまけ

内容は全部英語で書くこと(当然だけど)。あと、リリースするとissueやPull Requestがあったりする。自分の場合は他のMQTT libraryもだけど、多くは「つかえねぇ」「うごかねぇ」というのが大半で、MITなOSSだからコード見れば&しらねーよ的な感じではあるけど、この辺のやりとりで英語力が鍛えられた気がする。たまに、英語のタイポ・表現ミスのPRもあったりして、何気に嬉しかったりも。あと論文とかでも使われたりとかもね、上のCoAPのやつだとちょっとググったら出てくるこの辺とか。

IoT Latency and Power consumption: Measuring the performance impact of MQTT and CoAP
Comparative Analysis of IoT Communication Protocols
MQTT i CoAP integracija Arduino uređaja sa thethigs.io cloud platformom

まぁ…折角作ったライブラリがあれば、多くの人に使ってもらってフィードバックを貰った方が間違いなく良いから、Arduino IDE library(あとPIO)に登録してみるのも良いと思われ。


Maker Faire Bay Area 2019

そういや行ってきたの書いてなかったな…と思って残しておくことに。
写真類は Maker Faire Bay Area 2019 に残して公開してあるからご自由にどうぞです。

まず…SSSSをゲットした。初めてのSSSS、大量に積み込んだハンドスピナーが影響したのだろうかw
とは言っても登場する前にダブルチェックがある位で、まぁ大したことは無かったし、国内でのフライトでも特に何を言われることも無かった。まぁ…キャリーケースの中を開けても、大量のハンドスピナーと分解されたギターが入っている位で、開けたら別の意味でビックリだっただろう。

持っていった物は大量のハンドスピナーとギター。去年のMaker Faire Tokyo、Kyotoと同じ構成で。反応はどこの国でも一緒っすね。辛かったのが、去年全部配って無くなってしまったから、今回も100個全部揃えることになった。協力してくれた皆さんあざーっした!!

整理して置いても…
乱れまくるからそのまま放置で…
フレットレスギターは初めてとの事だったけど、普通に上手かった

会場はやたら広く感じた。出展者が少なくなったのか何なのか、通路が広い(気がする)。今年で最後というのが、ジワジワと感じるものがあった。そして出展者の多くを困らせたと思うのが、凄まじいほどの雨だった。まさに土砂降りで、雰囲気的にもつらみ。屋外の人たちは残念としか言いようがない。屋外の人たちが室内に来た分、屋内展示の方は凄まじいほどの人だったと思う。

毎年沢山の人で溢れているはずが、大雨でガラガラ。
Maker向けの朝食提供 by Amazon

超巨大な物はお約束的に沢山あった。光る・デカイ・火を吹く、というのは定番みたいな感じかな。

巨大なクマの中は幻想的な雰囲気

ひたすらデカくて動いて光る
実際に潜航もしたサブマリン

紹介しきれないほど、沢山の作品があった。写真類は Maker Faire Bay Area 2019 に残して公開してあるからご自由にどうぞです。なんじゃこりゃ?みたいなのが沢山あった面白い。そして、今年もパエリアパーティでビールを大量に飲んできた。

ギャル電さんを想起した
芝生でビールを飲みながらまったりBig Face!!
なんかディジャブ感があるなー…と思ったら
去年のExploratoriumの展示でBig Faceをしていた写真が残っていた(w

晴れた時に練り歩いていた。ドコドコ良い感じね。

SONY SPRESENSEさんのブース、大手が出展しなくなった中で継続していて偉いと思った
そしてGoogleもいつもの所で。

今年でBay Areaは終わりなのかどうか、2020年もあるのかしら…雰囲気は今年で終わりか…って感じはモリモリだった。
難しいと思うのが定性的な「感動や面白い!!」ことの数値化・定量化〜お金に還元することなのかなと。個人的には文化と社会の成熟は未発達な所から、商業主義に向かって(作る・売る・仕事をするだけ儲かる)、次は精神的な部分(感動や体験、一見するとお金にならない物に価値が付く)で回って発展していくと思っている。それだけに、本当に終わってしまうなら少し残念でもある。

偶然に撮った一枚。雨上がりの芝生で、紙飛行機(世界チャンプのだと思われ)を飛ばしていたチビッコがなんか印象的になった。将来なんか面白いのでも作ってくれたらなーって思ったりもするし、出展者の一人として楽しんで貰えたらなによりかなと。
“雨降って地固まる”って言葉もあるからね。