meshncore version
This commit is contained in:
+178
-5
@@ -109,16 +109,85 @@
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.main-row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
|
||||
#terminal {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
/* Right-side comms panel (MeshCore today, more later) */
|
||||
.comms-panel {
|
||||
flex: 0 0 340px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #16213e;
|
||||
border-left: 1px solid #0f3460;
|
||||
padding: 10px 12px;
|
||||
gap: 10px;
|
||||
min-height: 0;
|
||||
}
|
||||
.comms-panel h2 {
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
color: #e94560;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.comms-cfg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.comms-cfg input {
|
||||
background: #0f3460;
|
||||
color: #fff;
|
||||
border: 1px solid #1a4a7a;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
width: 100%;
|
||||
}
|
||||
.comms-cfg .row { display: flex; gap: 6px; }
|
||||
.comms-cfg .row input { flex: 1; }
|
||||
#meshMessages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
background: #0f1a30;
|
||||
border: 1px solid #0f3460;
|
||||
border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
font-family: Menlo, Monaco, "Courier New", monospace;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.4;
|
||||
min-height: 60px;
|
||||
}
|
||||
.mc-msg { margin-bottom: 4px; word-break: break-word; }
|
||||
.mc-msg .who { color: #60a5fa; }
|
||||
.mc-msg.tx .who { color: #4ade80; }
|
||||
.mc-msg .meta { color: #666; font-size: 0.85em; }
|
||||
.mc-empty { color: #666; }
|
||||
.comms-send { display: flex; gap: 6px; }
|
||||
.comms-send input { flex: 1; }
|
||||
.mesh-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%;
|
||||
background: #666; display: inline-block;
|
||||
}
|
||||
.mesh-dot.up { background: #4ade80; }
|
||||
.mesh-dot.down { background: #ef4444; }
|
||||
|
||||
.footer {
|
||||
background: #16213e;
|
||||
padding: 8px 20px;
|
||||
@@ -138,6 +207,14 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.main-row { flex-direction: column; }
|
||||
.comms-panel {
|
||||
flex: 0 0 45vh;
|
||||
border-left: none;
|
||||
border-top: 1px solid #0f3460;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -194,10 +271,31 @@
|
||||
<table id="filesTable" style="width:100%; border-collapse:collapse; font-size:0.85em;"></table>
|
||||
</div>
|
||||
|
||||
<div class="terminal-container">
|
||||
<div id="terminal"></div>
|
||||
<div class="main-row">
|
||||
<div class="terminal-container">
|
||||
<div id="terminal"></div>
|
||||
</div>
|
||||
|
||||
<div class="comms-panel" id="commsPanel">
|
||||
<h2>
|
||||
<span>📡 MeshCore</span>
|
||||
<span><span class="mesh-dot" id="meshDot"></span> <span id="meshState" style="font-size:0.75em;color:#aaa;">…</span></span>
|
||||
</h2>
|
||||
<div class="comms-cfg">
|
||||
<input type="text" id="meshChan" placeholder="Channel name (e.g. SensorsTest)">
|
||||
<div class="row">
|
||||
<input type="text" id="meshPsk" placeholder="PSK (base64, 16 or 32-byte key)">
|
||||
<button onclick="programChannel()">Program</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="meshMessages"><div class="mc-empty">No messages yet.</div></div>
|
||||
<div class="comms-send">
|
||||
<input type="text" id="meshInput" placeholder="Message…" onkeydown="if(event.key==='Enter')sendMesh()">
|
||||
<button onclick="sendMesh()">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="footer">
|
||||
<span id="rxCount">RX: 0 bytes</span>
|
||||
<span id="txCount">TX: 0 bytes</span>
|
||||
@@ -272,6 +370,7 @@
|
||||
|
||||
// Request initial status
|
||||
sendCommand({ cmd: 'getStatus' });
|
||||
sendCommand({ cmd: 'meshGet' });
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
@@ -293,6 +392,13 @@
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
// Control/mesh events arrive as text frames (0x00-prefixed JSON).
|
||||
if (typeof event.data === 'string') {
|
||||
let s = event.data;
|
||||
if (s.charCodeAt(0) === 0) s = s.slice(1);
|
||||
try { handleResponse(JSON.parse(s)); } catch (e) {}
|
||||
return;
|
||||
}
|
||||
if (event.data instanceof ArrayBuffer) {
|
||||
const data = new Uint8Array(event.data);
|
||||
|
||||
@@ -344,8 +450,75 @@
|
||||
const portNames = ['Internal', 'USB Serial', 'External'];
|
||||
term.writeln(`\r\n\x1b[33m[Switched to ${portNames[msg.port]}]\x1b[0m\r\n`);
|
||||
break;
|
||||
|
||||
case 'mesh':
|
||||
appendMeshMsg(msg);
|
||||
break;
|
||||
|
||||
case 'meshcfg':
|
||||
updateMeshCfg(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- MeshCore comms panel ----
|
||||
let meshEnabled = false;
|
||||
function updateMeshCfg(c) {
|
||||
meshEnabled = !!c.enabled;
|
||||
const dot = document.getElementById('meshDot');
|
||||
const state = document.getElementById('meshState');
|
||||
const chan = document.getElementById('meshChan');
|
||||
const psk = document.getElementById('meshPsk');
|
||||
if (!meshEnabled) {
|
||||
dot.className = 'mesh-dot';
|
||||
state.textContent = 'not in build';
|
||||
} else if (c.up) {
|
||||
dot.className = 'mesh-dot up';
|
||||
state.textContent = `up · rx ${c.rx} tx ${c.tx}`;
|
||||
} else {
|
||||
dot.className = 'mesh-dot down';
|
||||
state.textContent = 'radio down';
|
||||
}
|
||||
// Don't clobber a field the user is editing.
|
||||
if (document.activeElement !== chan && typeof c.channel !== 'undefined') chan.value = c.channel;
|
||||
if (document.activeElement !== psk && typeof c.psk !== 'undefined') psk.value = c.psk;
|
||||
const disabled = !meshEnabled;
|
||||
document.getElementById('meshInput').disabled = disabled;
|
||||
}
|
||||
function appendMeshMsg(m) {
|
||||
const box = document.getElementById('meshMessages');
|
||||
const empty = box.querySelector('.mc-empty');
|
||||
if (empty) empty.remove();
|
||||
const div = document.createElement('div');
|
||||
div.className = 'mc-msg' + (m.dir === 'tx' ? ' tx' : '');
|
||||
const t = new Date().toLocaleTimeString();
|
||||
let meta = m.dir === 'tx' ? '↑' : '↓';
|
||||
if (m.channel) meta += ' ' + m.channel;
|
||||
if (typeof m.rssi !== 'undefined' && m.rssi) meta += ` ${m.rssi}dBm`;
|
||||
div.innerHTML = `<span class="meta">[${t}] ${escapeHtml(meta)}</span> ` +
|
||||
`<span class="who">${escapeHtml(m.sender || '?')}:</span> ` +
|
||||
escapeHtml(m.text || '');
|
||||
box.appendChild(div);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
function escapeHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, c =>
|
||||
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
||||
}
|
||||
function programChannel() {
|
||||
const name = document.getElementById('meshChan').value.trim();
|
||||
const psk = document.getElementById('meshPsk').value.trim();
|
||||
if (!psk) { alert('Enter a PSK (base64 16- or 32-byte key)'); return; }
|
||||
sendCommand({ cmd: 'meshPsk', name: name, psk: psk });
|
||||
setTimeout(() => sendCommand({ cmd: 'meshGet' }), 300);
|
||||
}
|
||||
function sendMesh() {
|
||||
const inp = document.getElementById('meshInput');
|
||||
const text = inp.value;
|
||||
if (!text) return;
|
||||
sendCommand({ cmd: 'meshSend', text: text });
|
||||
inp.value = '';
|
||||
}
|
||||
|
||||
function sendCommand(cmd) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
|
||||
Reference in New Issue
Block a user