UNPKG

pulseplot

Version:
1,294 lines (1,281 loc) 69.3 kB
/** Determine divisor and SI prefix. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2019 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ const E = [ { name: "yotta", scale: 1e24, prefix: "Y" }, { name: "zetta", scale: 1e21, prefix: "Z" }, { name: "exa", scale: 1e18, prefix: "E" }, { name: "peta", scale: 1e15, prefix: "P" }, { name: "tera", scale: 1e12, prefix: "T" }, { name: "giga", scale: 1e9, prefix: "G" }, { name: "mega", scale: 1e6, prefix: "M" }, { name: "kilo", scale: 1e3, prefix: "k" }, { name: "", scale: 1, prefix: "" }, { name: "milli", scale: 1e-3, prefix: "m" }, { name: "micro", scale: 1e-6, prefix: "µ" }, { name: "nano", scale: 1e-9, prefix: "n" }, { name: "pico", scale: 1e-12, prefix: "p" }, { name: "femto", scale: 1e-15, prefix: "f" }, { name: "atto", scale: 1e-18, prefix: "a" }, { name: "zepto", scale: 1e-21, prefix: "z" }, { name: "yocto", scale: 1e-24, prefix: "y" } ]; function Z(r, t = 10) { if (r == 0) return E[8]; r = r / t; for (let s = 0; s < E.length; ++s) if (r >= E[s].scale) return E[s]; return E[E.length - 1]; } const T = [ { name: "year", scale: 31557513, prefix: "Y" }, { name: "month", scale: 2635200, prefix: "M" }, { name: "day", scale: 86400, prefix: "D" }, { name: "hour", scale: 3600, prefix: "h" }, { name: "minute", scale: 60, prefix: "m" }, { name: "second", scale: 1, prefix: "s" }, { name: "milli", scale: 1e-3, prefix: "ms" }, { name: "micro", scale: 1e-6, prefix: "µs" }, { name: "nano", scale: 1e-9, prefix: "ns" }, { name: "pico", scale: 1e-12, prefix: "ps" }, { name: "femto", scale: 1e-15, prefix: "fs" }, { name: "atto", scale: 1e-18, prefix: "as" }, { name: "zepto", scale: 1e-21, prefix: "zs" }, { name: "yocto", scale: 1e-24, prefix: "ys" } ]; function et(r, t = 10) { if (r == 0) return T[8]; r = r / t; for (let s = 0; s < T.length; ++s) if (r >= T[s].scale) return T[s]; return T[T.length - 1]; } /** @file Various utils. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2019 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ function A(r) { return r && (typeof r == "string" ? document.querySelector(r) : r); } function G(r, t) { if (!t || typeof t != "string") return t; if (r[t]) return r[t]; const s = t.toLowerCase(); for (let e in r) if (e.toLowerCase() == s) return r[e]; for (let e in r) if (e.toLowerCase().startsWith(s)) return r[e]; return null; } function st(r) { var t = new DOMParser().parseFromString(r, "text/html"); return t.body.textContent || ""; } /** @file DropZone JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2019 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ class it { constructor(t, s) { t = t || "#dropZone", t = A(t), this.dropZone = t, this.fileLoader = s, this.inputEls = [], this.dragEnteredEls = [], window.addEventListener("dragenter", this), window.addEventListener("dragleave", this), window.addEventListener("dropleave", this), ["dragenter", "dragover", "dragleave", "drop"].forEach((i) => this.dropZone.addEventListener(i, this, !1)); } addInput(t) { t = t || "#inputfile", t = A(t), t.addEventListener("change", this, !1), this.inputEls.push(t); } destroy() { window.removeEventListener("dragenter", this, !1), window.removeEventListener("dragleave", this, !1), window.removeEventListener("dropleave", this, !1), ["dragenter", "dragover", "dragleave", "drop"].forEach((s) => this.dropZone.removeEventListener(s, this, !1)); for (let s of this.inputEls) s.removeEventListener("change", this, !1); } allowDrag(t) { t.dataTransfer.dropEffect = "copy", t.preventDefault(); } handleDrop(t) { t.preventDefault(), this.handleFileSelect(t); } // https://www.html5rocks.com/en/tutorials/file/dndfiles/ handleFileSelect(t) { t.stopPropagation(), t.preventDefault(); const s = "dataTransfer" in t ? t.dataTransfer.files : t.target.files; for (let e = 0, i; i = s[e]; e++) { const n = new FileReader(); n.onload = (l) => { i.fileBuffer = l.target.result, this.fileLoader(i); }, n.readAsArrayBuffer(i); } } // events handleEvent(t) { const s = t.type; if (typeof this[s] == "function") return t.preventDefault(), this[s](t); } dragenter(t) { this.dragEnteredEls.push(t.target), document.documentElement.classList.add("dragdrop"), this.dropZone.classList.add("active"); } dragover(t) { this.dropZone.classList.add("hover"), this.allowDrag(t); } dragleave(t) { const s = this.dragEnteredEls.indexOf(t.target); s > -1 && this.dragEnteredEls.splice(s, 1), this.dragEnteredEls.indexOf(this.dropZone) < 0 && this.dropZone.classList.remove("hover"), this.dragEnteredEls.length === 0 && (document.documentElement.classList.remove("dragdrop"), this.dropZone.classList.remove("active")); } dropleave() { this.dragEnteredEls.splice(0), this.dropZone.classList.remove("hover"), this.dropZone.classList.remove("active"), document.documentElement.classList.remove("dragdrop"); } drop(t) { const s = new Event("dropleave"); window.dispatchEvent(s), this.handleDrop(t); } change(t) { this.handleFileSelect(t); } } /** @file Hexbuffer JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ function j(r, t = 2) { return r = Math.round(r), (r + 65536).toString(16).substr(-t).toUpperCase(); } class R { constructor(t = "") { this.fromString(t); } fromString(t) { this.line = t.replace(/\s/g, ""), this.index = 0; } hasNibble() { return this.index + 1 <= this.line.length; } hasByte() { return this.index + 2 <= this.line.length; } hasWord() { return this.index + 4 <= this.line.length; } peekNibble() { return parseInt(this.line.substr(this.index, 1), 16); } peekByte() { return parseInt(this.line.substr(this.index, 2), 16); } peekWord() { return parseInt(this.line.substr(this.index, 4), 16); } getNibble() { const t = parseInt(this.line.substr(this.index, 1), 16); return this.index += 1, t; } getByte() { const t = parseInt(this.line.substr(this.index, 2), 16); return this.index += 2, t; } getWord() { const t = parseInt(this.line.substr(this.index, 4), 16); return this.index += 4, t; } pushNibble(t) { this.line += j(t, 1); } pushByte(t) { this.line += j(t, 2); } pushWord(t) { this.line += j(t, 4); } } /** @file Histogram JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ const nt = 16; class lt { constructor(t) { typeof t < "u" ? (this.count = 1, this.sum = t, this.mean = t, this.devi = 0, this.min = t, this.max = t) : (this.count = 0, this.sum = 0, this.mean = null, this.devi = 0, this.min = null, this.max = null); } add(t) { this.count++, this.sum += t, this.mean = this.sum / this.count, this.min = this.min === null ? t : Math.min(t, this.min), this.max = this.max === null ? t : Math.max(t, this.max), this.devi = (this.max - this.min) / 2; } fuse(t) { this.count += t.count, this.sum += t.sum, this.mean = this.sum / this.count, this.min = Math.min(this.min, t.min), this.max = Math.max(this.max, t.max), this.devi = (this.max - this.min) / 2; } contains(t) { return t >= this.min && t <= this.max; } } class z { constructor(t, s = 0.2) { this.bins = [], this.histogram_sum(t, s); } get length() { return this.bins.length; } /// Generate a histogram (unsorted) histogram_sum(t, s = 0.2) { const e = t.length; for (let i = 0; i < e; ++i) { let n; for (n = 0; n < this.bins.length; ++n) { const l = t[i], o = this.bins[n].mean; if (Math.abs(l - o) < s * Math.max(l, o)) { this.bins[n].add(t[i]); break; } } n == this.bins.length && n < nt && this.bins.push(new lt(t[i])); } } /// Delete bin from histogram delete_bin(t) { this.bins.splice(t, 1); } /// Swap two bins in histogram swap_bins(t, s) { if (t < this.bins.length && s < this.bins.length) { const e = this.bins[t]; this.bins[t] = this.bins[s], this.bins[s] = e; } } /// Sort histogram with mean value (order lowest to highest) sort_mean() { if (!(this.bins.length < 2)) for (let t = 0; t < this.bins.length - 1; ++t) for (let s = t + 1; s < this.bins.length; ++s) this.bins[s].mean < this.bins[t].mean && this.swap_bins(s, t); } /// Sort histogram with count value (order lowest to highest) sort_count() { if (!(this.bins.length < 2)) for (let t = 0; t < this.bins.length - 1; ++t) for (let s = t + 1; s < this.bins.length; ++s) this.bins[s].count < this.bins[t].count && this.swap_bins(s, t); } /// Fuse histogram bins with means within tolerance fuse_bins(t = 0.2) { if (!(this.bins.length < 2)) for (let s = 0; s < this.bins.length - 1; ++s) for (let e = s + 1; e < this.bins.length; ++e) { const i = this.bins[s].mean, n = this.bins[e].mean; Math.abs(i - n) < t * Math.max(i, n) && (this.bins[s].fuse(this.bins[e]), this.delete_bin(e), e--); } } /// Trim zero-width bins trim_bins(t = 0) { for (let s = 0; s < this.bins.length; ++s) this.bins[s].mean <= t && this.delete_bin(s); } /// Find bin index find_bin_index(t) { for (let s = 0; s < this.bins.length; ++s) if (this.bins[s].contains(t)) return s; return -1; } /// Print a histogram console_print() { for (let t = 0; t < this.bins.length; ++t) { const s = this.bins[t]; console.log(`[${t}] ${s.count} × ${s.mean.toFixed(1)} ±${s.devi.toFixed(1)} µs [${s.min};${s.max}]`); } } string_print(t = ", ") { let s = []; for (let e = 0; e < this.bins.length; ++e) { const i = this.bins[e]; s.push(`${i.count}× ${i.mean.toFixed(1)} <small>±${i.devi.toFixed(1)}</small> µs`); } return s.join(t); } } class ot { constructor(t, s = 0.2) { this.analyse_pulses(t, s), this.create_rfraw(t); } /// Create histograms from pulse data analyse_pulses(t, s, e = 0.2) { this.pulses = [], this.gaps = [], this.periods = [], this.pulse_sum = 0, this.gap_sum = 0; for (let n = 0; n < t.length - 2; n += 2) { const l = t[n], o = t[n + 1]; this.pulses.push(l), this.gaps.push(o), this.periods.push(l + o), this.pulse_sum += l, this.gap_sum += o; } const i = t[t.length - 2]; this.pulses.push(i), this.pulse_sum += i, this.pulse_gap_ratio = this.pulse_sum / this.gap_sum, this.pulse_gap_skew = this.pulse_gap_ratio - 1, this.hist_pulses = new z(this.pulses, e), this.hist_gaps = new z(this.gaps, e), this.hist_periods = new z(this.periods, e), this.hist_timings = new z(t, e), this.hist_pulses.trim_bins(e), this.hist_gaps.trim_bins(e), this.hist_periods.trim_bins(e), this.hist_timings.trim_bins(e), this.hist_pulses.fuse_bins(e), this.hist_gaps.fuse_bins(e), this.hist_periods.fuse_bins(e), this.hist_timings.fuse_bins(e); } guess() { const t = this.hist_pulses, s = this.hist_gaps, e = this.hist_periods; if (t.sort_mean(), s.sort_mean(), t.bins.length > 0 && t.bins[0].mean == 0 && t.delete_bin(0), this.pulses.length == 1) return { name: "Single pulse detected. Probably Frequency Shift Keying or just noise..." }; if (t.length == 1 && s.length == 1) return { name: "Un-modulated signal. Maybe a preamble..." }; if (t.length == 1 && s.length > 1) return { name: "Pulse Position Modulation with fixed pulse width", modulation: "PPM", short: s.bins[0].mean, long: s.bins[1].mean, gap: s.bins[1].max * 1.2, // Set limit above next lower gap reset: s.bins[s.length - 1].max * 1.2 // Set limit above biggest gap }; if (t.length == 2 && s.length == 1) { const i = t.bins[0].mean, n = t.bins[1].mean; return { name: "Pulse Width Modulation with fixed gap", modulation: "PWM", short: i, long: n, tolerance: (n - i) * 0.4, reset: s.bins[s.length - 1].max * 1.2 // Set limit above biggest gap }; } else if (t.length == 2 && s.length == 2 && e.length == 1) { const i = t.bins[0].mean, n = t.bins[1].mean; return { name: "Pulse Width Modulation with fixed period", modulation: "PWM", short: i, long: n, tolerance: (n - i) * 0.4, reset: s.bins[s.length - 1].max * 1.2 // Set limit above biggest gap }; } else if (t.length == 2 && s.length == 2 && e.length == 3) { const i = t.bins[0].mean; return { name: "Manchester coding (PCM)", modulation: "MC", short: i, // Assume shortest pulse is half period long: i, // Not used reset: s.bins[s.length - 1].max * 1.2 // Set limit above biggest gap }; } else if (t.length == 2 && s.length >= 3) { const i = t.bins[0].mean, n = t.bins[1].mean; return { name: "Pulse Width Modulation with multiple packets", modulation: "PWM", short: i, long: n, gap: s.bins[1].max * 1.2, // Set limit above second gap tolerance: (n - i) * 0.4, reset: s.bins[s.length - 1].max * 1.2 // Set limit above biggest gap }; } else { if (t.length >= 3 && s.length >= 3 && Math.abs(t.bins[1].mean - 2 * t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(t.bins[2].mean - 3 * t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(s.bins[0].mean - t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(s.bins[1].mean - 2 * t.bins[0].mean) <= t.bins[0].mean / 8 && Math.abs(s.bins[2].mean - 3 * t.bins[0].mean) <= t.bins[0].mean / 8) return { name: "Pulse Code Modulation (Not Return to Zero)", modulation: "PCM", short: t.bins[0].mean, // Shortest pulse is bit width long: t.bins[0].mean, // Bit period equal to pulse length (NRZ) reset: t.bins[0].mean * 1024 // No limit to run of zeros... }; if (t.length == 3) { t.sort_count(); const i = t.bins[1].mean, n = t.bins[2].mean, l = i < n ? i : n, o = i < n ? n : i; return { name: "Pulse Width Modulation with sync/delimiter", modulation: "PWM", short: l, long: o, sync: t.bins[0].mean, // Set to lowest count pulse width reset: s.bins[s.length - 1].max * 1.2 // Set limit above biggest gap }; } else return { name: "No clue..." }; } } create_rfraw(t) { const s = this.hist_timings; if (s.bins.length < 1 || s.bins.length > 8 || t.length > 494) return ""; let e = new R(); for (let l of s.bins) e.pushWord(l.mean); for (let l = 0; l < t.length - 1; l += 2) { const o = t[l], a = t[l + 1], h = s.find_bin_index(o); h >= 0 ? e.pushNibble(h | 8) : o == 0 || console.error("RfRaw encoding mark bucket not found:", o, h); const c = s.find_bin_index(a); c >= 0 ? e.pushNibble(c) : a == 0 || console.error("RfRaw encoding space bucket not found:", a, c); } e.line.length % 2 && e.pushNibble(0), e.pushByte(85); let i = new R(); i.pushByte(170), i.pushByte(176), i.pushByte(2 + e.line.length / 2 - 1), i.pushByte(s.bins.length), i.pushByte(1); let n = new R(); n.pushByte(170), n.pushByte(177), n.pushByte(s.bins.length), this.rfrawB0 = i.line + e.line, this.rfrawB1 = n.line + e.line; } console_log() { const t = this.guess(); console.log("Analyzing pulses..."), console.log(`Total count: ${this.pulses.length}`), console.log("Pulse width distribution:"), this.hist_pulses.console_print(), console.log("Gap width distribution:"), this.hist_gaps.console_print(), console.log("Pulse period distribution:"), this.hist_periods.console_print(), console.log("Pulse timing distribution:"), this.hist_timings.console_print(), console.log(`DC bias (Pulse/Gap skew): ${(this.pulse_gap_skew * 100).toFixed(1)}`), console.log("Guessing modulation:"), console.log(t); } print_plain(t) { const s = this.guess(); t.innerHTML = ` <div>Pulses: ${this.hist_pulses.string_print()}</div> <div>Gaps: ${this.hist_gaps.string_print()}</div> <div>Periods: ${this.hist_periods.string_print()}</div> <div>Timings: ${this.hist_timings.string_print()}</div> <div>${s.name}</div> `; } /* const locale = new Intl.NumberFormat().resolvedOptions().locale const formatter = new Intl.NumberFormat(locale, { style: 'percent', signDisplay: 'exceptZero', maximumFractionDigits: 1, }) formatter.format(0.5) */ print(t, s) { const e = this.guess(); t && (t.innerHTML = `<table> <tr><th>Pulses</th><td>${this.hist_pulses.string_print("</td><td>")}</td></tr> <tr><th>Gaps</th><td>${this.hist_gaps.string_print("</td><td>")}</td></tr> <tr><th>Periods</th><td>${this.hist_periods.string_print("</td><td>")}</td></tr> <tr><th>Timings</th><td>${this.hist_timings.string_print("</td><td>")}</td></tr> </table> `), s && (s.innerHTML = ` <div><small>DC bias (Pulse/Gap skew): ${(this.pulse_gap_skew * 100).toFixed(1)}%</small><br> Guessing modulation: <strong>${e.name}</strong><br> modulation: <strong>${e.modulation}</strong> short: <strong>${e.short ? e.short.toFixed(1) : "-"}</strong> long: <strong>${e.long ? e.long.toFixed(1) : "-"}</strong> sync: <strong>${e.sync ? e.sync.toFixed(1) : "-"}</strong> gap: <strong>${e.gap ? e.gap.toFixed(1) : "-"}</strong> reset: <strong>${e.reset ? e.reset.toFixed(1) : "-"}</strong><br> <small>RfRaw (rx): <strong>${this.rfrawB1 ? this.rfrawB1 : "-"}</strong></small><br> <small>RfRaw (tx): <strong>${this.rfrawB0 ? this.rfrawB0 : "-"}</strong></small> </div> `); } } /** @file Bitbuffer JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ class M { constructor(t = [], s = 0) { Array.isArray(t) ? (this.bytes = t, this.len = s || t.length * 8) : this.fromString(t); } fromString(t) { this.bytes = [], this.len = 0; let s = -1; if (t = t.trim(), t.startsWith("{")) { const e = t.indexOf("}"); if (e < 0) return; s = parseInt(t.slice(1), 10), t = t.slice(e + 1); } t.startsWith("0x") && (t = t.slice(2)); for (let e of t) { const i = parseInt(e, 16); this.pushNibble(i); } s >= 0 && (this.len = s); } pushZero() { this.push(0); } pushOne() { this.push(1); } pushSymbol(t) { t == "0" ? this.push(0) : t == "1" && this.push(1); } push(t) { t = t ? 128 : 0, this.bytes[~~(this.len / 8)] |= t >> this.len % 8, this.len += 1; } pushNibble(t) { for (let s = 3; s >= 0; --s) this.push(t >> s & 1); } pushByte(t) { for (let s = 7; s >= 0; --s) this.push(t >> s & 1); } pushBreak() { const t = ~~((this.len + 7) / 8); this.bytes[t] = -1, this.len = (t + 1) * 8; } toBitArray() { let t = []; for (let s = 0; s < this.len; ++s) { const i = (this.bytes[~~(s / 8)] || 0) >> 7 - s % 8 & 1; t.push(i); } return t; } toHexString() { let t = `{${this.len}}`; for (let s = 0; s < this.len; s += 8) { const e = this.bytes[~~(s / 8)] || 0; e < 0 ? t += " / " : (t += " ", t += (e >> 4).toString(16).toUpperCase(), s + 4 < this.len && (t += (e & 15).toString(16).toUpperCase())); } return t; } } /** @file Pulse Slicer JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ function U(r, t) { return t.modulation == "PCM" ? at(r, t) : t.modulation == "MC" ? ut(r, t) : t.modulation == "PPM" ? ft(r, t) : t.modulation == "PWM" ? ct(r, t) : t.modulation == "DM" ? pt(r, t) : t.modulation == "NRZI" ? dt(r, t) : t.modulation == "CMI" ? mt(r, t) : t.modulation == "PIWM" ? gt(r, t) : []; } function at(r, t) { return !t.long || t.long == t.short ? rt(r, t) : ht(r, t); } function rt(r, t) { const s = t.short, e = t.gap, i = new M(); let n = [], l = 0; for (let o = 0; o < r.length; o += 1) { const a = 1 - o % 2, h = r[o]; if (e && h > e) i.pushBreak(); else { const c = ~~(h / s + 0.5); for (let f = 0; f < c; ++f) n.push([l + h / c * f, l + h / c * (f + 1), a]), i.push(a); } l += h; } return { hints: n, bits: i }; } function ht(r, t) { const s = t.short, e = t.long, i = t.gap, n = s * 0.5, l = s * 1.5, o = new M(); let a = [], h = 0; for (let c = 0; c < r.length; c += 2) { const f = r[c], d = r[c + 1]; if (f < n || f > l) { o.pushBreak(), h += f + d; continue; } let g = f * e / s, b = d + f - g; if (b < e / 2 && (g = f + d, b = 0), a.push([h, h + g, "1"]), o.pushOne(), h += g, i && d > i) { o.pushBreak(), h += b; continue; } const x = ~~(b / e + 0.5); for (let y = 0; y < x; ++y) a.push([h + b * y / x, h + b * (y + 1) / x, "0"]), o.pushZero(); h += b; } return { hints: a, bits: o }; } function ft(r, t) { const s = t.short, e = t.long, i = t.sync, n = t.gap, l = s * 0.5, o = s * 1.5, a = e * 0.5, h = e * 1.5, c = i * 0.5, f = i * 1.5, d = new M(); let g = [], b = 0; for (let x = 0; x < r.length; x += 2) { const y = r[x], _ = r[x + 1], k = b; b += y + _, _ > l && _ < o ? (g.push([k, b, "1"]), d.pushOne()) : _ > a && _ < h ? (g.push([k, b, "0"]), d.pushZero()) : _ > c && _ < f ? (g.push([k, b, "X"]), d.pushBreak()) : n && _ > n && d.pushBreak(); } return { hints: g, bits: d }; } function ct(r, t) { const s = t.short, e = t.long, i = t.sync, n = t.gap, l = s * 0.5, o = s * 1.5, a = e * 0.5, h = e * 1.5, c = i * 0.5, f = i * 1.5, d = new M(); let g = [], b = 0; for (let x = 0; x < r.length; x += 2) { const y = r[x], _ = r[x + 1], k = b; let P = b + y + _; _ > n && (P = b + y + n), b += y + _, y > l && y < o ? (g.push([k, P, "1"]), d.pushOne()) : y > a && y < h ? (g.push([k, P, "0"]), d.pushZero()) : y > c && y < f && (g.push([k, P, "X"]), d.pushBreak()), n && _ > n && d.pushBreak(); } return { hints: g, bits: d }; } function N(r, t, s) { for (let e = t; e < r.length; e += 2) { if (~~(r[e] / s + 0.5) > 1) return 0; if (~~(r[e + 1] / s + 0.5) > 1) return 1; } return 0; } function ut(r, t) { const s = t.short, e = new M(); let i = [], n = N(r, 0, s), l = 0, o = 0; for (let a = 0; a < r.length; a += 2) { const h = r[a], c = ~~(h / s + 0.5), f = r[a + 1], d = ~~(f / s + 0.5); c == 1 ? (n ? o = l : (i.push([o, l + h, "0"]), e.pushZero(), o = l + h), n = !n) : c == 2 ? (n ? (e.pushBreak(), o = l + h / 2) : (i.push([o, l + h / 2, "0"]), e.pushZero(), o = l + h / 2), n = !1) : c > 2 && (n || (i.push([o, l + h / c, "0"]), e.pushZero()), o = l + h - h / c, e.pushBreak(), n = N(r, a + 1, s)), d == 1 ? (n ? o = l + h : (i.push([o, l + h + f, "1"]), e.pushOne(), o = l + h + f), n = !n) : d == 2 ? (n ? (e.pushBreak(), o = l + h + f / 2) : (i.push([o, l + h + f / 2, "1"]), e.pushOne(), o = l + h + f / 2), n = !1) : d > 2 && (n || (i.push([o, l + h + f / d, "1"]), e.pushOne()), o = l + h + f - f / d, e.pushBreak(), n = N(r, a + 1, s)), l += h + f; } return { hints: i, bits: e }; } function pt(r, t) { const s = t.short, e = new M(); let i = [], n = 0, l = null; for (let o = 0; o < r.length; o += 2) { const a = r[o], h = ~~(a / s + 0.5), c = r[o + 1], f = ~~(c / s + 0.5); !l && h == 1 && f == 1 ? (i.push([n, n + a + c, "0"]), e.pushZero()) : h == 1 && f == 1 ? (i.push([l, n + a, "0"]), e.pushZero(), l = n + a) : l && h == 1 && f == 2 ? (i.push([l, n + a, "0"]), e.pushZero(), i.push([n + a, n + a + c, "1"]), e.pushOne(), l = null) : h == 2 && f == 1 ? (i.push([n, n + a, "1"]), e.pushOne(), l = n + a) : h == 2 && f == 2 ? (i.push([n, n + a, "1"]), e.pushOne(), i.push([n + a, n + a + c, "1"]), e.pushOne()) : !l && h == 1 ? (i.push([n, n + a + s, "0"]), e.pushZero(), e.pushBreak()) : !l && h == 2 ? (i.push([n, n + a, "1"]), e.pushOne(), e.pushBreak()) : (l && (i.push([l, l + s * 2, "0"]), e.pushZero()), l = null, e.pushBreak()), n += a + c; } return { hints: i, bits: e }; } function dt(r, t) { const s = t.short, e = new M(); let i = [], n = 0, l = 0; for (let o = 0; o < r.length; o += 1) { const a = r[o], h = ~~(a / s + 0.5); l && (i.push([l, n + s / 2, "1"]), e.pushOne()), l = n + s / 2; for (let c = 1; c < h; ++c) i.push([l, l + a / h, "0"]), e.pushZero(), l += a / h; n += a; } return { hints: i, bits: e }; } function mt(r, t) { const s = t.short, e = new M(); let i = [], n = 0, l = null; for (let o = 0; o < r.length; o += 2) { const a = r[o], h = ~~(a / s + 0.5), c = r[o + 1], f = ~~(c / s + 0.5); h == 1 && f == 1 ? (l || (l = n - a), i.push([l, n + a, "0"]), e.pushZero(), l = n + a) : h == 1 && f == 2 ? (l || (l = n - a), i.push([l, n + a, "0"]), e.pushZero(), l = n + a + c, i.push([n + a, l, "1"]), e.pushOne()) : h == 1 && f == 3 ? (l || (l = n - a), i.push([l, n + a, "0"]), e.pushZero(), l = n + a + c * 2 / 3, i.push([n + a, l, "1"]), e.pushOne()) : h == 2 && f == 1 ? (i.push([l, n + a, "1"]), e.pushOne(), l = n + a) : h == 2 && f == 2 ? (i.push([l, n + a, "1"]), e.pushOne(), l = n + a + c, i.push([n + a, l, "1"]), e.pushOne()) : h == 2 && f == 3 ? (i.push([l, n + a, "1"]), e.pushOne(), l = n + a + c * 2 / 3, i.push([n + a, l, "1"]), e.pushOne()) : h == 3 && f == 1 ? (i.push([l, n + a / 3, "0"]), e.pushZero(), i.push([n + a / 3, n + a, "1"]), e.pushOne(), l = n + a) : h == 3 && f == 2 ? (i.push([l, n + a / 3, "0"]), e.pushZero(), i.push([n + a / 3, n + a, "1"]), e.pushOne(), l = n + a + c, i.push([n + a, l, "1"]), e.pushOne()) : h == 3 && f == 3 ? (i.push([l, n + a / 3, "0"]), e.pushZero(), i.push([n, n + a / 3, "1"]), e.pushOne(), i.push([n + a / 3, n + a, "1"]), e.pushOne(), i.push([n + a, n + a + c * 3 / 2, "1"]), e.pushOne(), l = n + a + c * 3 / 2) : h == 1 ? (i.push([l, n + a, "0"]), e.pushZero(), e.pushBreak(), l = n + a) : h == 2 ? (i.push([l, n + a, "1"]), e.pushOne(), e.pushBreak(), l = n + a) : e.pushBreak(), n += a + c; } return { hints: i, bits: e }; } function gt(r, t) { const s = t.short, e = new M(); let i = [], n = 0; for (let l = 0; l < r.length; l += 1) { const o = r[l], a = ~~(o / s + 0.5); a == 1 ? (i.push([n, n + o, "1"]), e.pushOne()) : a == 2 ? (i.push([n, n + o, "0"]), e.pushZero()) : e.pushBreak(), n += o; } return { hints: i, bits: e }; } /** @file Pulse Builder JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ function bt(r) { return Array.isArray(r) ? r : r.split(/\D/).map((s) => parseInt(s, 10)); } class wt { constructor(t) { Object.assign(this, t), this.build(); } build() { this.pulses = [], this.warmup_raw && this.addRaw(this, this.warmup_raw); const t = this.repeat || 1; for (let s = 0; s < t; ++s) { if (this.gap && s > 0 && (this.pulses.push(0), this.pulses.push(this.gap)), this.preamble) { const i = new M(this.preamble); this.addCode(this, i); } if (this.preamble_raw && this.addRaw(this, this.preamble_raw), this.sync_raw && this.addRaw(this, this.sync_raw), this.syncword) { const i = new M(this.syncword); this.addCode(this, i); } const e = new M(this.payload); if (this.addCode(this, e), this.postamble) { const i = new M(this.postamble); this.addCode(this, i); } this.postamble_raw && this.addRaw(this, this.postamble_raw); } return this.coalesce(), this.jitter && this.addJitter(this), this; } addCode(t, s) { return t.modulation == "PCM" ? this.buildPCM(t, s) : t.modulation == "MC" ? this.buildMC(t, s) : t.modulation == "PPM" ? this.buildPPM(t, s) : t.modulation == "PWM" ? this.buildPWM(t, s) : t.modulation == "DM" ? this.buildDM(t, s) : t.modulation == "NRZI" ? this.buildNRZI(t, s) : t.modulation == "CMI" ? this.buildCMI(t, s) : t.modulation == "PIWM" ? this.buildPIWM(t, s) : console.log("Unknown modulation!"), t; } addRaw(t, s) { return s = bt(s), s.length % 2 && s.push(0), t.pulses = t.pulses.concat(s), t; } coalesce() { let t = [], s = 0; for (let e = 0; e < this.pulses.length; e += 2) { const i = this.pulses[e] || 0, n = this.pulses[e + 1] || 0; !i && !n || (!s && !i && t.length ? t[t.length - 1] += ~~n : n ? (t.push(~~(i + s)), t.push(~~n), s = 0) : s += i); } return s && (t.push(~~s), t.push(0)), this.pulses = t, this; } addJitter(t) { const s = t.jitter; let e = []; for (let i = 0; i < t.pulses.length; ++i) { const n = t.pulses[i]; if (n == 0) e.push(n); else if (s < 1) { const l = Math.random() * 2 * s + 1 - s; e.push(~~(n * l)); } else { const l = Math.random() * 2 * s - s; e.push(~~(n + l)); } } return t.pulses = e, t; } // returned hints array contains triples of start,end,symbol /// Pulse-code modulation (PCM) /// https://en.wikipedia.org/wiki/Pulse-code_modulation /// either NRZ or RZ buildPCM(t, s) { return !t.long || t.long == t.short ? this.buildNRZ(t, s) : this.buildRZ(t, s); } /// NRZ(L) NRZL Non-return-to-zero level /// https://en.wikipedia.org/wiki/Non-return-to-zero buildNRZ(t, s) { const e = t.short; let i = t.pulses; const n = s.toBitArray(); for (let l = 0; l < n.length; ++l) n[l] ? (i.push(e), i.push(0)) : (i.push(0), i.push(e)); return t; } /// Return-to-zero level /// https://en.wikipedia.org/wiki/Return-to-zero buildRZ(t, s) { const e = t.short, i = t.long, n = i - e; let l = t.pulses; const o = s.toBitArray(); for (let a = 0; a < o.length; ++a) o[a] ? (l.push(e), l.push(n)) : (l.push(0), l.push(i)); return t; } /// Pulse-position modulation (PPM) /// https://en.wikipedia.org/wiki/Pulse-position_modulation buildPPM(t, s) { const e = t.pulse || ~~(t.short / 3), i = t.short, n = t.long; let l = t.pulses; const o = s.toBitArray(); for (let a = 0; a < o.length; ++a) l.push(e), o[a] ? l.push(i) : l.push(n); return t; } /// Pulse-width modulation (PWM) /// https://en.wikipedia.org/wiki/Pulse-width_modulation buildPWM(t, s) { const e = t.short, i = t.long, n = t.short_gap || t.long, l = t.long_gap || t.short; let o = t.pulses; const a = s.toBitArray(); for (let h = 0; h < a.length; ++h) a[h] ? (o.push(e), o.push(n)) : (o.push(i), o.push(l)); return t; } /// Manchester code (MC) /// https://en.wikipedia.org/wiki/Manchester_code buildMC(t, s) { const e = t.short; let i = t.pulses; const n = s.toBitArray(); for (let l = 0; l < n.length; ++l) n[l] ? (i.push(e), i.push(e)) : (i.push(0), i.push(e), i.push(e), i.push(0)); return t; } /// Differential Manchester Encoding (DM) aka Biphase Mark Code (CC) /// https://en.wikipedia.org/wiki/Differential_Manchester_encoding buildDM(t, s) { const e = t.short; let i = t.pulses; const n = s.toBitArray(); let l = !1; for (let o = 0; o < n.length; ++o) { const a = n[o]; !a && !l ? (i.push(e), i.push(e)) : !a && l ? (i.push(0), i.push(e), i.push(e), i.push(0)) : a && !l ? (i.push(e * 2), i.push(0), l = !l) : a && l && (i.push(0), i.push(e * 2), l = !l); } return t; } /// Non-return-to-zero, inverted (NRZI) https://en.wikipedia.org/wiki/Non-return-to-zero#NRZI /// NRZ(I) NRZI Non-return-to-zero inverted Refers to either an NRZ(M) or NRZ(S) code. /// NRZ(M) NRZM Non-return-to-zero mark Serializer mapping {0: constant, 1: toggle}. /// NRZ(S) NRZS Non-return-to-zero space Serializer mapping {0: toggle, 1: constant}. /// A 1 is transmitted as a transition, and a 0 is transmitted as no transition. buildNRZI(t, s) { const e = t.short; let i = t.pulses; const n = s.toBitArray(); let l = !n[0]; for (let o = 0; o < n.length; ++o) n[o] && (l = !l), l ? (i.push(e), i.push(0)) : (i.push(0), i.push(e)); return t; } /// Coded Mark Inversion (CMI) https://en.wikipedia.org/wiki/Coded_mark_inversion /// encodes zero bits as a half bit time of zero followed by a half bit time of one, /// and one bits are encoded as a full bit time of a constant level, /// the level used for one bits alternates each time one is coded. buildCMI(t, s) { const e = t.short; let i = t.pulses; const n = s.toBitArray(); let l = !1; for (let o = 0; o < n.length; ++o) n[o] ? l ? (i.push(0), i.push(e * 2), l = !l) : (i.push(e * 2), i.push(0), l = !l) : (i.push(0), i.push(e), i.push(e), i.push(0)); return t; } /// Pulse-Interval-Width Modulation (PIWM) /// Exotic differential coding buildPIWM(t, s) { const e = t.short; let i = t.pulses; const n = s.toBitArray(); for (let l = 0; l < n.length; ++l) n[l] ? i.push(e) : i.push(e * 2); return n.length % 2 && i.push(e * 3), t; } } /** @file RfRaw JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ class S { static isRfRaw(t) { return t.trim().startsWith("AA"); } static getPulses(t) { let s = []; t = t.replace(/5\s*5[+\s]+A\s*A\s*B/g, "55;AAB"); const e = t.split(";"); for (let i of e) { const n = S.getCodePulses(i); s = s.concat(n); } return s; } static getCodePulses(t) { console.log("parsing rfraw data: ", t); let s = []; const e = new R(t); if (e.getByte() != 170) return s; const n = e.getByte(); let l, o, a; if (n == 177) o = e.getByte(); else if (n == 176) l = e.getByte(), o = e.getByte(), a = e.getByte(); else return s; let h = []; for (let g = 0; g < o; ++g) h[g] = e.getWord(); console.log(`buckets: ${o} (len ${l} repeats ${a}): `, h); let c = !1; const f = e.index; for (; e.hasNibble(); ) if (e.getNibble() > 7) { c = !0; break; } if (e.index = f, console.log("RfRaw v3 format: ", c), !c) { for (s.push(0); e.hasNibble() && e.peekByte() != 85; ) { const g = e.getNibble(); if (g > o) break; s.push(h[g]); } return s.push(0), a ? new Array(a).fill(s).flat() : s; } let d = !0; for (; e.hasNibble() && e.peekByte() != 85; ) { const g = e.getNibble(), b = g & 7; if (b > o) break; g & 8 ? (d || s.push(0), s.push(h[b]), d = !1) : (d && s.push(0), s.push(h[b]), d = !0); } return d || s.push(0), a ? new Array(a).fill(s).flat() : s; } } /** @file BroadlinkRM JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2025 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ class K { static isBroadlinkRM(t) { try { bin = atob(t); } catch { return !1; } if (bin.length < 4) return !1; const s = bin.charCodeAt(0); return s == 178 || s == 177 || s == 215 ? !0 : (s == 38, !1); } static decodeData(t) { let s = { pulses: [], frequency: 0, repeats: 0, errors: "" }, e = ""; try { e = atob(t); } catch { return s.errors = "Invalid Base64", console.warn(s.errors), s; } if (e.length < 4) return s.errors = "Invalid data header", console.warn(s.errors), s; const i = e.charCodeAt(0); if (i == 178 || i == 177) s.frequency = 43392e4; else if (i == 215) s.frequency = 315e6; else return i == 38 ? (s.errors = "Unsupported code type", console.warn(s.errors, i), s) : (s.errors = `Invalid code type (${i.toString(16)})`, console.warn(s.errors, i), s); s.repeats = e.charCodeAt(1), (e.charCodeAt(2) | e.charCodeAt(3) << 8) + 2 != e.length && (s.errors = "Code length mismatch", console.warn(s.errors)); for (let l = 4; l < e.length; l++) { let o = e.charCodeAt(l); o == 0 && (o = e.charCodeAt(++l) | e.charCodeAt(++l) << 8), s.pulses.push(o); } return s; } } /** @file Pulse Data Viewer JS. @author Christian W. Zuckschwerdt <zany@triq.net> @copyright Christian W. Zuckschwerdt, 2020 @license This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. */ function xt(r) { return new Promise((t, s) => { var e = new FileReader(); e.onerror = () => { s(e.error); }, e.onload = () => { t(e.result); }, e.readAsText(r); }); } window.File && window.FileReader && window.FileList && window.Blob || alert("The File APIs are not fully supported in this browser."); let J; window.addEventListener("resize", function() { clearTimeout(J), J = setTimeout(function() { const r = new Event("debouncedResize"); window.dispatchEvent(r); }, 250); }, !1); const yt = { PCM: "PCM", PWM: "PWM", PPM: "PPM", MC: "MC", DM: "DM", NRZI: "NRZI", CMI: "CMI", PIWM: "PIWM" }, _t = { ManchesterGET: "Manchester-GET", ManchesterIEEE: "Manchester-IEEE", DifferentialManchester: "Differential-Manchester" }, vt = [ { text: "off", value: "" }, { text: "PCM", value: "PCM" }, { text: "PWM", value: "PWM" }, { text: "PPM", value: "PPM" }, { text: "MC", value: "MC" }, { text: "DM", value: "DM" }, { text: "NRZI", value: "NRZI" }, { text: "CMI", value: "CMI" }, { text: "PIWM", value: "PIWM" } ], Mt = [ { text: "off", value: "" }, { text: "Manchester G.E.Thomas", value: "ManchesterGET" }, { text: "Manchester IEEE", value: "ManchesterIEEE" }, { text: "Differential Manchester", value: "DifferentialManchester" } ]; class kt { /** Get all possible slicer options. */ static get slicerOptions() { return vt; } /** Get all possible coding options. */ static get codingOptions() { return Mt; } /** Initialize a new Pulseplot. @param {Object} options @param {number} [options.width=0] - canvas width in px, 0 = auto @param {number} [options.height=150] - canvas height in px @param {number} [options.yHi=50] - Marks y-height @param {number} [options.yLo=100] - Spaces y-height @param {number} [options.yHintLo=115] - Hints y-bottom @param {number} [options.yHintHi=35] - Hints y-top @param {number} [options.yHintText=125] - Hints text y-offset @param {number} [options.scroll=0] - Initial scroll offset @param {number} [options.zoom=1] - Initial zoom factor @param {boolean} [options.unlockable=false] - Enable unlockable @param {boolean} [options.scrollzoom=false] - Enable scrollZoom @param {Slicers} [options.slicer] - Slicer to apply, unset = auto @param {string|Object} options.parent - Container element or selector @param {string|Object} [options.renderInfo] - Info element or selector @param {string|Object} [options.theme] - Theme name or options, see {@link setTheme} */ constructor(t = {}) { this.constrain = { histogram: (i) => parseInt(i, 10) || 0, slicer: (i) => G(yt, i) || "", coding: (i) => G(_t, i) || "" }; const s = { width: 0, // 0 = auto scroll: 0, zoom: 1 }; this.setTheme(t.theme); const e = this.heightDefaults(t.height); t = Object.assign({}, s, e, t), this.tag = Math.random().toString().substring(2), this.parent = A(t.parent), this.data = t.data || {}, this.width = t.width || this.parent.clientWidth, this.height = t.height, this.yHi = t.yHi, this.yLo = t.yLo, this.yHintLo = t.yHintLo, this.yHintHi = t.yHintHi, this.yHintText = t.yHintText, this.scroll = t.scroll, this.zoom = t.zoom, this.slicer = t.slicer, this.renderInfo = t.renderInfo, this.opts = t, t.data && this.setData(t.data, t.fileinfo), t.unlockable && this.enableUnlockable(), t.scrollzoom && this.enableScrollZoom(), window.ResizeObserver ? (this.resizeObserver = new ResizeObserver(() => { this.redrawCanvas(); }), this.resizeObserver.observe(this.parent)) : window.addEventListener("debouncedResize", this, !1); } /** Set a color theme. @param {string|Object} options - Theme name or theme options @param {string} [options.hiStroke='#3c3'] - Mark stroke color @param {string} [options.hiFill='rgba(51,204,51,0.1)'] - Mark fill color @param {number} [options.hiLine=3] - Mark line width @param {number[]} [options.hiDash=[]] - Mark dash @param {string} [options.loStroke='#c33'] - Space stroke color @param {string} [options.loFill='rgba(204,204,204,0.1)'] - Space fill color @param {number} [options.loLine=3] - Space line width @param {number[]} [options.loDash=[]] - Space dash @param {string} [options.edgeStroke='#ccc'] - Edges stroke color @param {number} [options.edgeLine=1] - Edges line width @param {number[]} [options.edgeDash=[]] - Edge dash @param {string} [options.textFill='#666'] - Texts color @param {string} [options.dotFill='#999'] - Dots color @param {string} [options.hintStroke='#aaf'] - Hints stroke color @param {number[]} [options.hintDash=[3, 2]] - Hints dash @param {number} [options.hintLine=1] - Hints line width @param {string} [options.hintAltStroke='#f99'] - Error hints stroke color @param {number[]} [options.hintAltDash=[3, 2]] - Error hints dash @param {number} [options.hintAltLine=3] - Error hints line width @param {string} [options.hintFill='#448'] - Hints text color @param {string} [options.timeLabelFill='#333'] - Time label text color @param {string} [options.timeMinorFill='#CCC'] - Time minor tick color @param {string} [options.timeMajorFill='#666'] - Time major tick color @param {string} [options.font='10px sans-serif'] - Text font */ setTheme(t) { let s = { hiStroke: "#3c3", hiFill: "rgba(51,204,51,0.1)", hiLine: 3, hiDash: [], loStroke: "#c33", loFill: "rgba(204,204,204,0.1)", loLine: 3, loDash: [], edgeStroke: "#ccc", edgeLine: 1, edgeDash: [], textFill: "#666", dotFill: "#999", hintStroke: "#aaf", hintDash: [3, 2], hintLine: 1, hintAltStroke: "#f99", hintAltDash: [3, 2], hintAltLine: 3, hintFill: "#448", timeLabelFill: "#333", timeMinorFill: "#CCC", timeMajorFill: "#666", font: "10px sans-serif" }; const e = {}; return e.dark = { hiStroke: "#b0b", hiFill: "rgba(191,0,191,0.2)", hiLine: 3, hiDash: [], loStroke: "#27c", loFill: "rgba(102,102,102,0.2)", loLine: 3, loDash: [], edgeStroke: "#555", edgeLine: 1, edgeDash: [], textFill: "#ccc", dotFill: "#555", hintStroke: "#555", hintDash: [3, 2], hintLine: 1, hintAltStroke: "#c55", hintAltDash: [3, 2], hintAltLine: 3, hintFill: "#ccc", timeLabelFill: "#DDD", timeMinorFill: "#666", timeMajorFill: "#999", font: "10px sans-serif" }, typeof t == "string" && e[t] && (s = e[t]), this.theme = Object.assign({}, s, t), this.redrawCanvas(); } /** Get all height options based on total height value. @param {number} [height=120] - Total height @returns {Object} - All height option defaults */ heightDefaults(t = 120) { const s = parseInt(this.theme.font, 10) || 10, e = 22 + ~~(t / 10), i = 1 + ~~(t / 10); return { height: t, yHi: e + i, yLo: t - s - i, yHintLo: t - s, yHintHi: e, yHintText: t }; } /** Enables mouse and touch scroll zoom on double click. */ enableUnlockable() { if (this.unlockable) return; this.unlockable = !0; const t = this.canvasNode; ["dblclick"].forEach((e) => t.addEventListener(e, this, !1)); } /** Disables mouse and touch scroll zoom on double click. */ disableUnlockable() { if (!this.unlockable) return; this.unlockable = !1; const t = this.canvasNode; ["dblclick"].forEach((e) => t.removeEventListener(e, this, !1)); } /** Enables mouse and touch scroll zoom. */ enableScrollZoom() { if (this.unlocked) return; this.unlocked = !0; const t = this.canvasNode; let s = ["mousedown", "mousemove", "mouseup", "wheel"]; this.unlockable || s.push("dblclick"), "ontouchstart" in window && (s = s.concat("touchstart", "touchmove", "touchend")), s.forEach((e) => t.addEventListener(e, this, !1)); } /** Disables mouse and touch scroll zoom. */ disableScrollZoom() { if (!this.unlocked) return; this.unlocked = !1; const t = this.canvasNode; let s = ["mousedown", "mousemove", "mouseup", "wheel"]; this.unlockable || s.push("dblclick"), "ontouchstart" in window && (s = s.concat("touchstart", "touchmove", "touchend")), s.forEach((e) => t.removeEventListener(e, this, !1)); } /** Release all event handlers and resources. */ destroy() { this.dropZone && this.dropZone.destroy(), this.resizeObserver && this.resizeObserver.disconnect(), window.removeEventListener("debouncedResize", this, !1), this.disableScrollZoom(); } createDropZone(t) { t = t || this.parent.getElementsByClassName("dropzone")[0]; const s = this.setData.bind(this); this.dropZone = new it(t, s); } setSlicer(t) { if (t && t.modulation ? this.slicer = t : this.data && this.data.modulation ? this.slicer = this.data : this.slicer = this.guess, !this.data.pulses || !this.data.pulses.length) return; const s = U(this.data.pulses, this.slicer); if (this.data.hints = s.hints, this.data.bits = s.bits, s.bits) { const e = this.timingsNode, i = this.messagesNode; this.analyzer.print(e, i), console.log(s.bits.toHexString()), i && (i.innerHTML += `<div>Bits: <strong>${s.bits.toHexString()}</strong></div>`); } this.redrawCanvas(); } /** Set an option on the Pulseplot to some value. @param {string} opt - Option name, see {@link #new_Pulseplot_new new} @param {Object} value - The new value for the option */ setOption(t, s) { return this[t] = this.constrain[t](s), this.processData(); } /** Set a number of options on the Pulseplot to some values. @param {Object} opts - A key/value object of options to set, see {@link #new_Pulseplot_new new} */ setOptions(t) { for (let s in t) this[s] = this.constrain[s](t[s]); return t