おんどとりという(株)ティアンドデイが販売している温度・湿度のデータロガーを利用しています。
一世代前までは製品がWebサーバーを兼ねておりHTMLで結果を取得していたのですが、
現行製品ではクラウド化され仕様が変わってしまいました。
とはいえ、現行製品でもソケット通信をする事で、情報が取得できるらしいのですが、
Delphiでのコーディングがわからず・・・ご教授頂けませんでしょうか?
環境 Delphi 10.3
参考 pythonでのコーディング
import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.111.52", 57172))
print "send "
s.send(bytearray([0xAB,0x01,0x16,0x52,0x0b,0x00,0x01,0x33,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x38,0x00]))
data = s.recv(100)
s.close()
d = data.encode('hex')
print (int(str(d[12:14]+d[10:12]),16) -1000)/10.0
print (int(str(d[16:18]+d[14:16]),16) -1000)/10.0
引用元
https://www.denshi.club/pc/iot/iot2python.html
Delphiで組む前に Scokecttoolなどのソケット通信可能なフリーソフトで
そのサーバーがどういう値を要求していてどういう値を返すのかを確認してみてください。
いきなりプログラム書いて動かなかったらプログラムが原因かサーバー含むやりとりが原因かわかりません。
次にDelphiでのコーディングですが、Indy使わないでAPIとやりとりするなら
この辺が参考になりますが、ですがDelphi付属のを使った方が楽ですね
こける Wired-Winsockを使ってみようぜ '99/01/24
http://www.asahi-net.or.jp/~nk2w-ishr/winsock0.htm
take様、アドバイスありがとうございます。
教えて頂いたサイトや、その他サイトを参考に、コーディングしてみましたが、
send,recvの部分がどうすれば良いか、わかりませんでした。
引き続き、ご支援よろしくお願いします。
----------------------------------------------------------------------------------
uses Winsock;
----------------------------------------------------------------------------------
var
WsData: WSAData;
MySock: TSocket;
MyAddr: TSockAddr;
RecBuf: String;
intSockStatus : Integer;
begin
Memo1.Lines.Clear;
if WSAStartup($101, WsData)<>0 then
begin
Memo1.Lines.Add('Winsock Error.');
Exit;
end;
MySock := socket(AF_INET, SOCK_STREAM, 0);
if intSockStatus = INVALID_SOCKET then
Memo1.Lines.Add('Invalid socket.');
MyAddr.sin_family := AF_INET;
MyAddr.sin_port := htons(57172);
MyAddr.sin_addr.S_addr := inet_addr('192.168.111.52');
intSockStatus := connect(MySock,MyAddr,SizeOf(MyAddr));
if intSockStatus <> 0 then
Memo1.Lines.Add('Connect Error');
intSockStatus := send(MySock,'AB0116520b000133000400000000003800',Length('AB0116520b000133000400000000003800'),0);
if intSockStatus <> 0 then
Memo1.Lines.Add('Send Error');
intSockStatus := recv(MySock,RecBuf,100,0);
if intSockStatus <> 0 then
Memo1.Lines.Add('Recv Error')
else
Memo1.Lines.Add(RecBuf);
WSACleanup;
----------------------------------------------------------------------------------
うまく伝わっていないのかな?
1.Sockettool等、TCP/IP送受信するソフトで、その機械に送って受信データが来ましたか?
2.Delphiには簡単に使えてサンプルも多い Indyというコンポーネントが付いていますが、それを使わない理由は?
3.コーディングされてますけど、動かないのかコンパイルが通らないのかわかりません。 通らないのであればどこが通らないのか?
コンパイル通って動かないエラーが出るなら そのエラーメッセージは?
1.受信データは来ておりません。
sendの書き方が間違っているのは明確なんですが、どうすれば正しい構文になるかがわかっていないです。
2.take様のアドバイスで参考URLがあったので、そちらをベースに他サイトも調べ、indyを利用せずwinsockになりました。
indyが利用できるのであれば、indyの方が良いと思っています。
3.コンパイルは通っています。1の通り、sendの使い方が間違っている様で、ステータス=34となります。
send,recvは引用元のpythonコーディングを見ると、hexになっているので、今のままだと文字列扱いなんですよね?
上記の通り、そこがわかっていないです。indyでも挑戦しようとしたのですが、同じ壁に当たりました。
最初に記載した引用元に送受信データのフォーマットが記載されております。
送信データ
https://www.denshi.club/pc/.assets/onndotori04.png
受信データ(基本フォーマット)
https://www.denshi.club/pc/.assets/onndotori05.png
受信データ(当該データ)
https://www.denshi.club/pc/.assets/onndotori06a.png
素人相手でヤキモキさせてしまい、大変申し訳ありませんが、引き続きご教授お願い致します。
送受信方法は検索するとすぐ出るのですが・・・
https://nadesi.com/delphi/winsock/3-0-connect.htm
これを参考に ボタン1つとメモ1つ置いてボタンイベント内に書いてみました。
Delphi2007までは動作するけどそれ以降のバージョンだと動かないみたいなので
少し修正したのがこちらになります。
これで送信と受信は動きます。
その後におそらく
・URLじゃなくてIPアドレスを指定する場合は?
・URLを間違うと固まるし変換に時間がかかるのなんとかならない?
・受信データが来るまで固まるのなんとかならない?
など寄せられるかもしれませんが、それは便利なIndyを使うか
ご自身で解決していただければと思います。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,WinSock, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
WSAData: TWSAData;
phe: PHostEnt;
s: TSocket;
sockadd: sockaddr_in;
buf,str: Ansistring;
cnt: Integer;
bufs: array[0..1024] of Byte;
begin
// WinSock 初期化
WSAStartup($0101, WSAData);
//---------------------------
// ソケットを作成 ... (1)
s := socket(PF_INET, SOCK_STREAM, 0);
if(s = INVALID_SOCKET)then
raise Exception.Create('ソケットを作成できませんでした。');
//---------------------------
// ホスト情報を得る
phe := gethostbyname('www.google.com');
if phe = nil then raise Exception.Create('ホストがみつかりません。');
// 接続先アドレスを設定 ... (2)
ZeroMemory(@sockadd, Sizeof(sockadd));
sockadd.sin_family := AF_INET;
sockadd.sin_port := htons(80);
sockadd.sin_addr := PInAddr(phe.h_addr_list^)^;
//---------------------------
// 接続
if WinSock.connect(s, sockadd, sizeof(sockadd)) <> 0 then
raise Exception.Create('接続できませんでした。');
SetLength(buf, 1024 * 8); // 8Kのバッファ
//---------------------------
// 送信 ...(3)
str := 'GET ./ HTTP/1.0'+#13#10#13#10;
FillChar(bufs, SizeOf(bufs), #0);
StrCopy(@buf[1], PChar(str));
StrCopy(@bufs[1], PChar(str));
send(s, bufs, Length(bufs), 0);
//send(s, str[1], Length(str), 0);
//---------------------------
// 受信 ...(4)
while True do
begin
SetLength(buf, 1024 * 8); // 8Kのバッファ
ZeroMemory(PChar(buf), Length(buf)); // メモリの初期化
cnt := WinSock.recv(s, buf[1], Length(buf)-1, 0);
//cnt := WinSock.recv(s, bufr[1],SizeOf(bufr), 0);
if cnt = 0 then Break;
if cnt = SOCKET_ERROR then
begin
cnt := WSAGetLastError;
writeln('受信エラー=', cnt); Break;
end;
SetLength(buf,cnt);
Memo1.Lines.Add(buf);
//Writeln(string(PChar(buf))); // 受信したデータを画面に表示
if Length(buf) > cnt then Break; // データを読みきった
end;
//---------------------------
// 切断...(5)
if (shutdown(s, SD_BOTH) <> 0) then writeln('シャットダウンに失敗');
closesocket(s);
WSACleanup;
end;
end.
take様、アドバイスありがとうございます。
今回教えて頂いたサイトも参考にさせて頂いております。
winsockの基本的な流れは何とかなりそうなんですが、
先回にも記載させて頂いた通り、「sendとrecv部分がhexでやり取りする」為、
そこをどうすれば良いのかがわかっていないです。
indyで実装するとしても、同じ壁に当たりました。
hexのバッファを作り上げる方法が、検索しても見つけられず・・・という状況です。
テキストだろうがバイナリだろうが送受信出来ますよ
そもそもサンプルで書いた
str := 'GET ./ HTTP/1.0'+#13#10#13#10;
は 後半バイナリ指定のような
サンプルが
s.send(bytearray([0xAB,0x01,0x16,0x52,0x0b,0x00,0x01,0x33,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x38,0x00]))
こうなら
str := #$ab#$01#$16 ... #$00;
こう書き換えたらバイナリでしょ?
紹介したSokettool を使う場合は 送信データに
<ab><01><16>...
こうやって指定すれば指定できます。
じゃあバイナリって何?って聞かれても困りますが
> 「sendとrecv部分がhexでやり取りする」
テキストではなくバイナリで、ということかと思います。この場合はTBytes型
https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.SysUtils.TBytes
を使うことになります。TBytesはarray of Bytes、つまり符号なし8bit整数の(動的)配列で、1つづつの要素はByte型です。
var
SendData: TBytes;
begin
SendData := [$AB,$01,$16,$52,$0b,$00,$01,$33,$00,$04,$00,$00,$00,$00,$00,$38,$00];
とかこんな感じで扱います。動的配列についてはここも参考にしてください。
https://docwiki.embarcadero.com/RADStudio/Sydney/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E5.8B.95.E7.9A.84.E9.85.8D.E5.88.97
以下にTcpClientのユニット(クラス)を公開しました。
https://mam-mam.net/delphi/winsock.html
このユニットを使って
Uses MamTcpClient;
procedure TForm1.Button4Click(Sender: TObject);
var TcpClient:TMamTcpClient;
RecvData:TBytes;
SendData: TBytes;
begin
SendData :=
[$AB,$01,$16,$52,$0b,$00,$01,$33,
$00,$04,$00,$00,$00,$00,$00,$38,$00];
TcpClient:=TMamTcpClient.Create();
TcpClient.Port:=57172;
TcpClient.Host:='192.168.111.52';
if TcpClient.Open then
begin
TcpClient.SendBytes(SendData); //送信(バイト型配列)
sleep(100); //とりあえずちょっと待つ
RecvData:=TcpClient.RecvBytes; //受信(バイト型配列)
Memo1.Lines.Add( format('%f',[ (RecvData[6]*256+RecvData[5]-1000)/10 ]) );
Memo1.Lines.Add( format('%f',[ (RecvData[8]*256+RecvData[7]-1000)/10 ]) );
TcpClient.Close;
end;
TcpClient.free;
end;
では如何でしょうか。
take様、HFUKUSHI様、mam様、アドバイスありがとうございます。
おかげで解決できました。
皆さま、ご指導ありがとうございました。
mam様のユニットとサンプルは、そのまま稼働しました。
take様、HFUKUSHI様のアドバイスを基に、以下のコーディングをしました。
レアケースとは思いますが、同じ悩みを持たれる方が発生した時の為、残しておきます。
----------------------------------------------------------------------------------
uses Winsock; //←追加
----------------------------------------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
var
WsData: TWSAData;
MySock: TSocket;
MyAddr: TSockAddr;
intSockStatus : Integer;
RecvData:TBytes;
SendData: TBytes;
begin
Memo1.Lines.Clear;
if WSAStartup($101, WsData)<>0 then
begin
Memo1.Lines.Add('Winsock Error.');
Exit;
end;
try
try
MySock := socket(AF_INET, SOCK_STREAM, 0);
if intSockStatus = INVALID_SOCKET then
raise Exception.Create('Invalid socket.');
MyAddr.sin_family := AF_INET;
MyAddr.sin_port := htons(57172);
MyAddr.sin_addr.S_addr := inet_addr('192.168.111.52');
intSockStatus := connect(MySock,MyAddr,SizeOf(MyAddr));
if intSockStatus = SOCKET_ERROR then
raise Exception.Create('Connect Error');
SendData :=
[$AB,$01,$16,$52,$0b,$00,$01,$33,
$00,$04,$00,$00,$00,$00,$00,$38,$00];
if send(MySock,SendData[0],Length(SendData),0) = SOCKET_ERROR then
raise Exception.Create('Send Error');
SetLength(RecvData, 10);
ZeroMemory(PChar(RecvData), Length(RecvData)); // メモリの初期化
intSockStatus := WinSock.recv(MySock, RecvData[1], Length(RecvData)-1, 0);
Memo1.Lines.Add( format('%f',[ (RecvData[7]*256+RecvData[6]-1000)/10 ]) );
Memo1.Lines.Add( format('%f',[ (RecvData[9]*256+RecvData[8]-1000)/10 ]) );
except
on e:exception do
begin
Memo1.Lines.Add(e.Message);
end;
end;
finally
closesocket(MySock);
WSACleanup;
end;
end;
不要かもしれませんが、念の為の記録として・・・そして、自身のメモとしても・・・
Indyだと以下で動作しました。
※例外処理が圧倒的に不足しております。
----------------------------------------------------------------------------------
uses IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdGlobal; //←追加
----------------------------------------------------------------------------------
procedure TForm1.Button2Click(Sender: TObject);
var
MyIdTCPClient : TIdTCPClient;
RecvData : TIdBytes;
SendData : TIdBytes;
begin
MyIdTCPClient := TIdTCPClient.Create(nil);
try
MyIdTCPClient.Port := 57172;
MyIdTCPClient.Host := '192.168.111.52';
MyIdTCPClient.Connect;
if MyIdTCPClient.Connected then
begin
SendData :=
[$AB,$01,$16,$52,$0b,$00,$01,$33,
$00,$04,$00,$00,$00,$00,$00,$38,$00];
MyIdTCPClient.IOHandler.Write(SendData,Length(SendData),0);
MyIdTCPClient.IOHandler.ReadBytes(RecvData,10);
Memo1.Lines.Add( format('%f',[ (RecvData[6]*256+RecvData[5]-1000)/10 ]) );
Memo1.Lines.Add( format('%f',[ (RecvData[8]*256+RecvData[7]-1000)/10 ]) );
MyIdTCPClient.Disconnect;
end;
finally
FreeAndNil(MyIdTCPClient);
end;
end;
ツイート | ![]() |