🚃

ソケット通信でセンサデータを送受信

2021/11/04に公開

製造業のIoT、MLには時系列データが欠かせません。センサーの時系列データをデータベースサーバーに渡すために、ソケット通信をPythonで構築しました。以下のコードでラズパイ間で無線でセンサーデータを送受信できました。

環境

  • Raspberry pi3 x2
  • Python 3.8

サーバー

センサデータをソケット通信でクライアントに送信します。

server.py
import socket
from datetime import datetime
import pickle

class SocketServer():
  def __init__(self, host, port):
    self.host = host
    self.port = port
  # サーバー起動
  def run_server(self):
    with socket.socket(socket.AF_INET, socket.SOCKET_STREAM) as server_socket:
      server_socket.setsockopt(socket.SQL_SOCKET, socket.SO_REUSEADDR, 1)
      server_socket.bind((self.host, self.port))
      server_socket.listen(CLIENTNUM)
      print("[{}] run server".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
      while True:
        # クライアントから接続要求受け入れ
        client_socket, address = server_socket.accept()
	print("[{0}] connect clinet -> address : {1}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), address))
	# クライアントごとにThread起動 send/recvのやり取りをする。
	t = threading.Thread(target=self.conn_client, args=(client_socket, address))
	t.setDaemon(True)
	t.start()
	
  def conn_client(self, client_socket, address):
    with client_socket:
      while True:
        # クライエントからデータ受信
        rcv_data = client_socket.recv(DATASIZE)
	if rcv_data:
	  print(rcv_data)
	  # rcv_dataをclientに送る。
	  # BM280_readData()はデータを出力する関数。出力はlist型。
	  bme280_data = BME280_readData()
	  client_socket.send(pickle.dumps(bme280_data))
        else:
	  break
    print("[{0}] disconnect client -> address : {1}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), address))
    
if __name__ == "__main__":
  HOST_IP = "192.168.xx.xx"
  SOCKET_PORT = 50000
  SocketServer(HOST_IP, SOCKET_PORT).run_server()

クライアント

クライアントからサーバーにリクエストを送ります。
recv関数でデータを受信します。

client.py
import socket
from datetime import datetime
import time
import pickle

DATESIZE = 1024 # 受信データバイト数
INTERVAL = 3 # ソケット接続時のリトライ待ち時間
RETRYTIMES = 5 # ソケット接続時のリトライ回数

class SocketClient():
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.socket = None

    def connect(self):
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)       
        # サーバーとの接続 RETRYTIMESの回数だけリトライ
        for x in range(RETRYTIMES):
            try:
                client_socket.connect((self.host, self.port))
                self.socket =  client_socket
                print('[{0}] server connect -> address : {1}:{2}'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), self.host, self.port) )
                break
            except socket.error:
                # 接続を確立できない場合、INTERVAL秒待ってリトライ
                time.sleep(INTERVAL)
                print('[{0}] retry after wait{1}s'.format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), str(INTERVAL)) )
 
    # サーバーへデータ送信関数
    def send(self):
        # ターミナルから入力された文字を取得
        input_data = input()
        input_data = input_data.encode('utf-8')
        self.socket.send(input_data) # データ送信
    
    # サーバーからデータ受信関数
    def recv(self):
        rcv_data = self.socket.recv(DATESIZE) # データ受信
        rcv_data = rcv_data.decode('utf-8')
	data = pickle.loads(response)
	# temperature_bme280 = float(data[0])
        # relativehumidity_bme280 = float(data[1])
        # absolutehumidity_bme280 = float(data[2])
        # barometricpressure_bme280 = float(data[3])
        return data
    
    # 上記の送信/受信関数を順番に行う
    def send_rcv(self):
        self.send()
        return self.recv()

    # ソケットをクローズする関数
    def close(self):
        self.socket.close() # ソケットクローズ
        self.socket = None
             
if __name__ == '__main__':
    HOST_IP = "192.168.xx.xx"
    PORT = 50000
    client = SocketClient(HOST_IP, PORT)
    client.connect()
    while True:
        if client.socket is not None:
            client.send_rcv()
        else:
            break

参考

https://www.raspberrypirulo.net/entry/socket-server
https://www.raspberrypirulo.net/entry/socket-client

Discussion