ArduinoでOBDモニタみたいなのを作った話。

ちらっとOBDモニタみたいなのが欲しくて、Arduinoなら作れるんじゃないかな、と思って作ってみた。.NET環境がメインでC(に近いArduino)なんて初めてですが、ポインタがない分簡単に理解できそう(罠)
使った物

IDEに関しては、 arduino.cc にあるものではなく、arduino.org にある、Version 1.7.11 のIDEでないと、MultiLCDでの文字表示が正常に出来なかったのでメモ。
OBDの通信プロトコルは複数あり(Ref: 自動車の自己診断機能 - Wikipedia)基本的には、使用するライブラリの OBD.h の108行目付近にある

init(OBD_PROTOCOLS protocol = PROTO_AUTO)

を、その手前あたりにenum で定義されている OBD_PROTOCOLS に合わせれば大丈夫そう。(ほとんどの場合は PROTO_AUTO で大丈夫らしい(自車もPROTO_AUTOでいけた))
また、取得出来るパラメータは同じく OBD.h に定義されてるけれども、車種によって取得出来ないものもあるので注意。

ちなみに自車はZC72S(K12B)(RS)ですが、今のところ

  • PID_ENGINE_REF_TORQUE
  • PID_FUEL_PRESSURE
  • PID_ENGINE_OIL_TEMP
  • PID_AMBIENT_TEMP
  • PID_FUEL_PRESSURE

は取れませんでした(OBD.hでDEFINEしてる値)

ちなみに現在こんなところまで出来てます

OBDは常に通電しているので、エンジンカットしても値が表示されたままだし、Arduinoは基本、電源を切るというのがない(スリープモードはあるらしい)ので、現在スリープモードとかの実装の検討とか作業してます。

ここのサンプルコードを参考にして作ってます。

String.Format とStrings.Format

仕事で大ハマリしたのでメモ。
String.Formatは、.NET FrameworkのSystem.Stringクラスにある。(文字コードUTF-16ベース)
Strings.Formatは、Microsoft.VisualBasic.Stringsクラスにある。

(以下サンプルはVB.NETで書きます)

それぞれ引数が違ってて、
String.Formatは(format As String, arg0 As Object)と取るのに対し、
Strings.Formatは(Expression As Object, Style As String)で順番が違う上に、引数のfomatとStyleそれぞれの指定方法が違う。(ここで大ハマリした)

String.Formatは、.NETっぽく、"{0}" とか、"{0:yyyyMMdd}"とか、"{0:0000}"等と指定できるのに対し、
Strings.Formatは旧VB(VB6)からの互換っぽいメソッドなので、指定方法が"0000" とか"yyyyMMdd"とかの指定方法になる。

String.FormatはParamArrayがサポートされており、String.Format("{0}, {1}, {2}", arg0, arg1, arg2)みたいに複数の引数とフォーマットがサポートされているのに対し、
Strings.FormatはExpressionにあたる変換元パラメータが一つだけ。

あと、String.Formatは、String.Format って書くのに対して
Strings.Formatは、(VB.NET上では)ただ単にFormatって書くとそれがStrings.Formatとして認識される。(表示されるパラメータの順序が違うのでIntellisenseでわかる)

同じコード(クラス)内にString.FormatとStrings.Formatが混在してややこしくなった昨日の午後。
まだリファクタリングしてない。明日やる。

Dictionary型を使って簡単にConfigっぽいものを扱う

生きてます。(生存戦略報告)
Advent Calenderネタを書いたのがちょうど一年前。筆無精しまくっててだいぶご無沙汰になってしまいました。

今回はDictionary型を使ってConfigっぽいものを手軽に扱ってみようというネタです。
設定ファイルを扱う方法は、.configだったり、.iniだったり、レジストリだったり、いろいろ方法ありますが、とりあえずDictionary型を使って読み書きしてみます。
Dictionary型って、いわゆるハッシュテーブルとか連想配列って呼ばれるやつですね。

#えっと、今まで(VB.NETを触りだして10年以上)こんな型があるなんて知らなかったことを正直に白状しておきます。

たとえば、こんな感じの設定ファイル(config.txt)があったとして

data=abc
pos_x=120
pos_y=240

その設定ファイルを読み込むには、たとえばこんな読み込み用関数を作っておいて

Dim config As New Dictionary(Of String, String) 
Dim configDelimiter As Char = "="c  ' configなファイルの区切り文字

Function ReadConfig(filename As String) As Dictionary(Of String, String)
    'configファイルの読み込み
    Dim configLines As String() = IO.File.ReadAllLines(filename)

    Dim dict As New Dictionary(Of String, String)

    '読み込んだconfigのDictionary型への追加
    For Each configLine As String In configLines
        dict.Add(configLine.Split(configDelimiter)(0).Trim(" "c), configLine.Split(configDelimiter)(1).Trim(" "c))
    Next

    Return dict
End Function

読み込んであげると楽です

config = ReadConfig("Config.txt")

読み込んだデータは、For Each で取り出すことも出来ます。取り出されたのはKeyValuePair型になるっぽい。

'Configの列挙
For Each kvp As KeyValuePair(Of String, String) In config
    Console.WriteLine("{0} - {1}", kvp.Key, kvp.Value)
Next

値の追加とか

config.Add("Today", Today.ToShortDateString)
config.Add("Now", Now.ToString)

Keyが存在しているのにValueをAddしようとすると、例外を吐くので、Addする前はContainsKey(keyname)で、存在チェックをしてから更新したり追加したりするといいと思います。

If config.ContainsKey("Now") Then
    config("Now") = Now.ToString
Else
    config.Add("Now", Now.ToString)
End If

値の取得は config("pos_x") みたいな感じで取得可能です。あ、今回の例では New Dictionary(of String,String) で初期化しているので、必要あればParseしたりしてください。

また、逆に設定ファイルへの書き込みは、こんな関数を作っておいて

Sub WriteConfig(filename As String, config As Dictionary(Of String, String))
    Dim writeData As String = ""
    For Each kvp As KeyValuePair(Of String, String) In config
        writeData &= kvp.Key & configDelimiter  & kvp.Value & vbCrLf
    Next

    IO.File.WriteAllText(filename, writeData)
End Sub

書き込んであげるといいと思います。

WriteConfig("OutConfig.txt", config)

自分自身のアセンブリをSHA256とかでハッシュ取って保存しておき、実行時に検証する、っていうアセンブリの簡単な改ざんチェックとか、いろいろ用法あると思います。(今回のDictionary型じゃなくて.configとかでもできそうだけど)

例によって(?)上記サンプルでは例外対策とか、読み込み時にデリミタ文字が複数出てきたときの対処とか、全くしてないので必要あればTryしてCatchしてFinallyしたりしてみてください。

追記というか捕捉です(後で書こうと思って書くの忘れた)

2014-12-06 - Elfariaのチラシの裏の補足というか追記。
[アドオンの管理]→[追跡防止]→[個人用リスト] の追跡防止設定も無効にしたら、やっと表示されるようになったので追記でした。というか捕捉。
前回のままだと追跡防止の個人用リストが有効になってると、まだ表示されない部分があったので。

Webサイトが途中までしか表示されなかったり、サイトのjsが動いていないような挙動をする場合

Internet Explorer で、Webサイトの表示が途中までしか表示されなかったり、たとえばまとめサイトのヘッドラインが表示されないとか、GIGAZINEの記事中の画像が記事の後半になると表示されなくなったりとか、Togetterの「続きを読む」ボタンが押せなかったりとか、Twitterの埋め込みTLが正常に表示されなかったりする場合のチェック項目。
基本的には「IEのキャッシュや履歴の削除」、「index.datの再構築」とかで直る場合が多いですが、今回はInternet ExplorerでアクセスしたサイトにDo Not Track 要求を送信する」が有効になっていると、正常にページが表示されなくなることがあることが判明。

このチェックボックスがオンになっていると、Webページが表示されないことがあった。チェックボックスをオフにして、PCを再起動。
この現象に1ヶ月くらい悩んでた。デフォルトではオフなので、きっとどこかで設定をオンにしたんだと思う。Tracking防止のために。でもTracking防止したら見れるサイトも見れなくなったので困った。
きっかけはTVアニメ「DOG DAYS″」オフィシャルサイトのページを見ようとして、画像が全く表示されなかったので不思議に思ってブラウザの更新ボタン近辺を見たら、Do Not Trackでブロックされたという表示が。ブロックを解除したら正常に見ることができた。

LINQ2CSVを使ったCSVの扱い

#久しぶりの更新
Visual Basic Advent Calendar 2014 - Qiita の記事です。

参考:LINQ to CSV (CodeProject, CSVデータをLINQで扱うライブラリ, LinqToCsv) - いろいろ備忘録日記
こちらではC#で書かれているので、VB.NETに書き直してみるとだいたいこんな感じ。

パッケージや詳細な使用方法に関してはこちら(英語サイト)
LINQ to CSV library - CodeProject

たとえば、こんなCSVファイルがあったとして(データは適当なのでツッコミ勘弁してください)

氏名 住所 電話番号 性別 生年月日 年齢
山田太郎 東京都なんとか 00-0000-0000 1950/1/1 64
田中二郎 東京都なんとか 00-0000-0001 1950/1/2 64
鈴木三郎 東京都なんとか 00-0000-0002 1950/1/3 64

このCSVデータを処理するとこんな感じです。(列名はCSVデータに含まないものとする)

まず、nugetでパッケージのインストール

Install-Package LINQToCSV

クラスを作る時にCSVの各列に属性を与える
属性でフィールドの何番目の列か(最初が1)を指定します。

Imports LINQtoCSV

Class cCSVDataDefine

    Private _氏名 As String
    <CsvColumn(FieldIndex:=1)>
    Public Property 氏名() As String
        Get
            Return _氏名
        End Get
        Set(ByVal value As String)
            _氏名 = value
        End Set
    End Property

    Private _住所 As String
    <CsvColumn(FieldIndex:=2)>
    Public Property 住所() As String
        Get
            Return _住所
        End Get
        Set(ByVal value As String)
            _住所 = value
        End Set
    End Property

    Private _電話番号 As String
    <CsvColumn(FieldIndex:=3)>
    Public Property 電話番号() As String
        Get
            Return _電話番号
        End Get
        Set(ByVal value As String)
            _電話番号 = value
        End Set
    End Property

    Private _性別 As String
    <CsvColumn(FieldIndex:=4)>
    Public Property 性別() As String
        Get
            Return _性別
        End Get
        Set(ByVal value As String)
            _性別 = value
        End Set
    End Property

    Private _生年月日 As String
    <CsvColumn(FieldIndex:=5)>
    Public Property 生年月日() As String
        Get
            Return _生年月日
        End Get
        Set(ByVal value As String)
            _生年月日 = value
        End Set
    End Property

    Private _年齢 As String
    <CsvColumn(FieldIndex:=6)>
    Public Property 年齢() As String
        Get
            Return _年齢
        End Get
        Set(ByVal value As String)
            _年齢 = value
        End Set
    End Property
End Class
                                      • -

読み込み

    Dim ds_Csv As New LINQtoCSV.CsvContext
    Dim dsfile As String = "foobar.csv"

    Dim ds_csvDesciption As New CsvFileDescription
    ds_csvDesciption.SeparatorChar = ","	’CSVファイルの区切り文字
    ds_csvDesciption.FirstLineHasColumnNames = False	'一行目はカラム名かどうか
    ds_csvDesciption.EnforceCsvColumnAttribute = True
    ds_csvDesciption.QuoteAllFields = False	’””で囲まれたデータかどうか
    ds_csvDesciption.TextEncoding = System.Text.Encoding.GetEncoding("Shift-JIS") ' いつものSystem.Text.Encoding です。エンコード指定。

    Dim ds_datalist As New List(Of cCSVDataDefine)

    ds_datalist = ds_Csv.Read(Of cCSVDataDefine)(dsfile, ds_csvDesciption).ToList

TextEncodingは適宜合わせてください。

                                      • -

取得

    For Each d in ds_Datalist
	Console.WriteLine(d.氏名 & ", " & d.住所 & ", " & d.電話番号 & ", " & d.性別 & ", " & d.生年月日 & ", " & d.年齢 )
	
    Next
                                      • -

書き込み(書き込み用データ作成)

    Dim ExportData As New List(Of cCSVDataDefine)

    For i As Integer = 1 to 100
        Dim d As New cCSVDataDefine
            d.氏名= "ユーザー" & i
            d.住所 = "FooBar-" & i
            d.電話番号 = Format(i,"00-0000-0000") 
            d.性別 = IIf(i Mod 2 = 0, "男", "女")
            d.生年月日 = Now.Date.AddDays(i).ToString("yyyy/MM/dd")
            d.年齢 = i

            ExportData.Add(d)
    Next
                                      • -

書き込み

    Dim csv As New CsvContext

    Dim csvDescription As New CsvFileDescription()
    csvDescription.SeparatorChar = ","
    csvDescription.FirstLineHasColumnNames = False
    csvDescription.EnforceCsvColumnAttribute = True
    csvDescription.TextEncoding = System.Text.Encoding.GetEncoding("Shift-JIS")


    Dim outfile As String ="ExportData.csv"
    csv.Write(Of cCSVDataDefine)(ExportData, outfile, csvDescription)

このような感じでCSVファイルを扱えます。

結局、Azure VMじゃなくてVPSで設定しました。

前回の日記で書いたAzureにVM置いてfetchmailpostfixで…という話。
結局うまくいかず(きっとエンドポイントの関係とかファイアーウォール越えとか逆引きの関係とかだと思う)、DTIのServerman@VPSをつかって構築しました。1ヶ月約500円のプランで。