Compare commits
4 Commits
78dc8ffc77
...
p2p
| Author | SHA1 | Date | |
|---|---|---|---|
| d682753e42 | |||
| 53976cdd70 | |||
| 94635a8cc7 | |||
| de0547615c |
File diff suppressed because one or more lines are too long
Submodule led-driver updated: 8403df531d...3286c4002d
24
scripts/mpremote_send_ch5.sh
Executable file
24
scripts/mpremote_send_ch5.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Upload and run a device-side ESP-NOW sender script.
|
||||||
|
# Default channel is 5 and default destination is broadcast.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/mpremote_send_ch5.sh [port] [dest_mac_hex] [payload_hex]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# scripts/mpremote_send_ch5.sh /dev/ttyACM0
|
||||||
|
# scripts/mpremote_send_ch5.sh /dev/ttyACM0 ffffffffffff 4c0501000000
|
||||||
|
|
||||||
|
PORT="${1:-/dev/ttyACM0}"
|
||||||
|
DEST_HEX="${2:-ffffffffffff}"
|
||||||
|
PAYLOAD_HEX="${3:-4c0501000000}"
|
||||||
|
CHANNEL=5
|
||||||
|
DEVICE_SCRIPT="send_ch5.py"
|
||||||
|
|
||||||
|
mpremote connect "${PORT}" fs cp "scripts/mpremote_send_ch5_device.py" ":${DEVICE_SCRIPT}"
|
||||||
|
mpremote connect "${PORT}" exec "
|
||||||
|
import ${DEVICE_SCRIPT%.*}
|
||||||
|
${DEVICE_SCRIPT%.*}.send_once('${DEST_HEX}', '${PAYLOAD_HEX}', ${CHANNEL})
|
||||||
|
"
|
||||||
42
scripts/mpremote_send_ch5_device.py
Normal file
42
scripts/mpremote_send_ch5_device.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""Device-side ESP-NOW sender (MicroPython, channel 5)."""
|
||||||
|
|
||||||
|
import espnow
|
||||||
|
import network
|
||||||
|
import ubinascii
|
||||||
|
|
||||||
|
|
||||||
|
CHANNEL = 5
|
||||||
|
DEST_HEX = "ffffffffffff"
|
||||||
|
PAYLOAD_HEX = "4c0501000000"
|
||||||
|
|
||||||
|
|
||||||
|
def _set_channel(channel):
|
||||||
|
sta = network.WLAN(network.STA_IF)
|
||||||
|
sta.active(True)
|
||||||
|
sta.config(pm=network.WLAN.PM_NONE)
|
||||||
|
sta.config(channel=channel)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_peer(esp, dest, channel):
|
||||||
|
try:
|
||||||
|
esp.add_peer(dest, channel=channel)
|
||||||
|
except TypeError:
|
||||||
|
esp.add_peer(dest)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def send_once(dest_hex=DEST_HEX, payload_hex=PAYLOAD_HEX, channel=CHANNEL):
|
||||||
|
dest = ubinascii.unhexlify(dest_hex)
|
||||||
|
pkt = ubinascii.unhexlify(payload_hex)
|
||||||
|
_set_channel(channel)
|
||||||
|
e = espnow.ESPNow()
|
||||||
|
e.active(True)
|
||||||
|
_add_peer(e, dest, channel)
|
||||||
|
ok = e.send(dest, pkt, True)
|
||||||
|
print("sent", ok, "ch", channel, "dest", dest_hex, "len", len(pkt))
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
send_once()
|
||||||
@@ -76,6 +76,13 @@ function normalizeDeviceMacKey(mac) {
|
|||||||
.replace(/[:-]/g, '');
|
.replace(/[:-]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeMacInput(raw) {
|
||||||
|
return String(raw || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[:-]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
function findPingResponse(responses, deviceId) {
|
function findPingResponse(responses, deviceId) {
|
||||||
if (!responses || typeof responses !== 'object') return null;
|
if (!responses || typeof responses !== 'object') return null;
|
||||||
const want = normalizeDeviceMacKey(deviceId);
|
const want = normalizeDeviceMacKey(deviceId);
|
||||||
@@ -430,6 +437,69 @@ async function loadDevicesModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createDeviceFromModal() {
|
||||||
|
const nameEl = document.getElementById('devices-add-name');
|
||||||
|
const trEl = document.getElementById('devices-add-transport');
|
||||||
|
const macEl = document.getElementById('devices-add-mac');
|
||||||
|
const addrEl = document.getElementById('devices-add-address');
|
||||||
|
const statusEl = document.getElementById('devices-add-status');
|
||||||
|
const btn = document.getElementById('devices-add-btn');
|
||||||
|
const name = (nameEl && nameEl.value.trim()) || '';
|
||||||
|
const transport = (trEl && trEl.value) || 'espnow';
|
||||||
|
const mac = normalizeMacInput(macEl && macEl.value);
|
||||||
|
const address = (addrEl && addrEl.value.trim()) || '';
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
if (statusEl) statusEl.textContent = 'Name is required';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mac.length !== 12) {
|
||||||
|
if (statusEl) statusEl.textContent = 'MAC must be 12 hex characters';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (transport === 'wifi' && !address) {
|
||||||
|
if (statusEl) statusEl.textContent = 'Address is required for Wi-Fi devices';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Adding…';
|
||||||
|
}
|
||||||
|
if (statusEl) statusEl.textContent = 'Creating device…';
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name,
|
||||||
|
transport,
|
||||||
|
type: 'led',
|
||||||
|
mac,
|
||||||
|
address: transport === 'wifi' ? address : mac,
|
||||||
|
};
|
||||||
|
const res = await fetch('/devices', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
if (statusEl) statusEl.textContent = data.error || 'Create failed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statusEl) statusEl.textContent = 'Device added';
|
||||||
|
if (nameEl) nameEl.value = '';
|
||||||
|
if (macEl) macEl.value = '';
|
||||||
|
if (addrEl) addrEl.value = '';
|
||||||
|
await loadDevicesModal();
|
||||||
|
} catch (e) {
|
||||||
|
if (statusEl) statusEl.textContent = e.message || 'Create failed';
|
||||||
|
} finally {
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = 'Add device';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderDevicesList(devices) {
|
function renderDevicesList(devices) {
|
||||||
const container = document.getElementById('devices-list-modal');
|
const container = document.getElementById('devices-list-modal');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -750,6 +820,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const editForm = document.getElementById('edit-device-form');
|
const editForm = document.getElementById('edit-device-form');
|
||||||
const editCloseBtn = document.getElementById('edit-device-close-btn');
|
const editCloseBtn = document.getElementById('edit-device-close-btn');
|
||||||
const editDeviceModal = document.getElementById('edit-device-modal');
|
const editDeviceModal = document.getElementById('edit-device-modal');
|
||||||
|
const addTransport = document.getElementById('devices-add-transport');
|
||||||
|
const addAddress = document.getElementById('devices-add-address');
|
||||||
|
const addBtn = document.getElementById('devices-add-btn');
|
||||||
|
|
||||||
if (devicesBtn && devicesModal) {
|
if (devicesBtn && devicesModal) {
|
||||||
devicesBtn.addEventListener('click', () => {
|
devicesBtn.addEventListener('click', () => {
|
||||||
@@ -768,6 +841,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (addTransport && addAddress) {
|
||||||
|
const syncAddAddress = () => {
|
||||||
|
addAddress.hidden = addTransport.value !== 'wifi';
|
||||||
|
};
|
||||||
|
addTransport.addEventListener('change', syncAddAddress);
|
||||||
|
syncAddAddress();
|
||||||
|
}
|
||||||
|
if (addBtn) {
|
||||||
|
addBtn.addEventListener('click', () => createDeviceFromModal());
|
||||||
|
}
|
||||||
|
|
||||||
const devicesPingBtn = document.getElementById('devices-ping-btn');
|
const devicesPingBtn = document.getElementById('devices-ping-btn');
|
||||||
if (devicesPingBtn) {
|
if (devicesPingBtn) {
|
||||||
devicesPingBtn.addEventListener('click', () => {
|
devicesPingBtn.addEventListener('click', () => {
|
||||||
|
|||||||
@@ -166,6 +166,19 @@
|
|||||||
<div id="devices-modal" class="modal">
|
<div id="devices-modal" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h2>Devices</h2>
|
<h2>Devices</h2>
|
||||||
|
<div class="form-group" style="margin-bottom:0.75rem;">
|
||||||
|
<div class="profiles-actions" style="gap:0.5rem;flex-wrap:wrap;">
|
||||||
|
<input type="text" id="devices-add-name" placeholder="Device name" autocomplete="off" style="min-width:10rem;">
|
||||||
|
<select id="devices-add-transport">
|
||||||
|
<option value="espnow">ESP-NOW</option>
|
||||||
|
<option value="wifi">Wi-Fi</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="devices-add-mac" placeholder="MAC (12 hex)" autocomplete="off" style="min-width:10rem;">
|
||||||
|
<input type="text" id="devices-add-address" placeholder="Address (IP/host for Wi-Fi)" autocomplete="off" style="min-width:12rem;" hidden>
|
||||||
|
<button type="button" class="btn btn-primary btn-small" id="devices-add-btn">Add device</button>
|
||||||
|
</div>
|
||||||
|
<small id="devices-add-status" class="muted-text" aria-live="polite"></small>
|
||||||
|
</div>
|
||||||
<div id="devices-list-modal" class="profiles-list"></div>
|
<div id="devices-list-modal" class="profiles-list"></div>
|
||||||
<div class="modal-actions" style="align-items:center;gap:0.75rem;flex-wrap:wrap;">
|
<div class="modal-actions" style="align-items:center;gap:0.75rem;flex-wrap:wrap;">
|
||||||
<button type="button" class="btn btn-secondary" id="devices-ping-btn" title="ESP-NOW broadcast ping (3 s)">Ping drivers</button>
|
<button type="button" class="btn btn-secondary" id="devices-ping-btn" title="ESP-NOW broadcast ping (3 s)">Ping drivers</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user