GOGATSU やぶ

【C++】クラス間を疎結合にする -コールバック関数- (1)

 C/C++のコールバック関数について、実装方法を確認し、まとめたもの。

きっかけ

 C++のためのAPIデザイン2章にて、「コールバック関数を使用することで、より結合度の低い実装をどう実現できる」という内容に関し、理解を深めたいと思った。

 実装してみたところ、メンバ変数をコールバック関数として渡す、という処理でつまづいたので、今回はそもそもの実装方法等を確認した。

確認1. 非メンバ関数を、main関数で呼び出す。

 関数ポインタを使って、実装する。

 グローバル関数を関数ポインタ経由で呼び出す場合は、関数がメモリ上のどの位置に存在するかが分かればよいので、関数ポインタさえ保持していれば呼び出し可能です。
まくまくC/C++ノート メンバ関数の関数ポインタを扱う方法

#include <iostream>
#include <string>

/* === 非メンバ関数 === */

void func(std::string name)
{
  std::cout << "funcが呼ばれました" << std::endl;
  std::cout << name << std::endl;
}

/* === main関数 === */

int main()
{
  std::string name = "Pochi";

  void(*cb)(std::string name) = &(func); //関数ポインタ定義
  cb(name);
}

 C++11から、関数オブジェクトを作成するためのライブラリが使用できるようになった。

std::function - C++入門

 上記のstd::functionを使用して、書き直してみる。
 typedefやusingで型定義をして、見やすくする。

//void(*cb)(std::string name) = &(func);

  //typedef
  typedef void (*CallbackType)(std::function<void(const std::string name)>);  

  //using
  using CallbackType = std::function<void(const std::string name)> ;

  CallbackType cb;
  cb(name);

確認2. ModuleAクラスのメンバ関数をmain関数で呼び出す。

 ModuleAクラスを追加し、ModuleA::SendMessage()を先ほどの非メンバ関数と同じ方法で呼び出すと失敗した。

/* === クラス定義 === */

class ModuleA
{
  public:
  void SendMessage(const std::string& name)
  {
    std::cout << "sendMessage to " << name << std::endl;
  };
  private:
};


/* === main関数 === */

int main()
{
  std::string name = "Pochi";
  using CallbackType = std::function<void(const std::string& name)> ;
  CallbackType cb;

  ModuleA module_a;
  cb(module_a.SendMessage(name)); //エラー
}

 調べたところ、非メンバ関数メンバ関数は、持っている情報が異なるため、同じ方法で呼び出すことはできないらしい。

関数ポインタとメンバ関数ポインタ

 非メンバ関数はどのクラスにも属さないので、関数がメモリのどの位置にいるのかがわかればよく、関数ポインタさえ分かれば呼び出しが可能。

 メンバ関数は、実行時にオブジェクト自身を示す隠しポインタ(thisポインタ)が渡される。
 このthisポインタを使って、メンバ関数は異なるオブジェクトに応じた振る舞いを実現している。
 このため、メンバ関数を渡すときは、メモリ上のどこに、メンバ関数が所属するオブジェクトが存在しているかという情報も必要になる。

 普段メンバ関数内で、メンバ変数を直接指名して使っているが、実際にはthisポインタを省略して使っている。

// 例

class Object
{
  public:
    std::string GetName() const
    {
      // return name_;
      return this->name_;
    };
    void SetName(std::string& name);
  private:
    std::string name_;
};
関数オブジェクトを作成する。

 C++11のstd::bindというライブラリで、オブジェクトへの参照と、メンバ関数のアドレスを持った関数オブジェクトが作成できるようなので、こちらを使う。

std::bind - C++入門

・std::bindで参照を束縛する例 ・std::bindでクラスのメンバ関数を束縛する例

//メンバ関数をmain関数で呼び出す
#include <iostream>
#include <string>
#include <functional>


/* === クラス定義 === */
class ModuleA
{
  public:
  void SendMessage(const std::string& name)
  {
    std::cout << "sendMessage to " << name << std::endl;
  };
  private:
};

/* === main関数 === */

int main()
{
  std::string name = "Pochi";
  using CallbackType = std::function<void(const std::string& name)> ;
  CallbackType cb;

  ModuleA module_a;

  cb = std::bind(&ModuleA::SendMessage, std::ref(module_a), std::placeholders::_1);

  cb(name);
}

 これでうまく動いてくれた。

確認3. ModuleAクラスの関数を、ModuleBクラスのメンバ関数内で呼び出す。

 ModuleBクラスを追加し、確認2と同じ方法で呼び出してみる。
 メンバ変数にコールバック関数をもたせ、メンバ関数内で実行するよう実装する。

#include <string>
#include <functional>
#include <iostream>

/* ===クラス定義===*/
class ModuleB
{
  public:
    using CallbackType = std::function<void(const std::string& name)>;

    /* コールバック関数呼び出し*/
    void DoCallback(std::string& name)
    {
        std::cout << "ModuleB::DoCallback" << std::endl;

        if(callback_)
        {
          return callback_(name);
        }
    }

    /* コールバック関数設定 */
    void SetCallback(CallbackType cb, std::string& name)
    {
      callback_ = cb;
      name_ = name;
    };

  private:
    CallbackType callback_;
    std::string name_;
};

class ModuleA
{
  public:
  void SendMessage(const std::string& name)
  {
    std::cout << "sendMessage to " << name << std::endl;
  };
  private:
};

/* ===main関数=== */
int main()
{
  ModuleA module_a;
  ModuleB module_b;
  ModuleB::CallbackType cb;

  std::string name = "Pochi";

  cb = std::bind(&ModuleA::SendMessage, std::ref(module_a), std::placeholders::_1);

  //ラムダ式で書いた場合
  cb = [&module_a](const std::string& _1)->void{return module_a.SendMessage(_1);};

  module_b.SetCallback(cb,name);
  module_b.DoCallback(name);
}

 関数オブジェクト作成部分を、ラムダ式で書き直してみる。

  //cb = std::bind(&ModuleA::SendMessage, std::ref(module_a), std::placeholders::_1);

  //ラムダ式
  cb = [&module_a](const std::string& _1)->void{return module_a.SendMessage(_1);};

 成功したので、確認終了。

次にすること

 以下の記事や書籍を参考に、実践的な使用方法を考えてみる。
 疎結合にできるという実感と納得が得られるところまでもっていきたい。

C++におけるコールバックのデザインパターン - Qiita

C++ の小さな技術を紹介するシリーズ【小技C++ 全9回】#3<コールバック> - Qiita

参考記事

c++ - class メンバー関数をコールバックとして渡したい - スタック・オーバーフロー

ロベールのC++教室 - 第57章 メンバ関数ポインタ天国 -

メンバ関数の関数ポインタを扱う方法 | まくまくC/C++ノート

【C++】コピーコンストラクタ、代入演算子の使用を禁止したいとき

 クラスのメンバ変数に、ポインタ変数を持たせた場合。
 クラスのオブジェクト破棄時、ポインタ変数のメモリ解放がうまくいかず、実行時エラーになった。

 オブジェクトのコピーをする際の挙動を意識しないといけないようで、コピーコンストラクタや代入演算子の使用を禁止したいケースとして勉強になったので、まとめておく。

(delete定義の方法については省略)

■参考書籍:C++のためのAPIデザイン

■参考記事 : 関数のdefault/delete宣言 - cpprefjp C++日本語リファレンス



コード例/エラー再現

 例として、以下のようなポインタ変数name_をメンバにもつHumanClassを作成する。
 ポインタ変数のメモリ解放は、デストラクタで行うものとする。

#include <iostream>
#include <string>

class Human
{
  public:
    std::string* name_;  //ポインタ変数
    
    Human()
    : name_(new std::string("yabu"))  //メモリ確保
    {
      std::cout << "Human: コンストラクタが呼び出されました" << std::endl;
      std::cout << "human name_ addr : " << name_ << std::endl;
    };

    ~Human()
    {
      std::cout << "Human: デストラクタが呼び出されました" << std::endl;
      std::cout << "human name_ addr : " << name_ << std::endl;
      delete name_;  //メモリ解放
    };
};


コピーコンストラクタが呼び出される場合

 オブジェクト生成時に、初期化のため既存のオブジェクトを渡したり、(ほとんどないかもしれないが)関数にオブジェクトを値渡しする場合などに、コピーコンストラクタを実行する。

 その他どういったケースがあるのか、書籍等参考にまとめる。

  1. オブジェクトが値渡しされた場合。
  2. オブジェクトが値で返された場合。
  3. オブジェクトが「MyClass a = b;」構文で初期化された場合。
  4. オブジェクトが{}で囲まれた、初期化子リスト内に置かれた場合
  5. オブジェクトが例外時にthrow/catchされた場合

 次からオブジェクトをコピーした際の挙動について、コピーコンストラクタ、代入演算子それぞれ使用した場合を考えていく。


コピーコンストラクタによるオブジェクトの代入

 Human human_02に、既存のオブジェクトhuman_01を入れて、初期化する。
 このとき、基本的にアドレスや値がそのまま代入される。
 デストラクタもコピーされる。


f:id:ybsatsuki:20220108140045p:plain


f:id:ybsatsuki:20220108151139p:plain


f:id:ybsatsuki:20220108151156p:plain


f:id:ybsatsuki:20220108151222p:plain



#include <iostream>
#include <string>

class Human
{
  public:
    std::string* name_;
    
    Human()
    : name_(new std::string("yabu"))
    {
      std::cout << "コンストラクタ : human name_ addr : " << name_ << std::endl;
    };

    ~Human()
    {
      std::cout << "デストラクタ : human name_ addr : " << name_ << std::endl;
      delete name_;
    };

    //コピーコンストラクタ
    Human(const Human& human)
    : name_(human.name_)
    {
      std::cout << "コピーコンストラクタ : human name_ addr : " << name_ << std::endl;
    }
};


int main()
{
  std::cout << "main関数に入りました" << std::endl;

  Human human_01; 

  //既存のオブジェクトで初期化
  Human human_02 = human_01;

  std::cout << "human_01 name_ : " << *human_01.name_ << std::endl;
  std::cout << "human_02 name_ : " << *human_02.name_ << std::endl;

  std::cout << "main関数を抜けました" << std::endl;
}

実行結果。

main関数に入りました

コンストラクタ : human name_ addr : 0x60000025d120
コピーコンストラクタ : human name_ addr : 0x60000025d120

human_01 name_ : yabu
human_02 name_ : yabu

main関数を抜けました

デストラクタ : human name_ addr : 0x60000025d120  
デストラクタ : human name_ addr : 0x60000025d120  

main_03(72245,0x11b67a600) malloc: *** error for object 

代入演算子によるオブジェクトの代入

 基本的に、コピーコンストラクタによる代入と同じような仕組みで、バグが出る。
 ただし上記のバグに加え、代入演算子の場合、human_02にもすでに割り当てられたメモリが存在することから、消すことのできないメモリが2つになる。


f:id:ybsatsuki:20220108152114p:plain


f:id:ybsatsuki:20220108152133p:plain


f:id:ybsatsuki:20220108152152p:plain


f:id:ybsatsuki:20220108152204p:plain


//HumanClassは変更なし

int main()
{
  std::cout << "main関数に入りました" << std::endl;

  Human human_01, human_02; 

  //最初に確保したアドレスの中身を確認するため、一旦退避
  uint8_t* pre_addr = reinterpret_cast<uint8_t*>(human_01.name_);

  //代入演算子による代入
  human_02 = human_01;

  std::cout << "human_01 name_ : " << *human_01.name_ << std::endl;
  std::cout << "human_02 name_ : " << *human_02.name_ << std::endl;

  std::cout << "human_01 name_ pre_addr : " << pre_addr << std::endl; 

  std::cout << "main関数を抜けました" << std::endl;
}

実行結果。

main関数に入りました

コンストラクタ : human name_ addr : 0x600001dcd100  #human_01
コンストラクタ : human name_ addr : 0x600001dcd120  #human_02

human_01 name_ : 0x600001dcd100
human_02 name_ : 0x600001dcd100

human_02 name_ pre_addr :yabu

main関数を抜けました

デストラクタ : human name_ addr : 0x600001dcd100  #human_01
デストラクタ : human name_ addr : 0x600001dcd100  #human_02

main_03(38471,0x109838600) malloc: *** error for object 0x600001dcd100: pointer being freed was not allocated

あとがき

 次はムーブコンストラクタについて、みてみたいなと思うなど。  

【ICMP】【補数】IPプロトコルヘッダのチェックサム計算(1の補数和の1の補数)

 ICMPフォーマットのチェックサム計算実装までに必要だった周辺知識として、ICMPの概要、補数の理解、ビット操作によるデータの取り出しについてまとめておく。

※コード一部省略。

ICMP

1. 概要

 ICMPはネットワークの通信状態を確認する際に使用するプロトコル
 パケットの取りこぼしや、宛先に指定されたIPアドレスにたどり着けない場合に、エラーを検出し、報告する役割を持っている。

2. ICMPフォーマットの全体図

rfc792

pingコマンドで送信するICMPフォーマット(13ページ目)

f:id:ybsatsuki:20211230155821p:plain
RFC 792

ICMPヘッダのフォーマットとサイズの基本 | IT情報サイト ”ITアベイラボ”

f:id:ybsatsuki:20211230145827p:plain
IT アベイラボ ICMPヘッダのフォーマットとサイズの基本

 Type(タイプ)については、メッセージフォーマットのタイプが決められているみたい。
 エコー要求であれば8、エコー応答であれば0。
 他でいうと、今回の作業には関係ないが、例えばICMPリダイレクトに使用されるICMPメッセージフォーマットの場合、タイプは5。

ICMPリダイレクトとは

f:id:ybsatsuki:20211230155521p:plain
RFC 792

 RFCの説明書を読み進めた結果、Codeは0、Idとシーケンス番号は送信側が複数のICMPパケットの識別用に用いるためのデータと理解。

チェックサム

1. 概要

 IPプロトコルヘッダのデータを数値にし、全て加算した結果を送信することで、受信側でも同様にデータを加算した結果が合致していれば、データの抜け漏れがないと判断する。

2. チェックサムの計算方法

rfc1071

 チェックサムは、ICMP タイプから始まる ICMP メッセージの 16 ビット単位の 1 の補数の合計の、さらに 16 ビットの 1 の補数である。
 チェックサムを計算するとき、チェックサムフィールド自身はゼロと見なされるべきである。
 全体の長さが奇数の場合、チェックサムを計算するために値ゼロのオクテットがひとつ追加される。

つまり、
チェックサムの初期値は0
・1の補数和を計算し、さらにその値の1の補数をとる。
・全体の長さは、チェックサムの計算に合わせて偶数にする→16ビット単位で計算する。

と理解。

補数の定義から補数和まで

補数表現とは?1の補数と2の補数の違いと計算方法まとめ | サービス | プロエンジニア


1.「補数」とは

補数には、「基数の補数」と「減基数の補数」の二種類がある。

基数:10進数の場合は10、2進数の場合は2など、基準とする文字の個数。
減基数:基数-1の値。

f:id:ybsatsuki:20211231094528p:plain


2. 2の補数、1の補数

2進数における、
・「基数の補数」が2の補数
・「減基数の補数」が1の補数

f:id:ybsatsuki:20211231111022p:plain


3. 2の補数、1の補数の求め方

・2の補数は、元の数を反転させ、1を加算したもの。
・1の補数は、元の数を反転させたもの。

例)
・2の7乗
・8ビット表示

f:id:ybsatsuki:20211231111520p:plain


4. 2の補数和、1の補数和

1の補数 ‐ 通信用語の基礎知識

2の補数和では、桁上がりが生じた場合は、桁上がりを捨てる。
1の補数和では、桁上がりが生じた場合は、一番右の桁に足し込む。
1の補数では、+0の否定として、-0がある。
 0を表現する数が、1の補数の方が多いことから、表現範囲が2の補数と1の補数とでは異なってくる。

f:id:ybsatsuki:20211231114224p:plain

実装方法

1. ICMPフォーマット
struct IcmpPacket
{
  uint8_t type; //8
  uint8_t code; //10
  uint16_t check_sum; //0
  uint16_t id; //20
  uint16_t sn; //30
};
2. チェックサム計算 

(1) 1の補数和
データを加算した結果、0x33EBCだったと仮定する。
(例の値はこちらから)

① 加算後の状態
f:id:ybsatsuki:20211231115628p:plain

② 桁上がりした数(この場合3)を終端のデータに足したい
f:id:ybsatsuki:20211231120355p:plain

③そのために、
・①の状態から16ビット右シフトさせたデータ
・①の状態から下位16ビット分のデータ
を作る。

f:id:ybsatsuki:20211231121054p:plain

④下位16ビット分取り出すには、0xffffを被せる。

f:id:ybsatsuki:20211231121532p:plain

...

  //1の補数和
  while (sum>>16)
  {
    sum = (sum & 0xffff) + (sum >> 16);
  }

...

(2). 1の補数
1の補数は、元の値を反転させたもの。

...

  //1の補数(反転させる)
  uint16_t checksum = ~sum;

  return checksum;
}

出力結果

  std::cout << std::bitset<32>(0x33EBC) << std::endl;  
  //0000 0000 0000 0011 0011 1110 1011 1100

  std::cout << std::bitset<32>(icmp.check_sum) << std::endl;  
  //0000 0000 0000 0000 1100 0001 0100 0000

その他:uint8_tの値をstd::coutで出力したら、ASCIIコードに変換される。

c++ — uint8_tはcoutで印刷できません

 uint8_tはunsigned char型の別名であり、coutで出力しようとするとASCHIIコードで表示される。

   uint8_t icmp_type;
 
   icmp_type = 8;

   std::cout <<  icmp.type << std::endl;  //空白
   std::cout <<  (unsigned)icmp.type << std::endl;  //8

【ブラウザ自作】【ビット演算】振り返りメモ

今日やったとこまでメモ。ブラウザ自作に着手、実務のビット演算慣れ。

簡易pingコマンド

読んでいる雑誌
WEB+DB PRESS Vol.120|技術評論社

・straceコマンドをインストールして、アプリケーションが発行するシステムコールをテキストに吐き出してみた。

Linuxシステムコール番号
Ubuntもこれでいけた。
Linuxのシステムコール番号を探す - Qiita

ビット演算

・ビット演算のシフト、論理和論理積で遊んだ。

・やりたいことはちゃんとある。
 取ってきたデータを、ビットの開始〜終了位置を指定して設定したい。

/* 例) 8ビット変数に対し、1111を2ビット目〜5ビット目まで入れる。
*  0000 0000
*  ↓
*  0011 1100
*/

これをC++のテンプレートでつくりたい。

/* 取得
* 0000 0000 0000 1111  取得したい値
* 1111   1111  1111  1111     ←ビットマスクをかぶせて、積集合
*/

・この値を2ビット移動させて、0011 1100のマスクビットつくって、
・元データの該当箇所(2ビット目から5ビット目だけ)を反転させて、積集合とる。
・部分的に反転させることできたっけ。
MacのCPUではリトルエンディアンでメモリに格納することを確認した...ということはビッグエンディアンで入れたと思っても、Macのメモリから読み出したときにはリトルエンディアンになってるってことかな。

 うーん、進まなくて悔しい!
 次回もやるぞい。

【技術学習log】【C++】【Java】【TS】テンプレート調査メモ

テンプレートとエンディアンメモ。
同じジェネリックでも、C++、TS、Javaでだいぶちがう。

テンプレートの概要

テンプレート (C++) | Microsoft Docs

 C++ では、プログラマが明示的に宣言するか、コンパイラによって推測される、すべての変数に特定の型を割り当てる必要があります。
 ただし、操作する型に関係なく、多くのデータ構造とアルゴリズムは同じように見えます。
 テンプレートを使用すると、クラスまたは関数の操作を定義し、その操作がどの具象型で動作するかをユーザーが指定できるようになります。

ジェネリック・プログラミング

C++ジェネリックプログラミング入門 - Qiita

 ジェネリックプログラミングとは、 特定の型に依存しない実装をするプログラミングスタイル のこと。

 TSではジェネリクスC++ではテンプレートと呼ばれている。
 C++の「特殊化」のような機能はジェネリクスにはないなど、言語によって違うので完全イコールではない。

C++のテンプレート

9.1. 関数テンプレート - ゼロから学ぶ C++

TSのジェネリクス

ジェネリクス — 仕事ですぐに使えるTypeScript ドキュメント

TSの説明がわかりやすかった。

Javaジェネリクス

Javaのドキュメントでは、型を指定し、キャストする際に教えてあげることで、違う型の要素が入っているときはコンパイルエラーにしてくれる、という側面が強そう。

読んだ感じ、C++とは使用する目的が全然違う気がする。

ジェネリクス

特殊化とは?

わかりやすかった。

9.3. 特殊化 - ゼロから学ぶ C++

テンプレート定義

Microsoft.docsを読んでいく。

docs.microsoft.com

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

 T はテンプレートパラメーターです。
 キーワードは、 このパラメーターが型のプレースホルダーであることを示します。

非型パラメータ

【C++】非型テンプレート・パラメータ【用途別の使い方】 | MaryCore

パラメータとして渡せる値は「定数、定数式、関数、構造体、ポインタ」等様々です。

エンディアン(ポインタキャスト)

同じCPUであれば、リトルエンディアンでintからuint16_tにキャストしても、値は取ってきてくれる。
でもポインタはちょっと違うけど、と聞いたので、記事だけ調べておく。
★実行してない。

これかな。

ポインタのキャストでエンディアンを意識しないとバグる例 | コウモリのちょーおんぱ

基盤むきだしのロボットがほしかったので、作ってみた。

 本記事は、TechPlay女子部AdventCalender18日目の記事です。


 配線やモータ、基盤むきだしのロボットがほしい。
 というわけで、ロボットキットを購入して作ってみることにしました。

 購入のきっかけから、ロボットの組み立て、サンプルコードを動かすところまでを書いています。


【目次】


【きっかけ】

 もともとCPUやメモリを、目に見える形でそばに置いておきたいと思っていました。
 基盤をながめながら、「この出力ピンに今電気信号が渡っている」などということを、したり顔で想像したいのです。

 あとはロボットがどんな仕組みで動いているのか知りたくて、作るのが一番はやいのかなと思い、チャレンジしてみました。

【購入・準備】

■ロボットキット:PiSloth www.amazon.co.jp

リチウムイオン電池
Amazon | KEEPPOWER 3500mAh P1835J 保護回路付き リチウムイオンバッテリー 日本製セル (2本組) | KEEPPOWER | 充電式電池

 ラズパイ、電池、SDカードは別途購入が必要です。
(逆さまになっていますが、SDカードの下にあるのが電池です。)

 ラズパイのバージョンはどれでも良さそうですが、当時品薄だったため、残っていたRaspberry Pi 3 Model Bを購入しました。

f:id:ybsatsuki:20211216224009j:plain

■工具
 工具は小さめのドライバーを購入。
 でもキットの中に、ちゃんと入ってました。

f:id:ybsatsuki:20211216224859j:plain

f:id:ybsatsuki:20211216224838j:plain

【組み立て】

説明書

マニュアル電子版(英語) SunFounder Raspberry Pi Robot - PiSloth — SunFounder pisloth documentation

マニュアル紙版(英語)

f:id:ybsatsuki:20211218074838j:plain

Let's 組み立て(ダイジェスト)

ネジは大きさと本数を書いたシールが貼られています。
多少余分に入っているようで、親切です。

f:id:ybsatsuki:20211218061214j:plain

モーターです。
こちらは4つ全部使います。

f:id:ybsatsuki:20211218061534j:plain

最初は電池ボックスの取り付けから。

f:id:ybsatsuki:20211218061908j:plain

次にモーターの取り付け。

f:id:ybsatsuki:20211218061444j:plain

胴体部分の基礎ができてきたところで、ラズパイ!
テンション上がった。

f:id:ybsatsuki:20211218062040j:plain

ロボットを動かすための基盤とがっちゃんこ。
一瞬でラズパイ基盤の大部分が見えなくなりました。
うーん、そうか。見えなくなっちゃうのか〜。
致し方なし。

f:id:ybsatsuki:20211218063224j:plain

モーターを、PWMピンに接続。
(※ちなみにこの位置だと、ロボットのダンスモードは起動しませんでした)

PWMとは、モータ制御に嬉しい電波変調方法のひとつだそうです。

参考記事: モータ制御に欠かせない技術“PWM”って何?:H8で学ぶマイコン開発入門(9)(1/3 ページ) - MONOist

f:id:ybsatsuki:20211218063406j:plain

足なしの状態。
このままでもかわいいけども。

f:id:ybsatsuki:20211218074138j:plain

残りモータ二個を使って、足をつけます。

f:id:ybsatsuki:20211218074239j:plain

これで外見は完成です。
大体2時間弱でできました。
パチパチ👏

f:id:ybsatsuki:20211218094134j:plain

【OSインストール】

■ラズパイOS Installing the OS — SunFounder pisloth documentation

■EzBlockOS Quick User Guide for EzBlock 3 — ezblock3 documentation

 OSをSDカードにインストールし、ロボットに取り付けたラズパイ基盤に入れます。

 PiSlothは、別途iphoneにEzBlockというアプリを入れれば、iphoneからも操作できます。

 アプリを使用するには、EzBlock専用に作ったEzBlockOSが必要らしいので、今回はこちらをインストール。

 EzBlockOSが必要と知る前に、一度ラズパイOSをインストールしたのですが、EzBlockを起動しようとすると、ファイルがないと怒られてしまいました。
 SDカードをフォーマットしてまっさらにしてから、EzBlockOSを入れ直しました。

 コマンドで動かすだけなら、説明書読む限りラズパイOSで良さそうです。

SSH接続】

IPアドレスを探す方法:
ラズベリーパイをディスプレイに接続せずSSH設定する方法

 基盤にSDカードを入れたら、わたしのMacさんからSSH接続します。
 (ちなみにIPアドレスは、EzBlockアプリからも確認できます。)

 これはいままでのどのSSH接続よりも楽しかったです。
 住所(IPアドレス)に家(コンピュータ)があることを目の当たりにしながら接続すると、すごく「つながった」感がありました。
 virtualBoxやDockerで仕込んだ仮想空間に行くより、机の上にいるラズパイに行く方が、「Macです!接続にきました。パスワードはこちらです。」という訪問の手応えがあるというか。

 ...楽しさを上手く伝えられたかはわかりませんが、とりあえず接続は成功しました。
 公式マニュアルでgitのパスを確認しつつ、コードを落とします。

 私はコードの編集をvscodeでやりたかったので、vscodeのリモート接続も設定しました。

Pythonコード!!】

■ライブラリ
GitHub - sunfounder/robot-hat: Robot Hat python library

■PiSloth用
GitHub - sunfounder/pisloth: PiSloth from SunFounder

sunfounder/robot-hat/motor.py

from .pwm import PWM
from .pin import Pin

class Motor():
    PERIOD = 4095
    PRESCALER = 10
    def __init__(self):

        self.left_rear_pwm_pin = PWM("P13")
        self.right_rear_pwm_pin = PWM("P12")
        self.left_rear_dir_pin = Pin("D4")
        self.right_rear_dir_pin = Pin("D5")

...

 おおお、Pythonコード!
 ライブラリのmotor.pyを覗いてみると、さきほどちょこっと出てきたPWMの設定などが書かれてる!!
 わいわい。わいわい。

sunfounder/pisloth/dancing.py

#!/usr/bin/python3
import sys
sys.path.append(r'/opt/ezblock')
from sloth import Sloth
__SLOTH__ = Sloth([1,2,3,4])


def forever():
    __SLOTH__.do_action('moon walk left', 2, 100)
    __SLOTH__.do_action('moon walk right', 2, 100)
    __SLOTH__.do_action('turn right', 1, 100)

...

 moon walk?むーんうぉーく?
 このロボット、ムーンウォークできるのか。
 しかも後ろじゃなく、左右に。

【サンプルコードで、動かしてみた】

 動かすまでに、差し込んだピンの位置が違ったり、電池ボックスのコネクタを奥まで差し込んでなかったり、わりと四苦八苦しました。

 やっと動いた...。
 よかったよかった...。

【今後:プログラムの変更など】

 今一番気になっているのは、基盤とのインタフェースと思われるライブラリコードです。
 GPIOピンやPWM、ADコンバータなどもまだまだ知りたいので、コードと基盤をみながら読み解いていこうかなと思ってます。

 ふいい、楽しかった〜!!

 

 

【技術書】【設計】iteratorパターン

手持ちの設計パターンを増やすために、読むことにした。
今日は第1章通し読みして、頭に残ったことや関連記事などメモ。

Java言語で学ぶデザインパターン入門第3版 | 結城 浩 | コンピュータ・IT | Kindleストア | Amazon

抽象クラスやインターフェース

具体的なクラスだけでプログラミングするのではなく、抽象クラスやインタフェースを使ってプログラミングする。

問題

ちょっとしかみてないけど、考えるのに良さそう

www.techscore.com

C++iterator

思い出したのでついでに読んだ。

C++ iterator 入門


読みながら思いついたことメモ

・他の人の設計みてコーディングするのに、役立ちそう。
・読んでも頭に入りづらいので、今度はクラス図とか紙に書いてみる。
・一度一周して、練習問題解いてみる。