MSMQTT_Test for SenseWay LoRaWAN 2019/09/05..11
・2019/09/11 データの読み方が間違っていたのを手直し。ついでに CSV ファイル保存を追加。
Delphi で MQTT、JSON を扱うサンプルです。
SenseWay LoRaWAN のデータを読み込み (Subscribe)、書き込み (Publish)のテストに使えます。
※ Trial 版の MSMQTT コンポーネントを使用しています。
1時間を超えると、エラーダイアログが出ます。一度終了させて再起動すると、また1時間ほど使えるようです。
ClientID (Username と同じ), Username, Password を入力し、[ Connect ] ボタンをクリックすると、センスウェイサーバにつながります。
[ Subscribe] ボタンをクリックすると、サーバ(MQTT ブローカー)からデータが取得されます。
devEUI の入力は [Publish] の時に必要です。[ Subscribe ] で取得対象を制限しない(すべてのデータを取得)ときは、不要です。
■ダウンロード
MSMQTT_Test.zip (Exe 本体のみ。アイコンは作成していません。)
// Delphi 10.3 Community Edition { 2019/09/11 データの読み方が間違っていたのを手直し CSV 保存を追加 } unit TMQTT_TESTUnit4; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, TMS.MQTT.Global, Vcl.StdCtrls, TMS.MQTT.Client, System.JSON, System.Generics.Collections, DateUtils, Vcl.Grids, IniFiles, Vcl.Buttons; type TForm4 = class(TForm) TMSMQTTClient1: TTMSMQTTClient; Button1: TButton; Memo1: TMemo; Button4: TButton; GroupBox1: TGroupBox; Button3: TButton; GroupBox2: TGroupBox; Button2: TButton; Edit6: TEdit; Label6: TLabel; Label8: TLabel; Edit8: TEdit; Label9: TLabel; Edit9: TEdit; Label10: TLabel; Edit10: TEdit; Label11: TLabel; Edit11: TEdit; Label12: TLabel; Edit12: TEdit; GroupBox3: TGroupBox; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Edit4: TEdit; Edit5: TEdit; Label7: TLabel; Edit7: TEdit; StringGrid1: TStringGrid; Label13: TLabel; SpeedButton1: TSpeedButton; procedure Button1Click(Sender: TObject); procedure TMSMQTTClient1SubscriptionAcknowledged(ASender: TObject; APacketID: Word; ASubscriptions: TTMSMQTTSubscriptions); procedure Button2Click(Sender: TObject); procedure TMSMQTTClient1PublishReceived(ASender: TObject; APacketID: Word; ATopic: string; APayload: TArray<System.Byte>); procedure TMSMQTTClient1PublishReceivedEx(ASender: TObject; APacketID: Word; ATopic: string; APayload: TTMSMQTTBytes); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure TMSMQTTClient1PacketReceived(ASender: TObject; APacketInfo: TTMSMQTTPacketInfo); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } FSubscribeRequestPacketId : Word; cmdMode : integer; procedure rxToSg(const src: string); end; var Form4: TForm4; implementation {$R *.dfm} // Delphi XE 10でJSON文字列から返された日付時刻をTDateTimeに解析する方法 // https://codeday.me/jp/qa/20190628/1129895.html function JSONDateToDatetime(JSONDate: string): TDatetime; var Year, Month, Day, Hour, Minute, Second, Millisecond: Word; begin Year := StrToInt(Copy(JSONDate, 1, 4)); Month := StrToInt(Copy(JSONDate, 6, 2)); Day := StrToInt(Copy(JSONDate, 9, 2)); Hour := StrToInt(Copy(JSONDate, 12, 2)); Minute := StrToInt(Copy(JSONDate, 15, 2)); Second := StrToInt(Copy(JSONDate, 18, 2)); Millisecond := Round(StrToFloat(Copy(JSONDate, 19, 4))); result := EncodeDateTime(Year, Month, Day, Hour, Minute, Second, Millisecond); end; procedure TForm4.Button1Click(Sender: TObject); // 接続 begin TMSMQTTClient1.BrokerHostName := Edit1.Text; TMSMQTTClient1.BrokerPort := StrToIntDEf(Edit2.Text, 1883); // 1883; TMSMQTTClient1.ClientID := Edit3.Text; TMSMQTTClient1.Credentials.Username := Edit4.Text; TMSMQTTClient1.Credentials.Password := Edit5.Text; TMSMQTTClient1.LastWillSettings.Topic := ''; cmdMode := 101; // 接続 TMSMQTTClient1.Connect(); end; procedure TForm4.Button2Click(Sender: TObject); // Subscribe var Topic : string; begin Topic := Edit6.Text; Topic := stringReplace(Topic, '<username>', Edit4.Text, []); // MQTTでSubscribeすればデータを取得することができる if TMSMQTTClient1.IsConnected then begin Memo1.Lines.Clear; FSubscribeRequestPacketId := TMSMQTTClient1.Subscribe( //'lora/' + UserNAme + '/+/#', // ワイルドカード(すべてのデータ) //'lora/'+ UserName+ '/+/rx', // 受信データのみの時 Topic, TTMSMQTTQoS.qosAtMostOnce ); end; end; procedure TForm4.Button3Click(Sender: TObject); // Publish var Payload : TBytes; s : string; Topic : string; begin Topic := Edit8.Text; Topic := stringReplace(Topic, '<username>', Edit4.Text, []); Topic := stringReplace(Topic, '<devEUI>', Edit7.Text, []); // データ書き込み s := '{"conf":false,"ref":"'+ Edit10.Text + '","port":' + Edit11.Text + ',"data":"' + Edit12.Text + '"}'; Payload := TEncoding.ANSI.GetBytes(s); if TMSMQTTClient1.IsConnected then begin TMSMQTTClient1.Publish( //'lora/' + UserName + '/' + devEUI + '/tx', Topic, Payload, TTMSMQTTQoS.qosAtMostOnce, True ); end; end; procedure TForm4.Button4Click(Sender: TObject); // Disconnect begin // 切断 if TMSMQTTClient1.IsConnected then begin TMSMQTTClient1.Disconnect; end; end; procedure TForm4.FormCreate(Sender: TObject); var ini : TIniFile; begin with StringGrid1 do begin Cells[0, 0] := 'date(JST)'; Cells[1, 0] := 'date(UTC)'; Cells[2, 0] := 'rssi'; Cells[3, 0] := 'snr'; Cells[4, 0] := 'gwid'; Cells[5, 0] := 'fq'; Cells[6, 0] := 'cnt'; Cells[7, 0] := 'data'; Cells[8, 0] := 'mt'; Cells[9, 0] := 'devEUI'; Cells[10, 0] := 'dr'; Cells[11, 0] := 'port'; Cells[12, 0] := 'data1'; Cells[13, 0] := 'data2'; Cells[14, 0] := 'data3'; Cells[15, 0] := 'data4'; end; ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); try Edit1.Text := ini.ReadString('Host', 'HostName', Edit1.Text); Edit2.Text := ini.ReadString('Host', 'Port', Edit2.Text); Edit3.Text := ini.ReadString('Host', 'ClientID', ''); Edit4.Text := ini.ReadString('Host', 'UserName', ''); Edit5.Text := ini.ReadString('Host', 'Password', ''); Edit7.Text := ini.ReadString('Gateway', 'devEUI', ''); Edit6.Text := ini.ReadString('Subscribe', 'Topic', Edit6.Text); Edit8.Text := ini.ReadString('Publish', 'Topic', Edit8.Text); Edit9.Text := ini.ReadString('Publish', 'conf', Edit9.Text); Edit10.Text := ini.ReadString('Publish', 'ref', Edit10.Text); Edit11.Text := ini.ReadString('Publish', 'port', Edit11.Text); Edit12.Text := ini.ReadString('Publish', 'data', Edit12.Text); finally ini.Free; end; end; procedure TForm4.FormDestroy(Sender: TObject); var ini : TIniFile; begin ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); try ini.WriteString('Host', 'HostName', Edit1.Text); ini.WriteString('Host', 'Port', Edit2.Text); ini.WriteString('Host', 'ClientID', Edit3.Text); ini.WriteString('Host', 'UserName', Edit4.Text); ini.WriteString('Host', 'Password', Edit5.Text); ini.WriteString('Gateway', 'devEUI', Edit7.Text); ini.WriteString('Subscribe', 'Topic', Edit6.Text); ini.WriteString('Publish', 'Topic', Edit8.Text); ini.WriteString('Publish', 'conf', Edit9.Text); ini.WriteString('Publish', 'ref', Edit10.Text); ini.WriteString('Publish', 'port', Edit11.Text); ini.WriteString('Publish', 'data', Edit12.Text); finally ini.Free; end; end; procedure TForm4.rxToSg(const src: string); var JSONValue: TJSONValue; JSONObject: TJSONObject; JSONPair: TJSONPair; JSONArray: TJSONArray; S: string; i, j, gwCount : Integer; path : string; begin JSONValue := TJSONObject.ParseJSONValue(src); if JSONValue is TJSONArray then begin end else if JSONVAlue is TJSONObject then begin gwCount := 0; // "gw"(ゲートウェイ)は1つとは限らない // "gw" と "mod "の2つのうち、最初の "gw" の配列数を得る JSONObject := JSONValue as TJSONObject; JSONPair := JSONObject.Pairs[0]; if JSONPair.JsonValue is TJSONArray then begin JSONArray := JSONPair.JsonValue as TJSONArray; gwCount := JSONArray.Count; end; with StringGrid1 do begin // 記入位置(行)を探す j := 1; for i := RowCount -1 downto 0 do begin if Cells[0, i] <> '' then begin j := i + 1; break; end; end; // 行を追加 if j = RowCount then RowCount := RowCount + 1; Row := j; if gwCount > 0 then begin // すべて文字列として取得可能だが、JSON のデータ型に合わせている // "gw": ゲートウェイのデータ(複数ある場合は配列列挙) for i := 0 to gwCount -1 do begin path := 'gw[' + i.ToString + ']'; // データ受信時刻(UTC) Cells[1, j] := JSONValue.GetValue<string>(path + '.date'); // JST Cells[0, j] := FormatDateTime('YYYY/MM/DD hh:nn:ss', IncHour(JSONDateToDatetime(Cells[1, j]), 9)); // 受信信号強度 Cells[2, j] := IntToStr(JSONValue.GetValue<Integer>(path + '.rssi')); // 信号雑音比 Cells[3, j] := FloatToStr(JSONValue.GetValue<double>(path + '.snr')); // Gateway ID Cells[4, j] := JSONValue.GetValue<string>(path + '.gwid'); end; end; // "mod": モジュール(デバイス)のデータ // 使用周波数 Cells[5, j] := FloatToStr(JSONValue.GetValue<double>('mod.fq')); // サーバでのカウント値 Cells[6, j] := IntToStr(JSONValue.GetValue<integer>('mod.cnt')); // データ (16 進数表記) Cells[7, j] := JSONValue.GetValue<string>('mod.data'); // ACK 要求データ(Confirm)か否(UnConfirm)か Cells[8, j] := JSONValue.GetValue<string>('mod.mt'); // モジュール(デバイス)固有アドレス DevEUI Cells[9, j] := JSONValue.GetValue<string>('mod.devEUI'); // DR 値 (SpreadFactor と BandWidth の組み合わせ) Cells[10, j] := JSONValue.GetValue<string>('mod.dr'); // LoRa ポート番号(ユーザが 1~223 の間で使用可能) Cells[11, j] := IntToStr(JSONValue.GetValue<integer>('mod.port')); // データを 10 進表記に s := Cells[7, j]; for i := 0 to 3 do begin if s.Length >= (i + 1) * 4 then Cells[12 + i, j] := IntToStr(StrToInt('$' + Copy(s, 1 + i * 4, 4))) else Cells[12 + i, j] := ''; end; end; end; end; procedure TForm4.SpeedButton1Click(Sender: TObject); // CSV 保存 var i, j : integer; fname : TFileName; s : string; sl : TStringList; begin fname := ExtractFilePath(ParamStr(0)) + FormatDateTime('YYYYMMDDHHNNSS', Now) + '.csv'; sl := TStringList.Create; try with StringGrid1 do begin for i := 0 to RowCount -1 do begin if Cells[0, i] <> '' then begin s := ''; for j := 0 to ColCount - 1 do begin s := s + Cells[j, i]; if j < ColCount - 1 then s := s + ','; end; sl.Add(s); end else break; end; end; if sl.Count > 1 then begin sl.SaveToFile(fname); ShowMessage(IntToStr(sl.Count -1) + ' データを保存しました.'); end else ShowMessage('データがありません.'); finally sl.Free; end; end; procedure TForm4.TMSMQTTClient1PacketReceived(ASender: TObject; APacketInfo: TTMSMQTTPacketInfo); begin if (cmdMode = 101) and TMSMQTTClient1.IsConnected then begin Memo1.Lines.Clear; Memo1.Lines.Add('Connected'); cmdMode := 0; end; end; procedure TForm4.TMSMQTTClient1PublishReceived(ASender: TObject; APacketID: Word; ATopic: string; APayload: TArray<System.Byte>); var Payload : string; begin Payload := TEncoding.ANSI.GetString(APayload); Memo1.Lines.Add(#13#10 + FormatDateTime('YYYY/MM/DD hh:nn:ss', Now)); Memo1.Lines.Add('Topic = ' + ATopic); Memo1.Lines.Add('Payload = ' + Payload); if (Pos('/rx', ATopic) > 0) and (Pos('gw', Payload)>0) and (Pos('mod', Payload)>0) then rxToSG(Payload); end; procedure TForm4.TMSMQTTClient1PublishReceivedEx(ASender: TObject; APacketID: Word; ATopic: string; APayload: TTMSMQTTBytes); begin // こちらも同じ結果 { Memo1.Lines.Add(#13#10 + FormatDateTime('YYYY/MM/DD hh:nn:ss', Now)); Memo1.Lines.Add('TopicEx = ' + ATopic); Memo1.Lines.Add('PayloadEx = ' + TEncoding.ANSI.GetString(APayload)); } end; procedure TForm4.TMSMQTTClient1SubscriptionAcknowledged(ASender: TObject; APacketID: Word; ASubscriptions: TTMSMQTTSubscriptions); begin if (APacketID = FSubscribeRequestPacketId) and ASubscriptions[0].Accepted then begin Memo1.Lines.Add('subscribed'); end; end; end.