UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

Raspberry PI Zero とUSB接続してpython制御する例

Raspberry PI Zeroをスマフォの代わりに使って、PCやAndroidの 「umehoshiアプリ」で遠隔操作を可能とする作品

『 ABS樹脂ケース(蝶番式・中) 112−TS(P-00277)』 に穴などの加工をして、 それに、 Camera を付けた[Raspberry PI Zero WH]と[UMEHOSHI ITA ]をUSBで繋いだ構成を IoT機器対応モバイルバッテリー(5000mAh)と共に詰め込んでいます。
この組み立て過程をこのページで紹介しています。
モバイルバッテリーから[Raspberry PI Zero W]に電源が供給され、[Raspberry PI Zero WH]からUSB接続で[UMEHOSHI ITA]に供給される構成です。
([Raspberry PI Zero WH]と[UMEHOSHI ITA ]のUSB接続は通信も兼ねて、[Raspberry PI Zero W]から[UMEHOSHI ITA]を制御します)
そしてモータ用の電源は、下部に配置した単3×4の電池で[UMEHOSHI ITA]のDCジャックより供給しています。
(Raspberry 用のシャットダウン用スイッチも追加しました)
(下記画像のクリックでイメージが変わります。) このClickでYoutube 紹介動画へ移動できます。


[Raspberry PI Zero WH]はこのページでセットアップをしたものを 使って組み立てています
このセットアップにより、[Raspberry PI Zero WH]をWifiで接続する方法は次の2通りです。
(両者ともSSHのクライアントを使って[Raspberry PI]を操作します。(「pi」のIDで[abc123]のパスワードで接続)
 Are you sure you want to continue connecting (yes/no/[fingerprint])?でyesを入力します。
 Host key verification failed.のエラーであれば「ssh-keygen -R IPアドレス」入力で、該当のIPアドレス情報を消して再接続します。)

  1. (1) [Raspberry PI]を192.168.0.1のアクセスポイントに接続して、 同一ネットワーク内のPCから[Raspberry PI]にSSHで接続する方法です。
    PC側の端末で、[Raspberry PI]の「ssh pi@192.168.0.123」で接続してできます。
    Wifi環境のアクセスポイントがない所ではできませんが、[Raspberry PI]でインターネットも使える設定が可能です。 (アクセスポイントに合わせて[Raspberry PI]のセットアップを変更してください。)

  2. (2) [Raspberry PI]自身をWifiのアクセスポイントにする方法です。 操作するPCやAndroidでは、[Raspberry PI]のSSID:pizero、パスプレーズ:abcd1234のWifiアクセスポイントに接続します。 この場合に[Raspberry PI]へのアクセスは、「ssh pi@192.168.100.1」で接続します。

以上どちらかのWifiを介して次のような遠隔操作できる環境を作ります

[Raspberry PI]の自作TCPサーバで、次のよう基本的な動作ができるように作ります。

  1. 遠隔操作用の指示ファイル(.umhファイル)をTCPで受信

  2. [Raspberry PI]は受信した.umhファイル内の(UME専用Hexコマンド)をUSBを介して[UMEHOSHI ITA]に送信して動作させます。

  3. UME専用Hexコマンドを受信してその処理を行った[UMEHOSHI ITA]は、その応答メッセージをUSBを介して[Raspberry PI]に送信します。

  4. [Raspberry PI]側でTCPサーバでは、USBを介して受信した応答メッセージをTCPクライアントに送信します。

つまり自作TCPサーバは、umehoshiアプリのサーバ側の処理に近い機能を盛り込んだものと言えます。
また、自作TCPサーバでは、[Raspberry PI]ロボットモータなどの制御プログラムを[Raspberry PI]を起動時に埋め込む機能を付加します。
(この[UMEHOSHI ITA]側のプログラム情報はこのページにあります。)
それによって起動後は、遠隔操作用の「.umh」の転送だけでロボットを制御できるようにします。
この主要なコマンド(プログラムを入れたファイル名)は次のようになります。

前進uGoForward.umh
後進uGoBack.umh
左回転uGoLeft.umh
右回転uGoRight.umh

以上を実現するために、次のpythonのコードを作りました。

ソース名概要
umeusb.py
USBで[UMEHOSHI ITA]と通信する以下の関数群と変数を定義しています。
usb:このグローバル変数が指し示す接続相手とUSB通信するためのオブジェクト
imort時にオープンされて、下記各関数はそれを利用する。
init_sub(): SUBのSerial初期化
check_sum(s:str) -> bool: check sum エラーでFalseを返す
usb_send(s, endstr=b"\n",quantity=2):sを送信して、受信したバイナリを返す。タイムアウト時は""を返す
         endstrがquantity個受信した時点でリターンする。(コマンドの応答が2行であることが前提)
send_cmd( ss, cmdchar="SRG"):  usb_sendを利用してコマンド文字列群を引数で指定可能(S R G以外の先頭行文字列は無視)
send_cmdfile(filepath, cmdchar="SRG"): send_cmdを利用して、filepathの「UME専用Hexコマンド」をUSB先の[UMEHOSHI ITA]に送信
usb_receive_func: 利用時に変更するusb受信で処理する関数を、記憶する変数
単体の実行で、キー入力したhexコマンドをUSBで[UMEHOSHI ITA]に送信可能
umetcp.py
TCPで[UMEHOSHI ITA]と通信する以下の関数群を定義しています。(他にカメラを参照する変数も初期化)
imort時にオープンされて、下記各関数はそれを利用する。
send_file(sock, filename): filenameのファイルをsockの相手に送信する。
send_message(sock,msg): msgの1行の文字列をsockの相手に送信する。
recieve_file(sock): sockからの受信ファイルでそのファイルを保存する。
recieve_message(sock): sockからの受信した文字列を表示する。
receiveData(sock): sockからの1つの受信処理で、上記の各受信処理を呼び出して作られる
単体の実行で、Wifiを介して「umehoshiアプリからのメッセージ受信、ファイル受信、カメラ撮影と送信が可能
umehoshi.py
TCPサーバとして起動して、受信したコマンドで[UMEHOSHI ITA]と通信するメインの処理を行っています。
上記モジュール(umeusb.py、umetcp.py)をimportして利用してTCP受信でUSBへコマンド送り制御します。
my_usb_receive_func(bin):usb受信に対する処理で、umeusbのデフォルトを置き換え、TCP接続相手へ応答メッセージを送出する
my_tcp_receive_file_func(file): 受信したumehoshiアプリ用「.umh」のファイルより、E専用Hex文字列をusbへ出力する

各ソースコードは後述しています。/home/pi/umehoshi のディレクトリを作って、 その中に上記3つのファイル入れて置きます。

umehoshi.pyをサービスとして、電源投入時に、実行させる設定を行います。
この自動起動には、Systemdを使った方法でを使います。
Systemdはサービスとして、起動、シャットダウン、再起動などをコマンドで操作ができるのようになります。
個々のサービスなどの設定を行うファイル「 Unitファイル」を次のように作ります。
ユーザ作成のUnitファイルの置き場は、「/etc/systemd/system」と決まって、次のファイルを使います。
「/etc/systemd/system/umehoshi.service」をsudoの編集操作で操作します。

[Unit]
Description = umehoshiserver.

[Service]
ExecStart= /home/pi/umehoshi/umehoshi.py
Type=simple

[Install]
WantedBy=multi-user.target

(基本的には、「Description」にサービス名、「ExecStart」に実行したいプログラムを記載しています。)
サービス制御用のコマンドの表を以下に示します。

コマンド操作意味
(1)sudo systemctl start umehoshi.serviceサービスを開始(自動実行の登録前の検証時の実行で使う)
(2)sudo systemctl stop umehoshi.serviceサービスを停止
(3)sudo systemctl enable umehoshi.serviceサービスの自動起動を有効化します
(4)sudo systemctl status umehoshi.serviceサービスの動作状況を確認します。
(5)sudo systemctl disable umehoshi.serviceサービスの自動起動を無効化します。

下記Systemdの設定は、後述しているumeusb.py、umetcp.py、umehoshi.pyの各動作確認ができてから行った方が良いでしょう。
手順(1): 「sudo systemctl start umehoshi.service」実行して60秒後にumehoshi.pyの動作をumeclient.pyで確認します。
手順(2): (1)が動作できたら、「sudo systemctl enable umehoshi.service」で起動の登録を行い、[Raspberry PI]をシャットダウンさせて電源を差し込み直します。

以上で 「umehoshiアプリ」からの遠隔操作が可能となるはずです。
(「umehoshiアプリ」で、pizeroのアクセスポイントに接続して、192.168.100.1に接続して使います。可能な操作はメッセージを送る。 「.umh」や「.hex」ファイル(S R のhexコマンド群)を転送して実行させる。写真を撮る操作です。)

なおこのumehoshiserverのサービスが実行中の場合は、tcpやUSBのポートが使用中の状態になります。
よって、後述しているumeusb.py、umetcp.py、umehoshi.pyを別途コマンドで起動することができなくなります。
これらをコマンド操作で実行させる場合は、「sudo systemctl stop umehoshi.service」でサービスを停止して操作する必要があります。

umeusb.pyのコード

以前に紹介したPythonのUSB通信プログラムを変更して作ったもので、単独実行でキー入力した「UME専用Hexコマンド」を [UMEHOSHI ITA ]にプログラムを転送して制御できるコードです。
(usb = serial.Serial(port = '/dev/ttyACM0', baudrate = 115200)のport設定は、[Raspberry PI]のUSB位置で変更する必要があるかもしれません)
send_cmdfileで、「.umh」と「.hex」のファイル両方に対応して、「UME専用Hexコマンド」だけをusbに送信しています。

import serial
import threading
import sys
import time

def defalut_usb_receive_func(bin):
    'usb受信のイベントで実行するデフォルト処理'
    print( bin.decode('utf-8') ,end="") # USBより受信したデータを表示

usb_receive_func = defalut_usb_receive_func # usb受信で実行する処理
# 上記を変更することで、USB受信に対する振る舞いを変更できる。

"usb:このグローバル変数が指し示す接続相手とUSB通信するためのオブジェクト"
"imort時にオープンされて、下記各関数はそれを利用する。"
usb = None

def init_sub(): 
    global usb
    try:
        usb = serial.Serial(port = '/dev/ttyACM0', baudrate = 115200)
        #usb = serial.Serial(port = 'COM8', baudrate = 115200,timeout = 10)
    except Exception as e:
        print(e)
        sys.exit(1)
    print( usb ) # USB のシリアルオブジェクト表示

def check_sum(s):
    ''' b = check_sum("G10800090000067")
    '''
    ck = 0
    n = len(s)
    if n == 0: return
    for i in range(n-2):
        ck += ord(s[i])
        # print(ck)
        # print(s[i] , end ="")
    # print(' ',s[-2:] , end ="")
    ck += int(s[-2:] , 16)
    ck &= 0x0f
    if ck != 0 :
        print("check sum error", ck)
        return False
    return True

def usb_send(s, endstr=b"\n",quantity=2):
    '''  b = usb_send("G10800090000067", endstr=b"\n",quantity=1):
    sを送信して、受信したバイナリを返す。タイムアウト時は""を返す
    endstrがquantity個受信した時点でリターンする。
    quantity=2のデフォルト値は、送信した1行に対するエコー文字列の1行と
    応答文字列の1行を意味します。
    '''
    if check_sum(s) == False: return b""
    usb.write(s.encode('utf-8'))
    usb.write(b'\r\n')
    rtnval=b""
    while True:
        if quantity <= 0:
            return rtnval # 受信したバイナリーを返す。
        b=usb.readline()
        if b == "": return ""
        rtnval+=b
        n=b.count(endstr)
        quantity-=n

def send_cmd( ss, cmdchar="SRG"):# sの例[S048000800000FFAF000086]
    a = ss.split('\n')
    for s in a:
        s=s.strip()
        if s == "" or not s[0] in cmdchar : continue # S R G 以外のコマンドを無視
        b = usb_send(s) # デフォルトで2行のudb応答文字列の受信を含むコマンド出力
        usb_receive_func( b ) # USB受信で得られたbで行う処理

def send_cmdfile(filepath, cmdchar="SRG"):
    with open(filepath , "r", encoding="utf-8") as f:
        ss = f.readlines()
    if filepath.endswith(".umh") : ss = ss[1:] # 先頭のボタン仕様行を除去
    ss="".join(ss)
    #print(ss)
    send_cmd( ss, cmdchar)

# USB受信スレット例(b'end\r\n'受信まで繰り返す)
def read_loop():
    while True:
        try:
            b=usb.readline() # binary
            if(b == b'end\r\n'):
                print("recieve 'end'")
                break
            usb_receive_func( b ) # USB受信の処理
        except Exception as e:
            #print(e)
            break
    #
    print("end read_loop()")

if __name__ == "__main__x": # 「UME専用Hexコマンド」ファイルの送信テスト用
    init_sub()
    send_cmdfile("app_pwm.c.hex")

if __name__ == "__main__": # キー入力した「UME専用Hexコマンド」を送信して[UMEHOSHI ITA]を制御するテスト用
    # スレッドに read_loop 関数を渡して実行を始める
    init_sub()
    t_id = threading.Thread(target=read_loop)
    t_id.start()
    while True:
        s = input("USBへ送信する文字列>")# 例[G10800090000067]
        if s == "": break
        usb_send( s, quantity=0 )
        time.sleep(0.1)
    usb.close()
    print("Ended !")
上記ファイルの単独実行例を示します。(キー入力した「UME専用Hexコマンド」の文字列を[UMEHOSHI ITA]に送って、その応答メッセージを表示)
(最後は、Ctrl+Cなど強制終了)
pi@raspberrypi:~/umehoshi $ python3 umeusb.py
Serial<id=0xb6890c30, open=True>(port='/dev/ttyACM0', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
USBへ送信する文字列>G10800090000067
G10800090000067
:108000900000CF0ABCECC71A6C23291CA1A32139A8A223
USBへ送信する文字列>
Ended !

umetcp.pyのコード

TCPで[UMEHOSHI ITA]と通信する以下の関数群を定義しています。(他にカメラを参照する変数も初期化)
umehoshiアプリTCP送受信メッセージの仕様に従って、 その1部であるM f T から始まる文字列に対する処理を定義しています。
それぞれの先頭文字は、[メッセージ文字列受信]、[ファイル受信]、[写真を撮ってファイルを要求] の意味になっており、
receiveData関数でこの分岐処理をしています。

import os
import sys

path_datas = "datas" # 受信したhexファイルの記憶ディレクトリ
path_pics = "pictures" # 撮影したファイルの記憶ディレクトリ

if os.path.isdir(path_datas) == False:
    os.mkdir(path_datas)

if os.path.isdir(path_pics) == False:
    os.mkdir(path_pics)

try:
   import picamera # ★ raspberry Pi用
   camera = picamera.PiCamera() # ★
except: pass

def defalut_tcp_recieve_file(filename):
    'tcpでファイルを受信した直後の処理'
    filesize = os.path.getsize(path_datas + "/" + filename)
    # 受信したファイル名とサイズを表示
    print( "recieve file:{},size:{}".format(filename, filesize)) 

tcp_receive_file_func = defalut_tcp_recieve_file # tcpファイル受信で実行する処理

def defalut_tcp_recieve_message(msg):
    print( msg ) 

tcp_receive_message_func = defalut_tcp_recieve_message # tcpメッセージ受信で実行する処理

def send_file(sock, filepath):
    filesize = os.path.getsize(filepath)
    filename = os.path.basename(filepath)
    s = "f{} {}".format(filename, filesize)
    bin=(s+"\r\n").encode("utf-8")#binaryへ変換
    print(bin, end="  ")
    sock.sendall(bin) #filename filesize 送信
    with open(filepath, "rb") as f:
        bin = f.read(filesize)
    # print("送信data:" , bin )
    sock.sendall(bin) # ファイル内容一括送信
    print("送信byte:" , len(bin) )

def send_message(sock,msg):
    bin=('M'+msg+"\r\n").encode("utf-8")#binaryへ変換
    sock.sendall(bin)#一括送信

def recieve_file(sock):
    buf=b""
    while True:
        bin = sock.recv(1)#1byte受信
        if len(bin) != 1: break
        buf += bin
        if buf[-2:] == b"\r\n":# 1行の文字列受信終了?
            s = buf[0:-2].decode('utf-8')#バイナリから
            #print(s)
            a=s.split(' ')
            filename, filesize = a[0], int(a[1])
            print(filename, filesize) # 受信ファイルとサイズ確定
            #bin = sock.recv(filesize) # ファイルの一括受信
            bin=b""
            while len(bin) < filesize:
                bin += sock.recv(filesize-len(bin)) # ファイルの受信
            #print( bin )
            with open( path_datas + "/" + filename, "wb") as f:
                f.write(bin)
            tcp_receive_file_func(filename)
            break

def recieve_message(sock):
    buf=b""
    while True:
        bin = sock.recv(1)#1byte受信
        if len(bin) != 1: break
        buf += bin
        #print("----", buf)
        if buf[-2:] == b"\r\n":# 1行の文字列受信終了?
            s = buf[0:-2].decode('utf-8')#バイナリから
            tcp_receive_message_func(s) # 受信文字列の対処
            break

def receiveData(sock):
    print( "start receiveData" )
    while True:
        bin = sock.recv(1)#1byte受信
        if len(bin) != 1: break
        if bin == b"f" : recieve_file(sock)
        elif bin == b"M" : recieve_message(sock)
        elif bin == b"T" : 
            sock.recv(1)#'\r'受信
            sock.recv(1)#'\n'受信
            pic_file_name="serverpicture.jpg"
            if 'camera' in globals(): 
                camera.capture(path_pics + "/" + pic_file_name) # ★写真撮影
                print("写真撮影")
            send_file(sock,path_pics + "/" + pic_file_name)
            print("送信ファイル:" + pic_file_name)
        else:
            print(bin , end="")
    #
    print( "receiveData ended." )
        

if __name__ == "__main__":
    import socket
    portnumber=59154
    ip = "192.168.100.1"
    ip = "192.168.0.123" #[Raspberry PI Zero WH]の設定に合わせてください
    server_addr =(ip, portnumber)
    print("Serverの情報:",server_addr)
    serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversock.bind(server_addr)  # IPとポート番号を指定します
    print("接続要求を待つ")
    serversock.listen()
    sock, address = serversock.accept()#サーバの接続受け入れ
    try: 
        receiveData( sock ) # 命令を受信して処理するループ
    except: pass
    sock.close()
    serversock.close()

上記ファイルの確認用実行例を示します。
sshで「192.168.0.123」に接続した後、PC側のpythonが動作するコマンドプロンプトで「python umeclient.py] サーバ側IPアドレス(192.168.0.123)は[Raspberry PI]の設定に合わせて変更して使ってください。
[Raspberry PI]をアクセスポイントしてWifi環境がない所でも使う最終設定は192.168.100.1ですが、
ここでは、既存アクセスポイントを介して[Raspberry PI]の192.168.0.123に接続している状態の例です。
なお、右下では「umehoshiアプリ」の代わりにその一部を実現するumeclient.pyを使って検証しています。
(写真表示の警告や終了時のエラーは無視してください)

[Raspberry PI]に接続中のssh内容PC側の操作用コマンドプロンプト
pi@raspberrypi:~/umehoshi $ python3 umetcp.py
Serverの情報: ('192.168.0.123', 59154)
接続要求を待つ
start receiveData
hello
uGoForward.umh 24
recieve file:uGoForward.umh,size:24

写真撮影
b'fserverpicture.jpg 250169\r\n'  送信byte: 250169
送信ファイル:serverpicture.jpg

pi@raspberrypi:~/umehoshi $
D:\raspi_zero\umehoshi>python umeclient.py
IP (defualt:192.168.0.123)Address>192.168.0.123
接続成功
start receiveData
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>M
送信したい文字列入力>hello
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>w
b'fuGoForward.umh 24\r\n'  送信byte: 24
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>T
serverpicture.jpg 250169
recieve file:serverpicture.jpg,size:250169
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>quit

umehoshi.pyのコード

TCPサーバとして起動させて、受信したコマンドで[UMEHOSHI ITA]と通信するメインの処理を行っています。
上記モジュール(umeusb.py、umetcp.py)をimportして利用してTCP受信でUSBへコマンド送り制御します。
[Raspberry PI Zero WH]に合わせたIPアドレスでTCPサーバをバインドしていますが、[Raspberry PI Zero WH]の設定に合わせてください。
以下では、192.168.100.1にしている最終設定例です。
(これは、のSystemdを使った自動的な起動に合わせた設定です。デバックなどの確認時は192.168.0.123で動作を確認すると良いでしょう)
起動自に、app_pwm_pizero.c.hexのファイルを読み取って、[UMEHOSHI ITA]を初期化しています。
このソース(app_pwm_pizero.c)はこのページにあります。 これをビルドして作っており、これを置き換えることで、起動時の[UMEHOSHI ITA]プログラムが変更できます。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#   サーバ側「sudo python3 umehosi.py」で動かす
import os
import umetcp
from umetcp import send_file, send_message, recieve_file, recieve_message, \
   receiveData , path_datas
import socket
import umeusb
import sys
import time

sock = None # クライアント側のソケット

os.chdir("/home/pi/umehoshi/")
# ip = "192.168.0.123" #[Raspberry PI Zero WH]の設定に合わせてください
ip = "192.168.100.1"
if ip == "192.168.100.1":
    time.sleep(60) # 60秒待機

args=sys.argv

umeusb.init_sub()

start_path = path_datas + "/" + "app_pwm_pizero.c.hex"

if os.path.isfile( start_path ) == True:
    umeusb.send_cmdfile(start_path)
    umeusb.send_cmd("R00800050000061")

def my_usb_receive_func(bin):
    'usb受信のイベントで実行するデフォルト処理'
    global sock
    #print("-----------",bin)
    ss = bin.decode('utf-8')
    a=ss.split('\n')
    for s in a:
        s = s.strip()
        if sock != None:
            send_message(sock, s) # [UMEHOSHI ITA]からの応答メッセージをTCPで返す
        else: print( s )

umeusb.usb_receive_func = my_usb_receive_func # USB受信データの処理を置き換える。

def my_tcp_receive_file_func(filename): 
    ''' 受信したumehoshiアプリ用「.umh」のファイルより、
     「UME専用Hexコマンド」の文字列をusbへ出力する'''
    name,ext = os.path.splitext(filename)
    if not ext == ".umh" : return
    with open(path_datas + "/" + filename) as f: ss=f.readlines()
    ss="".join(ss)
    #print(ss)
    umeusb.send_cmd(ss) # TCPで受信した 「.umh」データを[UMEHOSHI ITA]へ送る

umetcp.tcp_receive_file_func = my_tcp_receive_file_func # tcp受信データの処理を置き換え

portnumber=59154
hostname=socket.gethostname()

if len(args) >= 2: ip = args[1] # 引数でIPアドレス指定があれば使う
server_addr =(ip, portnumber)
print("Serverの情報:",hostname, server_addr)
serversock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversock.bind(server_addr)  # IPとポート番号を指定します
print("接続要求を待つ")
serversock.listen()
while True:
   print("接続を待って、接続してきたら許可")
   sock, address = serversock.accept()#サーバの接続受け入れ
   print("接続相手:",address)
   try:
      receiveData( sock ) # 受信ループ
   except Exception as e:
      print(e, "相手が閉じた?")
   sock.close()

実行例を示します。
[Raspberry PI Zero WH]のアクセスポイント(ssid:pizero)に接続してから、192.168.100.1のsshターミナルでRaspberry PIを操作します。 [Raspberry PI Zero WH]のアクセスポイント(ssid:pizero)が有効になるまでに時間がかかる ことに合わせて、60秒ほど経過してから 各種の初期化が行われるようにしています。
よって、左下のpython3 umehoshi.py実行後、60秒間は何も表示しない状況があることに注意してください。
その後、"app_pwm_pizero.c.hex"を[UMEHOSHI IT]に転送する画面へと続きます。
この転送が終わってから、pythonが動作するPC側のコマンドプロンプトで「python umeclient.py]で遠隔操作をしている例です。
「192.168.100.1」で接続している例です。(接続で左側では「接続相手: ('192.168.100.5', 52442)」が追加表示されています)
PC側ではその後、「M」を入力して " hello"の文字列を送り、
次に「w」を入力して [uGoForward.umh]のファイル転送でモータを正転させて、
次に「T」を入力して 写真撮影とそのファイル転送をさせた後、quit入力で終了させています。
(写真表示の警告や終了時のエラーは無視してください)

[Raspberry PI]に接続中のssh内容PC側の操作用コマンドプロンプト
pi@raspberrypi:~/umehoshi $ python3 umehoshi.py
Serial(port='/dev/ttyACM0', baudrate=115200, bytesize=8, 
parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)
S048000800000FFDF000083
SET:80008000
S048000800400FFFF00007D
SET:80008004
・・・表示を省略・・・
S108000537C00FFFF000000050000CEFFFFFF3200000034
SET:8000537C
S108000538C001480008008000000000000000000000024
SET:8000538C
R00800050000061
START:80005000
Serverの情報: raspberrypi ('192.168.100.1', 59154)
接続要求を待つ
接続を待って、接続してきたら許可
接続相手: ('192.168.100.5', 52442)
start receiveData
   hello
uGoForward.umh 24
写真撮影
b'fserverpicture.jpg 126136\r\n'  送信byte: 126136
送信ファイル:serverpicture.jpg
[Errno 104] Connection reset by peer 相手が閉じた?
接続を待って、接続してきたら許可



 【Raspberry PIの初期化が終わるまで60秒待ちます】





D:\raspi_zero\umehoshi>python umeclient.py
IP (defualt:192.168.0.123)Address>192.168.100.1
接続成功
start receiveData
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>M
送信したい文字列入力>   hello
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>w
b'fuGoForward.umh 24\r\n'  送信byte: 24
R0080005200005F
START:80005200

送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>T
serverpicture.jpg 126136
recieve file:serverpicture.jpg,size:126136
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>

quit

Raspberry PI側では、PCの接続が終わると、「接続を待って、接続してきたら許可」の表示が出て、次の接続が可能になっています。
以上の確認ができてから、Systemdによる自動起動の設定をすると良いでしょう。



umeclient.pyのコード

上記の[UMEHOSHI ITA]で実行させるumehoshi.pyに対するクライアントプログラムで、実験的なコードです。
上記のサーバ側IPアドレス(192.168.100.1 or 192.168.0.123)に合わせて変更して使ってください。
[Raspberry PI]をアクセスポイントしてWifi環境がない所でも使う最終設定では192.168.100.1にして使います。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#  クライアント側 「sudo python3 umeclient.py」で動かす

import socket
from umetcp import send_file, send_message, recieve_file, recieve_message,receiveData,path_datas

import os
import sys
import time
import threading
import umetcp
import numpy as np
from PIL import Image
import matplotlib
matplotlib.use('TkAgg') #うまくいかなければ MacOSX Qt5Agg Qt4Agg Gtk3Agg GTK3Cairo TkAgg WxAgg Agg Cairo の中から選ぶ
import matplotlib.pyplot as plt

def my_tcp_recieve_file(filename):
    'tcpでファイルを受信した直後の処理'
    filepath = path_datas + "/" + filename
    filesize = os.path.getsize(filepath)
    # 受信したファイル名とサイズを表示
    print( "recieve file:{},size:{}".format(filename, filesize)) 
    if filename == 'serverpicture.jpg':
        img = Image.open(filepath) # 元となる画像の読み込み
        np_img = np.array( img )
        plt.imshow( np_img ) # 各OSの標準ビューアが開く。
        plt.show( ) #block= False )

umetcp.tcp_receive_file_func = my_tcp_recieve_file

portnumber=59154
# ip="192.168.0.123" #[Raspberry PI Zero WH]の設定に合わせてください
ip="192.168.100.1"
s = input("IP (defualt:{})Address>".format(ip))
if s != "": ip = s
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, portnumber))
print("接続成功")
t_id = threading.Thread(target=receiveData, args=(sock,) )
t_id.start()
loopFlag = True
cmd=""
while loopFlag:
    s = input("送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>")
    if s != "": cmd = s
    if cmd == 'M':
        msg = input("送信したい文字列入力>")
        send_message(sock,msg)
    elif cmd == 'f':
        print(os.listdir(path_datas))
        filename = input("送信したいファイル名入力>")
        if not os.path.isfile(path_datas + "/" + filename):
            print( filename, "no exist")
            countine
        send_file(sock, path_datas + "/" + filename)
    elif cmd == 'quit': loopFlag=False
    elif cmd == 'w':
        send_file(sock, path_datas + "/" + "uGoForward.umh")
    elif cmd == 'x':
        send_file(sock, path_datas + "/" + "uGoBack.umh")
    elif cmd == 'a':
        send_file(sock, path_datas + "/" + "uGoLeft.umh")
    elif cmd == 'd':
        send_file(sock, path_datas + "/" + "uGoRight.umh")
    elif cmd == 'Q':
        send_file(sock, path_datas + "/" + "uLeftUp.umh")
    elif cmd == 'Z':
        send_file(sock, path_datas + "/" + "uLeftDown.umh")
    elif cmd == 'E':
        send_file(sock, path_datas + "/" + "uRightUp.umh")
    elif cmd == 'C':
        send_file(sock, path_datas + "/" + "uRightDown.umh")
    elif cmd == 'T':
        sock.sendall( ('T'+"\r\n").encode("utf-8") )# 写真を撮って、そのファイルを要求
    #
    time.sleep(0.5)

sock.close()
sys.exit(0)

「umehoshi.service」のサービス実行中であれば 「umehoshiアプリ」の代わりに、このファイルで動作が確認できます。
このロボットの電源投入後2分経過(実測値は1分43秒)してから、 [Raspberry PI Zero WH]のアクセスポイント(ssid:pizero)に接続し、 pythonが動作するPC側のコマンドプロンプトで「python umeclient.py]で遠隔操作をしている例です。

D:\raspi_zero\umehoshi>python umeclient.py
IP (defualt:192.168.0.123)Address>192.168.100.1
接続成功
start receiveData
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>M
送信したい文字列入力>   hello
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>w
b'fuGoForward.umh 24\r\n'  送信byte: 24
R0080005200005F
START:80005200

送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>T
serverpicture.jpg 126136
recieve file:serverpicture.jpg,size:126136
送信したい情報を選択入力'M'/'f'/'quit'/w/x/a/d/Q/Z/E/C/T>

quit