Socket通信の書き換え(pythonより)

解決


hiro  2021-10-01 07:38:29  No: 149853  IP: 192.*.*.*

おんどとりという(株)ティアンドデイが販売している温度・湿度のデータロガーを利用しています。

一世代前までは製品が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

編集 削除
take  2021-10-04 00:01:22  No: 149854  IP: 192.*.*.*

Delphiで組む前に Scokecttoolなどのソケット通信可能なフリーソフトで
そのサーバーがどういう値を要求していてどういう値を返すのかを確認してみてください。

いきなりプログラム書いて動かなかったらプログラムが原因かサーバー含むやりとりが原因かわかりません。

次にDelphiでのコーディングですが、Indy使わないでAPIとやりとりするなら
この辺が参考になりますが、ですがDelphi付属のを使った方が楽ですね

こける Wired-Winsockを使ってみようぜ '99/01/24
http://www.asahi-net.or.jp/~nk2w-ishr/winsock0.htm

編集 削除
hiro  2021-10-05 00:28:52  No: 149866  IP: 192.*.*.*

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;
----------------------------------------------------------------------------------

編集 削除
take  2021-10-05 05:02:05  No: 149868  IP: 192.*.*.*

うまく伝わっていないのかな?

1.Sockettool等、TCP/IP送受信するソフトで、その機械に送って受信データが来ましたか?
2.Delphiには簡単に使えてサンプルも多い Indyというコンポーネントが付いていますが、それを使わない理由は?
3.コーディングされてますけど、動かないのかコンパイルが通らないのかわかりません。 通らないのであればどこが通らないのか?
 コンパイル通って動かないエラーが出るなら そのエラーメッセージは?

編集 削除
hiro  2021-10-06 00:11:14  No: 149870  IP: 192.*.*.*

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

素人相手でヤキモキさせてしまい、大変申し訳ありませんが、引き続きご教授お願い致します。

編集 削除
take  2021-10-06 01:25:20  No: 149871  IP: 192.*.*.*

送受信方法は検索するとすぐ出るのですが・・・

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.

編集 削除
hiro  2021-10-06 03:15:57  No: 149872  IP: 192.*.*.*

take様、アドバイスありがとうございます。

今回教えて頂いたサイトも参考にさせて頂いております。
winsockの基本的な流れは何とかなりそうなんですが、
先回にも記載させて頂いた通り、「sendとrecv部分がhexでやり取りする」為、
そこをどうすれば良いのかがわかっていないです。

indyで実装するとしても、同じ壁に当たりました。

hexのバッファを作り上げる方法が、検索しても見つけられず・・・という状況です。

編集 削除
take  2021-10-06 04:03:23  No: 149873  IP: 192.*.*.*

テキストだろうがバイナリだろうが送受信出来ますよ

そもそもサンプルで書いた
 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>...
こうやって指定すれば指定できます。

じゃあバイナリって何?って聞かれても困りますが

編集 削除
HFUKUSHI  2021-10-06 04:18:06  No: 149874  IP: 192.*.*.*

> 「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

編集 削除
mam  URL  2021-10-10 04:31:31  No: 149876  IP: 192.*.*.*

以下に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;

では如何でしょうか。

編集 削除
hiro  2021-10-15 07:49:00  No: 149879  IP: 192.*.*.*

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;

編集 削除
hiro  2021-10-18 07:34:50  No: 149882  IP: 192.*.*.*

不要かもしれませんが、念の為の記録として・・・そして、自身のメモとしても・・・
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;

編集 削除