Visual Studioで指摘される「名前指定の規則違反」を直す

Visual  Studio を使ってボタンのイベントを作ると button1_Click な感じにキャメルケース(camelCase)になるのだが、「名前指定の規則違反」を指摘されて、こんな感じに大文字で始まらなければいけない、と言われる。

image

毎度、なんじゃこりゃ?と思っている訳で、結構前から C# – イベントハンドラーで構文エラー「~button1_Click~」(100021)|teratail 問題があったりするわけだが。これ、命名規約のルールとして、

  • プライベートなメソッドは小文字で始める
  • パブリックなメソッドは大文字で始める

というパターンが妥当で(実際、以前からそうなっていたし)、イベントの自動生成もプライベートなフィールド名前(button1)から自動でイベントメソッド名を付けている訳で、このキャメルケースでいいような気もする。

というか、Microsoft の初期設定はまあいいから、プライベートなメソッドはキャメルケースにして小文字で始めたいんですけど。

どこで命名規約を決めているのか?

「ツール」→「オプション」で、「テキストエディタ」→「C#」→「コードスタイル」→「名前指定」で命名規約は決めてある。

image

デフォルトでは、3つだけ指定してあって、

  • インターフェースを「I」で始めること
  • 型(クラス)をパスカルケース(大文字始まり)にすること
  • フィールド以外のメンバはパスカールケース(大文字始まり)にすること

というのが決めれれていて、最後の「フィールド以外」ってのに引っ掛かっている。なので、手っ取り早いのは、この規約を消してしまうことだ。右にある「×」ボタンを押して規約から外してしまうと、先の自動生成のイベント名に適用されなくなって警告がでなくなる。

「フィールド以外のメンバー」の適用範囲を public のみにする

もう少し意図通りに動かしたいときは、「仕様の管理」をクリックして「フィールド以外のメンバー」の適用範囲を変えてやる。

image

デフォルトは、こんな感じにアクセシビリティが、public から private まで全てになっている。ここで private とかを外して public だけにする。

image

意図的に、public だけパスカルケースにする

image

C# のエディタを閉じて、開き直すと無事警告がでなくなる。

image

命名規約に return がないのが問題?

これで無事、イベントで生成されるメソッド名に警告がでなくなるのだが、じゃあ、最初に話を戻して

  • プライベートなメソッドは小文字で始める
  • パブリックなメソッドは大文字で始める

という2つの規約を作りたいときはどうするのか?という問題が残る。Visual Studio の命名規則の適用って、上から順番に or で対処していくパターンなので、Outlook のメール振り分けのように途中で return ってのはない。なので、重要度に「なし」をするとうまく合格させることもできるのだが、ここで例外を作るとロジック的に複雑になってしまう。なので、適用範囲を絞りながら少しずつ追加するのがベターじゃないかなと。

でもって、プライベートなメソッドは必ずキャメルケース(小文字始まり)で、ということにするには、こんな風に規則を追加する。

image

最後の2つで、メソッドの命名規則(private と public)を分けることになる。

image

カテゴリー: C# | Visual Studioで指摘される「名前指定の規則違反」を直す はコメントを受け付けていません

EFのクラスをMVVMのINotifyPropertyChangedに対応させる裏技

WPFでEF(Entitiy Framework)を使っていると、リスト表示には楽なのだが、データを反映するためにはINotifyPropertyChangedを継承させなくてはいけなくて、そこが面倒くさい。いちいち、ViewModel クラスで EF のクラスをくるめば良いのだが、それを自動化したい。
特にマスター管理用の画面なんかを作るときは、ASP.NET MVCのように足場を自動化させておきたいというのもある。

EFで出力されるクラスは、下記のようなクラスなので、

public partial class 社員名
{
    public int 社員ID { get; set; }
    public Nullable<int> 部署ID { get; set; }
    public Nullable<int> 営業担当FLG { get; set; }
    public string 社員名1 { get; set; }
}

MVVM対応のINotifyPropertyChangedインターフェースを継承したこんなクラスにしたいわけだ。

public partial class 社員名 : ObservableObject 
{
    private int _社員ID ;
    public int 社員ID {
    	get { return _社員ID; }
    	set { SetProperty( ref _社員ID, value, nameof(社員ID)); }
    }
    private Nullable<int> _部署ID ;
    public Nullable<int> 部署ID {
    	get { return _部署ID; }
    	set { SetProperty( ref _部署ID, value, nameof(部署ID)); }
    }
    private Nullable<int> _営業担当FLG ;
    public Nullable<int> 営業担当FLG {
    	get { return _営業担当FLG; }
    	set { SetProperty( ref _営業担当FLG, value, nameof(営業担当FLG)); }
    }
    private string _社員名1 ;
    public string 社員名1 {
    	get { return _社員名1; }
    	set { SetProperty( ref _社員名1, value, nameof(社員名1)); }
    }
}

こうしておくと、リストビューで一覧表示をさせておいて、選択時にテキストボックスへ表示、そしてテキストボックスでの変更がリストビューに反映される、というところが自動化される。

Model1.ttを直接書き替える

元のクラスにスクリプトを通して、MVVM対応のクラスを作ろうかとも思ったのだが、もっと簡単に EF で出力される Model1.tt を直接書き替えてしまう。T4 で書かれている Model1.tt はファイルに保存した途端にテンプレートに従って各クラスが出力される。

public class CodeStringGenerator
{
	...
    public string Property(EdmProperty edmProperty)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
			// ★ここを書き替え
@"private {1} _{2} ;
	{0} {1} {2} {{
		get {{ return _{2}; }}
		set {{ SetProperty( ref _{2}, value, nameof({2})); }}
	}}",

			// "{0} {1} {2} {{ {3}get; {4}set; }}",
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
    }
	...
    public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
			// ★ここを書き替え
            "{0} {1}partial class {2}{3} : ObservableObject ",
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)));
    }
	...
}

大ざっぱだが、CodeStringGeneratorクラスのPropertyメソッドとEntityClassOpeningメソッドの内容を書き替えてしまう。

EntityClassOpening は、エンティティのクラスを出力するところなので、INotifyPropertyChangedインターフェースを実装した「ObservableObject」クラスを継承するように書き替える。ObservableObjectクラスは別途自前で用意しておく。
Property メソッドは、プロパティを出力するところなので、SetProperty メソッドを呼び出して変更通知が飛ぶようにする。

これを保存すると、各エンティティのクラスが、

public partial class 社員名 : ObservableObject 
{
    private int _社員ID ;
    public int 社員ID {
    	get { return _社員ID; }
    	set { SetProperty( ref _社員ID, value, nameof(社員ID)); }
    }
	...
}

のように書き変わる。

のような画面が、バインドだけで作れるようになる。

カテゴリー: 開発, C# | EFのクラスをMVVMのINotifyPropertyChangedに対応させる裏技 はコメントを受け付けていません

DataGrid の SelectedItem にバインドしていると、ItemsSource 再設定時に落ちる

ちょっとばかし、ハマったのでメモ書きをしておく。

WPFのDataGridにデータバインドする

WPFでリスト表示をするときに、ListViewかDataGridを使うと思うのだが、ここで MVVM を使って ItemsSource にバインドする。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height=&quot;50&quot;/>
        <RowDefinition Height=&quot;*&quot;/>
    </Grid.RowDefinitions>
    <DataGrid Grid.Row=&quot;1&quot; x:Name=&quot;lv&quot;
                ItemsSource=&quot;{Binding Items}&quot;
                SelectedItem=&quot;{Binding Item}&quot;
                SelectedIndex=&quot;{Binding ItemIndex}&quot;      >
    </DataGrid>

ViewModel を作って

public class ViewModel : ObservableObject
{
private List<商品> _items;
public List<商品> Items
{
    get { return _items; }
    set { SetProperty(ref _items, value, nameof(Items)); }
}

private 商品 _item;
public 商品 Item
{
    get { return _item; }
    set { SetProperty(ref _item, value, nameof(Item)); }
}
private int _itemIndex;
public int ItemIndex 
{
        get { return _itemIndex; }
        set { SetProperty(ref _itemIndex, value, nameof(ItemIndex)); }
    }
}

ロード時に表示させる。

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    _vm = new ViewModel();
    var ent = new Database1Entities();
    _vm.Items = ent.商品.ToList();
    this.DataContext = _vm;
}
ViewModel _vm;

実行すると、こんな感じ。

行を選択するとバインドしている SelectedItem にオブジェクト(ここでは商品オブジェクト)が入るので、詳細データとかを別画面で表示するには便利だったりする。
SelectedIndex でもいいような気もするのだが、実は、DataGrid はヘッダをクリックするとソートする機能が初めから入っていて、結構便利。でもって、ソートしたときはインデックスが変わってしまうので、SelectedIndex では困るので、SelectedItem から選択したオブジェクトを拾うと良いという訳。渡している List<商品> と DataGird 自身が持っている Items の中身が異なる(ソートされている)のでずれがでてくる。

リストをリフレッシュすると落ちる

さて、普段は DataGrid の中身は変わらないので更新することはないのだが、なんらかの理由でリストを再描画させたいとしよう。このときに、ItemsSource にバインドしている ViewModel 側の Items を更新すればよいと思って、次な感じにすると、

private void clickInit(object sender, RoutedEventArgs e)
{
    var ent = new Database1Entities();
    _vm.Items = ent.商品.ToList();
}

~~~
System.NullReferenceException が発生しました
HResult=0x80004003
Message=オブジェクト参照がオブジェクト インスタンスに設定されていません。
~~~

なる例外が発生して落ちる。それも、_vm.Items に代入しようとしているところで落ちるので始末が負えない。
実は、初期表示をしていて、カーソルを DataGird にあてない状態(選択行が無い状態)の場合は落ちなくて、一度選択した後には例外が発生して落ちるのである。
かなり不思議な現象である。ネットでもあちこち困っている人がいるもの、ぴっちりとした解決策は無いように見える、が、

実は、このように Item に null を代入すると落ちなくなる。

private void clickReload(object sender, RoutedEventArgs e)
{
    var ent = new Database1Entities();
    _vm.Item = null;	// ★
    _vm.Items = ent.商品.ToList();
}

そう、ViewModel を見るとわかるのだが、DataGrid の SelectedItem へのバインドが悪さをしている。Items に新しいリストを設定して、ItemsSource プロパティを更新してしまうと、SelectedItem が行先を見失う(?)ことになって NULL例外が発生するらしい。このため、先に SelectedItem に null を設定してやって、カーソルを外した後で ItemsSource に新しいリストを設定することになる。

おまけ

じゃあ、ObservableCollection を使って、一度クリアしたあとに1つずつ追加しけばいいじゃないか、と思うかもしれないが、実はそれでも落ちる。

private void clickReload(object sender, RoutedEventArgs e)
{
    var ent = new Database1Entities();
    _vm.Item = null;	// ★
    _vm.Items.Clear();
    foreach (var it in ent.商品) _vm.Items.Add(it);
}

普通の Items の更新と同じように、★部分の null の設定がないと、_vm.Items.Clear() 行で例外が発生して落ちてしまう。これも、SelectedItem が示し先を見失ってしまうためらしい。

カテゴリー: 開発, C#, XAML | DataGrid の SelectedItem にバインドしていると、ItemsSource 再設定時に落ちる はコメントを受け付けていません

DataSetはLINQの夢を半分だけ見られるか?

1か月もブログ記事を書かないとサーバー側のキャッシュがおかしくなる(っぽい)ので、穴埋めに。

Visual Studio 2017には「LINQ to SQL」がないよ

ふと、書籍の改訂版を作っていて、2015から2017に移行するときにテンプレートに「LINQ to SQL」がないことに気付きました。

Visual Studio 2017

Visual Studio 2015

LINQ to SQLが何じゃ?と思うかもしれませんが(実際、何じゃ?という代物なんですが)、DataSetを使っていた後に C#/VB で LINQ が使えるようになって、今の Entity Framework が出る前に SQL Server を LINQ で扱うとという「非常にピンポイント」なコンポーネントだったんですね。確か、同時期に「EF Context」が出てたとおもうのですが、EF Contextのほうはデザイナがなくて、データベースからドラッグ&ドロップするにはこの「LINQ to SQL」のほうが(解説に)便利だったんですよ。

ところで、今「EF 6.x DbContext」を追加しようとすると「置換トークン ‘$edmxInputFile$’ を、生成元になる .edmx ファイルの実際の名前で上書きしてください。」と出るのは、不具合なのかUpdate不足なのか…不明ですが。それはさておき。

「LINQ to SQL」自体は、SQL Serverにしか対応していなくて、MySQLやSQLiteには使えないから当時から汎用性がないのは承知なのですが、逆に言えば SQL Server だけ使うことが決まっていればこれでも良い訳です。まあ、今から作るとなると、EF のほうを使ったほうがいいので、「ADO.NET Entity Data Model」を使ったほうがいいよねということになります。

今後 2017 のテンプレートとして追加されるかどうかは不明なのですが、まあディスコンという奴です。

DataSetってLINQができたっけ?

じゃあ、仕方がない古いところで DataSet って LINQ ってできたっけ?と思ってみたのがこんなところです。

古き良き時代?のDataSetを追加して、SQL Server から2つのテーブルをドラッグ&ドロップします。すると型付きの DataTable ができますよね。

でもって、3つのボタンを付けて動かしてみます。

private void button1_Click(object sender, EventArgs e)
{
    var ad = new DataSet1TableAdapters.ProductTableAdapter();
    var ta = ad.GetData();
    var q = from t in ta select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button2_Click(object sender, EventArgs e)
{
    var ad = new DataSet1TableAdapters.ProductTableAdapter();
    var ta = ad.GetData();
    var q = from t in ta where t.Id == 2 select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button3_Click(object sender, EventArgs e)
{
    var ad1 = new DataSet1TableAdapters.ProductTableAdapter();
    var tproduct = ad1.GetData();
    var ad2 = new DataSet1TableAdapters.StoreTableAdapter();
    var tstore = ad2.GetData();

    var q = from p in tproduct
            join s in tstore on p.Id equals s.ProductId
            select $"{p.Name}({s.Description}) {s.Stored}個";
    listBox1.DataSource = q.ToList();
}

動かしてみると、おお、なんか(一見)LINQが動いているように見えますね。キチンとテーブル結合(join)も動いているし。

なんだ、これって DataSet が進化して LINQ が使えるようになったんじゃん、と喜ぶの早いところで、実は ad.GetData() のところでテーブルの内容をごっそりもってきて単純に List 同士で join しているだけなんですね。なので、button3_Click メソッド内に書いてあるように、2つのテーブルをそれぞれ TableAdapter を使って GetData で全検索。そして、List 化された tproduct と tstore を join しているだけなので、メモリは膨大になります。まあ、今の PC だと 1万件位ならば大丈夫でしょうが、100万件位になると PC は固まるだろうし、そもそもネットワーク負荷が膨大。

素直に EF を使う

なので、素直に「ADO.NET Entity Data Model」を使おうって話です。
書き替えたのがこれ。

private void button1_Click(object sender, EventArgs e)
{
    var ent = new testdbEntities();
    var q = from t in ent.Product select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button2_Click(object sender, EventArgs e)
{
    var ent = new testdbEntities();
    ent.Database.Log = sql => { System.Diagnostics.Debug.WriteLine(sql); };
    var q = from t in ent.Product where t.Id == 2  select t.Name;
    listBox1.DataSource = q.ToList();
}

private void button3_Click(object sender, EventArgs e)
{
    var ent = new testdbEntities();
    ent.Database.Log = sql => { System.Diagnostics.Debug.WriteLine(sql); };
    var q = from p in ent.Product
            join s in ent.Store on p.Id equals s.ProductId
            select $&quot;{p.Name}({s.Description}) {s.Stored}個&quot;; // ★
    listBox1.DataSource = q.ToList();
}

ent.Database.Log なところは、遅延実行される SQL をデバッグ出力しているところです。先の DataSet には、この Log なるところがない(そもそも List の結合なので SQL 文を発行してるわけではない)ところで区別がつきます。

あと、試してみると解りますが、★の部分は動きません。実行エラーになります。DataSet のときは、List の結合で C# の範囲内で動くから ToString 等の文字列関係のものは動くのですが、EF の場合は SQL を発行する(SQL Server側で動く)ので ToString 等の関数がないので「実行エラー」になります。こんなところでも、LINQ がどっちで動いているかどうかわかります。

でもって、DataSetはどうするのか?

よく覚えていないのですが、確か DataSet の型付きDataTableの生成の仕組みが変わって、System.Data.TypedTableBase を継承するようになったからですよね。

DataRowCollection に LINQ 機能を付与する ( ソフトウェア ) – 憂国なプログラマ – Yahoo!ブログ
https://blogs.yahoo.co.jp/hilapon/7600156.html

DataSet / DataTableに対してLINQを使う方法 – @kotyのブログ
http://koty.hatenablog.com/entry/20110524/1306249991

2009年頃の記事を見ると DataRowCollection は IEnumerable を継承していないので LINQ が動きませんとなっていますが、現時点での生成コードを見ると Rows のほうも IEnumerable を継承するように書き変わっています。

つまり「ひそかに DataSet は進化している」訳です。まあ、それでも EF のほうを使う訳ですが。

なので、データベースに直接アクセスしてデータを引っ張ってくる場合は EF を使った遅延実行が必須なのですが、アプリケーション内で扱うマスターデータとか定義値を羅列した enum タイプのデータを join する場合は、DataTable を使うという方法もアリですね。
よくやる、初回だけデータベースから拾ってきて combobox とか listbox とかデータバインドするというアレです。まあ、EF でも同じことはできるので、新規の場合は意味はないのですが、既存の DataTable コードを EF に直さなくても、自前キャッシュを作り直して LINQ を導入してみるのもありかな、と思った次第です。

カテゴリー: 開発, C# | DataSetはLINQの夢を半分だけ見られるか? はコメントを受け付けていません

年末なので ROS を調べてちらほらと

ざっと、調べていったことを感想をメモしておきます。

参考文献

読んでいる書籍は以下の通り。

実はチュートリアルも含めて、公式の ros.org にあったりするのですが、書籍/Kindle でブーストしています。邦訳オライリー本は、英語オライリー本が元ネタになるので、実は中身はちょっと古めです。ですが、実はそれで十分だったりします。訳は後述します。

ROS.org | Powering the world’s robots

OpenRTM と ROS

実は、ロボット制御の方法として一方で OpenRTM-aist | The power to connect があります。OpenRTM のほうは国産でなので、日本でロボットをやっていく分には OpenRTM のほうが良いのでは?と思っていた(個人的に)時期もあるのですが、自分はいまのところ ROS な方向で勉強中。そういえば、OpenRTM は Open と名はついているけどオープンではなかったものですが、今ざっとみるとオープンソースな感じになったようです。

Ubuntu と ROS

なんで、ROS は Linux 上で(特に Ubuntu 上で)を想定しいるんだろうか?と思っていたのですが理由がわかりました。

  • ROS の安定パッケージを作りやすくする
  • Ubuntu の LTS (Long Time Support) と時期を合わせている。

2年ほど前 PLAN2 を購入しようと決めたときに、ROS 対応の Intel Edison 版というのがありました。そのときは漠然と ROS + Linux のイメージしかなかったのですが、Edison をつかうことにより ROS パッケージをそのまま使うことができるというメリットがあったのです。実は ROS には Arduino にシリアル接続するパッケージもあるのですが、プログラムの作りやすさ(配布しやすさ?)としては Linux 上のプロセスとして統一したほうが開発効率がよい、というところでしょう。

ただし、現実には Edison 自体がディスコンになってしまったので、PLEN2 + ROS 対応というのはサポート的に難しくなってしまったということろです。

ROS はひとつのロボットを扱う

複数のプロセスが協調動作をする(実際は master がいるのですが)ROS では、ひとつのプロセスがひとつの制御機器を扱う(複数扱えるけど、ひとつに対応させたほうがよさそう)ので、複数のプロセスで「ひとつのロボットを動かす」というのが前提になっています。じゃあ、協調動作するロボット(双腕のロボットとか、複数台が協調するロボットアームとか)はどうするのかというと、まあ、そのまま ROS で作ってもよいし、次のバージョンである ROS2 で作るのがよいというところなんでしょう。ROS2 自体はまで調べていないので言及はしませんが、

  • 組み込み機器に対応している(現状のROSでは、Linux機器に限られる)
  • 各プロセスが直接相互に通信する(現状は master を通している)

というスタイルになるそうです。私としては、まずひとつのロボットを動かせる位に ROS をやってからと思っているので、もうちょっとスタートは前から

ROS Indigo で十分かも

Distributions – ROS Wiki を見ると、ROS は毎年1回定期的にバージョンアップされています。おそらく、Ubuntu のバージョンアップと合わせる形でもあるのでしょうし、ロボットのようなハードウェアを扱う場合、長期的に安定して制御装置が動く必要があるので「あまり頻繁なミドルウェアの更新は望ましくない」としたのでしょう。

そんな中で、現在の最新の ROS は、Lunar というものなのですが、実は 2014年にリリースされた Indigo とサポート期間が同じになっています。

ROS の場合、Ubuntu の apt-get で様々なパッケージを入れることになりますが、それぞれの ROS ライブラリが Linux のライブラリに依存していいます。それらの依存状況によって、毎年の ROS の更新に対して ROS パッケージが更新されていたりされていなかったりするんですよね。このあたりは、Linux の各種ライブラリのバージョンに ROS パッケージ + ROS そのものが依存するという制限のためです。

この理由から、一番 ROS が盛んだった(っぽい)ROS Indigo のパッケージ群 ROS packages が一番充実しています。おそらく、この時期から ROS2 の開発が進みだしたしたので、それ以降のパッケージの更新に手が回っていないのではないか?と想像しています。

最初、最新版がいいだろうと思って ROS Lunar でやろうとしていたのですが、あとで ROS Indigo を入れて試しています。ただし、Indigo を使おうとすると、当時にリリースしていた Ubuntu のバージョンに合わせないと動かないところが多いので、そのあたりは痛し痒し。

ロボットを使わない ROS の動作環境

ロボットや各種センサーを扱うための ROS ですが、ROS 自体は主にプロセス間通信を扱うのでセンサー無しでも利用できます。

なので、ROS を試すだけなれば、

  • VMWare 上に Ubuntu を入れて ROS を導入する
  • Ubuntu on Windows を入れて ROS を導入する
  • ラズパイの Ubuntu を使い ROS を導入する

ということが可能です。これは、Windows 10 の Ubuntu on Windows(WSL/ Windows Subsystem for Linux)で ROS を入れた例ですが、きちんと ROS の roscore/talker/listener のプログラムが動いてます。

image

ROS Lunar のインストール手順は、ひたすら lunar/Installation/Ubuntu – ROS Wiki を追っていくだけです。ペタペタとコピペしていけば、時間はかかかりますが確実に ROS がインストールできます。

超簡単な ROS の仕組み

要は、出版-購読型モデル です。Publish/Subscribe パターンが分かっていれば、ROS の各プロセスの動きに納得がいくし、新しくパッケージを作るのもやりやすいでしょう。

image

要は、プロセス間通信をするときに、Publisher(送り手)とSubscriber(受け手)が直接会話をしません。いったん、トピックという形式にに直して中央の master(ROS では roscore )を通します。これ、いちいち master を通すのが面倒という話もありますが、利点として、

  • publisher は、受け手の生き死に関わらず topic を送れる(master に送るので)
  • subscriber は、送り手の生き死ににかかわらず topic を受け取れる(master がキープしているので)

という利点があります。いわゆる amazon の在庫や流通の倉庫みたいな役割を持たせるのが pub/sub パターンです。これで、何が便利/嬉しいかというと(これ、英語では Are you happy ? … これで良いか?と聞かれるので、Happy = 嬉しい/幸せの直訳ですよ)、

  • 高速なセンサーの類は、ひとまず高速に master にデータをアップロードできる
  • 定期的なデータ抽出は、センサーの状態にかかわらず Subscriber 役の PC などから取り出せる

ということです。いわゆる、クラウドの IoT Hub の仕組みです。というかクラウドの IoT Hub がこれですよね。

そんな訳で、センサーやカメラ機器、場合によっては Android のようなものが Publisher として作ることができます。それを抽出する側の PC だったり Web サーバーだったりスマホだったりするのが Subscriber として作ればよいのです。もちろん、両方の機能を持たせてコンバーターとして働くプロセスを作る事も可能です(実際、そういうパッケージがある)。

データは、Topic で引き渡します。ROS の場合は、あらかじめ msg ファイルを作ることになり、大方のデータ形式は用意されています。ここの Topic で扱う構造体は、オライリーの ROS 本では「標準のものを推奨」していますが、自前で構築する場合は、BLE の GATT よろしくバンバン作ってしまったほうがメッセージのやり取りをするのが便利ではないか、と思っています。まあ、それだと既存の機器につながらなくなるパターンに陥るのですが、要所要所にデータコンバートをする Pub/Sub なプロセスを挟み込めばいいのです(多少、通信効率は落ちるけど)。

 

プログラム言語は Python と C++

ROS の各種サンプルは Python で書かれはいますが、C++ も使えます。というか C++ を使ったほうが便利ですよ、きっとたぶん。

ちょっとした操作ならば Python でもいいんですが、あれこれと各種ライブラリを使ってコンバートしたり既存の科学計算を利用しようとすると、そのままライブラリを組み込める C++ のほうが便利です。とは言いつつ、OpenCV も Python から扱えるし(ROS 自体にも OpenCV を扱うパッケージもあるし)、Python で突き進んでもいいかもしれませんね。

まあ、私の場合、C++ のほうがやりやすいので。

本格的な ROS の Python コードは長かったりするのですが、基本は Pub/Sub パターンで作られた ROS ライブラリを使うことになるので、ひとつのプログラムは 100 行程度で書ける感じです(まだ本格的に作ってはいないので)。たとえば、画像を加工する部分のコードは、ROS であってもなくても関係ないわけで、自前ライブラリとして TDD を使って書いておきます。それを Pub/Sub のパターンに乗せるために、100 行位追加すればよいので、センサーの類を使って通信するプログラムの場合は ROS を使うと開発効率がいいのでは?と思っています。

 

そんな訳で、まだ本格的に ROS を使っている訳ではないのですが、あれこれの情報を見てざっとインストールしたところまでの感想です。どうやら、ラズパイの Ubuntu を入れれば ROS のインストールもさっくりと済むらしいので、

  • Ubuntu on Raspberry Pi
  • タミヤのギアボックス
  • Arduino でモーターと距離センサーの制御
  • USB カメラ から入力して PC で閲覧

までを作ってみようかなと。そうそう、同時にデルタ式の 3D プリンタを組み立ててロボットアームに仕立て上げるのも(1年越しになってるし)。

カテゴリー: ROS | 年末なので ROS を調べてちらほらと はコメントを受け付けていません

LattePanda と Siv3D(仮)

このエントリーは Siv3D Advent Calendar 2017 – Qiita の 12 日目のエントリーです。前のエントリーは、para7 さんの OpenSiv3Dのトランプ描画機能(PlayingCard) です。

LattePanda とは

唐突ですが、LattePanda という組み込みボードがあります。組み込みボードというとRaspberry Pi が有名だったり、もっと小さくやろうと思うと Arduino が良かったりというのもあるのですが、Raspberry Pi は OS が Rasbian という Linux であったり、Arduino の場合はそもそも OS が無かったりという特徴があります。じゃあ、LattePanda はなんじゃ?というと、

  • Arduino のような GPIO ピンを持っていて
  • Windows 10 が動く

という特徴を持つボードです。

image

まあ、さっそく種明かしをしてしまえば、Windows 10 が動くボードと Arduino が I2C でくっついているだけのボードなので、普通のノート PC と Arduino を USB ケーブルで接続してしまえば同じことができます。中古なノートPCを買って、格安な Arduino を買えばモニタ付きで(当然 LattePanda にはモニタがありません)組み込みな環境が整えられるので、じゃあ LattePanda を使う意味があるのか?という問題は多々あるのですが、

  • 大きさがラズパイ並みに小さい(ラズパイよりも一回り大きい程度)
  • 電源が USB micro で 5V2A 程度で動く
  • モニタが HDMI で繋げる
  • フルな Windows 10 が動く

大きさがラズパイ並みということはモニタを必要としない場合には、展示品の脇において制御するときなど場所が小さくて済みます。電源もノートPCのような特殊なアダプタを必要とせずスマートフォンの充電器で十分です。ちなみに、スマホのモバイルバッテリで動かすこともできます(もちろん、バッテリー切れになると OS ごと落ちるので、かなり難点ですが)。

また、HDMI コネクタがついているので、大抵のモニタならば行けます。秋ごろに学研ワールドアイという球型モニタで試してみましたが十分いけました。

そして、なんといってもフルで Windows 10 が動くということでしょう。ラズパイ+Windows IoT Core という組み合わせもあるのですが(私としては、それはそれで使いようがあるのだけれど)、この小ささでフルの Windows 10 が動くのであれば動作するアプリを UWP アプリにしなくても、Windows アプリのままいけるということです。

価格としては秋月 http://akizukidenshi.com/catalog/g/gM-12549/ で 14k 位、直で DFROBOT https://www.dfrobot.com/product-1405.html から買うと $120 位なのであまり変わりません。私が買ったときは、直で買うしかなかったので郵送料が掛かりましたがあまり値段は変わっていないハズです。

ということは Siv3D が動くということだな

フルで Windows 10 が動くということは DirectX とかなんか適当な GPU 絡みのなんとかも動くはずです。ためしに、OpenSiv3D のサンプルコードをダウンロードを動かしてみたのが、これです。

image

球型モニタを使っているので走馬燈のような不思議なものができますね。まあ、走馬燈のほうが相当安いんですが。それは言わない約束で。

という訳で、こういう小型のボードと OpenSiv3D あるいは Siv3D の組み合わせると、ユーザーインターフェース記述が手軽な Siv3D とボードの小ささ(裏に隠れるという意味で)の相乗効果で何か新しい道が開けるのではないか?(新しくなくてもいいけど)と思った次第なのです。

 

じゃあ、具体的に何か?ってのをプログラマならばコードを書かねば、と思ってたところなのですがね、ちょっと先週の土曜日に子供からインフルエンザを移されたらしく沈没しておりまして、現在も沈没中。でもって、寝ながら考えていたネタが、hota1024 さんの Siv3Dで15パズルを極限までハイクオリティーにしてみる にネタ被りしてしまったという。要はアズレンの16パズルを16パズルにするというネタだったのですが…どうしたものか。

そんな訳で、形だけでもバトン的に AkiraKoizumi さんの「Siv3Dでグラデーション文字を作る」まで繋げておきます。完治したら、ここの記事を増やしておきますので、しばしお待ちを、では。

追記 2017/12/30

帰省中に、えいっと25駒のスライドパズルを作りました。LattePanda を持ってくるのを忘れてしまったので、LattePanda で動かす場合には、ってのは後から書きますが。ひとまず動作するコードだけをざっと。

# include <Siv3D.hpp> // OpenSiv3D v0.1.7

// 画像を指定位置でクリップする
s3d::Image &clip(const s3d::Image &src, s3d::Image &dest, int l, int t, int w, int h)
{
	for (int y = 0; y < h; y++ ) {
		for (int x = 0; x < w; x++) {
			dest[y][x] = src[t + y][l + x];
		}
	}
	return dest;
}

/// スライドパズルのボードクラス
class Board {

	int xmax;
	int ymax;
	int cellWidth, cellHeight;
	std::vector<Texture*> *_cells;
	std::vector<Texture*> *_goal;
	Image *_image;
	Texture *_textrueOriginal;
	Texture *_blank;

public:
	/// ボードの初期化
	Board(int width, int height) {
		xmax = width;
		ymax = height;
		_cells = new std::vector<Texture*>(xmax * ymax);
	}
public:
	/// 画像を設定する
	void setImage(const wchar_t *path) {
		_image = new Image(FilePath(String(path)));
		_textrueOriginal = new Texture(*_image);

		cellWidth = _image->width() / xmax;
		cellHeight = _image->height() / ymax;
		Image imageTemp(cellWidth, cellHeight);
		for (int y = 0; y < ymax; y++) {
			for (int x = 0; x < xmax; x++) {
				clip(*_image, imageTemp, x * cellWidth, y * cellHeight, cellWidth, cellHeight);
				_cells->at(x + y * xmax) = new Texture(imageTemp);
			}
		}
		// 25枚目だけ黒で塗りつぶし
		const Image imageBlank(cellWidth, cellHeight, Color(100, 100, 100));
		_blank = _cells->at(xmax*ymax -1) = new Texture(imageBlank);

		// 最初の配置を覚えておく
		_goal = new std::vector<Texture*>();
		for (auto it : *_cells) {
			_goal->push_back(it);
		}
	}

	/// シャッフル
	void shuffle()
	{
		std::random_device seed_gen;
		std::mt19937 engine(seed_gen());
		std::shuffle(_cells->begin(), _cells->end(), engine);
	}

	/// 完成かどうかをチェックする
	bool IsGoal() {
		bool same = true;
		for (int i = 0; _cells->size(); i++) {
			if (_cells->at(i) != _goal->at(i)) {
				same = false;
				break;
			}
		}
		return same;
	}

	// ブランクセルの位置を取得
	s3d::Point GetBlank() {
		for (int y = 0; y < ymax; y++) {
			for (int x = 0; x < xmax; x++) {
				if (_cells->at(x + y * xmax) == _blank) {
					return s3d::Point(x, y);
				}
			}
		}
		// 本来はここには来ない
		return s3d::Point(0, 0);
	}

	// ブランクのセルを動かす
	void MoveTo(int mx, int my)
	{
		s3d::Point pt = GetBlank();
		s3d::Point pt2 = s3d::Point(pt.x + mx, pt.y + my);
		// 移動先がはみ出ていないこと
		if (pt2.x < 0 || pt2.x >= xmax) return;
		if (pt2.y < 0 || pt2.y >= ymax) return;

		// セルを移動させる
		_cells->at(pt.x + pt.y * xmax) = _cells->at(pt2.x + pt2.y * xmax);
		_cells->at(pt2.x + pt2.y * xmax ) = _blank;
	}

	/// キー操作を処理する
	void InputKey() {
		if (s3d::KeyUp.down()) this->MoveTo(    0,-1);
		if (s3d::KeyDown.down()) this->MoveTo(  0,+1);
		if (s3d::KeyLeft.down()) this->MoveTo( -1, 0);
		if (s3d::KeyRight.down()) this->MoveTo(+1, 0);
	}
	/// マウス操作を処理する
	void InputMouse() {
		if (s3d::MouseL.down()) {
			auto m = Cursor::Pos();
			auto b = s3d::Point( GetBlank().x * cellWidth, GetBlank().y * cellHeight );
			if (b.y > m.y  && ( b.x <= m.x && m.x <= b.x + cellWidth ))			 
				this->MoveTo(0, -1);
			if (b.y + cellHeight < m.y && (b.x <= m.x && m.x <= b.x + cellWidth))
				this->MoveTo(0, +1);
			if (b.x > m.x && (b.y <= m.y && m.y <= b.y + cellHeight)) 
				this->MoveTo(-1, 0);
			if (b.x + cellWidth < m.x && (b.y <= m.y && m.y <= b.y + cellHeight)) 
				this->MoveTo(1, 0);
		}
	}
	/// 現在のボードを描画する
	void Draw() {
		for (int y = 0; y < ymax; y++) {
			for (int x = 0; x < xmax; x++) {
				_cells->at(x + y * xmax)->draw(x * cellWidth, y * cellHeight);
			}
		}
	}
	/// 現在のボードを描画する
	void GoalDraw() {
		_textrueOriginal->draw();
	}
};

void Main()
{
	Graphics::SetBackground(ColorF(0.8, 0.9, 1.0));
	const Font font(50);

	// ボードの作成
	Board board(5, 5);
	// ボードに画像を読み込む
#define IMAGE L&quot;C:\\Users\\masuda\\Pictures\\IMG_0139.jpg&quot;
#define IMAGE L&quot;C:\\Users\\masuda\\Pictures\\azurlane001.jpg&quot;
	board.setImage(IMAGE);
	// 配列をシャッフル
	board.shuffle();

	while (System::Update())
	{
		// すべて揃っているか?
		if ( board.IsGoal() ) {
			//  揃っていれば、ゲーム終了
			board.GoalDraw();
			font(L&quot;GOAL, Siv3D!&quot;).drawAt(Window::Center(), Palette::Black);
			continue;
		} 

		// キー入力
		board.InputKey();
		// マウス入力
		board.InputMouse();
		// 配列に従って、画像を表示
		board.Draw();

		font(Cursor::Pos()).draw(20, 400, ColorF(0.6));
		Circle(Cursor::Pos(), 30).draw(ColorF(1, 0, 0, 0.5));
	}
}

コード自体は、200行程度です。あまりにも雑に書きすぎてしまったので、クラスはボードクラス(Board)しかありませんが、まあ、ほどよく動作します。中で、std::shuffle してしまっているので本当にスライドゲームが完成するのかは不明…というひどいゲームですが。

実は、OpenSiv3D をきちんと書くのは(たった200行ですが)、これが初めてで、12/30の午前中に初めて、途中で買い物に行って夕方にクラス化するというパターンで、調べ調べしながら4時間ほどで作っています。思うに、この手のゲームのようなものは自分の思ったように作ることが重要で、あれこれなプログラミングなテクニックは後から追加していったほうが良いのでは?と思っています。仕事自体はシステム屋さんなところが多いので、設計してからコーディング(とはいえ、紙に設計するのではなく疑似コードレベルですが)というパターンが多く、数時間で思ったようなものを作るという機会は仕事ではなかなかありません。OpenSiv3D はそういうところで、手軽に使える「道具立て」ではないかな、と思っています。

プログラムは実行するとこんな感じです。

完成すれば、こんな感じになるはず。

C++ なのにメモリを解放していないという悪さ(苦笑)はありますが、そのあたりきちんとデストラクタを作って処理するか、const 使いつつスコープ内での自動解放な機能を使えばもう少し安全なコードになるはずです。できるだけ表にポインタが出ないのが Siv3D の良いところではないか、というのにポインタを使いまくっているのがアレなのですが、main 関数だけ見れば、なんかすっきりしているという具合でしょうか。そのあたりは、C++ なオブジェクト指向のよいところ。

 

カテゴリー: 開発, C++ | LattePanda と Siv3D(仮) はコメントを受け付けていません

Orange Pi で .NET Core を動かそう

このエントリーは C# Advent Calendar 2017 – Qiita の 8日目のエントリーです。前のエントリーは atsushieno さんの libsoundio-sharpとPInvokeGeneratorについて – ものがたり です。

Orange Pi とは何か?

端的に言えば、Raspberry Pi 互換機です。ラズパイのように40本のGPIOが設定してあったり、マウスやキーボードに繋げるためのUSBコネクタがあったり、モニタに繋げるための HDMI があったりします。

Orangepi http://www.orangepi.org/

互換機であるならば、普通にRaspberry Piを買った/使ったほうがいいだろうという話もありますが(実際に初心者的には無難なラズパイ3を薦めるところですが)、まあ、2台め3台目…n台目となると値段の関係もあって、格安な互換機を買うの面白いところです。

詳しく言えば、ラズパイ3とは大きさが違ったり、メモリ量が違ったり、場合によってはラズパイ3よりも性能が良かったりするので、実際に業務として使う分には慎重な比較が必要なのでしょうが、複数のボードを使ってあれこれ試したいときにはこういう冒険もよいでしょう。

Orange Pi に OS を入れる

Orange Pi One には Allwinner H3(ARM Cortex-A7) という CPU が乗っかっています。ラズパイにも ARM が乗っているし、巷の Android スマートフォンにも ARM が乗っかっています。Intel CPU との違いは主に消費電力が低いということですね。最近では Intel CPU もほどよく低電力で動きますが(Lattepanda が 5V2.2A 程度で動いている)、Orange Pi のような ARM CPU の場合は 5V2A 以下で動いています。まあ、最近のラズパイ3となると 2.4A 程度ないとうまく起動できなかったり、機械学習で GPU をフルに使おうとすると 5V2A の範囲では電力不足で落ちてしまったりするので、どちらの CPU を使うのか?というのは単体では選択の予定が出て来るのですが。

それはさておき、ラズパイには Raspbian という Linux な OS を入れますが、Orange Pi には armbian? https://www.armbian.com/ を入れます。Orange Pi の本家から OS がダウンロードできるハズなのですが、どうもダウンロードスピードが遅いのと、root なパスワードが分からない!!! のでw armbian を使います。Raspbian は Debian ベースですが、Orange Pi には Ubuntu を入れることになります。

root/1234 で入れるので、passwd でパスワードを変更しておきましょう :) そのあとはターミナルで扱えるように

adduser ユーザー名
gpasswd -a ユーザー名 sudo

で、自分で使うユーザーを作っておいて sudo できるようにしておきます。そうすると root を使う必要がなくなりますからね。

image

Tera Term からログインできるようになると、こんな風になります。

image

実際に動作しているところは、こんな感じです。「響け!ユーフォニアム 北宇治高校吹奏楽部、波乱の第二章」の文庫本よりも小さいですね。

 

.NET Core 2.0 をインストールする

さて、やっとこさ本題です。実は Orange Pi One を選択したのは、.NET Core 2.0 を動かしたかったからなのです。実は .NET Core 2.0 の動作環境は、ARMv7 以上が必要でして、更に格安な Raspberry Pi Zero の場合は ARMv6(ARM1176JZF-S)なので、.NET Core の対象外なんですよね(https://ja.wikipedia.org/wiki/Raspberry_Pi)。勿論、Raspberry Pi Zero に普通に Raspbian を入れて gcc や Python を使ってもいいのですが。そこは「.NET Core を動かす」という手段と目的を敢えて取り違えるということで。そうそう、mono は動くはずなので、apt-get install mono-complete すれば .NET 環境は即整います。

Intel な Ubuntu の環境で .NET Core を揃えるには、Prerequisites for .NET Core on Linux | Microsoft Docs を参照に必要なライブラリをインストールしていきます。最後に dotnet-sdk-2.0.0 をインストールしてセルフビルド環境を整えます。

しかし、現時点では ARM の .NET Core は提供されていません。正確に言えば「セルフビルド環境が提供されていないが、実行環境は用意されています」という状態です。妙な状態ではありますが、

  1. PC のクロス環境で ARM 版をビルドする
  2. ARM 環境にアセンブリをコピーして実行する

の2段階を踏むと、ラズパイ3やOrange Piなどの ARM 環境でも .NET Core を動かせるようになります。この環境ですが、先ごろ発表された Windows 10 for ARM によって解消されるのではないかなと期待しています。

まず、必要なライブラリをインストールします。

sudo apt-get install libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev unzip

通常ならばこの後に、sudo apt-get install dotnet-sdk-2.0.0 するところですが、ARM 環境なので必要ありません。逆に言えば、Intel な Linux 環境でも実行環境のみ用意するのであれば、dotnet-sdk-2.0.0 自体は必要ないという訳です。

.NET Core 2.0 でビルドする

いわゆる Hello World な C# プログラムを作って .NET Core 2.0 でビルドしてみましょう。PC の環境で .

dotnet new console -n hello

を実行します。

image

ひな型となる hello プログラムができるので、ARM 用に

dotnet publish -r linux-arm

でビルドをします。-r linux-arm で実行する環境を指定して、publish で全てのアセンブリを生成させます。

image

通常は、hello.dll しかないところですが、publish にしたので、その他のアセンブリ(*.dll)やライブラリ(*.so)がひとつのフォルダーにコピーされています。

image

これを winscp などで、Orange Pi 上にコピーします。

image

実は、必要なアセンブリやライブラリは Orange Pi 側で適切なパスを通してやれば全てをコピーする必要はないのですが、色々面倒なのでごっそりコピーしてしまいます。

.NET Core 2.0 な環境で実行する

Orange Pi 側で、実行ファイル hello に実行権限を付けます。

chmod +x hello

そして実行

./hello

image

単純に hello world しかでないコンソールなものですが、これで .NET Core 2.0 な環境で動いています。

この現象で何がわかるかというと、

  • Orange Pi(あるいはRaspberry Pi)には、.NET なパッケージをインストールしていない。
  • PC でクロスコンパイラしたアセンブリを Orange Pi にコピーするだけで、Orange Pi 上で実行ができる。

ところです。まあ、実際のところは、libicu-dev やらなにやらをインストールしているので開発環境がインストールされてはいるのですが(このあたり dev 付きじゃないほうで環境を整えることはできるんでしょうか?)、比較的 Linux な環境を肥大化させないで済むという利点はありますよね。

あと、重たいコンパイル/ビルドな処理を、PC 側でのクロス開発環境で済ませることができます。この場合は、クロス開発環境を Windows の PC 上で行いましたが、Intel な Ubuntu 上とか macOS な環境でも実行は可能なはずです(ここのあたりは試していない)。

Linux & .NET Core で何をする?

基本、.NET Core でできるものは mono でもできるので、Linux や Rasbian 上で .NET Core じゃないと駄目っていう話はないのですが、

  • ASP.NET Core + .NET Core の組み合わせで Linux 上で常駐(デーモン)化させる。
  • Python + C言語ライブラリや、Node.js + ライブラリの組み合わせで動作しているものを、.NET Core + ライブラリの組み合わせに置き換えられる

というメリットがあります。このあたり Azure や AWS を使ってクラウド化してしまうほうが良いというパターンもありますが、

  • 頻繁に該当 API を呼び出す必要ある。
  • そもそも、ネットワークが細い(LoRaWANとか)

場合にはローカルのライブラリを呼び出すのが必須条件になります。そういったチープなメモリとチープなCPU(Orange Pi等を見る限り、全然チープではありませんが)な環境で何かを動かすときに有効な手段です。勿論、これ以下で動かす場合には、そもそも Linux などの OS を動かすことなく(mbed OS とかあるけど)別な方法をとりますよね。

ちなみに、ラズパイやOrange Piのような ARM CPU 上では Visual Studio Code が動きません。Intel Linux だと動くので、仮想上の Ubuntu 上では使ってみたりするのですが、いまのところは PC でクロスビルド → Raspberry Pi/Orange Pi に転送、という手段を取っています。そういう意味では、セルフ環境がラズパイ上に無くてもあまり苦になりません。

 

簡単な *.so を呼び出してみる

というわけで、Linux 上で .NET Core な環境で閉じてしまってはあまり意味がないので、自前の C API ライブラリを作ってそれを呼び出してみましょう。OpenCV を呼び出したり、機械学習っぽいライブラリを呼び出す例でもいいのですが、OpenCV だと OpenCvSharp を使うのが手っ取り早かったり、機械学習や ROS のほうは私自身がまで実験中なので手が出なかった(苦笑)というのもあるので、まあ、自前の C API ライブラリということで。

Orange Pi 上で fibo.c を作ります。まあ、コンソールなので vi で。

image

g++ でコンパイルします。

g++ -c fibo.c
g++ -shared fibo.o -o libfibo.so

fibo.c の中の fibo 関数が C# へエクスポートされる C API になります。Windows 上の DLL だとあれこれと設定しないと駄目なんですが、Linux に限ってしまえば extern “C” を付けるだけで十分です。

C# 側の hello.cs を fibo 関数を呼び出すようにします。

using System;

namespace hello
{
 class Program
 {
  static void Main(string[] args)
  {
   Console.WriteLine("Hello World!");

   for ( int n=1; n&lt;=10; n++ ) {
    int ans = fibo( n );
    Console.WriteLine( $"{n} -&gt; {ans}");
   }
  }
  [global::System.Runtime.InteropServices.DllImport("fibo", EntryPoint="fibo")]
  public static extern int fibo( int max );
 }
}

これを windows 上で dotnet publish -r linux-arm でクロスビルドして Orange Pi 上へ転送。

*.so ファイルは、hello と同じ場所から呼べるように ln -s しておくと便利です。

image

./hello で実行すれば、C API で作った自前のフィボナッチ関数が呼び出されていることがわかりますね。実際にあれこれやる場合は、構造体の問題とか文字コードの問題とかがあるので色々とややこしいのですが、C# と C API の連携が比較的簡単なことがわかります。つまりは、既存の C API のライブラリがあれば、いちいち C# に直す必要はなくて適当な dlimport を使って連携が可能な訳です。

 

dllimport のおまけ

C/C++ と C# の相互運用のあたりで swig を含めて改めて分かったのですが、実は dllimport は共有ファイルの名称は自動的に変換してくれるのですね。Interop with Native Libraries | Mono をみると、dllimport(“fibo”) と書いた場合は、

  • Windows 上では fibo.dll が呼び出される
  • Linux 上では libfibo.so が呼び出される
  • macOS 上では libfibo.dylib

な形で呼び変えてくれます。当然、.NET Core もこれに倣っていてファイル名を環境によって変えてくれます。

 

お次は、matarillo さんの「Null 非許容な参照型」ということで。

さらにおまけ

Xamarin なカレンダーにちょうど dllimport な話があるので、こちらも。

DllImport クロスプラットフォーム nuget パッケージ作成方法(unitypackage もあるよ!) – Qiita
https://qiita.com/TNaruto/items/ac36d87fed07e80ee8be

 

カテゴリー: 開発, C# | Orange Pi で .NET Core を動かそう はコメントを受け付けていません

SWIG の文字列(string)の扱いが少しややこしいので、Marshal.* を使ってみるテスト

内部コードを std::string/char * にするか std::wstring/wchar_t * にするかで、SWIG の挙動が変わるような感じなので、その前段階のテスト

戻り値をstring型にする SWIG の技

C#側からみればC++のDLLから文字列を取得したい場合、GetName( const char *, int ) としてバッファを渡すよりも、string GetName() な形で、string 型を返して欲しいわけです。そうなると、dllimport の部分も

 [DllImport("hellodll", EntryPoint = "GetNameStr")]
 static extern string GetNameStr();

な形にしておきたいところですよね。しかし、これはできません。戻り値は string に自動変換してくれないんですね。

不思議なことに、SWIG の場合は戻り値に string を使えて、まあこんな風に string 型を渡したり受け取ったりできます。

    var hello = new Hello();

    hello.SetName("Tomoaki Masuda");
    var name = hello.GetName();
    Console.WriteLine($"name: {name}");

当然のことながら、これは Hello クラス内でラップしている訳で、

  public string GetName() {
    string ret = helloswigPINVOKE.Hello_GetName(swigCPtr);
    return ret;
  }

となっていますが、dllimport を見ると、下記のようになっていて、string 型を返しています。

  [global::System.Runtime.InteropServices.DllImport("helloswig", EntryPoint="CSharp_Hello_GetName")]
  public static extern string Hello_GetName(global::System.Runtime.InteropServices.HandleRef jarg1);

dllimport で string 型を返しているので、どういうトリックになっているのかとコードを追ってみると、

  protected class SWIGStringHelper {

    public delegate string SWIGStringDelegate(string message);
    static SWIGStringDelegate stringDelegate = new SWIGStringDelegate(CreateString);

    [global::System.Runtime.InteropServices.DllImport("helloswig", EntryPoint="SWIGRegisterStringCallback_helloswig")]
    public static extern void SWIGRegisterStringCallback_helloswig(SWIGStringDelegate stringDelegate);

    static string CreateString(string cString) {
      return cString;
    }

    static SWIGStringHelper() {
      SWIGRegisterStringCallback_helloswig(stringDelegate);
    }
  }

文字列専用のヘルパークラスがあって、string 型を戻すときは

  1. C++ 側でここで設定されているデリゲート関数を呼び出す。
  2. C# 側であらかじめ string なデータを作る。
  3. C++ 側から、string なデータのポインタを返す。
  4. これを C# 側で string 型で受けて string として利用する。

というなかなか興味深いことをやっています。Python とか Java のコードは追っていないのですが、これは冗長ではあるけど、C#(.NETの世界)のマネージなメモリ空間でデータを扱えるようにする良い手段ですよね。

って、ことまでは分かったのですが、じゃあここの「冗長」な部分は、本来の C# ならばどうするのか?ってのが次です。

C#側でMarshal.PtrToStringAnsiを使う

サンプルコードは、

moonmile/hellodll: C++ の DLL から const char *, wchar_t * を読み込むテスト
https://github.com/moonmile/hellodll

にあります。
C++ の内部的には、std::string あるいは std::wstring を使っている状態で、C# へのインターフェースに、char * あるいは wchar_t * を使うという想定ですね。

– hellodll : C++ の共有ライブラリ
– hello : C# から hellodll を呼び出し
– helloCore: .NET Core から hellodll の呼び出し
– helloStd, helloStdCore : .NET Standard で hellodll を呼び出し、それを .NET Core で利用するパターン

C++ 側ではこんな(乱暴な)感じで、インターフェースを作っておきます。単純な文字列の受け渡しなので、_str や _wstr の中身は C# 側では弄らず、C++ 側でのみ弄るという想定です。C# で弄ったときは、あらためて SetNameStr(char *) のように文字列を渡して貰えばいいのです。

#include <string>
static std::string _str = &quot;&quot;;
static std::wstring _wstr = L&quot;&quot;;
extern &quot;C&quot; {
	__declspec(dllexport) int __stdcall Add(int x, int y) {
		return x + y;
	}

	__declspec(dllexport) void __stdcall SetNameStr(char *t) {
		_str = std::string(t);
	}
	__declspec(dllexport) char * __stdcall GetNameStr() {
		return (char*)_str.c_str();
	}
	__declspec(dllexport) void __stdcall SetNameWStr(const wchar_t *t) {
		_wstr = std::wstring(t);
	}
	__declspec(dllexport) const wchar_t * __stdcall GetNameWStr() {
		return _wstr.c_str();
	}
}

これを C# 側で受けると、こんな感じになります。C# から渡すときは string が使えるけど、C++ から貰うときは IntPtr で受けます。

 [DllImport("hellodll", EntryPoint = "SetNameStr")]
 static extern void SetNameStr(string t);
 [DllImport("hellodll", EntryPoint = "GetNameStr")]
 static extern IntPtr GetNameStr();
 [DllImport("hellodll", EntryPoint = "SetNameWStr", CharSet = CharSet.Unicode)]
 static extern void SetNameWStr(string t);
 [DllImport("hellodll", EntryPoint = "GetNameWStr", CharSet = CharSet.Unicode)]
 static extern IntPtr GetNameWStr();

IntPtr で受けた値を、Marshal.PtrToStringAnsi あるいは、Marshal.PtrToStringUni で変換します。char * と wchar_t * に対応するわけです。

  string s = "こんにちは C++ の世界";
  SetNameStr(s);
  var s1 = Marshal.PtrToStringAnsi(GetNameStr());

でもって、いちいち文字列を貰うたびに Marshal.PtrToStringAnsi を使うのは面倒なおで、次のようにヘルパークラスを作っておいて、

public class HelloDll
{
    public static void SetNameStr(string s) { _SetNameStr(s); }
    public static string GetNameStr() { return Marshal.PtrToStringAnsi(_GetNameStr()); }
    public static void SetNameWStr(string s) { _SetNameWStr(s); }
    public static string GetNameWStr() { return Marshal.PtrToStringUni(_GetNameWStr()); }


    [DllImport("hellodll", EntryPoint = "SetNameStr")]
    static extern void _SetNameStr(string t);
    [DllImport("hellodll", EntryPoint = "GetNameStr")]
    static extern IntPtr _GetNameStr();
    [DllImport("hellodll", EntryPoint = "SetNameWStr", CharSet = CharSet.Unicode)]
    static extern void _SetNameWStr(string t);
    [DllImport("hellodll", EntryPoint = "GetNameWStr", CharSet = CharSet.Unicode)]
    static extern IntPtr _GetNameWStr();
}

対応する static メソッドを呼び出せばよいのです。

string s = "こんにちは C++ の世界";
HelloDll.SetNameStr(s);
var s1 = Marshal.PtrToStringAnsi(GetNameStr());
Console.WriteLine(s1);

一見、ヘルパークラスは冗長な感じがしますが、インテリセンスが効くことと、名前空間などで区切ることができる、C++の短い名前をC#の長い名前に直すことができる、というメリットがあります。
少し高速さには掛けますが、所詮 C++/C# 変換しているところで遅くなっているので、そこは気にしないことにします。

C++と.NET Standard/Core の関係

試しに、.NET Standard を経由してみたのが、helloStd, helloStdCore のプロジェクトです。

一度、.NET Standard のクラスライブラリを作成しておいて、

public class HelloDll
{
    public static void SetNameStr(string s) { _SetNameStr(s); }
    public static string GetNameStr() { return Marshal.PtrToStringAnsi(_GetNameStr()); }
    public static void SetNameWStr(string s) { _SetNameWStr(s); }
    public static string GetNameWStr() { return Marshal.PtrToStringUni(_GetNameWStr()); }


    [DllImport("hellodll", EntryPoint = "SetNameStr")]
    static extern void _SetNameStr(string t);
    [DllImport("hellodll", EntryPoint = "GetNameStr")]
    static extern IntPtr _GetNameStr();
    [DllImport("hellodll", EntryPoint = "SetNameWStr", CharSet = CharSet.Unicode)]
    static extern void _SetNameWStr(string t);
    [DllImport("hellodll", EntryPoint = "GetNameWStr", CharSet = CharSet.Unicode)]
    static extern IntPtr _GetNameWStr();
}

.NET Core から .NET Standard のクラスライブラリを参照設定させて実行というスタイルです。

static void Main(string[] args)
{
    Console.WriteLine("Test helloCoreStd");
    string s = "こんにちは C++ の世界";

    HelloDll.SetNameStr(s);
    var s1 = HelloDll.GetNameStr();
    Console.WriteLine(s1);

    HelloDll.SetNameWStr(s + " in Unicode");
    var s2 = HelloDll.GetNameWStr();
    Console.WriteLine(s2);
    Console.WriteLine("end");
}

先の、.NET Core から C++ 直接呼出しと何が違うのかというと、いったん .NET Standard で包むことによって、.NET Core 以外の環境で動作ができるという話です。C++ のライブラリは環境ごとにビルドして配布(Windows/Ubuntu/Raspberry Piのように)する必要はありますが、.NET Standard のクラスライブラリはそのまま各種の .NET 開発環境で利用できるという訳ですね。所謂、インターフェースクラスの代わりになります。
そうなると、このクラス(アセンブリ)を利用して、Xamarin.Android/iOS で利用することも可能という話です。そうすると、自前の小さな C++ ライブラリをいちいち C# に書き替えることなく(あるいは Marshal.* や unsafe を駆使することなく)スマートフォン環境に組み込めるのではないかな、と思ったり。このあたりは、もう少し見ていかないといけませんが。

カテゴリー: 開発, C#, C++ | SWIG の文字列(string)の扱いが少しややこしいので、Marshal.* を使ってみるテスト はコメントを受け付けていません

SWIG を使って C++ のクラスを C# で読み込む方法

FFFTP2 のライブラリ化の前調査として、C++ のクラスに書き替えたときに、.NETから呼び出すことができるだろうか?というのを調べていました。基本 C# から C/C++ を呼び出すときは dllimport を使う訳ですが、これは「Cインターフェース」を使っているので、C++のクラスを直接呼び出すことはしていません。なので、C++の場合は、一度Cのインターフェースに直して呼び出さないといけないですよね。OpenCvSharp の場合も、OpenCV を呼び出すときに、dllimport を使っていますが、ひとつひとつ C インターフェスに直して組み替えています。ここの手間がなんとも言えないのと、将来的に C++ の構造が変わったときにはこの部分を手作業で直さないといけませんよね(実際 OpenCV2 から OpenCV3 の変換のときに発生している問題で)。となれば、Xamarin.Android のバインド機能よろしく、*.jar ファイルから適当な C# のクラスを吐き出すような仕組みが欲しいところです。

本式にやるならば? CppSharp っぽいのですが https://github.com/mono/CppSharp どうやら「棘の道」だそうなので、ちょっと避けて、SWIG http://swig.org/ を試しています。
以前、Python から Cライブラリを呼び出すのに SWIG を使う話をちらっと聞いた後に、それ以降調べていなかったのですが、どうやら C++ にも対応しています。

SWIGのwindows版をダウンロード

Visual Studio 2017 で作業をするので、windows 版を落とします。https://sourceforge.net/projects/swig/files/ にある swigwin のほうをダウンロードして展開すると、swig.exe と変換インターフェースがある Lib/*.i 等がでてきます。

ここでは C/C++ を C# に変換するので、Lib/charp/*.i が使われますね。

サンプル

サンプルは http://github.com/moonmile/helloswig にあります。

– helloswig : Visual Studio で作った C++ の DLL プロジェクト
– hello : DLL を利用する C# プロジェクト

hello プロジェクトは可搬性を確認するために、.NET Core プロジェクトで作ってあります。Windows 上のみで動かす場合や Linux 上で mono を使う場合は .NET Framework のプロジェクトでも可能です。Windows 上のみで動作を考えるならば、DLL は MFC DLL でも動作します。

helloswig プロジェクト

helloswig.cpp に実体を定義します。これは DLL 内で動くコードですね。

#include "stdafx.h"
#include "helloswig.h"

Hello::Hello() : _name("") {}

void Hello::SetName(const char *name ) {
	_name = std::string(name);
}

const char * Hello::GetName() {
	return _name.c_str();
}

int Hello::Add(int x, int y) { return x + y; }
int Hello::Mul(int x, int y) { return x * y; }

helloswig.h は外部インターフェースです。

#pragma once
#include <string>
class Hello
{
private:
	std::string _name;
public:
	Hello();
	void SetName(const char *name);
	const char *GetName();
	int Add(int x, int y);
	int Mul(int x, int y);
};

SWIG の場合、デフォルトでヘッダファイルの全てのクラス&全てのメソッドが公開対象になるので、この部分はエクスポート用のものを作ったほうがよいかもしれません。後、異なる環境でビルドすることを考えると windows.h などの読み込みはできないので、結構「綺麗なヘッダファイル」が必要かもしれません。

#pragma once
#include <string>
class Hello
{
private:
	std::string _name;
public:
	Hello();
	void SetName(const char *name);
	const char *GetName();
	int Add(int x, int y);
	int Mul(int x, int y);
};

helloswig.i は、SWIG で取り込むための設定ファイルです。%module の部分が DLL の名前(C#側の dllimport で指定されます)。%include が公開するための C++ インターフェスです。

/* File : helloswig.i */
%module helloswig

%{
#include "helloswig.h"
%}

/* Let's just grab the original header file here */
%include "helloswig.h"

*.i ファイルはそのままではビルドできないので、swig.exe を呼び出すようにします。
ファイルのプロパティで、「カスタムビルドツール」を指定して、

コマンドラインと出力ファイルを指定します。

swig.exe のパスは環境によって合わせてください。swig.exe のあるフォルダの Lib フォルダーから設定済みの *.i ファイルを読み込むようです。

echo Invoking SWIG...
echo on
..\swig.exe -c++ -csharp "%(FullPath)"
%40echo off

一度、ビルドを通すと helloswig_wrap.cxx のようなラップをしたクラスが作成されます。中身を見ると解るのですが、一度 C インターフェースに直してしています。

...
#ifdef __cplusplus
extern &quot;C&quot; {
#endif

SWIGEXPORT void * SWIGSTDCALL CSharp_new_Hello() {
  void * jresult ;
  Hello *result = 0 ;
  
  result = (Hello *)new Hello();
  jresult = (void *)result; 
  return jresult;
}


SWIGEXPORT void SWIGSTDCALL CSharp_Hello_SetName(void * jarg1, char * jarg2) {
  Hello *arg1 = (Hello *) 0 ;
  char *arg2 = (char *) 0 ;
  
  arg1 = (Hello *)jarg1; 
  arg2 = (char *)jarg2; 
  (arg1)->SetName((char const *)arg2);
}
...

これと同時に、helloswigPINVOKE.cs のような C# 側でインポートするクラスが作られます。

...
  [global::System.Runtime.InteropServices.DllImport("helloswig", EntryPoint="CSharp_new_Hello")]
  public static extern global::System.IntPtr new_Hello();

  [global::System.Runtime.InteropServices.DllImport("helloswig", EntryPoint="CSharp_Hello_SetName")]
  public static extern void Hello_SetName(global::System.Runtime.InteropServices.HandleRef jarg1, string jarg2);

  [global::System.Runtime.InteropServices.DllImport("helloswig", EntryPoint="CSharp_Hello_GetName")]
  public static extern string Hello_GetName(global::System.Runtime.InteropServices.HandleRef jarg1);
...

hello プロジェクト

C# 側は、もともと何もないクラスですが、C++ プロジェクト(helloswigプロジェクト)
で作成した *.cs ファイル(Hello.cs, helloswig.cs, helloswigPINVOKE.cs)を追加します。

Program.cs

using System;

namespace hello
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello swig world.");

            var hello = new Hello();
            hello.SetName("Tomoaki Masuda");
            var name = hello.GetName();
            Console.WriteLine($"name: {name}");

            hello.SetName("Jhon doe");
            name = hello.GetName();
            Console.WriteLine($"name: {name}");

            int x = 10;
            int y = 20;
            Console.WriteLine($"{x} + {y} = {hello.Add(x, y)}");
            Console.WriteLine($"{x} * {y} = {hello.Mul(x, y)}");

            Console.WriteLine("end");
        }
    }
}

C# 側の main コードは、通常に C# のコードになります。
SWIG で出力した C# コードをいちいち追加しないといけないのですが、まあそのあたりはバッチ処理をするとかリンクで解決するとかできるでしょう。

ビルドして実行する

.NET Core でインポートできる DLL は x64 のみなので、そのあたりを注意しながら実行ファイルを作ります。

  1. C++プロジェクト(helloswig)を x64/Debug あるいは x64/Release でビルド.
  2. C#プロジェクト(hello)をビルド。.NET Core なのでコマンドラインから dotnet build でも ok です。
  3. 作成した dll を .NET Core の実行ファイルの場所にコピー(bin/Debug/netcoreapp2.0/ にコピー)
  4. dotnet run する。

Ubuntu環境でビルド実行する

さて、Windows 環境で動作が確認できたので、Ubuntuに持って行って動かしてみましょう。コードをごっそりと Ubuntu に持って行って、共有ファイルを作成するための Makefile を作ります。

all: libhelloswig.so

libhelloswig.so: helloswig.o
	g++ -sharad helloswig.o helloswig_wrap.o -o libhelloswig.so
helloswig.o: helloswig.cpp
	g++ -c helloswig.cpp
helloswig_wrap.o: helloswig_wrap.cpp
	g++ -c helloswig_wrap.cpp

あまりに久々だったので、ベタな makefile ですが、まあこれで libhelloswig.so がビルドできます。
本来ならば、ubuntu 上で swig を動かしてラップする C# コードを吐き出すところですが、既に Windows 上で作成しているので再利用します。ちなみに、ubuntu 上では「sudo apt-get install swig」で SWIG がインストール可能です。

libhelloswig.so ファイルを指定するために LD_LIBRARY_PATH を設定して dotnet run します。

export LD_LIBRARY_PATH=~/swig/helloswig

ここで、ちょっと面白いのは .NET Core で DllImport を使うと、Windows の場合は「helloswig.dll」を参照し、Ubuntu 上では「libhelloswig.so」を参照するところですね。こんため、Windows の SWIG で出力した C# ラッパーがそのまま Ubuntu でも使えます。

  [global::System.Runtime.InteropServices.DllImport("helloswig", EntryPoint="CSharp_new_Hello")]
  public static extern global::System.IntPtr new_Hello();

共有ファイルに lib を付けておくと便利です。

Raspberry Pi上で実行する

では、ラズパイ上で動かしてみましょう。Raspberry Piでは、.NET Core がビルドできないのであらかじめ Windows 側で C# コードをビルドしておきます。

dotnet publish -r linux-arm

C++ のプロジェクトはRaspberry Pi上で make します。

これで、

– Windows
– Ubuntu
– Raspberry Pi

の3つの環境で、.NET Core + SWIG + C++ライブラリが同じソースで動作ができました。

SWIGの難点

SWIG自体は、C# へのコンバートだけでなく、JavaやPythonなどへのコンバートがサポートされているので、色々な言語で活用ができます。まあ、小さな自前の C/C++ ライブラリを Python などで使う分にはこれでいけそうな気がするのですが、Windows上の複雑なライブラリをコンバートしようとすると難しそうな点がいくつかあります。

– Windows.h などの Unix 系にないインクルードファイルは読み込めない
– LPCTSTR などの定義が読み込めない(別途定義が必要)
– 文字列系のインターフェースは const char * か wchar_t に限られる?

最後のひとつは、std_string.i や std_wstring.i のインターフェースはあるのですが、SWIGTYPE_p_std__string.cs や SWIGTYPE_p_wchar_t.cs のような独自なクラスが作られるのでちょっと違う感じなんですよね。ただし、const char * は string に変換されるので、dllimport の仕組みのままのようです。ただし、このあたりは独自に *.i ファイルを作ればよいので、ピンポイントで std::string から string に変換するとか、std::wstring からコード変換するとかはできそうな感じなのですが。このあたりは別途調査。

カテゴリー: 開発, C#, C++ | SWIG を使って C++ のクラスを C# で読み込む方法 はコメントを受け付けていません

格安ラズパイ互換機の Orange Pi One を活用する

ラズパイ互換機の一覧がイイ感じでまとまっていたので、死蔵していた Orange Pi One を引っ張り出して samba まで使えるようにしてみた経過です。

「Raspberry Pi」より強力なシングルボードコンピュータ20選–競合製品の長所と短所 – TechRepublic Japan
https://japan.techrepublic.com/article/35108435.htm

Raspberry Pi Zero W が1,000円程度で手に入るようになったので、以前より「格安」感は薄れていますが、ラズパイ3と同等レベル(あるいはそれ以上)のスペックを求めるときと、1個ではなく大量に使いたい(5個以上とか)ときには有効でしょう。1個だけ使う場合は、資料が多いラズパイを使ったほうが無難です。

100個以上使って製品化する場合には、専用の回路を組むとか、長期的な供給源の問題もありますが、まあ、あれこれと買ってみて試してみたい場合には結構よい手段です。大抵のものは、Raspbian 互換や Ubuntu が動くようになっています。また、中華製の組み込みボードは Android が動くものが多いので、別な使い方ができます。どちらにせよ、Unux 系コマンドを打つのに苦労が無ければ、それなりに使えます。

Orange Pi One とは

orange pi one – Orangepi http://www.orangepi.org/orangepione/

USBが1つしかない格安ラズパイ互換ボードです。aliexpress で直販していて、送料込みで、1,500円程度でしょうか。電源が、micro USB ではないのが難点ですが、4mm ジャックは SONY の PSP で使われているものなので 100 円ショップでも売っています。
内部メモリは 512MB しかないので、ちょうどラズパイAあたりのものと同じです。GPU などの高スペックが必要ないサーバー機に適しているでしょう。カメラをつなげる端子もないのもそれですよね。
特に、製品化や技適を気にしない実験施設であれば、orange pi zero もお薦めです。zerro のほうは WiFi/BT が内蔵しているので、無線化が可能です。ただし、少し試してみたところ WiFi が不安定(おそらく熱暴走っぽいところ)なので、大量なデータを流すものには向いていません。ですが、one も zero も非常に小さいので、ラズパイ2/3では難しい小物の電子工作に活用できそうです。

OSのダウンロード先

本家の http://www.orangepi.org/downloadresources/ から Raspbian互換がダウンロードできて、ラズパイと同様に micro SD カードに焼き込みができます。で、起動はできるんですが…実は、このパスワードが何処にも書いていない!!! pi/raspberry じゃないらしいんですよね。何度やっても駄目なので諦めて armbian を入れます。

armbian
https://www.armbian.com/download/

armbian は、有志が arm 関連の OS を公開しているところです。主に Ubuntu が使われていますが、たまに Android もあります。この中から、orange pi one を探して出して、「Ubuntu server」のほうをダウンロードします。

まあ、Desktop を入れて Scratch を使うのもいいのですが容量が少なくて済むので、今回は server 版を入れています。

起動

Armbian を起動して、ターミナルから接続するとこんな感じになります。

SSH は最初から有効になっているので、IP を調べて root/1234 でログインします。初回にパスワードを変更したあと、

adduser ユーザー名
gpasswd -a ユーザー名 sudo

として、自分のユーザーで sudo できるようにしておくと便利です。
CPU は常に50度以上になるので火傷には気を付けてください。まあ、指で触るとちょっと熱い程度なんですけど。何かフルで動かす場合には、ヒートシンクを付けたほうが良いのですが、そこまでフルに動かすことはないと思うので普段は大丈夫です。
後で試しますが、opencv をビルドするようなフルの場合には熱暴走をしたりします。そういうときは、10円玉で簡易ヒートシンクにするか(銅なので熱伝導率が高い)、扇風機に当てておけばokでしょう。

.NET Core 2.0 を動かしてみる

CPU がラズパイ2/3と同じ arm7v 系なので、.net core が動きます。

ラズパイの時と同じように PC 上で linux-arm を付けてビルドして、DLL を orange pi one 上にコピーします。
ラズパイに .NET Core 2.0 の動作環境を作るときは、

俺のラズパイ2で.NET Core 2.0が動くまで | Moonmile Solutions Blog http://www.moonmile.net/blog/archives/8732

を参考にしてください。

ファイル共有は、winscp を使ってもよいですし、samba でもよいでしょう。
1個しかないUSBにSSDを繋げて、samba をインストールすると簡易NASのできあがりです。

ひょっとするとラズパイ3よりも電力消費が低いので、電気代が安くなるかもしれません(微々たるものですが)。

この後は?

GPIOが付いているので、Wiring などを使ってLチカをするのもありなのですが、今回はちょっと Tensorflow の練習をしようと思って、opencv のダウンロードからスタート。

まっさらなUbuntu16.04LTSにOpenCV3.1とopencv_contribをインストール – Qiita https://qiita.com/schwalbe1996/items/cfa9fde7ea2b825e4e6b
Raspberry Pi 深層学習ライブラリで物体認識(Keras with TensorFlow・Open CV) – Qiita https://qiita.com/PonDad/items/c5419c164b4f2efee368

を参考にしながらインストール中です。

カテゴリー: 開発, 組み込みボード | 格安ラズパイ互換機の Orange Pi One を活用する はコメントを受け付けていません