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

100

100 solenoids were aggregated and made into one devices.

https://www.hirotakaster.com/wp-content/uploads/2023/06/IMG_6038_Trim-3.mp4

spec
Solenoid x 100(https://www.sparkfun.com/products/11015)
M5Stamp C3(I2C controller), self-designed PCB(16 solenoids assigned on 1 board)

I2C test
connect to 100 solenoids.

board designed.
IO Expander : MCP23017
MOSFET : 2SK4017
register: RK73B1JTTD103J 1608(0603)
diode : GS1010FL
connector : S2B-PH-K-S

Solenoid mounter 3D model is three parts.
Top is just an solenoids mounter, 2nd parts holds up to 100 solenoids and guides the cables, 3rd is guide to the output cables.

Ogaki Mini Maker Faire 2022

来月、2022/12/03, 04の土日に開催されるOgaki Mini Maker Faireに出展します。

自分は”ひかりもの“というもので、次の物たちを持っていきます。

Maker Faire Tokyo
Maker Faire Tokyo
なぜ色が変わって同期するか?という仕組み動画のネタバレ感

玉は色々と物を取り寄せてトータルで50個ほど作った。限定10個くらいなら、欲しい!!という人にはあげても良いかなーと思っていたりする。
そして、ESP入り(だった…あまりの激しさに破壊された)フラフープ

Maker Faire Bayarea
上モノ フェス
Exploratorium

そして、かなり改造しまくった LED/LCD ドリルギター

久々の大垣、そしてかなり久々の暗いエリアでヒカリモノ、ちょっぴり楽しみだったりする。

QUIC handshake with mbedTLS

プロトコルを知るには書いて動かしてみれ!!という事で、mbedTLSを使ってQUICのhandshakeまで行ってみることにした。時間があるときに、ちょくちょく調べたりしてmbedTLSを使ってNginX(quic impl)と通信を行うまで確認した。
mbedTLSでQUICの公式サポートは今のところは無い。まぁ、やってみたった的な感じ。みんな色んな言語で実装されている物があったりで、何か楽しそうだなーと思って。

qlog用にrandom/handshake_traffic_secret/application_traffic_secret/exporter_master_secretを吐き出してWiresharkで復号化して見れる。

■ 参考

RFC 9000 QUIC: A UDP-Based Multiplexed and Secure Transport
RFC 9001 Using TLS to Secure QUIC

■ コード

quic 側 : mbedtls_quic
mbedtls側 : mbedtls (mbedtls-3.2.1-quic branch)
mbedTLSは現時点での最新 3.2.1をベースにした。ただし、TLS 1.3への対応はとりあえずは動く程度な感じ。TLS extensionで対応していないサーバと通信すると見事にERRORを起こしたりするし、これから感はしている。
組込みで使われる物だけど、今なら素直にwolfsslを使う方が良い気はする。すでにQUICに対応しているし、みんな大好きESP32にも組込むことが出来る(esp-idfからビルドオプションで組込める)。

■ mbedtls側の改造

mbedtls は mbedtls_config.h をいじってTLSの機能を選択利用する実装になっている。超少ないリソースで動作するから、選択的に機能を選んで組込んでいく感じ。TLS 1.3以外の機能は不要なのでオフって使うことにする。diff はこの程度。改造したポイントは次あたり…

・MBEDTLS_QUIC_TRANSPORT 機能を mbedtls_config.h に追加
 これでQUICの処理が必要な部分についての変更を全体に加える。
・Transport Parameterの実装追加
 RFC 9000 7.4. Transport Parameters
 RFC 9001 8.2. QUIC Transport Parameters Extension
・QUICパケットの暗号化処理部分をフロント側で処理させる

mbedTLSはTLS 1.3のステートマシン・鍵生成器としてだけ使って、最小限の修正に留めている。あとTLS 1.3はTCP Stream想定なのか、複数に分割されたUDPパケット(Handshake時の証明書類が送られてくるところとか)がある場合、いわゆる EAGAIN のようにフロント側にパケット要求するような処理に変更していたりする。

■ QUIC側の処理

とりあえずハンドシェイクまでで、メインのコードはこんな感じ。

#include <stdio.h>
#include "quic_common.h"
#include "quic_mbedtls.h"

int main() {
    int ret = 0;

    // init
    quic_mbedtls_ctx ctx;
    quic_mbedtls_init(&ctx, "www.hirotakaster.com", 8443);
    quic_mabedtls_handshake(&ctx);

    // ouput keys
    dump_handshake_random_byte(&ctx);
    dump_handshake_traffic_secret(&ctx);
    dump_application_traffic_secret(&ctx);
    dump_exporter_master_secret(&ctx);
    dump_resumption_master_secret(&ctx);

    //
    sleep(2);

    return 0;
}

ハンドシェイクを行ってkeylogを出力するまで。ハンドシェイクまでだからコードは2個だけ、
quic_client.c : QUIC側のヘッダ処理類
quic_mbedtls.c : 暗号化類・ネットワークIO類
quic_mbedtls_init の中ではUDPのオープン・初期化処理を行って、quic_mabedtls_handshake でQUIC/TLS 1.3のハンドシェイクを行う。流れ的には次にwrite/readの処理を入れていく感じの想定で。mbedTLSではmbedtls_ssl_handshakeでBIO(mbedtls_ssl_set_bio)でネットワークとのread/writeをハンドリングすることが出来る。
鍵類はIO用の構造体の中に(デバッグ用としても)ざっくり入れている。

typedef struct {
    mbedtls_x509_crt cacert;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context drbg;
    mbedtls_ssl_context ssl;
    mbedtls_ssl_config conf;
    mbedtls_timing_delay_context timer;

    u_char initial_secret[32];
    u_char client_secret[32];
    u_char client_key[16];
    u_char client_iv[12];
    u_char client_hp[16];
    u_char server_secret[32];
    u_char server_key[16];
    u_char server_iv[12];
    u_char server_hp[16];

    BOOLEAN secret_initialized;
    u_char client_random_byte[32];

    u_char client_derive_secret[32];
    u_char client_derive_key[16];
    u_char client_derive_iv[12];
    u_char client_derive_hp[16];

    u_char server_derive_secret[32];
    u_char server_derive_key[16];
    u_char server_derive_iv[12];
    u_char server_derive_hp[16];

    QUIC_MBEDTLS_SOCK sock;
} quic_mbedtls_ctx;
QUIC KeySchedule : https://gist.github.com/martinthomson/c254bbc4214e8b3d4f38372b9afce18d

他のコードを参考にしないで、wiresharkとお友達になって勢いで書いたのもあって、最初にナニコレ?と思ったのが、可変長エンコーディングだった(RFC 9000 16.  Variable-Length Integer Encoding)。先頭の2ビットを見てから値が決まる感じで、パケットのいたる所でこれが利用されている。例えば、Initial Packet だと、

  RFC 9000 17.2.2. Initial Packet
  Initial Packet {
     Header Form (1) = 1,
     Fixed Bit (1) = 1,
     Long Packet Type (2) = 0,
     Reserved Bits (2),
     Packet Number Length (2),
     Version (32),
     Destination Connection ID Length (8),
     Destination Connection ID (0..160),
     Source Connection ID Length (8),
     Source Connection ID (0..160),
     Token Length (i),
     Token (..),
     Length (i),
     Packet Number (8..32),
     Packet Payload (8..),
   }

   RFC 9000 19.6. CRYPTO Frames
   CRYPTO Frame {
     Type (i) = 0x06,
     Offset (i),
     Length (i),
     Crypto Data (..),
   }

この (i) という所が可変長。Destination Connection IDとかに出ているのは、前のLengthから長さが決定されたりする。みたいな感じで、可変長がバンバン出てくる。まぁ、関数( quic_mbedtls_conv_vlie : 可変長読み込み、quic_mbedtls_set_vlie : 可変長書き込み)を用意して、あとはOKだった。

initial packetを送受信して、あれ?と思ったのが ACK だった(RFC 9000 19.3. ACK Frames)。この中にある、ack_delay で時間を測定してるんすね。そして、デバックしながらやっていると、NginX側からPINGフレームが飛んできて、生存確認も行っているのをやってみてから分かった。よく出来ているプロトコルっすねー。
あぁ…そこまでやってらんないぉ…ってことで、固定値を入れているけど、本当はちゃんとマイクロ秒でカウントしないといけないんだけど…そしてPINGを思いっきり無視&スルーしちゃっている><
以下な感じでパケット保護・復号化後のフレームを見ている。

int quic_mbedtls_decode_frame(const u_char *data, uint32_t length, QUIC_FRAME *frame) {
    int i, j = 0;

    for (i = 0; i < length;) {
        u_char d = data[i];
        if (data[i] == QUIC_FRAME_PADDING) {
            i += 1;
        } else if (data[i] == QUIC_FRAME_CRYPTO) {
            frame[j].frame = &data[i];
            i += 1;

            uint8_t o, l;
            uint64_t ov, lv;
            // get crypt frame offset
            quic_mbedtls_conv_vlie(data + i, &o, &ov);
            i += o;

            // get crypt frame length
            quic_mbedtls_conv_vlie(data + i, &l, &lv);
            i += l;
            i += lv;

            frame[j].type = QUIC_FRAME_CRYPTO;
            frame[j].length = 1 + o + l + lv;
            j += 1;
        } else if (data[i] == QUIC_FRAME_PING) {
            // とりあえず無視
            i += 1;

        } else if (data[i] == QUIC_FRAME_ACK_2 || data[i] == QUIC_FRAME_ACK_3) {
            frame[j].frame = &data[i];
            i += 1;

            uint8_t la, ad, arc, far, ar;
            uint64_t lav, adv, arcv, farv, arv;

            // get largetst ack
            quic_mbedtls_conv_vlie(data + i, &la, &lav);
            i += la;

            // get ack delay
            quic_mbedtls_conv_vlie(data + i, &ad, &adv);
            i += ad;

            // get ack range count
            quic_mbedtls_conv_vlie(data + i, &arc, &arcv);
            i += arc;

            // get first ack range
            quic_mbedtls_conv_vlie(data + i, &far, &farv);
            i += far;

            frame[j].type = QUIC_FRAME_ACK_2;
            frame[j].length = 1 + la + ad + arc + far;
            j += 1;
        }
    }
    return j;
}

ペイロードの暗号化・復号化は1個の関数で処理していて、ヘッダの保護を設定・解除したあとに、この関数で処理するようにしている。
ヘッダの保護については、鍵スケジュールで得られるkey/iv/hp類を利用。

int quic_mbedtls_crypt_frame(u_int8_t enc_dec,
            unsigned char *key, size_t keylen,
            unsigned char *iv, size_t ivsize,
            unsigned char *ad, size_t adsize,
            unsigned char *from, size_t fromsize,
            unsigned char *to, size_t tosize, size_t *olen) {
    int ret;
    mbedtls_gcm_context ctx;
    mbedtls_cipher_id_t cipher = MBEDTLS_CIPHER_ID_AES;
    int output_len;
    unsigned char tag_output[16];
    size_t tag_len = sizeof(tag_output);
    size_t tmpolen;

    mbedtls_gcm_init(&ctx);
    ret = mbedtls_gcm_setkey(&ctx, cipher, key,  keylen);

    ret = mbedtls_gcm_starts(&ctx, enc_dec, iv, ivsize); 
    ret = mbedtls_gcm_update_ad(&ctx, ad, adsize);
    ret = mbedtls_gcm_update(&ctx, from, fromsize, to, tosize, olen);
    ret = mbedtls_gcm_finish(&ctx, NULL, 0, &tmpolen, tag_output, sizeof(tag_output) );

    if (enc_dec == MBEDTLS_GCM_ENCRYPT) {
        memcpy(to + *olen, tag_output, sizeof(tag_output));
        *olen = *olen + 16;
    }

    mbedtls_gcm_free(&ctx);
    return ret;
}

// 暗号化
quic_mbedtls_crypt_frame(MBEDTLS_GCM_ENCRYPT,
                ctx->client_key, sizeof(ctx->client_key)*8,
                nonce, sizeof(nonce),
                send_buffer, plane_header_total_len,
                payload, payload_len,
                enc_payload, sizeof(enc_payload), &olen);

// 復号化
quic_mbedtls_crypt_frame(MBEDTLS_GCM_DECRYPT,
    (QUIC_FRAME_LONG_PACKET_TYPE_MASK(quic_mbedtls_recv_buffer[0]) == QUIC_PACKET_INITIAL ? ctx->server_key : ctx->server_derive_key),
    (QUIC_FRAME_LONG_PACKET_TYPE_MASK(quic_mbedtls_recv_buffer[0]) == QUIC_PACKET_INITIAL ? sizeof(ctx->server_key)*8 : sizeof(ctx->server_derive_key)*8),
            dec_nonce, sizeof(dec_nonce),
            quic_mbedtls_recv_buffer, packet.header_len,
            quic_mbedtls_recv_buffer + packet.header_len,
            packet.length - packet.packet_number_length,
            dec_payload, sizeof(dec_payload), &dec_olen);

■やっみて…

あー、QUIC面白いっすねーという感想だった。とりあえずQUIC/TLS 1.3ハンドシェイクする所までやってみるか…という勢いでやってみたのだけど、コードはぐしゃっと書いたからカヲっているw


組込み向けということで、サイズは731216byte(714kbyte弱)になってしまった…TLS 1.2をガッサリ消してもこのサイズというのは、地味に心が折れてしまう…orz
何とか200Kbyte以下くらいまでには抑えたいのだけど。

あと、mbedTLSの実装も一通り見ながら出来たのは良かったかも。なーんとなく、mbedTLSのオフィシャルにQUIC入るのかなぁ…というのは、ちょっぴり疑問な気はした。直撃でmbedTLSのコードセット類を大改造して、TLS1.3/UDP(QUIC)を両立させるには、かなりの内部修正が必要というか、API・関数類が思いっきり新規になるじゃないかなーと。その場合、何個かワイ的な感じでコードを追加して、mbedTLS用QUICみたいな実装がポコポコ出てきちゃったりするのかしら…みたいな気がしたり。
次はショートパケットでHTTP3でNginXをおしゃべりする所くらいまではやってみよかなと。

RPi4 ROS YDLIDAR X4でSLAM

Raspberry Piとlidar(YDLIDAR X4)を使ってSLAMの環境を作ってみた。手持ちでRPi・Lidarを持って歩いてSLAM・地図の生成からシュミレーションを行うまで。

https://www.hirotakaster.com/wp-content/uploads/2022/08/300873130_2075060639332932_2109182721347563783_n.mp4

■ 環境

  • Raspberry Pi 4 Model B 4Gbyte memory
  • Ubuntu 20.04.5 LST/ROS Noetic
  • YDLIDAR X4
  • モバイルバッテリー(5V/3A出力)
  • WiFi無線機子機

    Raspberry Piについては昨今、かなり入手が困難な状況が続いている。メルカリで出品されている物も、2倍以上の価格だったり、入手が難しい。一体どこで入手できるのかしら….????
    使いたいのに無いなー…という事をTLでつぶやいたら、HDMIが死んでいるRPi4を送って貰えることに。手持ちでRPi1〜4はあるものの、使っている物ばかりで、使えるもの1台も無いところに、世の中には神が居るものですね。

    モバイルバッテリーは5V/3A出力できる物を使うことに。一応、WiFi・LIDAR・GPIO(TB6612FNG Dual Motor Driver: モーターはlipo外付けで)を同時に動かしてみても問題なかった。
    WiFi無線子機は、RPi自体のWiFiは弱いので補強として使えればよき!!ということで、手持ちのがあったので使っている。

■ Ubuntuの設定

VNC経由でRPiを操作できるようにする。リモートでrvizの画面を確認したり、リモートが出来るとHDMI出力がなくても操作できるようになるので、何かと便利になる。
こちらの記事を参考にさせて頂きました。

・LTS Serverに “sudo apt-get install ubuntu-desktop” をインストール
・Setting => Sharing => Screen Sharing から”Allow connections to control the screen”にチェックを入れる
・Reuired a password を選択して、VNCから接続するときのパスワードを適宜設定しておく
・接続するNetoworkを設定しておく

また、WindowsのVNC Viewerからアクセスするとき暗号化がされていると接続できないので、以下のUbuntuの画面共有の暗号化をオフにしておく。

sudo gsettings set org.gnome.Vino require-encryption false

つぎに、RPiはHDMIからディスプレイの解像度を取得しているとのことで、HDMI・ディスプレイ無しでVNC接続するために、ダミーのグラフィックドライバをインストールして設定しておく。

sudo apt install xserver-xorg-video-dummy
sudo tee /usr/share/X11/xorg.conf.d/80-dummy.conf <<EOF
Section "Device"
    Identifier  "Configured Video Device"
    Driver      "dummy"
    VideoRam 256000
EndSection

Section "Monitor"
    Identifier  "Configured Monitor"
    HorizSync 5.0 - 1000.0
    VertRefresh 5.0 - 200.0
    # 1920x1080 59.96 Hz (CVT 2.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz
    Modeline "1920x1080_60.00"  173.00  1920 2048 2248 2576  1080 1083 1088 1120 -hsync +vsync
EndSection

Section "Screen"
    Identifier  "Default Screen"
    Monitor     "Configured Monitor"
    Device      "Configured Video Device"
    DefaultDepth 24
    SubSection "Display"
        Depth 24
        Modes "1920x1080"
    EndSubSection
EndSection

Section "InputClass"
    Identifier "system-keyboard"
    MatchIsKeyboard "on"
    Option "XkbLayout" "jp,us"
    Option "XkbModel" "jp106"
    Option "XkbVariant" ",dvorak"
    Option "XkbOptions" "grp:alt_shift_toggle"
EndSection
EOF

これでVNCでリモート接続環境ができあがり。一旦、RPiを再起動してHDMI接続無しでVNC接続できることを確認しておく。

■ ROS Noeticのインストール

次にROSの環境構築を行う。こちらの記事を参考にさせて頂きました。サクッとインストールで自分の場合は放置して気づいたら終わっていた。

# リポジトリの登録
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt install curl  -y
curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -

# ROS のインストール、ナビゲーション関係のパッケージも同時に
sudo apt update
sudo apt install ros-noetic-desktop-full -y
sudo apt-get -y install ros-noetic-navigation ros-noetic-move-base

# パッケージ類の依存関係をいい感じにしてくれるrosdepの初期化
sudo apt-get install python3-rosdep -y
sudo rosdep init
rosdep update

# 環境変数・パスの追加
echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc
source ~/.bashrc
source /opt/ros/noetic/setup.bash

# ビルドで必要になるcatkinのインストール
sudo apt-get -y install python3-rosinstall
sudo apt-get install -y python3-catkin-tools

■ YDLIDAR SDK/ドライバのインストール

YDLIDARの環境整備で、SDK・ドライバ類をインストールしていく。
YDLidar-SDK、ydlidar_ros_driver の順でインストールして動作確認していく。書いてある通りにインストールすれば良い感じ。

# YDLIDAR SDKからインストール
# 作業ディレクトリは"$HOME/work"以下を想定
sudo apt-get install swig -y
sudo apt-get install python3-pip -y

mkdir $HOME/work
cd $HOME/work
git clone https://github.com/YDLIDAR/YDLidar-SDK.git
mkdir YDLidar-SDK/build
cd YDLidar-SDK/build
cmake ..
make
sudo make install

# YDLIDAR ROS Driverのビルド
mkdir -p $HOME/work/ydlidar_ws/src/
cd $HOME/work/ydlidar_ws/src/
git clone https://github.com/YDLIDAR/ydlidar_ros_driver.git
cd $HOME/work/ydlidar_ws
catkin_make

# 環境変数に追加
echo "source $HOME/work/ydlidar_ws/devel/setup.bash" >> ~/.bashrc
source ~/.bashrc

# udevに追加(/dev/ydlidar を使えるようにする)
chmod 0777 src/ydlidar_ros_driver/startup/*
sudo sh src/ydlidar_ros_driver/startup/initenv.sh

ここまでやったら、あとは動作確認ですね。X4 lidarをRPi4に接続して、VNCで接続してそれぞれ立ち上げていきます。

# 左上のターミナル
roscore
# 左下のターミナル
roslaunch ydlidar_ros_driver X4.launch
# 右上のターミナル
rviz

rvizの左下の”Add”から”By topic”タブの、/scan LaserScan を選択。

あとは、左のパネルから
“Global Options” => “Fixed Frame” を”base _footprint” or “laser_frame” を選択
“LaserScan” => “Size (m)” を 0.06 とかに設定
すると、lidarでスキャンした点が表示されますね。これでlidar側の準備はとりあえず完了で、次はSLAMの環境を用意していく。

■ cartographerのビルド

cartographerはGoogleが公開しているSLAMパッケージですね。これをRPi4上でビルドしていきます。

sudo apt-get update
sudo apt install ninja-build stow -y
cd 
mkdir catkin_ws
cd catkin_ws
wstool init src
wstool merge -t src https://raw.githubusercontent.com/cartographer-project/cartographer_ros/master/cartographer_ros.rosinstall
wstool update -t src

sudo rosdep init
rosdep update
rosdep install --from-paths src --ignore-src --rosdistro=${ROS_DISTRO} -y
# ここで100%コケます。libabsl-dev の依存関係が解決できないというエラーで以下が表示されると思います。
# ERROR: the following packages/stacks could not have their rosdep keys resolved to system dependencies:
# cartographer: [libabsl-dev] defined as "not available" for OS version 
# そこで以下のコマンドを実行してlibabsl-devの行を削除して、再度、rosdistro install を実行
sed -i -e '/libabsl\-dev/d' src/cartographer/package.xml 

# cartographer用のコンパイラ、abseil-cpp をインストール
# 5分程度で終わりました。
src/cartographer/scripts/install_abseil.sh

####
# pythonのバージョンを変更
# ビルド時にpythonのデフォルトが2.7だと失敗します。そこで、update-alternatives コマンドでデフォルトのバージョンを3.8にします。
# 既にデフォルトが3.8の場合は飛ばしてOK、3.8、2.7を登録する
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.8 1

# update-alternatives で python 3.8 を選択する
sudo update-alternatives --config python
There are 2 choices for the alternative python (providing /usr/bin/python).

  Selection    Path                Priority   Status
------------------------------------------------------------
  0            /usr/bin/python2.7   1         auto mode
* 1           /usr/bin/python3.8   1         manual mode

Press <enter> to keep the current choice[*], or type selection number: 1
####

# cartographer をninjaでビルド
# makeでビルドすると、RPi4上ではコンパイルでスワップを起こしてビルドが止まったり失敗します。
# 上手い手順もあると思いますが、手順通りninjaでビルドします。
# 巨大なビルドなので他のプロセス&アプリは落としておいた方が良いです。1時間くらいはかかるので、放置しておきましょう。
# また、途中でビルドが止まって動かなくなる事があります。その場合はランレベルをsudo systemctl set-default multi-userに変えて、余計なプロセスを落としてビルドします。
catkin_make_isolated --install --use-ninja
source install_isolated/setup.bash
echo "source $HOME/catkin_ws/install_isolated/setup.bash" >> ~/.bashrc

次にros用の起動・設定ファイルを作成します。こちらの記事を参考にさせて頂きました。

/home/ubuntu/catkin_ws/install_isolated/share/cartographer_ros/launch/ydlidar_2d.launch を編集

<?xml version="1.0" ?>
<launch>
  <node name="ydlidar_lidar_publisher"  pkg="ydlidar_ros_driver"  type="ydlidar_ros_driver_node" output="screen" respawn="false" >
    <!-- string property -->
    <param name="port"         type="string" value="/dev/ydlidar"/>
    <param name="frame_id"     type="string" value="laser_frame"/>
    <param name="ignore_array"     type="string" value=""/>

    <!-- int property -->
    <param name="baudrate"         type="int" value="128000"/>
    <!-- 0:TYPE_TOF, 1:TYPE_TRIANGLE, 2:TYPE_TOF_NET -->
    <param name="lidar_type"       type="int" value="1"/>
    <!-- 0:YDLIDAR_TYPE_SERIAL, 1:YDLIDAR_TYPE_TCP -->
    <param name="device_type"         type="int" value="0"/>
    <param name="sample_rate"         type="int" value="5"/>
    <param name="abnormal_check_count"         type="int" value="4"/>

    <!-- bool property -->
    <param name="resolution_fixed"    type="bool"   value="true"/>
    <param name="auto_reconnect"    type="bool"   value="true"/>
    <param name="reversion"    type="bool"   value="false"/>
    <param name="inverted"    type="bool"   value="true"/>
    <param name="isSingleChannel"    type="bool"   value="false"/>
    <param name="intensity"    type="bool"   value="false"/>
    <param name="support_motor_dtr"    type="bool"   value="true"/>
    <param name="invalid_range_is_inf"    type="bool"   value="false"/>
    <param name="point_cloud_preservative"    type="bool"   value="false"/>

    <!-- float property -->
    <param name="angle_min"    type="double" value="-180" />
    <param name="angle_max"    type="double" value="180" />
    <param name="range_min"    type="double" value="0.1" />
    <param name="range_max"    type="double" value="12.0" />
    <!-- frequency is invalid, External PWM control speed -->
    <param name="frequency"    type="double" value="10.0"/>
  </node>

  <node
     pkg="tf"
     type="static_transform_publisher"
     name="base_link_connect"
     args="0 0 0 0 0 0 /base_link /laser_frame 100"
  />

  <node
    name="cartographer_occupancy_grid_node"
    pkg="cartographer_ros"
    type="cartographer_occupancy_grid_node"
    args="-resolution 0.05"
  />
  <node
    name="cartographer_node"
    pkg="cartographer_ros"
    type="cartographer_node"
    args="-configuration_directory $(find cartographer_ros)/configuration_files -configuration_basename ydlidar_2d.lua"
    output="screen">
  </node>

  <node
    name="rviz"
    pkg="rviz"
    type="rviz"
    required="true"
    args="-d $(find ydlidar_ros_driver)/launch/lidar.rviz"
  />
</launch>

/home/ubuntu/catkin_ws/install_isolated/share/cartographer_ros/configuration_files/ydlidar_2d.lua を編集

include "map_builder.lua"
include "trajectory_builder.lua"

options = {
  map_builder = MAP_BUILDER,
  trajectory_builder = TRAJECTORY_BUILDER,
  map_frame = "map",
  tracking_frame = "base_link",
  published_frame = "base_link",
  odom_frame = "odom",
  provide_odom_frame = false,
  publish_frame_projected_to_2d = false,
  use_odometry = false,
  use_nav_sat = false,
  use_landmarks = false,
  num_laser_scans = 1,
  num_multi_echo_laser_scans = 0,
  num_subdivisions_per_laser_scan = 1,
  num_point_clouds = 0,
  lookup_transform_timeout_sec = 0.2,
  submap_publish_period_sec = 0.3,
  pose_publish_period_sec = 5e-3,
  trajectory_publish_period_sec = 30e-3,
  rangefinder_sampling_ratio = 1.,
  odometry_sampling_ratio = 1.,
  fixed_frame_pose_sampling_ratio = 1.,
  imu_sampling_ratio = 1.,
  landmarks_sampling_ratio = 1.,
}

MAP_BUILDER.use_trajectory_builder_2d = true

TRAJECTORY_BUILDER_2D.min_range = 0.
TRAJECTORY_BUILDER_2D.max_range = 10.
TRAJECTORY_BUILDER_2D.missing_data_ray_length = 5.
TRAJECTORY_BUILDER_2D.use_imu_data = false
TRAJECTORY_BUILDER_2D.use_online_correlative_scan_matching = true

POSE_GRAPH.constraint_builder.min_score = 0.65
POSE_GRAPH.constraint_builder.global_localization_min_score = 0.7

POSE_GRAPH.optimization_problem.local_slam_pose_translation_weight = 1e5
POSE_GRAPH.optimization_problem.local_slam_pose_rotation_weight = 1e5
POSE_GRAPH.optimization_problem.odometry_translation_weight = 1e5
POSE_GRAPH.optimization_problem.odometry_rotation_weight = 1e5
POSE_GRAPH.optimization_problem.huber_scale = 1e3

TRAJECTORY_BUILDER_2D.ceres_scan_matcher.occupied_space_weight = 10
TRAJECTORY_BUILDER_2D.ceres_scan_matcher.rotation_weight = 40

TRAJECTORY_BUILDER_2D.submaps.num_range_data = 120
TRAJECTORY_BUILDER_2D.motion_filter.max_distance_meters = 0.1
TRAJECTORY_BUILDER_2D.motion_filter.max_angle_radians = math.rad(0.2)

return options

■ SLAM・地図の作成

準備が終わったので実行します。

# 左上のターミナル
roscore
# 左下のターミナル
roslaunch cartographer_ros ydlidar_2d.launch

rvizのコンパネから”Add”=>”By topic”=>Mapを選択する。

■ 地図の保存

出来た地図の保存はmap serverで行う。保存した地図は、同じくmap serverを使って配信する。

■ 3Dモデルでシュミレーション

シュミレーションはgazeboを使う。ここで、gazebo用にSTLのモデルを生成するわけで、便利なmap2gazeboがあったので、このまま使ってみる。mapについてはmap serverでpublishしたままに。
こちらを参考にさせていただきました。

■ Turtlebot3でnavigation

地図とシュミレーション環境が出来たので、Turtlebo3でナビゲーションしてみます。

https://www.hirotakaster.com/wp-content/uploads/2022/09/AGDRec_Trims.mp4

RPi4のスペックだと、かなりもっさりとした動きになるので、PC環境で行います。またROSの環境を作るのは面倒なので、Docker/VNCを利用します。ブラウザで利用できるので、めちゃくちゃ便利ですね。

docker run -p 6080:80 --shm-size=2048m tiryoh/ros-desktop-vnc:noetic

rviz/gazeboで動作させると

https://www.hirotakaster.com/wp-content/uploads/2022/09/novnc2-1.mp4

実際にロボットをlidarで測定した環境で動作させることが出来ました。ROSの先達さん達に感謝です。

elastic 8.x on google colab

here step is how to use elastic 8.3.1 on google colab.

download elastic, cgroup-tools

In [1]:
%%bash

rm -rf elasticsearch*
wget -q https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.3.1-linux-x86_64.tar.gz
tar -xzf elasticsearch-8.3.1-linux-x86_64.tar.gz
sudo chown -R daemon:daemon elasticsearch-8.3.1/
umount /sys/fs/cgroup
apt install cgroup-tools
Reading package lists...
Building dependency tree...
Reading state information...
The following package was automatically installed and is no longer required:
  libnvidia-common-460
Use 'apt autoremove' to remove it.
The following additional packages will be installed:
  libcgroup1
The following NEW packages will be installed:
  cgroup-tools libcgroup1
0 upgraded, 2 newly installed, 0 to remove and 62 not upgraded.
Need to get 108 kB of archives.
After this operation, 393 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu bionic/universe amd64 libcgroup1 amd64 0.41-8ubuntu2 [42.0 kB]
Get:2 http://archive.ubuntu.com/ubuntu bionic/universe amd64 cgroup-tools amd64 0.41-8ubuntu2 [66.2 kB]
Fetched 108 kB in 0s (1,138 kB/s)
Selecting previously unselected package libcgroup1:amd64.
(Reading database ... 155639 files and directories currently installed.)
Preparing to unpack .../libcgroup1_0.41-8ubuntu2_amd64.deb ...
Unpacking libcgroup1:amd64 (0.41-8ubuntu2) ...
Selecting previously unselected package cgroup-tools.
Preparing to unpack .../cgroup-tools_0.41-8ubuntu2_amd64.deb ...
Unpacking cgroup-tools (0.41-8ubuntu2) ...
Setting up libcgroup1:amd64 (0.41-8ubuntu2) ...
Setting up cgroup-tools (0.41-8ubuntu2) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Processing triggers for libc-bin (2.27-3ubuntu1.3) ...
/sbin/ldconfig.real: /usr/local/lib/python3.7/dist-packages/ideep4py/lib/libmkldnn.so.0 is not a symbolic link

WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

start elastic on backgroud

In [2]:
%%bash --bg

sudo -H -u daemon elasticsearch-8.3.1/bin/elasticsearch
Starting job # 0 in a separate thread.

create access password

In [3]:
!/content/elasticsearch-8.3.1/bin/elasticsearch-setup-passwords auto -url "https://localhost:9200"
******************************************************************************
Note: The 'elasticsearch-setup-passwords' tool has been deprecated. This       command will be removed in a future release.
******************************************************************************

Initiating the setup of passwords for reserved users elastic,apm_system,kibana,kibana_system,logstash_system,beats_system,remote_monitoring_user.
The passwords will be randomly generated and printed to the console.
Please confirm that you would like to continue [y/N]y


Changed password for user apm_system
PASSWORD apm_system = 5wJfMLTnkHwSY7lbpr6y

Changed password for user kibana_system
PASSWORD kibana_system = rE40UfIbndtwONL6q33D

Changed password for user kibana
PASSWORD kibana = rE40UfIbndtwONL6q33D

Changed password for user logstash_system
PASSWORD logstash_system = DOJfaIC9toV7tgJB33ON

Changed password for user beats_system
PASSWORD beats_system = iVpsYjuzPaL6wIOZQEKx

Changed password for user remote_monitoring_user
PASSWORD remote_monitoring_user = wsxURS5iLLi3hrSf8qw9

Changed password for user elastic
PASSWORD elastic = Uzv4aE6oFfpMrDVy1nql

check a elastic process, live page. “Enter host password for user ‘elastic’:” use above elastic user password.

In [5]:
!ps -ef | grep elastic
!curl --cacert /content/elasticsearch-8.3.1/config/certs/http_ca.crt -u elastic -H 'Content-Type: application/json' -XGET https://localhost:9200/?pretty=true
root         399     397  0 08:19 ?        00:00:00 sudo -H -u daemon elasticsearch-8.3.1/bin/elasticsearch
daemon       400     399  8 08:19 ?        00:00:14 /content/elasticsearch-8.3.1/jdk/bin/java -Xms4m -Xmx64m -XX:+UseSerialGC -Dcli.name=server -Dcli.script=elasticsearch-8.3.1/bin/elasticsearch -Dcli.libs=lib/tools/server-cli -Des.path.home=/content/elasticsearch-8.3.1 -Des.path.conf=/content/elasticsearch-8.3.1/config -Des.distribution.type=tar -cp /content/elasticsearch-8.3.1/lib/*:/content/elasticsearch-8.3.1/lib/cli-launcher/* org.elasticsearch.launcher.CliToolLauncher
daemon       467     400 33 08:19 ?        00:00:52 /content/elasticsearch-8.3.1/jdk/bin/java -Des.networkaddress.cache.ttl=60 -Des.networkaddress.cache.negative.ttl=10 -Djava.security.manager=allow -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j2.formatMsgNoLookups=true -Djava.locale.providers=SPI,COMPAT --add-opens=java.base/java.io=ALL-UNNAMED -XX:+UseG1GC -Djava.io.tmpdir=/tmp/elasticsearch-8870137204813447444 -XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError -XX:HeapDumpPath=data -XX:ErrorFile=logs/hs_err_pid%p.log -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m -Xms6493m -Xmx6493m -XX:MaxDirectMemorySize=3405774848 -XX:G1HeapRegionSize=4m -XX:InitiatingHeapOccupancyPercent=30 -XX:G1ReservePercent=15 -Des.distribution.type=tar --module-path /content/elasticsearch-8.3.1/lib -m org.elasticsearch.server/org.elasticsearch.bootstrap.Elasticsearch
daemon       490     467  0 08:19 ?        00:00:00 /content/elasticsearch-8.3.1/modules/x-pack-ml/platform/linux-x86_64/bin/controller
root         621      72  0 08:22 ?        00:00:00 /bin/bash -c ps -ef | grep elastic
root         623     621  0 08:22 ?        00:00:00 grep elastic
Enter host password for user 'elastic':
{
  "name" : "57c066b7e6a1",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "qnlGU_l8RsORus-vwAdpoQ",
  "version" : {
    "number" : "8.3.1",
    "build_type" : "tar",
    "build_hash" : "b9a6b2867996ba92ceac66cb5bafc6db25e7910e",
    "build_date" : "2022-06-29T18:39:55.731992798Z",
    "build_snapshot" : false,
    "lucene_version" : "9.2.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

create sample index.

In [10]:
!curl --cacert /content/elasticsearch-8.3.1/config/certs/http_ca.crt -u elastic -H 'Content-Type: application/json' -XPUT https://localhost:9200/playground -d '{"mappings": { "properties": { "name" : { "type": "keyword" } } } }'
Enter host password for user 'elastic':
{"acknowledged":true,"shards_acknowledged":true,"index":"playground"}

check a index.

In [11]:
!curl --cacert /content/elasticsearch-8.3.1/config/certs/http_ca.crt -u elastic -H 'Content-Type: application/json' -XGET https://localhost:9200/playground/_mapping?pretty=true
Enter host password for user 'elastic':
{
  "playground" : {
    "mappings" : {
      "properties" : {
        "name" : {
          "type" : "keyword"
        }
      }
    }
  }
}

create new data to es.

In [12]:
!curl --cacert /content/elasticsearch-8.3.1/config/certs/http_ca.crt -u elastic -H 'Content-Type: application/json' -XPOST https://localhost:9200/playground/_doc -d '{"name": "HOGE"}'
Enter host password for user 'elastic':
{"_index":"playground","_id":"oLPQ-4EBI_biyaanZoqU","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}

M5Stamp C3でBLE Mesh

手持ちで4個(M5Stamp C3/C3U)があるから、これでBLE MESHをやってみたくなった。BLEでメッシュネットワークを構成するというのが前々から気になっていた。大量のノードがあるとき、WiFiだと限界があるし、Zigbeeのように何か通信用のモジュールを追加するより、ESPで使えるBLEメッシュを使う方が自然でもあったりするかなと。
4個だけでやっても迫力に欠けるけど、Publish一発で全台の制御をしたり、試してみるには良さげな気がした。

そんで、今回はVScode/Platform IOから使ってみる。PlatformIOからESP32(C3)の開発だと、ESP-IDFのビルドをサクサク出来て便利ではある。PIOの設定(platformio.ini)はこんな感じ。

[env:esp32-c3-devkitm-1]
platform = espressif32
board = esp32-c3-devkitm-1
framework = espidf

menuconfigもPIOのタスク”Run Menuconfig”から。

そしてビルドも同じくタスクのBuildから。esp-idfの環境を自分で作らなくてもPIO上がよろしくやってくれる。特にWindows環境下なら、VScode/PlatformIOの方がESP-IDFの環境を整備するのが、めちゃくちゃ簡単になると思う。
esp-idf直接の場合もVScodeを使うとは思うのだけど、要は自前で環境を用意する or PlatformIOに自動でやってもらう(ビルドも) という違いになる。

そして実際に動かしたのはこちら。NordicのnRF Meshというアプリ経由で動かした感じ。

https://www.hirotakaster.com/wp-content/uploads/2022/06/b6558f509b6360cafb20b7e246eb0377.mp4

コード類はesp-idfのと同じものを使える。こちら。BLE Meshのサンプルをベースに簡単な感じ。

M5StampC3でLチカ(esp-idf)

M5Stamp C3(RISC-V)をあまり使ってなかったのもあって、ちょっと使ってみようかなと。搭載されているSK6812をArduino IDE経由、いわゆるAdafruit Neopixelライブラリを使って操作するのもだけど、esp-idf経由で動かしたくなった。

https://www.hirotakaster.com/wp-content/uploads/2022/05/movie.mp4

LEDの赤〜緑〜青でループさせている。動かす過程で、esp-idf用の何かライブラリ一発で出来ないかしら?と調べていたら、esp-idf-lib という素敵なものを見つけた。色々なcomponentが入っていて、led_stripというのを使えば何かいけそうな…何となくノリで書いてみたらいけた。

コードはまるっと置いといた。githubから持ってきて、idf.py build〜flashすればOK。CMakeLists.txt の EXTRA_COMPONENT_DIRS を自分の環境に合わせて修正してあげる必要あり。

#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "led_strip.h"

void setcolor(led_strip_t *led, rgb_t *color) {
    led_strip_set_pixel(led, 0, *color);
    led_strip_flush(led);
    led_strip_wait(led, 5 * 1000);
    vTaskDelay(10 / portTICK_PERIOD_MS);
}

void app_main(void) {

    // setup led strip
    led_strip_t ledstrip;

    ledstrip.is_rgbw = true;
    ledstrip.length = 1;
    ledstrip.gpio = GPIO_NUM_2;
    ledstrip.channel = RMT_CHANNEL_0;
    ledstrip.type = LED_STRIP_SK6812;

    led_strip_install();
    led_strip_init(&ledstrip);

    // set color r->g-b
    rgb_t color;
    color.r = color.g = color.b = 0;
    int vec = 1;
    int val = 0;
    int stg = 0;
    while (1)  {
        val += vec;
        if (val >= 255 || val <= 0) vec *= -1;
        switch (stg)  {
        case 0:
            color.r = val;
            if (val == 0) stg = 1;
            break;
        case 1:
            color.g = val;
            if (val == 0) stg = 2;
            break;
        case 2:
            color.b = val;
            if (val == 0) stg = 0;
            break;
        default:
            break;
        }
        setcolor(&ledstrip, &color);
    }
    esp_restart();
}

初期化でLED個数、GPIO(SK6812につながっているのは2)とかをしてあげて、あとは色(color)を作って、led_strip_set_pixel, led_strip_flush するいつもの感じ。

ちな、VScode(PlatformIO)、esp32-c3-devkitm-1でも問題なくM5Stamp C3で動いた。esp-idf-lib の components 以下をPIOのlibにごそっと持ってくればOK

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)