407 lines
10 KiB
Python
407 lines
10 KiB
Python
from machine import Pin, PWM
|
|
import time, network, espnow
|
|
import motors
|
|
|
|
BUZZ_PIN = 39
|
|
PAIR_BEEP_INTERVAL_MS = 1400
|
|
|
|
# ---------------- ESP-NOW helpers (match transmitter.py) ----------------
|
|
_espnow_sta = None
|
|
_espnow = None
|
|
_espnow_peers = []
|
|
_espnow_broadcast = b'\xff\xff\xff\xff\xff\xff'
|
|
|
|
|
|
def _espnow_parse_mac(mac):
|
|
if isinstance(mac, bytes):
|
|
return mac
|
|
if isinstance(mac, bytearray):
|
|
return bytes(mac)
|
|
if isinstance(mac, str):
|
|
mac = mac.replace(':', '').replace('-', '').replace(' ', '')
|
|
if len(mac) != 12:
|
|
raise ValueError('MAC must be 12 hex chars')
|
|
return bytes(int(mac[i : i + 2], 16) for i in range(0, 12, 2))
|
|
raise ValueError('MAC must be bytes or string')
|
|
|
|
|
|
def _espnow_init(channel=6, rate='RATE_LORA_250K'):
|
|
global _espnow_sta, _espnow
|
|
_espnow_sta = network.WLAN(network.STA_IF)
|
|
_espnow_sta.active(True)
|
|
_espnow_sta.disconnect()
|
|
_espnow_sta.config(channel=int(channel), protocol=network.WLAN.PROTOCOL_LR)
|
|
_espnow = espnow.ESPNow()
|
|
_espnow.active(True)
|
|
rate_val = getattr(espnow, str(rate), espnow.RATE_LORA_250K)
|
|
_espnow.config(rate=rate_val)
|
|
return _espnow
|
|
|
|
|
|
def _espnow_my_mac():
|
|
if _espnow_sta is None:
|
|
_espnow_init()
|
|
return _espnow_sta.config('mac')
|
|
|
|
|
|
def _espnow_add_peer(mac):
|
|
global _espnow_peers
|
|
if _espnow is None:
|
|
_espnow_init()
|
|
peer = _espnow_parse_mac(mac)
|
|
if peer not in _espnow_peers:
|
|
_espnow.add_peer(peer)
|
|
_espnow_peers.append(peer)
|
|
return peer
|
|
|
|
|
|
def _espnow_pack(data):
|
|
if isinstance(data, (list, tuple)):
|
|
return ','.join(str(v) for v in data)
|
|
return str(data)
|
|
|
|
|
|
def _espnow_send_peer(mac, data):
|
|
if _espnow is None:
|
|
_espnow_init()
|
|
peer = _espnow_add_peer(mac)
|
|
payload = _espnow_pack(data)
|
|
_espnow.send(peer, payload)
|
|
|
|
|
|
def _espnow_try_num(s):
|
|
try:
|
|
return int(s)
|
|
except:
|
|
pass
|
|
try:
|
|
return float(s)
|
|
except:
|
|
return s
|
|
|
|
|
|
def _espnow_unpack(msg):
|
|
if msg is None:
|
|
return []
|
|
if isinstance(msg, bytes):
|
|
msg = msg.decode('utf-8', 'ignore')
|
|
parts = str(msg).split(',')
|
|
out = []
|
|
for p in parts:
|
|
p = p.strip()
|
|
if p == '':
|
|
continue
|
|
out.append(_espnow_try_num(p))
|
|
return out
|
|
|
|
|
|
def _espnow_recv(timeout_ms=10):
|
|
if _espnow is None:
|
|
_espnow_init()
|
|
host, msg = _espnow.recv(timeout_ms)
|
|
return host, _espnow_unpack(msg)
|
|
|
|
|
|
def _mac_eq(a, b):
|
|
if a is None or b is None:
|
|
return False
|
|
return bytes(a) == bytes(b)
|
|
|
|
|
|
# ---------------- Pairing (receiver side) ----------------
|
|
PAIR_PREFIX = 'PAIR_REQ:'
|
|
PAIR_ACK_PREFIX = 'PAIR_ACK:'
|
|
PEER_FILE = 'peer_mac.txt'
|
|
peer_mac = None
|
|
|
|
|
|
def load_peer():
|
|
try:
|
|
with open(PEER_FILE, 'rb') as f:
|
|
mac = f.read()
|
|
if mac and len(mac) == 6:
|
|
return mac
|
|
except:
|
|
pass
|
|
return None
|
|
|
|
|
|
def save_peer(mac):
|
|
with open(PEER_FILE, 'wb') as f:
|
|
f.write(mac)
|
|
|
|
|
|
def buzz_pairing_chirp():
|
|
"""Very short tone while waiting to pair (passive piezo: PWM; active: GPIO pulse)."""
|
|
try:
|
|
pwm = PWM(Pin(BUZZ_PIN, Pin.OUT), freq=3800, duty_u16=24576)
|
|
time.sleep_ms(14)
|
|
pwm.deinit()
|
|
except:
|
|
b = Pin(BUZZ_PIN, Pin.OUT)
|
|
b.on()
|
|
time.sleep_ms(12)
|
|
b.off()
|
|
|
|
|
|
def try_pair_from_message(host, msg, my_hex):
|
|
"""If msg is a PAIR_REQ from a transmitter, ACK and return True when paired."""
|
|
global peer_mac
|
|
if not (msg and isinstance(msg, list) and len(msg) >= 2 and msg[0] == PAIR_PREFIX):
|
|
return False
|
|
tx_hex = str(msg[1]).strip().lower()
|
|
tx_mac = None
|
|
if len(tx_hex) == 12:
|
|
try:
|
|
tx_mac = _espnow_parse_mac(tx_hex)
|
|
except:
|
|
tx_mac = None
|
|
if tx_mac is None and host:
|
|
tx_mac = bytes(host) if not isinstance(host, bytes) else host
|
|
if not tx_mac:
|
|
return False
|
|
_espnow_add_peer(tx_mac)
|
|
_espnow_send_peer(tx_mac, [PAIR_ACK_PREFIX, my_hex])
|
|
save_peer(tx_mac)
|
|
peer_mac = tx_mac
|
|
print('Paired with transmitter', tx_hex if len(tx_hex) == 12 else tx_mac.hex())
|
|
return True
|
|
|
|
|
|
# Same channel and rate as transmitter.py
|
|
_espnow_init(1, 'RATE_LORA_500K')
|
|
peer_mac = load_peer()
|
|
if peer_mac:
|
|
_espnow_add_peer(peer_mac)
|
|
|
|
my_mac_hex = _espnow_my_mac().hex()
|
|
last_pair_beep = time.ticks_ms()
|
|
print('Receiver ready. My MAC:', my_mac_hex, 'Peer:', peer_mac.hex() if peer_mac else '(searching)')
|
|
|
|
leftMotor = motors.Motor(13, 14)
|
|
rightMotor = motors.Motor(15, 16)
|
|
|
|
# ---------------- Main loop ----------------
|
|
while True:
|
|
if not peer_mac:
|
|
host, msg = _espnow_recv(timeout_ms=120)
|
|
if try_pair_from_message(host, msg, my_mac_hex):
|
|
pass
|
|
else:
|
|
if time.ticks_diff(time.ticks_ms(), last_pair_beep) >= PAIR_BEEP_INTERVAL_MS:
|
|
buzz_pairing_chirp()
|
|
last_pair_beep = time.ticks_ms()
|
|
time.sleep_ms(10)
|
|
continue
|
|
|
|
host, data = _espnow_recv(timeout_ms=50)
|
|
if host and data and _mac_eq(host, peer_mac):
|
|
# Transmitter payload: 4 axes, adc_1, adc_10, btn2,5,6,7,11,12
|
|
print(data)
|
|
|
|
time.sleep_ms(5)
|
|
|
|
from machine import Pin, PWM
|
|
import time, network, espnow
|
|
import motors
|
|
|
|
BUZZ_PIN = 39
|
|
PAIR_BEEP_INTERVAL_MS = 1400
|
|
|
|
# ---------------- ESP-NOW helpers (match transmitter.py) ----------------
|
|
_espnow_sta = None
|
|
_espnow = None
|
|
_espnow_peers = []
|
|
_espnow_broadcast = b'\xff\xff\xff\xff\xff\xff'
|
|
|
|
|
|
def _espnow_parse_mac(mac):
|
|
if isinstance(mac, bytes):
|
|
return mac
|
|
if isinstance(mac, bytearray):
|
|
return bytes(mac)
|
|
if isinstance(mac, str):
|
|
mac = mac.replace(':', '').replace('-', '').replace(' ', '')
|
|
if len(mac) != 12:
|
|
raise ValueError('MAC must be 12 hex chars')
|
|
return bytes(int(mac[i : i + 2], 16) for i in range(0, 12, 2))
|
|
raise ValueError('MAC must be bytes or string')
|
|
|
|
|
|
def _espnow_init(channel=6, rate='RATE_LORA_250K'):
|
|
global _espnow_sta, _espnow
|
|
_espnow_sta = network.WLAN(network.STA_IF)
|
|
_espnow_sta.active(True)
|
|
_espnow_sta.disconnect()
|
|
_espnow_sta.config(channel=int(channel), protocol=network.WLAN.PROTOCOL_LR)
|
|
_espnow = espnow.ESPNow()
|
|
_espnow.active(True)
|
|
rate_val = getattr(espnow, str(rate), espnow.RATE_LORA_250K)
|
|
_espnow.config(rate=rate_val)
|
|
return _espnow
|
|
|
|
|
|
def _espnow_my_mac():
|
|
if _espnow_sta is None:
|
|
_espnow_init()
|
|
return _espnow_sta.config('mac')
|
|
|
|
|
|
def _espnow_add_peer(mac):
|
|
global _espnow_peers
|
|
if _espnow is None:
|
|
_espnow_init()
|
|
peer = _espnow_parse_mac(mac)
|
|
if peer not in _espnow_peers:
|
|
_espnow.add_peer(peer)
|
|
_espnow_peers.append(peer)
|
|
return peer
|
|
|
|
|
|
def _espnow_pack(data):
|
|
if isinstance(data, (list, tuple)):
|
|
return ','.join(str(v) for v in data)
|
|
return str(data)
|
|
|
|
|
|
def _espnow_send_peer(mac, data):
|
|
if _espnow is None:
|
|
_espnow_init()
|
|
peer = _espnow_add_peer(mac)
|
|
payload = _espnow_pack(data)
|
|
_espnow.send(peer, payload)
|
|
|
|
|
|
def _espnow_try_num(s):
|
|
try:
|
|
return int(s)
|
|
except:
|
|
pass
|
|
try:
|
|
return float(s)
|
|
except:
|
|
return s
|
|
|
|
|
|
def _espnow_unpack(msg):
|
|
if msg is None:
|
|
return []
|
|
if isinstance(msg, bytes):
|
|
msg = msg.decode('utf-8', 'ignore')
|
|
parts = str(msg).split(',')
|
|
out = []
|
|
for p in parts:
|
|
p = p.strip()
|
|
if p == '':
|
|
continue
|
|
out.append(_espnow_try_num(p))
|
|
return out
|
|
|
|
|
|
def _espnow_recv(timeout_ms=10):
|
|
if _espnow is None:
|
|
_espnow_init()
|
|
host, msg = _espnow.recv(timeout_ms)
|
|
return host, _espnow_unpack(msg)
|
|
|
|
|
|
def _mac_eq(a, b):
|
|
if a is None or b is None:
|
|
return False
|
|
return bytes(a) == bytes(b)
|
|
|
|
|
|
# ---------------- Pairing (receiver side) ----------------
|
|
PAIR_PREFIX = 'PAIR_REQ:'
|
|
PAIR_ACK_PREFIX = 'PAIR_ACK:'
|
|
PEER_FILE = 'peer_mac.txt'
|
|
peer_mac = None
|
|
|
|
|
|
def load_peer():
|
|
try:
|
|
with open(PEER_FILE, 'rb') as f:
|
|
mac = f.read()
|
|
if mac and len(mac) == 6:
|
|
return mac
|
|
except:
|
|
pass
|
|
return None
|
|
|
|
|
|
def save_peer(mac):
|
|
with open(PEER_FILE, 'wb') as f:
|
|
f.write(mac)
|
|
|
|
|
|
def buzz_pairing_chirp():
|
|
"""Very short tone while waiting to pair (passive piezo: PWM; active: GPIO pulse)."""
|
|
try:
|
|
pwm = PWM(Pin(BUZZ_PIN, Pin.OUT), freq=3800, duty_u16=24576)
|
|
time.sleep_ms(14)
|
|
pwm.deinit()
|
|
except:
|
|
b = Pin(BUZZ_PIN, Pin.OUT)
|
|
b.on()
|
|
time.sleep_ms(12)
|
|
b.off()
|
|
|
|
|
|
def try_pair_from_message(host, msg, my_hex):
|
|
"""If msg is a PAIR_REQ from a transmitter, ACK and return True when paired."""
|
|
global peer_mac
|
|
if not (msg and isinstance(msg, list) and len(msg) >= 2 and msg[0] == PAIR_PREFIX):
|
|
return False
|
|
tx_hex = str(msg[1]).strip().lower()
|
|
tx_mac = None
|
|
if len(tx_hex) == 12:
|
|
try:
|
|
tx_mac = _espnow_parse_mac(tx_hex)
|
|
except:
|
|
tx_mac = None
|
|
if tx_mac is None and host:
|
|
tx_mac = bytes(host) if not isinstance(host, bytes) else host
|
|
if not tx_mac:
|
|
return False
|
|
_espnow_add_peer(tx_mac)
|
|
_espnow_send_peer(tx_mac, [PAIR_ACK_PREFIX, my_hex])
|
|
save_peer(tx_mac)
|
|
peer_mac = tx_mac
|
|
print('Paired with transmitter', tx_hex if len(tx_hex) == 12 else tx_mac.hex())
|
|
return True
|
|
|
|
|
|
# Same channel and rate as transmitter.py
|
|
_espnow_init(1, 'RATE_LORA_500K')
|
|
peer_mac = load_peer()
|
|
if peer_mac:
|
|
_espnow_add_peer(peer_mac)
|
|
|
|
my_mac_hex = _espnow_my_mac().hex()
|
|
last_pair_beep = time.ticks_ms()
|
|
print('Receiver ready. My MAC:', my_mac_hex, 'Peer:', peer_mac.hex() if peer_mac else '(searching)')
|
|
|
|
leftMotor = motors.Motor(13, 14)
|
|
rightMotor = motors.Motor(15, 16)
|
|
|
|
# ---------------- Main loop ----------------
|
|
while True:
|
|
if not peer_mac:
|
|
host, msg = _espnow_recv(timeout_ms=120)
|
|
if try_pair_from_message(host, msg, my_mac_hex):
|
|
pass
|
|
else:
|
|
if time.ticks_diff(time.ticks_ms(), last_pair_beep) >= PAIR_BEEP_INTERVAL_MS:
|
|
buzz_pairing_chirp()
|
|
last_pair_beep = time.ticks_ms()
|
|
time.sleep_ms(10)
|
|
continue
|
|
|
|
host, data = _espnow_recv(timeout_ms=50)
|
|
if host and data and _mac_eq(host, peer_mac):
|
|
# Transmitter payload: 4 axes, adc_1, adc_10, btn2,5,6,7,11,12
|
|
print(data)
|
|
|
|
time.sleep_ms(5)
|
|
|