とうふ荘の手記てき!

プログラムとか

4つのソートアルゴリズムをGo言語で実装する

挿入ソート、マージソートヒープソートクイックソートについて知ったので、それらをGo言語で書いてみます。

github.com

挿入ソート(insertion sort)
  • その場ソート(ソートに必要なメモリが定数倍程度)
  • 少数の要素に対して効率よくソートを行える
  • 直感的な実装
  • 実行時間は Θ(n^ 2)(およそ n^ 2の関数)
// InsertionSort 挿入ソート
func InsertionSort(a []int) {
    var (
        i, j, key int
    )

    for j = 1; j < len(a); j++ {
        key = a[j]
        i = j - 1
        for i >= 0 && a[i] > key {
            a[i+1] = a[i]
            i--
        }
        a[i+1] = key
    }
    return
}
マージソート(merge sort)
  • 分割統治法
  • 実行時間 Θ(n lg n)で挿入ソートよりも効率が良い
  • 対象と同サイズのメモリが必要になる(大きな配列に対しては負担が大きい)
// MergeSort マージソート
func MergeSort(a []int) {
    mergesort(a, 0, len(a)-1)
    return
}

func mergesort(a []int, p, r int) {
    if p < r {
        q := (p + r) / 2     // 分割
        mergesort(a, p, q)   // 前半部ソート
        mergesort(a, q+1, r) // 後半部ソート
        merge(a, p, q, r)    // 統合
    }
}

func merge(a []int, p, q, r int) {
    n1 := q - p + 1                         // p~qまでの範囲分の配列
    n2 := r - q                             // q+1~rまでの配列
    larr := make([]int, n1)                 // 前半部分配列
    rarr := make([]int, n2)                 //後半部分配列を代入するための変数
    copy(larr, a[p:q+1])                    // 元配列の前半部分の入れる
    larr = append(larr, int(math.MaxInt64)) // 門番(とても大きな数字)を入れ実質終端とする
    copy(rarr, a[q+1:r+1])                  // 元配列の後半部分
    rarr = append(rarr, int(math.MaxInt64)) // 門番を代入
    i := 0                                  // larrのインデックス値
    j := 0                                  // rarrのインデックス値
    for k := p; r >= k; k++ {               // p~rの範囲内で部分配列を比較しながら統合
        // 昇順になっている部分配列同士を随時比較しながら統合する処理(元配列に挿入された要素を持つ部分配列はインデックス値を進める)
        if larr[i] < rarr[j] { // 小さな要素を優先的に代入(昇順ソート)
            a[k] = larr[i]
            i++
        } else {
            a[k] = rarr[j]
            j++
        }
    }
}
ヒープソート(heap sort)
  • 最悪実行時間が n lg nの関数で、大量の要素をソートする場合でも実行時間の増加量が少ない
  • その場ソートのアルゴリズムでソート対象の配列以外で必要となるメモリは定数倍となり、メモリに優しい
// HeapSort ヒープソート
func HeapSort(a []int) {
    h := buildMaxHeap(a) // heapを構成
    for i := len(a) - 1; i >= 1; i-- {
        h.swap(0, i)    // Maxヒープの根は最大値なので、最後尾と交換しつつ、順にヒープの大きさを縮めると昇順ソートが可能
        h.HeapSize--    // 後ろに現ヒープ内での最大値を持ってきたのでヒープサイズを一つ狭める
        h.maxHeapify(0) // ヒープの根をから順にMaxヒープを再構成
    }
    return
}

type heap struct {
    a        []int // heapの実態
    HeapSize int   // ヒープの大きさ(ヒープ最後尾のインデックス値)
}

// 指定されたノードの親ノードを返す
func (h *heap) parent(i int) int {
    return (i - 1) / 2
}

// 指定された親ノードの左ノードを返す
func (h *heap) left(i int) int {
    return 2*i + 1
}

// 指定された親ノードの右ノードを返す
func (h *heap) right(i int) int {
    return 2*i + 2
}

// 指定されたノードを根とする二分木をmax-heapとなるように構成する
func (h *heap) maxHeapify(i int) {
    var largest int // 左右ノードもしくは親ノードのうち最も大きなノードのインデックス値
    l := h.left(i)
    r := h.right(i)
    if l <= h.HeapSize && h.a[l] > h.a[i] {
        largest = l // 親と左側では左側のほうが要素が大きい
    } else {
        largest = i
    }
    if r <= h.HeapSize && h.a[r] > h.a[largest] {
        largest = r // 親もしくは左側よりも右側のほうが大きい
    }
    if largest != i { // 親ノードよりも左右ノードのほうが大きいときは再帰
        h.swap(i, largest)
        h.maxHeapify(largest)
    }
}

// ヒープ内の要素を交換する
func (h *heap) swap(i, j int) {
    tmp := h.a[i]
    h.a[i] = h.a[j]
    h.a[j] = tmp
}

func buildMaxHeap(a []int) heap {
    h := heap{}
    h.a = a
    h.HeapSize = len(a) - 1                      // ヒープの最後尾を指定(配列の最後尾)
    for i := (h.HeapSize - 1) / 2; i >= 0; i-- { // ヒープ最後尾の親ノードから順にmax-heapifyを掛けていってMaxヒープを構成
        h.maxHeapify(i)
    }
    return h
}
クイックソート(quick sort)
  • 最悪実行時間は n^ 2の関数で、平均的な実行時間は n lg nの関数
  • その場ソートのアルゴリズムでメモリに優しい(再帰実行を行う点を除いて)
  • 分割統治法
  • ソート済みの配列などに行うと実行時間は最悪になる(挿入ソートでは逆に最良の実行時間となる)
// QuickSort クイックソート
func QuickSort(a []int) {
    quicksort(a, 0, len(a)-1) // 最初は配列の先頭と最後尾を範囲に指定
    return
}

func quicksort(a []int, p, r int) {
    if p < r { // 開始地点よりも終了地点の方が大きいとき(範囲内の大きさが1以上のとき)
        q := partition(a, p, r) // 最後尾の値をピボットにして仕切りとなるインデックス値を求める
        quicksort(a, p, q-1)    // 仕切りより前の範囲でクイックソート
        quicksort(a, q+1, r)    // 仕切りより後の範囲でクイックソート
    }
}

// 最後尾を基準として仕切りの場所を定める
func partition(a []int, p, r int) int {
    var tmp int              // 値入れ替え用バッファ
    x := a[r]                // 最後尾をピボットにする
    i := p - 1               // 仕切りより前の範囲の最後尾を示すインデックス値
    for j := p; r > j; j++ { // jによってピボットとしている最後尾rの前までピボットとの大小を比較する
        if a[j] <= x { // ピボット以下であるとき仕切りとなるインデックス値を一つ増やしてそこに値を差し込む
            i++        // 仕切りを一つ分動かす
            tmp = a[i] // jにあった値を仕切り内部の最後尾と入れ替え(元々iにあった値はピボットよりも大きいことを確認済み)
            a[i] = a[j]
            a[j] = tmp
        }
    }
    i++        // 全て判定を終えたら仕切り内部の最後尾を指したiを一つ動かし仕切りの場所を決定する
    tmp = a[i] // 仕切りの場所にピボットを配置
    a[i] = a[r]
    a[r] = tmp
    return i // 仕切りの場所を返す
}
実行時間

githubに上げたソースコードからコンパイルしたツールを使って、0~1000までの乱数を50万個与えて各ソートアルゴリズムの実行時間を調べてみました。これを3回行ってその平均値を下に書いてみます。

挿入ソート マージソート ヒープソート クイックソート
57112 ms 204 ms 134 ms 195 ms

ルネサスのマイコンをVSCodeを使ってプログラム、そしてCS+でビルド、Flash Programmerで書き込みを行う。



なんでVScodeルネサスマイコンをプログラミングするの?

f:id:tofu-so-shioaji:20190421193746p:plain

Renesasのマイコン(RXファミリなど)をプログラミングするためにはCubeSuite+(CS+)と呼ばれる統合開発環境を使うのですが、私自身、普段はVisual Studio Code(VSCode)と言われているエディターを使っています。

code.visualstudio.com

VScodeは入力補完や拡張機能、コードレンズなど多機能で、エディターとしても軽量な方です。(最近は重くなってきたかも?)

なので、プログラミング、ビルド、マイコンへの書き込みまでをVScodeから行う最低限の手順を備忘録も兼ねてまとめてみました。

ただ、私自身あまりCS+について分かっておらず、勘違いや設定漏れ等が存在する可能性が極めて高いです。その場合は指摘してもらえると幸いです。

ここではVSCodeやCS+のインストールや環境構築は省略します。

なお、今回は以下のような実行環境で実行しました。

  • Windows10
  • CS+ for CC V8.01.00
  • Rebesas Flash Programmer V 3.05.01 無償版
  • Visual Studio Code バージョン: 1.35.0

また、予め今回のディレクトリの構成を示しておきます。

フォルダ構造(ツリー)

***/Test     // プロジェクトフォルダ
│  Test.c     // ソースコード
│  Test.mtpj  // プロジェクトファイル
│ ***略***
│
├─.vscode       // VSCodeに関する設定などを保存するフォルダ
│ ***略***
│
├─DefaultBuild  // ビルドに関するデータを保管するフォルダ
│  Test.mot   // 書き込み時に用いるデータ
│ ***略***
│
└─Flash Programmer
    │  Flash Programmer   // FlashProgrammerのログ(実行時に自動作成)
    │
    └─Test              // Flash Programmeのプロジェクトフォルダ
            Test.rpj    // Flash Programmeのプロジェクトファイル
プロジェクトを作成する

まずは、CS+でプロジェクトを作成します。

この時、「プロジェクト名のフォルダを作成する」にチェックを入れておいてください。後述のビルド、書き込みはこれを前提とした設定にしています。

VSCodeで開く

プロジェクト作成のために起動したCS+は終了させ、作成したプロジェクトのフォルダを右クリックから「Open with Code」を選択して、VSCodeでフォルダを開きます。

f:id:tofu-so-shioaji:20190402204821p:plain
フォルダをVSCodeで開いた状態

左側のリストで編集したい、ファイルを選んでプログラムを行います。

C/C++言語のプログラムを行う際、C/C++拡張機能を持っていない人はソースファイルを開いた際に拡張機能を入れるよう通知が出るはずです。

VSCodeをCS+向けに設定する
文字コードの設定

VSCodeのデフォルトの文字コードは「UTF-8」ですが、CS+は「Shift-JIS」でファイルを生成します。なので、VSCode側の設定でShift-JISのコードとして読み込むようにします。

Ctrl+,と入力し、設定を開き、「ワークスペースの設定」を選択するとその設定は現在開いているフォルダ内だけに適用されます。

その状態で「encoding」と画面上部の検索窓に入力して検索すると「Files: Encoding」という設定がヒットするはずです。これを「shiftjis」に変更すると、VSCodeが「Shift-JIS」でファイルを開くようになります。

iodefine.hに手を加える

CS+のプログラムをVSCodeで開く際に困るのが、CS+特有のキーワードをコード補完機能がうまく認識してくれないことです。

iodefine.hに記述されているSYSTEMPORT0といったレジストリにアクセスするための構造体のフィールドには予測変換機能が働かないどころか、文法エラーであると認識してしまいます。

これの原因は、SYSTEMの定義に書かれている__evenacessというキーワードがRenesasのコンパイラ特有のもので、VSCodeはこれを文法ミスであると認識するようです。よって、VSCodeがこのキーワードを認識出来ないようにします。(正確にはマクロで問題のキーワードを空白として認識させます。)

「iodefine.h」の先頭部分に以下の定義を追記します。

#ifndef __RX62NIODEFINE_HEADER__
#define __RX62NIODEFINE_HEADER__
#pragma bit_order left
#pragma unpack

#ifdef _VSCODE // 追記部分
#define __evenaccess
#endif
***略***

そして、Ctrl+Shift+Pでコマンドパレットを開き、「C/C++」等を入力すると、「edit configurations(JSON)」が候補に出てくるので、それを選択して、C/C++のプロパティを設定するファイル「c_cpp_properties.json」を開きます。

このファイルに_VSCODEを定義し、IntelliSense実行時のみ、キーワードを無視し、コンパイラがビルドする際は無視しないようにします。

***略***
 "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE",
                "_VSCODE"
            ],
***略***

これで、レジストリへアクセスする構造体の入力補完が使えるようになります。

f:id:tofu-so-shioaji:20190402213341j:plain
入力補完機能

インクルードパスを追加

CS+のライブラリが読み込まれるようにインクルードパスを追加します。

設定は同じく「c_cpp_properties.json」に設定します。

***略***
"includePath": [
                "${workspaceFolder}/**",
                "{CS+のインストール先}/Renesas Electronics/CS+/CC/CC-RX/V3.01.00/include"
            ],
***略***

Windowsでは一般的な「\(バックスラッシュ)」はディレクトリの区切り文字としては使用できません。「/」か「\\」に置き換えます。

ビルドをVSCcodeから実行できるようにする

VSCodeには作業を自動実行するためのタスクという機能があります。同じくCtrl+Shift+Pでコマンドパレットを開き、「Tasks」と入力すると、「タスクの構成(Configure Task)」が候補に出てくるのでそれを選択してから「テンプレートからtaks.jsonを生成」→「others」を選択し「tasks.json」を開きます。

最初に設定ファイルを作成した際のテンプレートは「Hello」と表示するコマンドを実行するだけなので消してしまっても問題ありません。代わりに以下のように記述します。

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build", // ビルドタスク
            "type": "shell",
            "command": "{CS+のインストール先}\\Renesas Electronics\\CS+\\CC\\CubeSuite+.exe",
            "args": [   // 引数指定
                "/bb",   // ビルドを実行
                "${workspaceRoot}\\${workspaceRootFolderName}.mtpj",    // プロジェクトファイル(.mtpj)をディレクトリから指定する
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
 ]
}

これは、CubeSuite+.exeをコマンドラインから引数をつけて実行するタスクです。group以下の設定によりこのタスクがビルドタスクであると認識されます。

(後述しますが、Ctr+Shift+Bのショートカットでビルドタスクは実行できます。)

実行の際、${workspaceRoot}はプロジェクトフォルダまでの完全なパスに置換されます。

${workspaceRootFolderName}はプロジェクトフォルダ名に置換されます。

commandでCubeSuite+.exeファイルが存在するディレクトリを指定します。

プロジェクトの新規作成時、プロジェクトフォルダ名とプロジェクト名を一致させておくことが前提です。

タスクを作成したら、Ctrl+Shift+Bでビルドタスクを実行します。成功すると以下のように表示されます。

f:id:tofu-so-shioaji:20190402223617j:plain
ビルド成功時のコンソール

VSCode内の左側のエクスプローラーからも「DefaultBuild」フォルダが作成され、.motファイルも生成できているのが確認出来ると思います。

Flash ProgrammerでVSCodeからでも書き込めるようにする

VSCodeでプログラミングを行い、ビルドの実行もVSCodeから行えるようになりました。最後にマイコンmotファイルを書き込む操作もVSCodeから呼び出せるようにします。

まず、Renesas Flash Programmerを開いて、新規プロジェクトを作成し、マイコンの選択、(Flash Programmerにおける)プロジェクト名の決定とディレクトリの指定、マイコンとの通信方式と周波数を指定し、初回の通信を行います。

今回は、CS+のプロジェクトフォルダ内に「Flash Programmer」というフォルダを作成し、その中にCS+のプロジェクト名と同一名のプロジェクトを作成することにします。(プロジェクトを作成する際に指定したディレクトリにプロジェクト名のフォルダが作成されるようです。)

また、初回の通信が終わった後に開く画面で、使用するmotファイルを選択し、プロジェクトを保存したらFlash Programmerは閉じてしまっても大丈夫です。

f:id:tofu-so-shioaji:20190609010453j:plain
Flash Programmerの新規プロジェクト作成画面の一例

tasks.jsonをコマンドパレットやエクスプローラーから開いて、以下のように設定を追加します。

"label": "Flash",を含む一連の設定が追加部分です。

{
    ***略***
    "tasks": [
        {
            "label": "Build", // ビルドタスク
            []
        },
        {
            "label": "Flash", // マイコンへの書き込み
            "type": "shell",
            "command": "{Flash Programmerのインストール先})\\Renesas Electronics\\Programming Tools\\Renesas Flash Programmer V3.05\\RFPV3.exe",
            "args": [   // 引数を指定
                "/silent",   // GUI非表示で起動
                "${workspaceRoot}\\Flash Programmer\\${workspaceRootFolderName}\\${workspaceRootFolderName}.rpj", // プロジェクトファイルが存在するディレクトリ
                "/log",  // ログを保存する
                "Flash Programmer/Flash Programmer.log", // ログを保存する場所
            ],
         }
    ]
}

"command"ではFlash Programmerがインストールされているディレクトリと、その中にある実行ファイルRFPV3.exeをフルパスで指定します。

また、RFPV3.exeを実行する際の引数リストargsFlash Programmerのプロジェクトファイルを指定しています。

今回は{CS+のプロジェクトとフォルダ}\\Flash Programmer\\{CS+プロジェクトと同一名のフォルダ}\\{CS+プロジェクトど同一名のFPのプロジェクトファイルのようになっています。

あえて、このように制約を課すことで、設定ファイルをコピペするだけで使えるようにします。

次にCtrl+Shift+Pでコマンドパレットを呼び出し、「Tasks」と入力して「タスクの実行」→「Flash」→「タスクの出力をスキャンせずに続行」を選択すると、コマンドが実行されます。

しかし、RFPV3.exeはほとんど実行結果を出力しないため、うまく行ったのかすごくわかりにくいです。そこでタスク作成時に設定しておいた、ログを読みに行きます。

f:id:tofu-so-shioaji:20190609015054j:plain
Flashタスクの実行結果。すごく分かりにくい。

タスクを実行してから暫く待つと「Flash Programmer」フォルダ内にFlash Programmer.logというログファイルが現れているはずです。これを開くと、Flash Programmerの実行結果がわかります。

f:id:tofu-so-shioaji:20190609015920j:plain
Flash Programmerのログファイルの例

以上で、無事にVSCodeだけでルネサスマイコンにプログラミングをして、コンパイルと書き込みまですることができました。

設定のまとめ

設定方法のみを以下にまとめます。

フォルダ構造(ツリー)

***/Test     // プロジェクトフォルダ
│  Test.c     // ソースコード
│  Test.mtpj  // プロジェクトファイル
│ ***略***
│
├─.vscode       // VSCodeに関する設定などを保存するフォルダ
│ ***略***
│
├─DefaultBuild  // ビルドに関するデータを保管するフォルダ
│  Test.mot   // 書き込み時に用いるデータ
│ ***略***
│
└─Flash Programmer
    │  Flash Programmer   // FlashProgrammerのログ(実行時に自動作成)
    │
    └─Test              // Flash Programmeのプロジェクトフォルダ
            Test.rpj    // Flash Programmeのプロジェクトファイル

.vscode/settings.json

{
    "files.encoding": "shiftjis"
}

iodefine.h

***略***
#ifndef __RX62NIODEFINE_HEADER__
#define __RX62NIODEFINE_HEADER__
#pragma bit_order left
#pragma unpack

#ifdef _VSCODE // 追記部分
#define __evenaccess
#endif
***略***

.vscode/c_cpp_properties.json

***略***
"includePath": [
                "${workspaceFolder}/**",
                "D:/Program Files (x86)/Renesas Electronics/CS+/CC/CC-RX/V3.01.00/include"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE",
                "_VSCODE"
            ],
***略***

.vscode/tasks.json

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build", // ビルドタスク
            "type": "shell",
            "command": "{CS+のインストール先}\\Renesas Electronics\\CS+\\CC\\CubeSuite+.exe",
            "args": [   // 引数指定
                "/bb",   // ビルドを実行
                "${workspaceRoot}\\${workspaceRootFolderName}.mtpj",    // プロジェクトファイル(.mtpj)をディレクトリから指定する
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "label": "Flash", // マイコンへの書き込み
            "type": "shell",
            "command": "{Flash Programmerのインストール先}\\Renesas Electronics\\Programming Tools\\Renesas Flash Programmer V3.05\\RFPV3.exe",
            "args": [   // 引数を指定
                "/silent",   // GUI非表示で起動
                "${workspaceRoot}\\Flash Programmer\\${workspaceRootFolderName}\\${workspaceRootFolderName}.rpj", // プロジェクトファイルが存在するディレクトリ
                "/log",  // ログを保存する
                "Flash Programmer/Flash Programmer.log", // ログを保存する場所
            ],
         }
    ]
}

Kicadの部品リスト(BOM)をExcelにするソフトウェア

Kicadの回路図エディタ(Eeschema)で出力した部品リスト(BOM)のXMLファイルをExcelに変換するソフトウェアです。

既に同様のものはたくさんインターネット上で入手できます。

インストール方法

こちらから実行ファイルを入手できます。 github.com

実行方法
bom-xml2excel.exe {XMLファイル} --out {出力するEXCELファイル名}

特別便利な機能とかはありませんが、使ってもらえると幸いです。

【C言語】自作ソート関数が配列を並べ替える様を眺める

概要

勉強でソート関数を作ったのですが、それだけだと味気なかったので、手塩にかけた関数がソートする様を見ることのできる関数を作りました。

アルゴリズムによっては対応できないものがあったり、並べ替える対象を決める処理を視覚化できるわけではないので、実用性はほぼありません。観賞用です。
(対応するのもint型の配列を並べ替えるものです。)  

使用例

バブルソート

f:id:tofu-so-shioaji:20190101161710g:plain
バブルソート

クイックソート

f:id:tofu-so-shioaji:20190101161719g:plain
クイックソート

ソースコード

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "sort_visualize.h"
#define ARRAY_NUM 10

void bubble_sort(int*, int);

/*
int型配列を監視して、ソートの様子を表示するプログラム
*/
int main() {
    int arr[ARRAY_NUM] = { 24, 33, 22, 10, 22, 12, 55, 10, 100, 12 };
    start_sort(bubble_sort, arr, ARRAY_NUM);

    return 0;
}

/*一般的なバブルソート*/
void bubble_sort(int* arr, int arr_num) {
    for (int i = 0; i < arr_num; i++) {
        for (int j = 0; j < arr_num - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                visualize_swap(arr + j, arr + j + 1);  /*入れ替え*/
            }
        }
    }

    return;
}
ソースコード・用法
  1. 下のソースコードsort_visualize.csort_visualize.をインクルードします。
  2. 自作のソート関数内で入れ換えるタイミングでインクルードした入れ替え関数visualize_swapを用いて入れ換えるように作ります。
  3. 作成した関数と入れ換える対象となる配列とその要素数をインクルードしたstart_sort関数に引数として渡して実行します。

gist0e2ed927b2407ab720ace9bc59f2c375

Go言語でも書いてみたいなぁ

Raspberry PiをAndroidからBluetoothで遠隔操作して入力するキーボードにする

どういうこと?


  1. Raspberry Pi Zero WをBluetoothによるシリアル通信でAndroidから制御できるようにします。
  2. Raspberry Pi Zero WをUSBキーボードにします。
  3. Androidで入力した文字をRaspberry PiがUSBキーボードとしてパソコンに打ち込みます。

どうして?(使用例)


大学にパソコン室というのがあって大学指定のノートパソコンよりも高機能なパソコンを講義中に使うことができます。
しかし、ログイン時には自分の大学アカウントのIDとパスワードを手打ちしなければなりません。
大学のアカウントということでパスワードはランダム文字列にしてあり講義の度に手打ちというのはなかなか……
パスワードマネージャーソフトを勝手にインストールはまずいでしょうし、ログイン前なので、ポータブル版も使えません……

f:id:tofu-so-shioaji:20180923210051p:plain

そこで、ログイン前でも使えて、インストール不要の入力機器として、USBキーボードに白羽の矢が立ちました。
遠隔操作可能なUSBキーボードを学校PCに挿して、スマホからパスワードマネージャーでパスを打ち込んでやれば、ラクラクログインできるという算段です。

今回はそれを作る方法を備忘録的に残しておこうと思います。
あと、同じような悩みを抱えている人のお役に立てれば幸いです。

やり方

用意・Raspberry Piのセットアップ


今回使用するのは、

  1. Raspberry Pi Zero W(OSは2018-06-27-raspbian-stretch-lite)
  2. Windows10(TeraTermを用いたSSHでRaspberryPiを操作)
  3. Android 7.1.1

ラズパイはBluetoothが使えるものでRaspberry Pi Zero WHでも大丈夫ですが、Raspberry Pi3などはUSBポートが複数あるので、USBデバイスにはできないそうです。
AndroidではBluetoothでシリアル通信が可能なターミナルアプリを利用するだけなので、iPhoneにて代替アプリがあるのならば、iPhoneでも同様のことはできると思います。
microSDにRaspbianを焼く工程や、Raspberry Piにログインする方法は省略します。 こちらの方が紹介している方法などでRaspberry Piにコマンドを打ち込める状態にしてください。

www.agilegroup.co.jp

準備が終わったら、Raspberry Piは普段使われる電源ポートではなく、USBポートを用いてパソコンのUSBコネクタと接続させます。
(USBポートからでも電力は供給されるみたいです。)

また、接続させるパソコンはRaspberryPiにSSHを行っているものには使わないことをおすすめします。(操作を間違え暴走すると、RaspberryPiを止める手立てがなくなるため)
AndroidにはOTGケーブル(microUSBと普通のパソコンに使われるUSB Type-Aの変換アダプタ)があれば、RaspberryPiを接続しキーを入力させることができます。


次に、aptコマンドで各種パッケージを更新し、RaspberryPiを再起動します。

sudo apt update
sudo apt -y upgrade
sudo reboot

そして、カーネルバージョンを確認します。

uname -a

今回実行した結果は以下の通りでした。

Linux raspberrypi 4.14.70+ #1144 Tue Sep 18 17:20:50 BST 2018 armv6l GNU/Linux

Raspberry Pi Zero (W) をUSBキーボード(USBガジェット)にする - Qiita

によると、4.4以上あれば大丈夫だそうです。
これで下準備は完了です。

RaspberryPiをUSBキーボードにする


次に、RaspberryPiを接続したパソコンに対してUSBキーボードとして認識されるようにします。
こちらの記事を参考にしました。

qiita.com

まず、USBデバイスとしてのセットアップ用シェルスクリプトを作成し実行します。
ホームディレクトリで作業をすることとして、フォルダを作ります。

cd ~
mkdir otg
cd otg

次に、otgフォルダ内で以下のシェルスクリプトsetupOTG.shを作成します。

nano setupOTG.sh

これでテキストエディタnanoが開いて、以下のシェルスクリプトを打ち込んだら、ctr+xで保存です。

#!/bin/bash

# See https://gist.github.com/gbaman/50b6cca61dd1c3f88f41 for more info

echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
echo "dwc2" | sudo tee -a /etc/modules

これを実行します。

sudo bash setupOTG.sh

そうしたら、一度再起動して設定を反映させてください。

sudo reboot

再度、RaspberryPiにログインしたら、作業用のディレクトリに戻ります。

cd ~/otg

次に、hid.shを作成します。これを実行した瞬間からPCにキーボードとして認識されますが、起動するたびに実行し直さなければならないスクリプトです。
まず作成します。

nano hid.sh

シェルスクリプトの内容は以下の通りです。

#!/bin/bash
modprobe libcomposite
cd /sys/kernel/config/usb_gadget/
mkdir -p g1
cd g1
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "deadbeef01234567890" > strings/0x409/serialnumber
echo "example.com" > strings/0x409/manufacturer
echo "Generic USB Keyboard" > strings/0x409/product
N="usb0"
mkdir -p functions/hid.$N
echo 1 > functions/hid.$N/protocol
echo 1 > functions/hid.$N/subclass
echo 8 > functions/hid.$N/report_length
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.$N/report_desc
C=1
mkdir -p configs/c.$C/strings/0x409
echo "Config $C: ECM network" > configs/c.$C/strings/0x409/configuration
echo 250 > configs/c.$C/MaxPower
ln -s functions/hid.$N configs/c.$C/
# End functions
ls /sys/class/udc > UDC

管理者権限で実行します。

sudo bash hid.sh

これでls /dev等で確認すると/dev/hidg0というファイルが現れているはずです。このファイルにデータを書き込むことで、キーボードからパソコンに文字が入力されます。
しかし、このままではls -l /dev/hidg0をすると権限が足りません。なので、以下のコマンドで権限を追加しておきます。

sudo chmod 777 /dev/hidg0

Raspberry Piからキーボードとして入力してみる


ここまでで、一応Raspberry Piによるキーボードが完成しました。
(hid.shを実行した時点でパソコンによってはキーボードが接続されたというメッセージが出ているはずです。)
ではキーボードとして使ってみます。
以下のコマンドは一行目が「a」を打ち込むもので、二行目が押されたキーを離すものです。
(操作を間違えてRaspberryPiによるキー入力でパソコンが操作できなくなった際は基本的に2行目のコマンドを実行すれば暴走は止まります。それでも止まらなかったら、RaspberryPiを引っこ抜いてマウス操作でPCを再起動するしかありません……)

sudo echo -ne "\0\0\x4\0\0\0\0\0" > /dev/hidg0
sudo echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0

そして、キーボードとして認識させるための処理が起動時に自動的に行われるようにします。

sudo nano /etc/rc.local

以下の内容を/etc/profileの最後に追記します。

sudo bash ~/otg/hid.sh
sudo chmod 777 /dev/hidg0

これで、再起動してもキーボードとして認識されるようになりました。
もう少し扱いやすくします。以下のパッケージを使います。

github.com

まず、gitが標準ではついていなかったので、以下のコマンドで入手

sudo apt -y install git

以下のコマンドでパッケージを入手し、ビルドします。

cd ~
git clone https://github.com/girst/hardpass-sendHID
cd hardpass-sendHID
make

ビルドしたら、以下のコマンドを実行することでより簡単に文字が打ち込めます。

echo -n "hello world!" | sudo ./scan /dev/hidg0 1 2

Raspberry PiAndroidからBluetoothで操作する


Bluetoothには擬似的なシリアル通信を行うプロファイルが存在し、AndroidBluetoothに対応したシリアルコンソールとRaspberry Piを通信させることでログインが行えるようになります。
以下のサイトを参考にしました。

qiita.com

tokina.hatenadiary.jp

まず、Raspberry PiAndroidBluetoothで接続します。
以下のコマンドでBluetoothを操作します。

sudo bluetoothctl

すると、対話モードに移行するので以下のコマンドを順次実行してください。

power on
discoverable on
agent on
default-agent

そして、AndroidBluetooth設定画面を開くと「raspberrypi」という項目が現れているはずなので、ペアリングを開始してください。

f:id:tofu-so-shioaji:20180928020049p:plain

ペアリングが開始されると、AndrodとRaspberryPiにPINコードが表示され、接続するか聞いてきます。
RaspberryPiとAndroidのPINコードが一致していたら承認して接続を確立してください。接続が確定したら、次回からは自動的に接続されるように設定します。

f:id:tofu-so-shioaji:20180928020106p:plain

承認後に表示されたDevice xx:xx:xx:xx:xx:xx Paired: yesxx:xx:xx:xx:xx:xxという部分を用いて、(人によって表示される内容は異なります。)

trust xx:xx:xx:xx:xx:xx

を実行すれば、再起動後も自動で繋がります。

quit

で対話モードを終了します。
そして、RaspberryPiのBluetoothにシリアル通信機能を追加します。一旦、Bluetoothサービスを終了させ、シリアル通信を追加するのは以下のコマンドです。

sudo systemctl stop bluetooth
sudo bluetoothd -C &
sudo hciconfig hci0 up
sudo sdptool add SP

これで、シリアル通信機能が使えるようになりました。
以下のコマンドで、シリアル通信を用いてRaspberry Piへログインできるようになります。

sudo rfcomm watch 0 1 agetty rfcomm0 115200

ここまで、実行したら次にAndroidを操作します。
以下のアプリを使います。

play.google.com

このアプリをインストールし開いたら、まずデバイスを選択します。
左上からメニューを開き、Devicesを選択し、raspberrypiを選択します。

f:id:tofu-so-shioaji:20180928020142p:plain

次に、メニューからTerminalに戻り、画面上部の接続アイコンを接続すれば接続が開始され、成功するとRaspberryPiにログインできます。

f:id:tofu-so-shioaji:20180928020156p:plain

(結構な頻度で接続に失敗するので何度かリトライしたり、RaspberryPiの方で一旦Ctr+Cで接続待機状態を終了させ、再度実行してみてください。)

ここまで確認したら、再度画面上部のアイコンを再度タッチして、接続を解除します。
RaspberryPiに戻り、ctr+Cでシリアル通信を終了します。
次に、RaspberryPiが起動したら、すぐにAndroidで操作できるようにします。
/etc/rc.localというファイルに記述した内容は起動時に実行されます。以下のコマンドで編集画面を開きます。

sudo nano /etc/rc.local

次に、以下のコマンドをrc.localexit 0の上に記述します。
exit 0の下に記述しないでください。)

sudo systemctl stop bluetooth
sudo bluetoothd -C &
sudo hciconfig hci0 up
sudo sdptool add SP
sudo rfcomm watch 0 1 agetty rfcomm0 115200

(参考にした記事ではsdptoolにSPを追加するまでの一連の処理は一度きりでいいはずなんですけどね……)
これで、再起動してもAndroidから接続することができます。

遠隔キーボードソフトremipiを作りました


最後に、USBキーボードとなったRaspberryPiで最初に述べたパスワード遠隔入力作戦をしやすいようにremipiというソフトを作りました。
このソフトは、日本語もある程度は送れます。

なお、入力対象は日本語キーボードを持ったWindowsを想定しています。MacLinux等で実行すると一部の機能が使えないかもしれません。

インストール方法

バイナリファイルをアップしているので、wgetで入手します。以下のコマンドで、バイナリファイルを実行可能なディレクトリにダウンロードします。

cd /usr/local/bin
sudo wget https://github.com/tofuso/remipi/releases/download/ver1.0/remipi
sudo chmod 777 remipi
cd ~

これで、ソフトがインストールされました。

remipi -h

を実行すると、以下のように帰ってくるはずです。

Usage of remipi:
  -d string
        デバイスファイル (default "/dev/hidg0")
  -s string
        キーボードに出力させる文字を指定してください。 (default "Hello World!")
  -t    指定すると対話モードで起動します。Ctr+Cまたは_quit_を実行して終了。

これでインストール完了です。

使い方

入力させるPCのUSBポートとRaspberryPiのUSBポートをケーブルで繋ぎ、BluetoothターミナルやSSH等でログインします。
そして、入力されるPC側は半角入力状態で、メモ帳等を開いて、入力待機状態にしてください。
この状態で、以下のコマンドを実行します。

remipi -s こんにちは!

すると、メモ帳に日本語が入力されているはずです。
このようにコマンドから、一発でひらがなを打ち込むことができます。
今度はメモ帳を閉じて、デスクトップ画面にして、以下のコマンドを実行してみてください。

remipi -s _win-r__sec_notepad_enter__sec_このPCをあやつっている!

文字ではないキーボードの操作も_で囲うことで行うことができます。
(詳しくはこちらで確認してみてください。)
なので、下のようなシャットダウン操作だってできてしまいます!

remipi -s "_win-r__sec_cmd.exe_enter__sec_shutdown -s -t 0_enter_"

このようなキーボードショートカットを利用してUSBキーボード単体で色々な事ができます。 これで、大学のパソコンに楽々ログインできるようになるはずです!

f:id:tofu-so-shioaji:20180928020510p:plain

ここまでお付き合いいただきありがとうございました!

それからどうしたの?(PS.)


無事に大学のPCにUSBキーボードとして認識させて、パスワードを遠隔操作で打ち込むことができました!
本当はセキュリティとかで弾かれるんじゃないかと思ってた……

ちなみにそのパソコンは授業では使用しないそうです。そんなぁ……

はじめまして

はじめまして、とうふ荘です。

前々からブログを備忘録とかでやってみたいなって思っていたので、一念発起ではじめました。

積極的になにかをやるというわけではないので、更新はどういったタイミングになるかわかりませんが、やってみたこととか、個人的な知見をまとめていきたいと思っています。

 

よろしくおねがいしますm(_ _)m

 

記事できるほどの内容ではないときは

twitter.com

に書き込むかもしれません。そちらもフォローとかしていただけると嬉しいです。