Geekになりたいママエンジニアのブログ

子育てを楽しみつつ空き時間に自己啓発をしているログです。投稿する内容はすべて個人の意見であり、所属する組織とは関係がありません。

Raspberry PI OSセットアップとLチカ

ラスパイを使って何か作ってみたい。と思いつつ何年もが経ってしまいましたが、ついに少し時間が取れたのでメモを残します。使用したのは Raspberry Pi Zero W V1.1 です。

OS のセットアップ

ラズパイをモニターに接続するケーブルがなかったので、こちら【ラズパイ】モニター接続なしでセットアップする方法 | メタエレ実験室を参考にして、モニター接続なしでセットアップ成功しました。

SSH 鍵の生成

今回モニターなしでセットアップするため、SSH の接続が必要です。そのために必要な鍵を生成します。

Microsoft Store より、Ubuntu on Windows をインストールして、その上で作業しました。
apps.microsoft.com

スタートメニューから "Ubuntu on Windows" を開き、以下のコマンドで SSH 鍵を生成します。

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/xxx/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:

公開鍵 id_rsa.pub と秘密鍵 id_rsa が指定した場所に生成されます。

Raspberry Pi Imager のインストール

Raspberry Pi Imager というツールを使ってラズパイ OS を書き込むことができます。こちらよりダウンロード+インストールします。私は Windows ユーザなので "Download for Windows" をセレクト。
Raspberry Pi OS – Raspberry Pi 

ラズパイ OS install

ラズパイの SD card をパソコンの SD card slot に挿入します。我が家にある Surface の場合、スタンドをあげて左下にスロットがありました。

SD card slot
SD card slot

スタートメニューから Raspberry Pi Imager を起動します。OS とストレージをそれぞれ以下のように設定します。

Raspberry Pi Imager
Raspberry Pi Imager

右下にある設定アイコンを開き必要な情報を入力します。

  • ホスト名:SSH 接続する際に必要になります。
  • SSH 認証の設定:「公開鍵認証」を選択し、先ほど生成した id_rsa.pub の中身をコピーして、"ユーザー pi のための authorized_keys "の欄にペーストします。
  • WiFiSSID とパスワードを入力します。
OS flash settings
OS flash settings

「保存」し、「書き込む」ボタンで書き込みます。書き込みが完了したら、SD card を取り出し、ラズパイに挿入します。

SSH 接続

スタートメニューから "Ubuntu on Windows" を開き、以下のコマンドで接続します。

$ ssh pi@raspberrypi.local
Linux raspberrypi 5.15.61+ #1579 Fri Aug 26 11:08:59 BST 2022 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Jan  4 15:17:40 2023

これでセットアップが完了です😆バイスの起動にかなり時間がかかり、ssh コマンドがつながるようになるまでに5分程度かかりました(Raspberry Pi Zero W V1.1の場合)。

Lチカにトライ!

Lチカとは LED をチカチカさせること、ソフトウェアの世界での「Hello world!」のようなもののようです。

GPIO ピンを理解する

GPIO (general-purpose input/output) ピンについては、以下の公式ドキュメントに分かりやすい説明があります。40本のピンそれぞれに役割が割り当てられています。
Raspberry Pi Documentation - Raspberry Pi OS

GPIO pins from www.raspberrypi.com
GPIO pins from Raspberry Pi Documentation - Raspberry Pi OS, © Raspberry Pi Ltd
GPIO pins from www.raspberrypi.com
GPIO pins from Raspberry Pi Documentation - Raspberry Pi OS, © Raspberry Pi Ltd

たとえば、1番のピンには常に3.3Vの電圧がかかっており、6番のピンはグラウンドです。1番のピンと6番のピンに LED をつなぐと、LED が点灯します。

LED点灯
LED点灯

回路図はこんな感じでしょうか。

回路図
回路図

プログラムで制御する

GPIO ピンは、プログラムから ON/OFF の制御ができます。例えば、GPIO2 のピン(3番ピン)を一秒おきに ON/OFF して LED を点滅させるプログラムはこんな感じで書けます。(こちらを参考にさせていただきました:ラズパイでLチカをしてみる(入門編) - Qiita

from gpiozero import LED
from time import sleep

led = LED(2) # GPIO 2 (3番ピン)

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

scpコマンドでファイルをラズパイにコピーします。

$ scp test.py pi@raspberrypi.local:src/test.py

3番ピンと6番ピンに差し替えて、ラズパイ上で python プログラムを実行します。

$ ssh pi@raspberrypi.local
$ python src/test.py

チカチカしてくれました💡

LED点滅成功!
LED点滅成功!

C++スマートポインター

C++では、メモリを動的に確保・解放することができます。しかしそれが困難のもとでもあり、動的に確保されたメモリが正しく解放されないと、メモリリークが発生してしまいます。メモリリークが蓄積するとアプリケーションの動作が遅くなったり、パソコン全体の動作が遅くなったりしてしまうことがあります。

なお、Visual Studioを利用している場合、デバッガを利用してメモリリークを検出する方法があります: CRT ライブラリを使用したメモリ リークの検出 - Visual Studio | Microsoft Docs

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

int main()
{
    auto leak = new int[3];
    // delete[] leak;

    _CrtDumpMemoryLeaks();
}

デバッガの出力:

Detected memory leaks!
Dumping objects ->
{76} normal block at 0x00A14F98, 12 bytes long.
 Data: <            > CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.

スマートポインター

C++11にて、スマートポインターであるstd::unique_ptr, std::shared_ptr, std::weak_ptrが追加されました。スマートポインターとは、動的に確保されたメモリを適切に管理してくれるような仕組みです。

std::unique_ptr

あるメモリに対して、1つのポインタのみが所有権を保持しているようなポインタです。ポインタが使用されなくなると、自動的にメモリが解放されます。生のポインタとほぼ同様のメモリ効率と処理速度を持ちます。

{
    // c++14以降であればstd::make_uniqueにてポインタの作成が可能。
    auto p1 = std::make_unique<int>(); 

    // コピーすることはできない。
    // auto p2 = p1; // コンパイルエラー。

    // moveセマンティクスによって所有権を移行させることができる。
    std::unique_ptr<int> p3 = std::move(p1);
} // scopeを出たところでメモリが解放される。

メモリ解放の際に使用されるdeleterを指定することができ、deleterによっては生ポインタと比べてメモリ効率などが落ちてしまいます。

std::shared_ptr

あるメモリに対して、複数のポインタが所有権をシェアしているようなポインタです。所有権を保持しているポインタがなくなったタイミングでメモリが解放されます。あるオブジェクトを複数のコードから参照したい場合に便利です。「コントロールブロック」と呼ばれる参照カウンタなどのデータを保持しているため、メモリ使用量などは生のポインタに比べて増えます。

void func(std::shared_ptr<int> ptr)
{
    std::cout << ptr.use_count() << std::endl;
}

int main()
{
    auto p1 = std::make_shared<int>(3);

    // 参照カウンタ1
    std::cout << p1.use_count() << std::endl;

    {
        auto p2 = p1;
        // 参照カウンタ2 (p1とp2)
        std::cout << p1.use_count() << std::endl; 
    }

    // 参照カウンタ1 (p2が解放されp1のみ)
    std::cout << p1.use_count() << std::endl; 

    // 関数内で参照カウンタ2 (引数としてコピーされ参照カウンタが増える)
    func(p1);  

    // 参照カウンタ1 (p1のみ)
    std::cout << p1.use_count() << std::endl; 
}

生のポインタを取り出すときにはget()を使います。この場合p1の所有権に変化はありません。

auto rawp = p1.get();
std::cout << *rawp << std::endl;

unique_ptrは簡単にshared_ptrに変換することができます。メモリ効率の良いunique_ptrで定義しておき、所有権のシェアが必要になったタイミングでshared_ptrに変換する、または関数の戻り値をunique_ptrにしておき、受取先で必要に応じてshared_ptrに変換するようなこともできます。

auto up = std::make_unique<int>(5);
std::shared_ptr<int> sp = std::move(up);

unique_ptrと同様メモリ解放の際に使用されるdeleterを指定することができます。

std::weak_ptr

shared_ptrによって参照されているあるメモリを、所有権を持つことなく参照することのできるポインタです。対象となるメモリが解放されるのをブロックしたくはないけれども、まだ解放されていないのであれば参照したい、といった使い方ができます。循環参照などでメモリが解放されずにリークしてしまう問題を防ぐことができます。shared_ptrと同様「コントロールブロック」と呼ばれるデータを保持します。

参照する際には、lock()を呼び出すことによってshared_ptrとして取り出します。もし既に対象メモリが解放されていたら、lock()はnullポインタを返します。

auto sp = std::make_shared<int>(3);

std::weak_ptr<int> wp(sp);
// 参照カウンタ1 (spのみ, wpは参照カウンタをアップデートしない。)
std::cout << sp.use_count() << std::endl; 

// sp.reset();

if (std::shared_ptr<int> p2 = wp.lock())
{
    // 参照先が利用可能な時のみ処理を行う。
    // 参照カウンタ2 (p1とp2)
    std::cout << sp.use_count() << std::endl; 
    std::cout << "p2 = " << *p2 << std::endl;
}

// 参照カウンタ1 (p1のみ)
std::cout << sp.use_count() << std::endl; 

イメージ

C++ smart pointers
C++ smart pointers

std::string_view

c++17にて追加されたstd::string_viewについてのメモです。

std::string_viewとは

std::string_viewとは、所有権を持たない文字列のクラスです。"view"という名前が示すように既に存在している文字列を参照しているだけのオブジェクトです。参照元の文字列は変更できません。文字列の先頭へのポインタと文字列長を保持しています。

const char* chars { "test" };
std::string s = chars;
std::string_view sv = chars;

for (auto it = s.begin(); it < s.end(); ++it)
{
    printf("%c", *it);
}
printf("\n");

// 読み取り処理はstringとほぼ同様に行える
for (auto it = sv.begin(); it < sv.end(); ++it)
{
    printf("%c", *it);
}
printf("\n");

s[0] = 'a';
sv[0] = 'a'; // Error! 値を変えることはできない

s.append("1");
sv.append("2"); // Error! append関数はない

s.c_str();
sv.c_str(); // Error! c_str関数はない

デバッガを確認すると、string_viewは元々のconst char*配列のアドレスをそのまま保持していることがわかります。

f:id:wfalps:20190420062109p:plain
debuggerでの表示

string_viewが有効な場面

std::string_viewは、std::stringのconstメンバー関数をほぼ全て持っています。そのため、文字列に変更を加えないのであれば、std::stringとほぼ変わりなく使用することができます。std::string_viewを使うと文字列を実際に保持しないため、メモリ使用量を抑えられます。また、文字列のコピーも防ぐことができます。

string_viewの制限

  • string_viewは、誰かが保持するchar配列を参照しているだけなので、参照元の文字列が途中でなくなってしまうこともありえます。また、文字列の変更をすることもできません。
  • std::stringはnull文字'\0'で終わることが保証されていますが、std::string_viewは、null文字'\0'で終わることが保証されません。そのため、c_str()で中身を取り出すこともできません。

std::stringにはなくstd::string_viewにのみある関数

  • remove_prefix(n): 文字列の先頭アドレスを後ろにn文字ずらす
  • remove_suffix(n): 文字列の長さをn文字短くする

終端null文字のことを考えなくていいので、自由に参照範囲を決められるstring_viewならではの関数です。

まとめ

一時的な文字列を処理する際にはとても便利なクラスです。とはいえ、引数としてstring_viewを受け取る関数を定義するなどのケースではメモリの管理が複雑になりそうなので、気を付けた方がよさそうです。

std::stringとstd::string_viewの違いイメージ
std::stringとstd::string_viewの違いイメージ

constとconstexprの違いメモ

C++11にて新しく導入された識別子、constexprについて勉強したメモです。なお、constexprはC++14にて大幅に改善され、このブログはC++14上で実験をして書いています。

const変数とconstexpr変数

まずconst変数とは、定数、つまり定義後に変更することのできない値です。

const int ci = 0;
ci = 5;  // Error! const変数の変更は不可

constexpr変数は定数であることに加えて、コンパイル時に既知の値を保持する値です。const変数が実行時に評価されるのに対してconstexpr変数はコンパイル時に評価されます。つまり、constexprを使用すれば、実行時の処理が少なくなるため高速化を実現できますし、メモリ使用量も減らすことができます。(コンパイル時間は長くなります)

constexpr int cei = 0; // コンパイル時に値が初期化される
cei = 5;  // Error! constexpr変数の変更は不可
int i = 0;
const int a = i; // OK
constexpr int b = i; // iはコンパイル時に既知の値でないためコンパイルエラー

const関数とconstexpr関数

constはメンバ関数を修飾することができます。その場合、その関数はその関数の呼び出し元であるオブジェクトを変更しないことを保証します。

constexprはメンバ関数に限らず一般の関数やコンストラクタなどを修飾することができます。その場合、constメンバ関数とは意味が異なり、その関数はできる限りコンパイル時に実行されます。「できる限り」というのが気持ち悪くもありますが、もし引数などがコンパイル時に不定だった場合、通常の関数と同様実行時に実行されます。

下記のコードをデバッグすると、bはコンパイル時に初期化されるため、ConstexprFunctionは実行時には1回(dの初期化時に)しか呼び出されていないことがわかります。

constexpr int ConstexprFunction(int i)
{
    return i * 10;
}

int main()
{
    // コンパイル時に実行される
    constexpr int a = 10;
    constexpr int b = ConstexprFunction(a);

    // 実行時に実行される
    int c = 10;
    int d = ConstexprFunction(c);
}

ただし、constexpr関数が受け取れる値、返せる値はリテラル型(=コンパイル時に決定される値を保持できる型)に限定されます。

まとめ

constexprを使うと、可能な時にはコンパイル時に処理を実行してくれるため、コードの実行速度向上やメモリ効率の向上を実現できます。また可能でない場面でも今までと同じように処理が実行時に走るだけです。constexprが利用可能な場面では積極的に使っていくのがよさそうです。

constとconstexprの違いイメージ
constとconstexprの違いイメージ

LINEのボットを作ってみました(Azure + node.js)

情報系の勉強をはじめたてのまだ学生だった頃、会話の相手をしてくれるようなロボットを作りたいと夢見ていました。最近チャットボットのようなサービスが流行りだし、そんな昔の夢を思い出しました😊今回は、今更感はありますが、LINEのチャットボットを作ることに挑戦してみようと思います。

LINEのドキュメント「ボットを作成する」
https://developers.line.me/ja/docs/messaging-api/building-bot/

LINEのボット用アカウントを作成する手順などは↑のページに書いてあるので割愛し、以下Webhookペイロードを受け取るためのサーバーを作成する手順を記録しておきます。

Webhookペイロードとは

友達申請が届いた場合やユーザからメッセージを受信した場合、LINEサーバーから登録したURLに通知が届きます。具体的には、”LINE developers”のコンソールにて、チャネル基本設定→メッセージ送受信設定→Webhook送信の設定を「利用する」にすると、登録してあるWebhook URL(チャネル基本設定→メッセージ送受信設定→Webhook URL)にPOSTリクエストが届きます。サーバー上にて届いたPOSTリクエストを解析し、友達申請に反応する、メッセージに返信するなどの処理を行うことができます。

node.jsにてページを作ってみる。

node.js on Azureでページを作成してみました。無料試用版なので今のところ無料です。クイック スタート:Node.js Web アプリを作成する - Azure App Service | Microsoft Learnに沿ってWebページを作成しましたが、とても簡単にHello World!ページを作成することができました!ちなみに、node.jsもAzureも今回初めて触る初心者です。

上記のページよりダウンロードしたサンプルコードには、index.jsというJavaScriptファイルが含まれています。このJavaScriptファイルがHTTPリクエストの解析やWebページの出力などを行っているようです。(同フォルダにあるweb.configにて、index.jsを使う旨が定義されています。)

index.jsの中身を見てみると、http.createServer関数の第一引数に指定した関数の中で、requestを処理してresponseを生成しています。

var server = http.createServer(function(request, response) {
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.end("Hello World!");
});

今回は、LINEサーバーから私が作成するページにPOSTリクエストが届いた際に処理を行うようにしないといけません。/webhook/というURLにPOSTリクエストが届いた場合に検出できるようにしたいと思います。

var server = http.createServer(function (request, response) {
    if ((request.url === '/webhook/') && (request.method === 'POST')) {
        // /webhook/ URLに届いたPOSTリクエストのみを処理します。
        response.writeHead(200, { "Content-Type": "text/plain" });
        response.end("POST request is detected");
    }
});

”LINE developers”のコンソールの チャネル基本設定→メッセージ送受信設定→Webhook URL にて接続確認ができました!

ちなみに、GETリクエストを送るとエラーになります。

Expressフレームワークを試してみる。

Expressとは、node.js上で動くwebフレームワークです。

npm install express --save

とコマンド入力するだけで簡単に導入することができます。

Expressを使うと、よりコードが簡潔になりました。URLが増えるようであれば、Expressを利用したほうがよさそうです。

const express = require('express');
const app = express();

var port = process.env.PORT || 1337;
app.listen(port);

app.post('/webhook/', function (request, response) {
    response.writeHead(200, { "Content-Type": "text/plain" });
    response.end("POST request is detected");
})

署名を検証する

次に届いたリクエストを解析・処理する部分を実装します。まずは、Webhook URLに届いたリクエストが本当にLINEサーバーから送られたものだということを確認します。この工程を省いてしまうと、たとえば悪意のあるユーザがWebhook URLに適当なリクエストを送ることで私のサーバーからLINE messageを送るようなことができるようになってしまいます。

const request = require('request');
const express = require('express');
const bodyParser = require('body-parser'); // 署名情報が格納されているrequest.bodyの中身を読むために必要

const app = express();
app.use(bodyParser.json());   

const port = process.env.PORT || 1337;
app.listen(port);

app.post('/webhook/', function (request, response) {
    const crypto = require('crypto');
    const signature = crypto
        .createHmac('SHA256', <channelSecret>)
        .update(JSON.stringify(request.body)).digest('base64');
    if (signature === request.headers['x-line-signature']) {
        // 署名OK!
    }
    else {
        // 不正な署名
        console.log("Signature error.");
        response.writeHead(500, { "Content-Type": "text/plain" });
    }
});

参考:https://developers.line.me/ja/reference/messaging-api/#signature-validation

メッセージに返信する

とりあえず、メッセージが届いた場合には、適当に返信するようにしました。replyMessage APIを呼び出すと、https://api.line.me/v2/bot/message/replyにPOSTメッセージが送られます。

const line = require('@line/bot-sdk');
const client = new line.Client({ channelAccessToken: '***'});

for (var i = 0; i < request.body.events.length; i++) {
    var event = request.body.events[i];
    if (event.type === 'message') {
        response.json(client.replyMessage(event.replyToken, {
            type: 'text',
            text: 'メッセージありがとうございます😊'
        }));
    }
 }

参考:
https://developers.line.me/ja/reference/messaging-api/#send-reply-message

行き詰まった点

Azureサーバーのログをどこから見ることができるのか分からずすこしはまりました。https://{チャネル名}.scm.azurewebsites.net/api/logstreamにて見ることができましたが、あまり使い勝手がよくないのでもっと良い方法があるかもしれません。

概要

下手な絵で概要を説明してみるシリーズです。

シーケンス図

時計の読み方の図を描いてみました(C++/WinRT)

3歳の息子が時計に興味を持ちだしました。公文の時計など時計の読み方を学べる時計はいくつかありそうなのですが、家や保育園にある普通の時計を読めるようになってほしいという思いと、お金をかけたくないという思い😅から、自分で「時計の読み方」プリントを作成しました。

時計の読み方
時計の読み方
これをリビングの壁に貼っています。「短い針」の方が難しいようで針が12の少し左にある場合に「11時」と読めず何度か間違えたりしていました。しかし、2週間ほど経った今では間違えることはほぼなくなり、さらに、たまに紙を見なくても時計を読めることができてきました!母は感動しています。
(この画像は個人利用であれば自由にプリントしていただいて構いません)

続きを読む