# ===== UDP 版：用電腦透過 WiFi 控制這隻昆蟲 =====
# 這份程式接在「積木版程式庫」後面執行（程式庫裡有 start()、stop()、tick() 這些動作函式）。
# 每一段前面的「積木：」寫的是 UIFlow 1.x 畫面上積木的實際名字；照著拉，UIFlow 產生的 Python 就和這裡一樣。
#
# 訊息格式（都是一行文字，用逗號分開）：
#   電腦 -> 昆蟲  HELLO,電腦IP,電腦port   掃描：請回報你是誰，並記住回覆要送到哪裡
#   電腦 -> 昆蟲  GO,動作                  做一個動作：forward、backward、left、right、uturn
#   電腦 -> 昆蟲  STOP                     緊急停止，馬上站好
#   電腦 -> 昆蟲  PING                     問目前狀態
#   昆蟲 -> 電腦  HERE,名字,IP / OK,動作 / PONG,狀態 / ERR,原因
# UIFlow 的 Receive data 積木拿不到是誰傳來的，所以電腦在 HELLO 裡告訴昆蟲自己的地址。

# UIFlow 用了 Socket 積木就會自動在最上面加這一行，不用自己拉。
import socket

# 積木：Variables → Create variable，用一模一樣的名字建立這 5 個：ready、data、reply、server_ip、server_port。
# UIFlow 會在程式最上面替每個變數產生一行「名字 = None」，就是下面這 5 行。
# 積木的變數和 Execute code 裡同名的變數是同一個：handle_udp() 改了 reply，積木讀得到。
ready = None
data = None
reply = None
server_ip = None
server_port = None

# 積木：Execute code（貼這一段 Setup 用的小工具）
import network
import uselect

UDP_PORT = 4210              # 昆蟲在這個 port 等電腦的訊息；電腦端的 bug_server.py 也用 4210
server_ip = ""               # 電腦的 IP，收到 HELLO 之後才知道
server_port = 0              # 電腦接收回覆的 port
reply = ""                   # 這一次要回給電腦的文字；空字串表示不用回


# 用 WiFi 晶片的 MAC 位址最後 4 個十六進位數字，替這隻昆蟲取一個不會和別隻重複的名字。
# 輸出：例如 "bug-3f2a"。
def bug_name():
    mac = network.WLAN(network.STA_IF).config("mac")
    return "bug-" + "%02x%02x" % (mac[-2], mac[-1])


# 這隻昆蟲在 WiFi 網路上的 IP 位址，例如 "192.168.1.23"。
def my_ip():
    return network.WLAN(network.STA_IF).ifconfig()[0]


# 處理電腦傳來的一則訊息，決定要做什麼、要回什麼。
# 輸入：data 收到的 bytes。
# 結果：需要回覆時，把回覆文字放進全域變數 reply（Loop 裡的 Send data 積木會把它送出去）。
def handle_udp(data):
    global server_ip, server_port, reply
    reply = ""
    text = data.decode().strip()
    parts = text.split(",")
    if parts[0] == "HELLO" and len(parts) == 3:
        # 記住電腦的地址，之後的回覆都送到這裡。
        server_ip = parts[1]
        server_port = int(parts[2])
        reply = "HERE," + bug_name() + "," + my_ip()
    elif parts[0] == "GO" and len(parts) == 2:
        if parts[1] in MOTIONS:
            start(parts[1])
            reply = "OK," + parts[1]
        else:
            reply = "ERR,unknown motion " + parts[1]
    elif parts[0] == "STOP":
        stop()
        reply = "OK,STOP"
    elif parts[0] == "PING":
        # 狀態：正在做的動作，或是 ready（站著等指令）。
        if motion is not None:
            reply = "PONG," + motion
        else:
            reply = "PONG,ready"
    else:
        reply = "ERR,unknown message"


# 積木：UDP server start（set ip address "0.0.0.0"，port 4210）
# 昆蟲是被電腦控制的那一端，但它要先「開門等訊息」，所以用的是 UDP server start 這塊積木。
udpsocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udpsocket.bind(('0.0.0.0', UDP_PORT))

# 積木：Execute code
# UIFlow 的 Receive data 積木會一直卡住等資料，昆蟲就不能每 20 毫秒走一步了。
# 所以先準備一個「看一眼有沒有資料」的工具，有資料才去接收。
udp_watch = uselect.poll()
udp_watch.register(udpsocket, uselect.POLLIN)

# 積木：Execute code（在螢幕上顯示名字和 IP，方便對照電腦端的清單）
lcd.print(bug_name(), 4, 42, 0xffffff)
lcd.print(my_ip(), 4, 58, 0xffffff)

# 積木：Button A wasPressed，裡面放 Execute code: stop()（緊急停止）
btnA.wasPressed(stop)

# 積木：Loop（永遠重複）
while True:
    # 積木：Execute code: ready = udp_watch.poll(0)  看一眼有沒有資料：有的話 ready 是非空的清單，沒有就是空清單
    ready = udp_watch.poll(0)
    # 積木：if（Logic），條件放變數積木 ready
    if ready:
        # 積木：set data to，值放 Receive data by 64 bytes
        # 用 Receive data 不用 Read：Read 會等到湊滿指定的位元組數，訊息長短不一就會卡住。
        data = udpsocket.recv(64)
        # 積木：Execute code: handle_udp(data)
        handle_udp(data)
        # 積木：if（Logic），條件用 and 接兩個比較：reply ≠ ""（空文字）、server_port ≠ 0
        if reply != "" and server_port != 0:
            # 積木：Send data reply to ip address server_ip port server_port（三格都放變數積木）
            udpsocket.sendto(reply, (server_ip, server_port))
    # 積木：Execute code: tick()   讓步態往前走 20 毫秒
    tick()
    # 積木：Wait 20 ms
    wait_ms(STEP_MS)
