UMEHOSHI ITA TOP PAGE    COMPUTER SHIEN LAB

UMEHOSHI ITA基板に取り付けたESP32のサーバーに接続してロボットを制御するクライアント例

esp32側をpythonでTCPサーバーにし、それを付けたUMEHOSHI ITA基板側のモータ制御ROM関数を呼び出してロボットを動かす構成の サーバー側はこちらのページで紹介しています。
対してこのページは、上記ロボット側ESP32のサーバーにWifiを介して接続し、制御するPC側のクライアントソフトの紹介です。


こちらのページで紹介しているロボットの設定で、次のように起動できるようになっています。
まず、USBを介して電源を供給して、SW1とSW2を押し、SW1(赤)のリセットを離してからSW2(白)を遅れて離します。
これで、パターン[・・・・]の音を出した後、音が停止すればESP32のPythonyが正しく動作できたと予想できます。
(異なるパターンの音を出した場合や、音が長く出る場合は、起動にに失敗していると予想できます。
その場合は、再びSW1とSW2のスイッチ操作をしてください。)

esp32のboot.pyのプログラムが起動すれば、PC側からWifiで'ESP-AP'のアクセスポイントに接続できるはずです。
'ESP-AP'のアクセスポイントに接続できれば、"192.168.222.0"のネットワークに属したことになります。
この時、ESP32は192.168.222.1のIPアドレスになっています。
そして、ESP32のserver.pyが実行されて、それにより59154のポート番号のTCPサーバーが動作します。

このサーバーに接続して制御するためのpythonで作成したクライアントプログラムの使い方と、
 このソースコード(client_ume_esp32.py)を下記で紹介しています。

PC側で実行するクライアントプログラム(client_ume_esp32.py)の使い方

pythonが使えるコマンドプロンプトで次のように実行します。(client_ume_esp32.pyのソース)
これで、ロボットの前進、左回転、後進、右回転、ブザー音、ファイルアップロード、UME専用Hexコマンド操作、 ファイルリスト取得、テキストファイル内容の表示、ファイルダウンロード、Pythonファイルのimport起動が可能です。
実行例で解説します。
D:\_D\_Hardware\esp32ロボット1>python client_ume_esp32.py
IP (defualt:192.168.222.1)Address>

Connect!
入力:M/F/'quit'/w/a/s/d/b/?>b
Send:R009D020D00003B
START:9D020D00:Response
入力:M/F/'quit'/w/a/s/d/b/?>w
Send:R009D020200004D
START:9D020200:Response
入力:M/F/'quit'/w/a/s/d/b/?>
上記では、最初にIP Addressの入力で、単にEnterを押しています。
これでデフォルトの192.168.222.1で59154のポート番号で接続要求を行っています。
ロボット側のサーバーが接続を許可した時、Connect!の文字が送られ、最初にそれを表示しています。
そして、『入力:M/F/'quit'/w/a/s/d/b/?>』の入力プロンプトが出ます。
上記ではbのビープとwの入力でロボット前進を実行させています。
入力に対応して送信した「UME専用Hexコマンド」が表示され、その応答文字列が表示されます。

プロンプトで『/w/a/s/d/b/』の表現がありますが、
これは前進がw、左回転がa、後進がs、右回転がd、「・・・・」のブザー音がbのキー入力で、 対応の動作をします。
これら『/w/a/s/d/b/』の文字に限って、1行で次の例のように複数を入力することができます。
入力:M/F/'quit'/w/a/s/d/b/?>wwww aaaaaa ss
Send:R009D020200004D
Send:R009D020200004D
START:9D020200:Response
Send:R009D020200004D
・・・省略・・・
上記は、前進を4回、左回転を6回、後進を2回実行させる入力です。
これは隙間なくwwwwaaaaaassと入力しても同じです。
また、この入力で単にEnterを押した場合、前回の入力情報と同じと判断されます。

入力でquitを入力すると、サーバーの終了文字列が送られて、サーバー終了します。
(サーバーの終了で、― ― ― ―の音が連続します。)
なお、コンソールではサーバー強制終了で、このクライアントプログラムも受信処理にエラーが出て終了します。
再び、接続して通信するためには、SW1とSW2の起動操作からやり直します。

入力:M/F/'quit'/w/a/s/d/b/?>』の入力プロンプトでFを入力するとファイルの送信です(ファイルのアップロードに相当します)。
PC側で入力した名前のファイルをESP32のルートへ送信します。下記に操作例を示します。
入力:M/F/'quit'/w/a/s/d/b/?>F
['boot.py', 'client_ume_esp32.py', 'flow_off.umh', 'log.txt', 'server.py', 'setap.py', 'uEsp32Init.umh', '_df.py']
送信したいファイル名入力>uEsp32Init.umh
Server:'uEsp32Init.umh' 28bytes received.
R009D021000004E:Command
START:9D021000:Response
入力:M/F/'quit'/w/a/s/d/b/?>F
['boot.py', 'client_ume_esp32.py', 'flow_off.umh', 'log.txt', 'server.py', 'setap.py', 'uEsp32Init.umh', '_df.py']
送信したいファイル名入力>_df.py
Server:'_df.py' 607bytes received.
入力:M/F/'quit'/w/a/s/d/b/?>
Fの操作でESP32に送信できるファイルは任意で、ファイルをネットを介してESP32のルートコピーされます。
また送信ファイルの拡張子が.umhの場合は、受信データのファイル化以外に、ファイル内容のUME専用Hexコマンド文字列を 「UMEHOSHI ITA」に送信して実行させることができます。
上の実行例で分かる通り、Fの入力の直後にPCのカレントディレクトリが表示され、送信ファイルが在るか確認できます。
そして、'uEsp32Init.umh'を入力で、それを送り、同時にその内容のコマンドを実行しています。
上記は9D021000のアドレスが示す関数を実行している例です。
続いて_df.pyのファイルを送信していますが、拡張子が.umh以外であれば実行はせずにルートディレクトリにファイル化するだけです。
(大きなサイズのファイル送信で失敗する場合があります。その場合はFの代わりに小文字のfの入力で送信を試みてください。 その場合は転送速度に時間が掛かりますが、可能な限り大きなファイル転送が可能となります。)


上記は1文字で操作する機能でしたが、次は「文字列」で操作する機能です。
入力:M/F/'quit'/w/a/s/d/b/?>』の入力プロンプトでMを入力すると MicroPythonによる次のファイル操作のメニューに変わります。
それは、『UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトです。
このメニュー操作が終わると前のメニュープロンプトに戻ります。

UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトでは入力する文字列によって 機能が判断されて実行されます。 ここで、「UME専用Hexコマンド」で'S'または'G'または'R'から始まる1行を入力で実行きます。
下記は、R009D020D00003Bのビープ関数起動のコマンド文字列を入力して実行させています。
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >R009D020D00003B
Send:R009D020D00003B
START:9D020D00:Response
入力:M/F/'quit'/w/a/s/d/b/?>

UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトでexc の後に、 .umhのファイルを指定すると、その内容のUME専用Hexコマンド文字列を実行します。
指定するファイルESP32のルートディレクトリに存在していなければなりません。
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >exc uEsp32Init.umh
Send:exc uEsp32Init.umh
R009D021000004E:Command
START:9D021000:Response
入力:M/F/'quit'/w/a/s/d/b/?>

UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトで、 ls -lと入力すると、 ファイルリストを表示できます。 (ESP32のルートディレクトリのファイルリストです。)
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >ls -l
Send:ls -l
ls_filelist:
         16  Run.umh
        607  _df.py
         98  boot.py
         17  log.txt
入力:M/F/'quit'/w/a/s/d/b/?>      11149  server.py
        454  setap.py
?
入力:M/F/'quit'/w/a/s/d/b/?>
表示データの受信は、別スレッドで行われるため、『入力:M/F/'quit'/w/a/s/d/b/?>』が上記のように途中で表示されてしまう場合があります。 そのような場合、上記のように「?」Enterと入力して、プロンプトを出して操作するとよいでしょう。

UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトで、 cat に続いたファイル名の入力でファイル内容を表示できます。
(ESP32のルートディレクトリに対象ファイルが存在しなければなりません。)
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >cat _df.py
Send:cat _df.py
cat_filepath:_df.py
import server
print = server.send
import os
stat = os.statvfs('/')
block_size = stat[0]  # 1ブロックのサイズ(バイト単位)
total_blocks = stat[2]  # f_blocks(総ブロック数)
free_blocks = stat[3] # 空きブロック数
print(f"block_size: {block_size} bytes, free_blocks: {free_blocks}")
free_space=block_size * free_blocks  # 空き容量(バイト単位)
print(f"Free space: {free_space} bytes")
total_size = block_size * total_blocks  # 総容量(バイト)
print(f"Total Storage: {total_size} bytes")
入力:M/F/'quit'/w/a/s/d/b/?>

上記の背景色が薄い部分がファイルの内容です。

UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトで、 get に続いたファイル名の入力でファイルをダウンロードできます。
(ESP32のルートディレクトリに対象ファイルが存在しなければなりません。)
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >get log.txt
Send:get log.txt
log.txt:size=17
server:send_file:log.txt
入力:M/F/'quit'/w/a/s/d/b/?>


UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトで、 del に続いたファイル名の入力でファイルを削除できます。
(ESP32のルートディレクトリに対象ファイルが存在しなければなりません。)
入力:M/F/'quit'/w/a/s/d/b/?>M
UME HEX Command /exc /ls -l/cat /get /del /import >get log.txt
Send:get log.txt
log.txt:size=17
server:send_file:log.txt
入力:M/F/'quit'/w/a/s/d/b/?>


UME HEX Command /exc /ls -l/cat /get /del /import >』のメニュープロンプトで、 import に続いてモジュール名入力でpythonファイル実行できます。
モジュール名とは、pythonソースファイルで、.pyの拡張子を除いたファイル名前です。
(ESP32のルートディレクトリに対象ファイルが存在しなければなりません。)
UME HEX Command /exc /ls -l/cat /get /del /import >import _df
Send:import _df
import_name:_df
block_size: 4096 bytes, free_blocks: 460
Free space: 1884160 bytes
Total Storage: 2097152 bytes
入力:M/F/'quit'/w/a/s/d/b/?>

この実行結果は、上述のcat _df.pyで示したファイルの実行結果を表示しています。その先頭に次のコードがあり、重要な意味があります。
import server
print = server.send
これにより、このコード以降のprintは、server.send関数に置き換えられてprintの出力は標準出力でなく、TCP通信でこのクライアントにメッセージとして送信され、 その受信データが表示されています。
このように、この上記import対象のpythonはprintをserver.sendに置き換えるとこの画面に表示できます。
(この場合のprint引数は1つの文字列だけを受け付けます。)
print命令を置き換えしないで使った出力内容は、UMEHOSHI ITA のPIC32MX270F256B-50I/SPのUART1への送信となります。
よって、print("R009D020D00003B")のように、UME専用Hexコマンド文字列で、制御することができます。
その応答はメッセージとして表示されます。その場合、pythonコードからこの画面に表示させたい文字列は、直接にserver.sendを使います。
その簡単なpython例を示します。(test.py)
import server
print("R009D020D00003B") # ビープ関数起動のUME専用Hexコマンド文字列をUART1へ送ってビープを鳴らす。
server.send("Beep command was sent.") # TCPでこのクライアントに送り、表示させる
このファイルをFで転送して import test.py で実行すれば、音を鳴らして「Beep command was sent.」のメッセージを表示させることになります。

以上の機能を実現する、client_ume_esp32.pyのソース内容

import os 		# client_ume_esp32.py
import socket
import sys
import time
import threading

path_datas="./"

# ----- 受信関連 ---------------------------------
def recieve_file(sock):
    buf=b""
    while True:
        bin = sock.recv(1)
        if len(bin) != 1: break
        buf += bin
        if buf[-2:] == b"\r\n":
            s = buf[0:-2].decode('utf-8')
            a=s.split(' ')
            filename, filesize = a[0], int(a[1])
            bin=b""
            while len(bin) < filesize:
                bin += sock.recv(filesize-len(bin))
            with open( path_datas + filename, "wb") as f:
                f.write(bin)
            #tcp_send_message(sock,filename + ":size=" + str(len(bin)))
            break
    print(filename + ":size=" + str(filesize)) # debug ---------------

def recieve_message(sock): # TCP文字列メッセージ受信
    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)
            break

def receiveData(sock): # 全てのTCP受信のスレッド用関数
    #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)
        else: print(bin , end="")
    #
    print( "receiveData ended." )

# ----- 送信関連 ---------------------------------
def send_file(sock, filepath):
    try:
        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) )
    except Exception as e:
        print( "send_file error:" + str(e))

def send_file_Max(sock, filepath):
    try:
        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) )
    except Exception as e:
        print( "send_file_Max error:" + str(e))

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

# ----- メイン ---------------------------------
portnumber=59154
ip="192.168.222.1"
s = input(f"IP (defualt:{ip})Address>")
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()
time.sleep(1.0)
loopFlag = True
cmd=""
while loopFlag:
    s = input("入力:M/F/'quit'/w/a/s/d/b/?>")
    if s != "": cmd = s
    if cmd == 'M':
        msg = input("UME HEX Command /exc /ls -l/cat /get /del /import >")
        send_message(sock,msg)
        cmd=' '
    elif cmd == 'f' or cmd == 'F':
        s='F'
        print(os.listdir(path_datas))
        filename = input("送信したいファイル名入力>")
        if not os.path.isfile(path_datas + "/" + filename):
            print( filename, "no exist")
            continue
        if cmd == 'f' : send_file(sock, path_datas + "/" + filename)
        if cmd == 'F' : send_file_Max(sock, path_datas + "/" + filename)
        cmd=' '
    elif cmd == 'quit':
        send_message(sock,'end_listen_loop')
        loopFlag=False
    else:
        for c in cmd:
            if c == 'b' : 
                send_message(sock,"R009D020D00003B") # Beep UME Hex コマンド
                #send_file_Max(sock, path_datas + "/" + "uBeep.umh")
                time.sleep(2.0)# 短い間隔のBeepの連続を禁止
            elif c == 'w':
                send_message(sock,"R009D020200004D") # Forward UME Hex コマンド
                #send_file_Max(sock, path_datas + "/" + "uForward.umh")
            elif c == 's':
                send_message(sock,"R009D020300004C") # Back UME Hex コマンド
                #send_file_Max(sock, path_datas + "/" + "uBack.umh")
            elif c == 'a':
                send_message(sock,"R009D020500004A") # Left UME Hex コマンド
                #send_file_Max(sock, path_datas + "/" + "uLeft.umh")
            elif c == 'd':
                send_message(sock,"R009D020400004B") # Right UME Hex コマンド
                #send_file_Max(sock, path_datas + "/" + "uRight.umh")
            time.sleep(0.05)
    #
    time.sleep(1.0)

sock.close()
sys.exit(0)