File size: 5,894 Bytes
1cf5f4b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laser rangefinder UART frame decoder (WebSerial) - ERDI LRF</title>
<meta name="description" content="Decode laser rangefinder UART frames in the browser: connect a module over WebSerial or paste hex. Supports ERDI 5C and 55 AA frames and Benewake TF-style 9-byte frames.">
<style>

  :root { --bg:#fff; --fg:#14181f; --muted:#5b6472; --line:#d9dee6; --accent:#0b5fff; }

  @media (prefers-color-scheme: dark) { :root { --bg:#0f1318; --fg:#e8ecf2; --muted:#9aa4b2; --line:#2a323d; --accent:#6ea2ff; } }

  body { margin:0; background:var(--bg); color:var(--fg); font:16px/1.5 system-ui,sans-serif; }

  main { max-width:860px; margin:0 auto; padding:24px 16px 64px; }

  h1 { font-size:1.5rem; margin:0 0 4px; } p.lead { color:var(--muted); margin:0 0 20px; }

  label { display:block; font-weight:600; margin:12px 0 4px; }

  select, textarea, button, input { font:inherit; padding:8px 10px; border:1px solid var(--line); border-radius:6px; background:transparent; color:inherit; }

  textarea { width:100%; box-sizing:border-box; min-height:84px; font-family:ui-monospace,monospace; }

  button { cursor:pointer; background:var(--accent); color:#fff; border-color:var(--accent); } button.secondary { background:transparent; color:var(--fg); border-color:var(--line); }

  .row { display:flex; gap:8px; flex-wrap:wrap; align-items:center; }

  #big { font-size:2.4rem; font-variant-numeric:tabular-nums; margin:16px 0 4px; }

  table { border-collapse:collapse; width:100%; font-size:.9rem; margin-top:12px; } th, td { text-align:left; padding:6px 8px; border-bottom:1px solid var(--line); font-variant-numeric:tabular-nums; }

  code { font-family:ui-monospace,monospace; } .muted { color:var(--muted); font-size:.9rem; }

</style>
</head>
<body>
<main>
  <h1>Laser rangefinder UART frame decoder</h1>
  <p class="lead">Connect a rangefinder module with WebSerial (Chrome / Edge) or paste captured hex. Runs entirely in your browser.</p>

  <label for="profile">Protocol profile</label>
  <select id="profile"></select>
  <div class="muted" id="baudnote"></div>

  <div class="row" style="margin-top:12px">
    <button id="connect">Connect serial port</button>
    <button id="disconnect" class="secondary" disabled>Disconnect</button>
    <span class="muted" id="status">WebSerial: checking...</span>
  </div>

  <label for="hex">Or paste hex bytes</label>
  <textarea id="hex" placeholder="5C 02 11 EC 5C 03 11 EB"></textarea>
  <div class="row" style="margin-top:8px"><button id="decode">Decode pasted hex</button><button id="clear" class="secondary">Clear table</button></div>

  <div id="big">-</div>
  <div class="muted" id="stats"></div>
  <table><thead><tr><th>#</th><th>Frame</th><th>Raw</th><th>Distance (m)</th><th>Note</th></tr></thead><tbody id="rows"></tbody></table>

  <p class="muted">The same <code>5C 02 11 EC</code> frame is 43.54 m on an ERDI H-series module and 4.354 m on a VB-series module: pick the profile that matches your model.
  Model pages and datasheets: <a href="https://erdilrf.com/models/?utm_source=github&utm_medium=pages&utm_campaign=lrf-uart-protocol-decoder">erdilrf.com/models</a>.
  Source: <a href="https://github.com/ErdiTech/lrf-uart-protocol-decoder">github.com/ErdiTech/lrf-uart-protocol-decoder</a>.</p>
</main>
<script type="module">

import { Decoder, PROFILES, parseHex } from "./decoder.js";

const $ = (id) => document.getElementById(id);

const sel = $("profile");

for (const [k, p] of Object.entries(PROFILES)) sel.add(new Option(p.label, k));

let dec = new Decoder(sel.value), count = 0, port = null, reader = null;

function note() { $("baudnote").textContent = "Default baud " + PROFILES[sel.value].baud + ", 8N1."; }

note();

sel.onchange = () => { dec = new Decoder(sel.value); note(); };

function show(frames) {

  for (const f of frames) {

    count++;

    const tr = document.createElement("tr");

    const d = f.distance_m === null ? "-" : f.distance_m.toFixed(3);

    for (const v of [count, f.hex, f.raw, d, f.out_of_range ? "out of range / no echo" : ""]) { const td = document.createElement("td"); td.textContent = v; tr.append(td); }

    $("rows").prepend(tr);

    while ($("rows").children.length > 200) $("rows").lastChild.remove();

    $("big").textContent = f.distance_m === null ? "out of range" : f.distance_m.toFixed(3) + " m";

  }

  $("stats").textContent = dec.frames + " valid frames, " + dec.skipped + " bytes skipped (resync)";

}

$("decode").onclick = () => { try { dec = new Decoder(sel.value); show(dec.push(parseHex($("hex").value))); } catch (e) { $("stats").textContent = e.message; } };

$("clear").onclick = () => { $("rows").textContent = ""; count = 0; };

$("status").textContent = "serial" in navigator ? "WebSerial available" : "WebSerial not supported in this browser (use Chrome or Edge); paste mode still works";

$("connect").onclick = async () => {

  if (!("serial" in navigator)) return;

  try {

    port = await navigator.serial.requestPort();

    await port.open({ baudRate: PROFILES[sel.value].baud });

    dec = new Decoder(sel.value);

    $("connect").disabled = true; $("disconnect").disabled = false; $("status").textContent = "connected";

    reader = port.readable.getReader();

    for (;;) { const { value, done } = await reader.read(); if (done) break; if (value) show(dec.push(value)); }

  } catch (e) { $("status").textContent = String(e.message || e); }

  finally { try { reader && reader.releaseLock(); } catch (_) {} }

};

$("disconnect").onclick = async () => { try { reader && await reader.cancel(); await port.close(); } catch (_) {} $("connect").disabled = false; $("disconnect").disabled = true; $("status").textContent = "disconnected"; };

</script>
</body>
</html>