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.