From d0118e4628c966f94655a8323e80bd3b8b8d7296 Mon Sep 17 00:00:00 2001 From: Randy Mackay Date: Thu, 6 Feb 2025 16:44:58 +0900 Subject: [PATCH 01/24] Chat: openai chat tool --- Chat/Chat.js | 677 ++++++++++++++++++++++++++++++++++++++++++++++++ Chat/Readme.md | 3 + Chat/index.html | 386 +++++++++++++++++++++++++++ 3 files changed, 1066 insertions(+) create mode 100644 Chat/Chat.js create mode 100644 Chat/Readme.md create mode 100644 Chat/index.html diff --git a/Chat/Chat.js b/Chat/Chat.js new file mode 100644 index 00000000..193cd5f9 --- /dev/null +++ b/Chat/Chat.js @@ -0,0 +1,677 @@ + +var DataflashParser +import('../modules/JsDataflashParser/parser.js').then((mod) => { DataflashParser = mod.default }); + +function plot_visibility(plot, hide) { + plot.parentElement.hidden = hide +} + +// Time stamp for each bin +function bin_time(low_bin, high_bin, bin_width) { + let time = array_from_range(low_bin, high_bin, 1.0) + time = array_scale(time, bin_width) + return array_offset(time, bin_width * 0.5) +} + +// Splint time into bins and count size of instances in each bin +function bin_count(time_in, size, bin_width, total) { + + // Bin index for given time array + function bin_index(time, bin_width) { + const len = time.length + let ret = new Array(len) + for (i = 0; i < len; i++) { + ret[i] = Math.floor(time[i] / bin_width) + } + return ret + } + + // Size of msg at index, deal with array size + function get_size(i) { + if (Array.isArray(size)) { + return size[i] + } + return size + } + + const bins = bin_index(time_in, bin_width) + + let low_bin = Infinity + let high_bin = -Infinity + const len = bins.length + for (let i = 0; i < len; i++) { + low_bin = Math.min(low_bin, bins[i]) + high_bin = Math.max(high_bin, bins[i]) + } + total.low_bin = Math.min(total.low_bin, low_bin) + total.high_bin = Math.max(total.high_bin, high_bin) + + const time = bin_time(low_bin, high_bin, bin_width) + + // Sort bins into counts + let count = new Array(time.length).fill(0) + for (let i = 0; i < len; i++) { + const bin = bins[i] + const size = get_size(i) + + // Add to msg count + count[bin - low_bin] += size + + // Add to total count + if (total.count[bin] == null) { + total.count[bin] = 0 + } + total.count[bin] += size + } + + // Normalize by bin width + count = array_scale(count, 1 / bin_width) + + return { time, count } +} + +// Take total object and return time and count +function total_count(total, bin_width) { + + if (total.count.length == 0) { + // No data + return { time: null, count: null } + } + + let time = bin_time(total.low_bin, total.high_bin, bin_width) + let count = total.count.slice(total.low_bin, total.high_bin + 1) + + // Fill in any any missing data and normalize for bin size + const len = count.length + for (let i = 0; i < len; i++) { + if (count[i] == null) { + count[i] = 0 + } + count[i] /= bin_width + } + + return { time, count } +} + +let log +function load_log(log_file) { + const start = performance.now() + + log = new DataflashParser() + log.processData(log_file, []) + + open_in_update(log) + + plot_log() + + const end = performance.now() + console.log(`Load took: ${end - start} ms`) +} + +function plot_log() { + + // micro seconds to seconds helpers + const US2S = 1 / 1000000 + function TimeUS_to_seconds(TimeUS) { + return array_scale(TimeUS, US2S) + } + + let stats = log.stats() + if (stats == null) { + alert("Failed to get stats") + return + } + + // Load user setting + const bin_width = parseFloat(document.getElementById("WindowSize").value) + const use_size = document.getElementById("Unit_bps").checked + const plot_labels = use_size ? rate_plot.bits : rate_plot.count + + // Plot composition + log_stats.data[0].labels = [] + log_stats.data[0].values = [] + for (const [key, value] of Object.entries(stats)) { + log_stats.data[0].labels.push(key) + log_stats.data[0].values.push(use_size ? value.size : value.count) + } + log_stats.data[0].hovertemplate = plot_labels.pie_hovertemplate + + let plot = document.getElementById("log_stats") + plot_visibility(plot, false) + Plotly.redraw(plot) + + let stats_text = document.getElementById("LOGSTATS") + stats_text.replaceChildren() + if (use_size) { + stats_text.appendChild(document.createTextNode("Total size: " + log.data.byteLength + " Bytes")) + } + + // Clear plot data + data_rates.data = [] + + let total = { + count: [], + low_bin: Infinity, + high_bin: -Infinity, + } + for (const [name, value] of Object.entries(stats)) { + if ((value.count == 0) || !(name in log.messageTypes) || !log.messageTypes[name].expressions.includes("TimeUS")) { + continue + } + + let time + if (!("instances" in log.messageTypes[name])) { + // No instances + time = TimeUS_to_seconds(log.get(name, "TimeUS")) + + } else { + // Instances + time = [] + for (const inst of Object.keys(log.messageTypes[name].instances)) { + time = [].concat(time, TimeUS_to_seconds(log.get_instance(name, inst, "TimeUS"))) + } + } + + const binned = bin_count(time, use_size ? (value.msg_size * 8) : 1, bin_width, total) + + data_rates.data.push({ + type: 'scattergl', + mode: 'lines', + x: binned.time, + y: binned.count, + name: name, + meta: name, + hovertemplate: plot_labels.hovertemplate + }) + + } + + // Show setup options + document.getElementById("plotsetup").hidden = false + + // Plot individual rates + data_rates.layout.yaxis.title.text = plot_labels.yaxis + + plot = document.getElementById("data_rates") + Plotly.purge(plot) + Plotly.newPlot(plot, data_rates.data, data_rates.layout, {displaylogo: false}) + plot_visibility(plot, false) + + // Total rate + const total_binned = total_count(total, bin_width) + total_rate.data[0].x = total_binned.time + total_rate.data[0].y = total_binned.count + + total_rate.data[0].hovertemplate = plot_labels.hovertemplate + total_rate.layout.yaxis.title.text = plot_labels.yaxis + + plot = document.getElementById("total_rate") + Plotly.purge(plot) + Plotly.newPlot(plot, total_rate.data, total_rate.layout, { displaylogo: false }) + plot_visibility(plot, false) + + // Clear listeners + document.getElementById("total_rate").removeAllListeners("plotly_relayout"); + document.getElementById("data_rates").removeAllListeners("plotly_relayout"); + + // Link all time axis + link_plot_axis_range([ + ["total_rate", "x", "", total_rate], + ["data_rates", "x", "", data_rates], + ]) + + // Link plot reset + link_plot_reset([ + ["total_rate", total_rate], + ["data_rates", data_rates], + ]) +} + +let system +function load_tlog(log_file) { + const start = performance.now() + + // Very basic Tlog parsing, does not look into messages, just gets type and size + let data = new DataView(log_file) + + // Start at 8 since were looking for MAVlink header which comes after 64 bit timestamp + const timestamp_length = 8 + + let first_timestamp + let end_time = 0 + + system = {} + let offset = timestamp_length + while (offset < log_file.byteLength) { + const magic = data.getUint8(offset) + let header + if (magic == 0xFE) { + // MAVLink 1 + // 6 byte header, 2 byte crc + const header_length = 8 + if ((offset + header_length) > log_file.byteLength) { + // Header does not fit in remaining space + break + } + header = { + version: 1, + header_length, + payload_length: data.getUint8(offset + 1), + sequence: data.getUint8(offset + 2), + srcSystem: data.getUint8(offset + 3), + srcComponent: data.getUint8(offset + 4), + msgId: data.getUint8(offset + 5), + signed: false + } + + } else if (magic == 0xFD) { + // MAVLink 2 + // 10 byte header, 2 byte crc + const header_length = 12 + if ((offset + header_length) > log_file.byteLength) { + // Header does not fit in remaining space + break + } + + const incompat_flags = data.getUint8(offset + 2) + //const compat_flags = data.getUint8(offset + 3) + + header = { + version: 2, + header_length, + payload_length: data.getUint8(offset + 1), + sequence: data.getUint8(offset + 4), + srcSystem: data.getUint8(offset + 5), + srcComponent: data.getUint8(offset + 6), + msgId: (data.getUint8(offset + 9) << 16) + (data.getUint8(offset + 8) << 8) + data.getUint8(offset + 7), + signed: (incompat_flags & 0x01) != 0 + } + + } else { + // Invalid header + offset += 1 + continue + } + + const total_msg_length = header.header_length + header.payload_length + (header.signed ? 13 : 0) + if ((offset + total_msg_length) > log_file.byteLength) { + // Message does not fit in remaining space + break + } + + const message = mavlink_msgs[header.msgId] + if (message == null) { + // Invalid ID + offset += 1 + continue + } + + // CRC-16/MCRF4XX checksum helper + function x25Crc(byte, crc) { + var tmp = byte ^ (crc & 0xFF) + tmp = (tmp ^ (tmp << 4)) & 0xFF + crc = (crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4) + return crc & 0xFFFF + } + + // Calculate checksum + let crc = 0xFFFF + const crc_len = header.header_length + header.payload_length - 2 + for (let i = 1; i < crc_len ; i++) { + crc = x25Crc(data.getUint8(offset + i), crc) + } + crc = x25Crc(message.CRC, crc) + + const expected_crc = data.getUint16(offset + crc_len, true) + if (crc != expected_crc) { + // Invalid crc + offset += 1 + continue + } + + // Get system + if (!(header.srcSystem in system)) { + system[header.srcSystem] = {} + } + let sys = system[header.srcSystem] + + // Get component + if (!(header.srcComponent in sys)) { + sys[header.srcComponent] = { + next_seq: header.sequence, + received: 0, + dropped: 0, + msg: {}, + version: new Set(), + signed: false + } + } + let comp = sys[header.srcComponent] + comp.received++ + + // Get message + if (!(message.name in comp.msg)) { + comp.msg[message.name] = { + time: [], + size: [], + version: new Set(), + signed: false + } + } + let msg = comp.msg[message.name] + + // Get timestamp + const time_stamp = data.getBigUint64(offset-timestamp_length) + if (first_timestamp == null) { + first_timestamp = time_stamp + + const date = new Date(Number(time_stamp/1000n)) + console.log("Start time: " + date.toString()) + } + + // Time since log start in seconds + const time = Number(time_stamp - first_timestamp) / 1000000 + + if (time < end_time) { + alert("Time went backwards!") + throw new Error() + } + end_time = time + + // Update message stats + msg.time.push(time) + msg.size.push(total_msg_length * 8) + msg.version.add(header.version) + msg.signed |= header.signed + + // Update component stats + comp.version.add(header.version) + comp.signed |= header.signed + + // Check sequence for dropped packets + let seq = header.sequence + if (seq < comp.next_seq) { + // Deal with wrap at 255 + seq += 255 + } + comp.dropped += seq - comp.next_seq + comp.next_seq = (header.sequence + 1) % 256 + + // Advance by message length + offset += total_msg_length + timestamp_length + } + + // Print stats for each system detected + let section = document.getElementById("MAVLink") + section.hidden = false + section.previousElementSibling.hidden = false + for (const [sys_id, sys] of Object.entries(system)) { + + let heading = document.createElement("h4") + heading.innerHTML = "System ID: " + sys_id + section.appendChild(heading) + + let table = document.createElement("table") + section.appendChild(table) + + for (const [comp_id, comp] of Object.entries(sys)) { + + let colum = document.createElement("td") + table.appendChild(colum) + + let fieldset = document.createElement("fieldset") + colum.appendChild(fieldset) + + let legend = document.createElement("legend") + legend.innerHTML = "Component ID: " + comp_id + fieldset.appendChild(legend) + + let name = "Unknown" + if (comp_id in MAV_COMPONENT) { + name = MAV_COMPONENT[comp_id] + } + + fieldset.appendChild(document.createTextNode("ID Name: " + name)) + fieldset.appendChild(document.createElement("br")) + + function get_version_string(version_set) { + let version = Array.from(version_set) + version = version.toSorted() + return version.join(", ") + } + + fieldset.appendChild(document.createTextNode("MAVLink Version: " + get_version_string(comp.version))) + fieldset.appendChild(document.createElement("br")) + + fieldset.appendChild(document.createTextNode("Signing: " + (comp.signed ? "\u2705" : "\u274C"))) + fieldset.appendChild(document.createElement("br")) + + const drop_pct = (comp.dropped/comp.received) * 100 + fieldset.appendChild(document.createTextNode("Dropped messages: " + comp.dropped + " / " + comp.received + " (" + drop_pct.toFixed(2) + "%)")) + fieldset.appendChild(document.createElement("br")) + + function add_include(parent, name) { + let check = document.createElement("input") + check.setAttribute('type', 'checkbox') + check.setAttribute('id', name) + check.checked = true + check.addEventListener('change', plot_tlog) + + parent.appendChild(check) + + let label = document.createElement("label") + label.setAttribute('for', name) + label.innerHTML = "Include" + parent.appendChild(label) + + return check + } + + comp.include = add_include(fieldset, sys_id + "," + comp_id) + + + let details = document.createElement("details") + fieldset.appendChild(details) + + let summary = document.createElement("summary") + summary.innerHTML = "Messages" + details.appendChild(summary) + + for (const [name, msg] of Object.entries(comp.msg)) { + let msg_fieldset = document.createElement("fieldset") + details.appendChild(msg_fieldset) + + let msg_legend = document.createElement("legend") + msg_legend.innerHTML = name + msg_fieldset.appendChild(msg_legend) + + msg_fieldset.appendChild(document.createTextNode("Count: " + msg.time.length)) + msg_fieldset.appendChild(document.createElement("br")) + + if (comp.version.size > 1) { + msg_fieldset.appendChild(document.createTextNode("MAVLink Version: " + get_version_string(msg.version))) + msg_fieldset.appendChild(document.createElement("br")) + } + + if (comp.signed) { + msg_fieldset.appendChild(document.createTextNode("Signing: " + (msg.signed ? "\u2705" : "\u274C"))) + msg_fieldset.appendChild(document.createElement("br")) + } + + msg.include = add_include(msg_fieldset, sys_id + "," + comp_id + "," + name) + + } + + } + } + + plot_tlog() + + const end = performance.now() + console.log(`Load took: ${end - start} ms`) +} + +function plot_tlog() { + + const bin_width = parseFloat(document.getElementById("WindowSize").value) + const use_size = document.getElementById("Unit_bps").checked + + const plot_labels = use_size ? rate_plot.bits : rate_plot.count + + // Clear plot data + data_rates.data = [] + + let total = { + count: [], + low_bin: Infinity, + high_bin: -Infinity, + } + let composition = {} + for (const [sys_id, sys] of Object.entries(system)) { + for (const [comp_id, comp] of Object.entries(sys)) { + const comp_include = comp.include.checked + for (const [name, msg] of Object.entries(comp.msg)) { + if (!comp_include) { + // Component is disabled, disable message checkbox + msg.include.disabled = true + continue + } + msg.include.disabled = false + if (!msg.include.checked) { + // Message is disabled + continue + } + + const binned = bin_count(msg.time, use_size ? msg.size : 1, bin_width, total) + + data_rates.data.push({ + mode: 'lines', + x: binned.time, + y: binned.count, + name: name, + meta: name, + hovertemplate: plot_labels.hovertemplate + }) + + const comp_name = "(" + sys_id + ", " + comp_id + ") " + name + composition[comp_name] = use_size ? array_sum(msg.size) : msg.size.length + } + } + } + + // Show setup options + document.getElementById("plotsetup").hidden = false + + // Plot individual rates + data_rates.layout.yaxis.title.text = plot_labels.yaxis + + let plot = document.getElementById("data_rates") + Plotly.purge(plot) + Plotly.newPlot(plot, data_rates.data, data_rates.layout, { displaylogo: false }) + plot_visibility(plot, false) + + // Total rate + const total_binned = total_count(total, bin_width) + total_rate.data[0].x = total_binned.time + total_rate.data[0].y = total_binned.count + + total_rate.data[0].hovertemplate = plot_labels.hovertemplate + total_rate.layout.yaxis.title.text = plot_labels.yaxis + + plot = document.getElementById("total_rate") + Plotly.purge(plot) + Plotly.newPlot(plot, total_rate.data, total_rate.layout, { displaylogo: false }) + plot_visibility(plot, false) + + // Plot composition + log_stats.data[0].labels = [] + log_stats.data[0].values = [] + for (const [key, value] of Object.entries(composition)) { + log_stats.data[0].labels.push(key) + log_stats.data[0].values.push(value) + } + log_stats.data[0].hovertemplate = plot_labels.pie_hovertemplate + + plot = document.getElementById("log_stats") + plot_visibility(plot, false) + Plotly.redraw(plot) + + // Clear listeners + document.getElementById("total_rate").removeAllListeners("plotly_relayout"); + document.getElementById("data_rates").removeAllListeners("plotly_relayout"); + + // Link all time axis + link_plot_axis_range([ + ["total_rate", "x", "", total_rate], + ["data_rates", "x", "", data_rates], + ]) + + // Link plot reset + link_plot_reset([ + ["total_rate", total_rate], + ["data_rates", data_rates], + ]) +} + +function replot() { + if (system != null) { + plot_tlog() + + } else if (log != null) { + plot_log() + + } +} + +async function load(e) { + reset() + + const file = e.files[0] + if (file == null) { + return + } + + if (file.name.toLowerCase().endsWith(".bin")) { + let reader = new FileReader() + reader.onload = function (e) { + loading_call(() => { load_log(reader.result) }) + } + reader.readAsArrayBuffer(file) + + } else if (file.name.toLowerCase().endsWith(".tlog")) { + let reader = new FileReader() + reader.onload = function (e) { + loading_call(() => { load_tlog(reader.result) }) + } + reader.readAsArrayBuffer(file) + } + +} + +// Axis labels used in different modes +const rate_plot = { + bits: { + hovertemplate: "%{meta}
%{x:.2f} s
%{y:.2f} bps", + yaxis: "bits per second", + pie_hovertemplate: '%{label}
%{value:,i} bits
%{percent}' + }, + count: { + hovertemplate: "%{meta}
%{x:.2f} s
%{y:.2f} messages", + yaxis: "messages per second", + pie_hovertemplate: '%{label}
%{value:,i} messages
%{percent}' + } +} + +function reset() { + + function setup_section(section) { + // Remove all children + section.replaceChildren() + + // Hide + section.hidden = true + section.previousElementSibling.hidden = true + } + + setup_section(document.getElementById("MAVLink")) +} diff --git a/Chat/Readme.md b/Chat/Readme.md new file mode 100644 index 00000000..25dea3f9 --- /dev/null +++ b/Chat/Readme.md @@ -0,0 +1,3 @@ +## Chat + +OpenAI based Chat module diff --git a/Chat/index.html b/Chat/index.html new file mode 100644 index 00000000..f3ecc87e --- /dev/null +++ b/Chat/index.html @@ -0,0 +1,386 @@ + + + + + Chat + + + + + + + +
+ + +
+ +
+ + + +

Chat

+ + +
+
+
+ + + +
+
+ + +
+ + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + + + + + + + From 4773e631a61c5d590bdfc9a59377c85236f22b68 Mon Sep 17 00:00:00 2001 From: Randy Mackay Date: Mon, 10 Feb 2025 20:32:00 +0900 Subject: [PATCH 02/24] Chat: add MAVLink support --- Chat/ChatMAVLink.js | 91 + Chat/MAVLink/local_modules/README.md | 6 + Chat/MAVLink/local_modules/jspack/.npmignore | 19 + Chat/MAVLink/local_modules/jspack/LICENSE | 26 + Chat/MAVLink/local_modules/jspack/README.md | 147 + Chat/MAVLink/local_modules/jspack/jspack.js | 891 + .../MAVLink/local_modules/jspack/package.json | 64 + .../local_modules/jspack/test/int64.js | 456 + Chat/MAVLink/local_modules/long/LICENSE | 202 + Chat/MAVLink/local_modules/long/README.md | 246 + Chat/MAVLink/local_modules/long/index.js | 1 + Chat/MAVLink/local_modules/long/package.json | 64 + Chat/MAVLink/local_modules/long/src/long.js | 1325 ++ Chat/MAVLink/mavlink.js | 18848 ++++++++++++++++ Chat/index.html | 154 +- Chat/mavlink_msgs.js | 478 + 16 files changed, 23015 insertions(+), 3 deletions(-) create mode 100644 Chat/ChatMAVLink.js create mode 100644 Chat/MAVLink/local_modules/README.md create mode 100644 Chat/MAVLink/local_modules/jspack/.npmignore create mode 100644 Chat/MAVLink/local_modules/jspack/LICENSE create mode 100644 Chat/MAVLink/local_modules/jspack/README.md create mode 100644 Chat/MAVLink/local_modules/jspack/jspack.js create mode 100644 Chat/MAVLink/local_modules/jspack/package.json create mode 100644 Chat/MAVLink/local_modules/jspack/test/int64.js create mode 100644 Chat/MAVLink/local_modules/long/LICENSE create mode 100644 Chat/MAVLink/local_modules/long/README.md create mode 100644 Chat/MAVLink/local_modules/long/index.js create mode 100644 Chat/MAVLink/local_modules/long/package.json create mode 100644 Chat/MAVLink/local_modules/long/src/long.js create mode 100644 Chat/MAVLink/mavlink.js create mode 100644 Chat/mavlink_msgs.js diff --git a/Chat/ChatMAVLink.js b/Chat/ChatMAVLink.js new file mode 100644 index 00000000..ffe569fc --- /dev/null +++ b/Chat/ChatMAVLink.js @@ -0,0 +1,91 @@ +// setup connection to vehicle given a url and connection/disconnection callback function +// this handles WebSocket and incoming MAVLink +function setup_connect(url_input, connection_callback_fn) { + + // websocket object + let ws = null + let expecting_close = false + let been_connected = false + + // Connect to WebSocket server + function connect(target, auto_connect) { + // Make sure we are not connected to something else + disconnect() + + // temporarily show connected + connection_callback_fn(true) + + // True if we have ever been connected + been_connected = false + + ws = new WebSocket(target) + ws.binaryType = "arraybuffer" + + expecting_close = false + + ws.onopen = () => { + // have been connected + been_connected = true + connection_callback_fn(true) + } + + ws.onclose = () => { + // set disconnectded + connection_callback_fn(false) + } + + ws.onerror = (e) => { + console.log(e) + ws.close() + } + + ws.onmessage = (msg) => { + // Feed data to MAVLink parser and forward messages + for (const char of new Uint8Array(msg.data)) { + const m = MAVLink.parseChar(char) + if ((m != null) && (m._id != -1)) { + // Got message with known ID + // Sent to each Widget + for (const widget of grid.getGridItems()) { + widget.MAVLink_msg_handler(m) + } + for (const widget of test_grid.getGridItems()) { + widget.MAVLink_msg_handler(m) + } + } + } + } + + } + + // Disconnect from WebSocket server + function disconnect() { + // close socket + if (ws != null) { + expecting_close = true + ws.close() + } + + // enable connect + connection_callback_fn(false) + } + + connect_button.onclick = () => { + const in_progress = (ws != null) && ((ws.readyState == WebSocket.CONNECTING) || (ws.readyState == WebSocket.CLOSING)) + if (in_progress) { + // Don't do anything if the socket is connecting or closing a connection + return + } + + if (!url_input.checkValidity()) { + // invalid address, re-fire the tip and focus the url + // url_input.focus() + return + } + + connect(url_input) + } + + // Try auto connecting to MissionPlanner + connect("ws://127.0.0.1:56781", true) +} diff --git a/Chat/MAVLink/local_modules/README.md b/Chat/MAVLink/local_modules/README.md new file mode 100644 index 00000000..ad5a1382 --- /dev/null +++ b/Chat/MAVLink/local_modules/README.md @@ -0,0 +1,6 @@ +This folder is a locally modified copy of some Node/npm packages 'jspack' and 'long'. we have copied them here and tweaked them to be compatible with our needs, please see their respective README.md file for their original info, which we have not changed. + +This README.md serves to make you aware that these two packages as stored here in the 'jspack' and 'long' folders ARE MODIFIED from the originals. +By placing this statement here, and putting a notice in long.js as well, we feel are in compliance with the LICENSE file of 'long' , which requires us to tell you they are modified. + +We have included their original license files, in compliance with them, as both license/s permit distribution of derived works in source and/or binary form. diff --git a/Chat/MAVLink/local_modules/jspack/.npmignore b/Chat/MAVLink/local_modules/jspack/.npmignore new file mode 100644 index 00000000..be1ea81a --- /dev/null +++ b/Chat/MAVLink/local_modules/jspack/.npmignore @@ -0,0 +1,19 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.pyc + +pids +logs +results + +npm-debug.log +node_modules + +**~ +**.swp diff --git a/Chat/MAVLink/local_modules/jspack/LICENSE b/Chat/MAVLink/local_modules/jspack/LICENSE new file mode 100644 index 00000000..d646dd72 --- /dev/null +++ b/Chat/MAVLink/local_modules/jspack/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2008, Fair Oaks Labs, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Chat/MAVLink/local_modules/jspack/README.md b/Chat/MAVLink/local_modules/jspack/README.md new file mode 100644 index 00000000..fee0cdc3 --- /dev/null +++ b/Chat/MAVLink/local_modules/jspack/README.md @@ -0,0 +1,147 @@ +jspack - library to pack primitives to octet arrays +==================================================== + +[![Build status](https://travis-ci.org/birchroad/node-jspack.svg?branch=master)](https://travis-ci.org/birchroad/node-jspack) + +## Disclaimer +The jspack module and documentation are essentially ports of the +Python struct module and documentation, with such changes as were necessary. The port was originally made by Fair Oaks Labs, Inc. and published at http://code.google.com/p/jspack/ +If any Python people are miffed that their documentation got ripped off, let me know, +and I'll gladly revise them. + +This module performs conversions between JavaScript values and C structs +represented as octet arrays (i.e. JavaScript arrays of integral numbers +between 0 and 255, inclusive). It uses format strings (explained below) as +compact descriptions of the layout of the C structs and the intended conversion +to/from JavaScript values. This can be used to handle binary data stored in +files, or received from network connections or other sources. + +## Install + npm install jspack + +## Reference + +The module defines the following functions: + +### Unpack(fmt, a, p) +Return an array containing values unpacked from the octet array a, +beginning at position p, according to the supplied format string. If there +are more octets in a than required by the format string, the excess is +ignored. If there are fewer octets than required, Unpack() will return +undefined. If no value is supplied for the p argument, zero is assumed. + +### PackTo(fmt, a, p, values) +Pack and store the values array into the supplied octet array a, beginning +at position p. If there are more values supplied than are specified in the +format string, the excess is ignored. If there are fewer values supplied, +PackTo() will return false. If there is insufficient space in a to store +the packed values, PackTo() will return false. On success, PackTo() returns +the a argument. If any value is of an inappropriate type, the results are +undefined. + +### Pack(fmt, values) +Return an octet array containing the packed values array. If there are +more values supplied than are specified in the format string, the excess is +ignored. If there are fewer values supplied, Pack() will return false. If +any value is of an inappropriate type, the results are undefined. + +### CalcLength(fmt) +Return the number of octets required to store the given format string. + + +## Formats +Format characters have the following meanings; the conversion between C and +JavaScript values should be obvious given their types: + + Format | C Type | JavaScript Type | Size (octets) | Notes + ------------------------------------------------------------------- + A | char[] | Array | Length | (1) + x | pad byte | N/A | 1 | + c | char | string (length 1) | 1 | (2) + b | signed char | number | 1 | (3) + B | unsigned char | number | 1 | (3) + h | signed short | number | 2 | (3) + H | unsigned short | number | 2 | (3) + i | signed int | number | 4 | (3) + I | unsigned int | number | 4 | (3) + l | signed long | number | 4 | (3) + L | unsigned long | number | 4 | (3) + q | signed long | number | 8 | (6) + Q | unsigned long | number | 8 | (6) + s | char[] | string | Length | (2) + f | float | number | 4 | (4) + d | double | number | 8 | (5) + +*Notes:* + + **(1)** The "A" code simply returns a slice of the source octet array. This is + primarily useful when a data structure contains bytes which are subject to + multiple interpretations (e.g. unions), and the data structure is being + decoded in multiple passes. + + **(2)** The "c" and "s" codes handle strings with codepoints between 0 and 255, + inclusive. The data are not bounds-checked, so strings containing characters + with codepoints outside this range will encode to "octet" arrays that contain + values outside the range of an octet. Furthermore, since these codes decode + octet arrays by assuming the octets represent UNICODE codepoints, they may + not "correctly" decode bytes in the range 128-255, since that range is subject + to multiple interpretations. Caveat coder! + + **(3)** The 8 "integer" codes clip their encoded values to the minima and maxmima + of their respective types: If you invoke Struct.Pack('b', [-129]), for + instance, the result will be [128], which is the octet encoding of -128, + which is the minima of a signed char. Similarly, Struct.Pack('h', [-32769]) + returns [128, 0]. Fractions are truncated. + + **(4)** Since JavaScript doesn't natively support 32-bit floats, whenever a float + is stored, the source JavaScript number must be rounded. This module applies + correct rounding during this process. Numbers with magnitude greater than or + equal to 2^128-2^103 round to either positive or negative Infinity. The + rounding algorithm assumes that JavaScript is using exactly 64 bits of + floating point precision; 128-bit floating point will result in subtle errors. + + **(5)** This module assumes that JavaScript is using 64 bits of floating point + precision, so the "d" code performs no rounding. 128-bit floating point will + cause the "d" code to simply truncate significands to 52 bits. + + **(6)** Since 64bit longs cannot be represented by numbers JavaScript, this version of + jspack will process longs as arrays in the form: ```[lowBits, hightBits]```. The + decoded long array contains a third element, the unsigned flag, which is ```false``` for signed + and ```true``` for unsigned values. + This representation is similar to what [Long.js](https://github.com/dcodeIO/Long.js), and + therefore the [Google Closure Libaray](https://github.com/google/closure-library), uses. + See [test/int64.js](test/int64.js) for examples how to work with Long.js. + +A format character may be preceded by an integral repeat count. For example, +the format string "4h" means exactly the same thing as "hhhh". + +Whitespace characters between formats are ignored; a count and its format must +not be separated by whitespace, however. + +For the "A" format character, the count is interpreted as the size of the +array, not a repeat count as for the other format characters; for example, "10A" +means a single 10-octet array. When packing, the Array is truncated or padded +with 0 bytes as appropriate to make it conform to the specified length. When +unpacking, the resulting Array always has exactly the specified number of bytes. +As a special case, "0A" means a single, empty Array. + +For the "s" format character, the count is interpreted as the size of the +string, not a repeat count as for the other format characters; for example, +"10s" means a single 10-byte string, while "10c" means 10 characters. When +packing, the string is truncated or padded with 0 bytes as appropriate to make +it conform to the specified length. When unpacking, the resulting string always +has exactly the specified number of bytes. As a special case, "0s" means a +single, empty string (while "0c" means 0 characters). + + +By default, C numbers are represented in network (or big-endian) byte order. +Alternatively, the first character of the format string can be used to indicate +byte order of the packed data, according to the following table: + + Character | Byte Order + ---------------------------------- + < | little-endian + > | big-endian + ! | network (= big-endian) + +If the first character is not one of these, "!" is assumed. diff --git a/Chat/MAVLink/local_modules/jspack/jspack.js b/Chat/MAVLink/local_modules/jspack/jspack.js new file mode 100644 index 00000000..20095fed --- /dev/null +++ b/Chat/MAVLink/local_modules/jspack/jspack.js @@ -0,0 +1,891 @@ +/** + * @license + + Copyright © 2008 Fair Oaks Labs, Inc. + All rights reserved. + + This file is Modified from the original, by buzz 2020: + - ran thru http://www.jsnice.org/ and manually renamed the variables to be clearer + - added optionally enabled debugging/verbose/printfs throughout + - bugfixes and integration so it now passes our mavlink.js testsuite/s + - please see README.md in the upper level folder. +*/ +'use strict'; + +//var Long = require('long'); + +let DEBUG = false; + +/** + * @return {undefined} + */ +function JSPack() { + var el; + /** @type {boolean} */ + var booleanIsBigEndian = false; + var m = this; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @param {number} len + * @return {?} + */ + //Raw byte arrays + // m._DeArray = function(octet_array_a, offset_p, len) { + // if (DEBUG) console.log("zzz1"); + // return [octet_array_a.slice(offset_p, offset_p + len)]; + //}; + + /** + * @param {!Array} to_octet_array_a + * @param {number} offset_p + * @param {number} len + * @param {!NodeList} from_array_v + * @return {undefined} + */ + // m._EnArray = function(to_octet_array_a, offset_p, len, from_array_v) { + // if (DEBUG) console.log("zzz2"); + /** @type {number} */ + // var i = 0; + // for (; i < len; to_octet_array_a[offset_p + i] = from_array_v[i] ? from_array_v[i] : 0, i++) { + // } + //}; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // ASCII characters + m._DeChar = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz3"); + return String.fromCharCode(octet_array_a[offset_p]); + }; + /** + * @param {!Array} to_octet_array_a + * @param {number} offset_p + * @param {string} from_str_array_v + * @return {undefined} + */ + // m._EnChar = function(to_octet_array_a, offset_p, from_str_array_v) { + // if (DEBUG) console.log("zzz4"); + // /** @type {number} */ + // to_octet_array_a[offset_p] = from_str_array_v.charCodeAt(0); + // }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + //Little-endian (un)signed N-byte integers + m._DeInt = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz5"); + /** @type {number} */ + var lsb = booleanIsBigEndian ? el.len - 1 : 0; + /** @type {number} */ + var nsb = booleanIsBigEndian ? -1 : 1; + /** @type {number} */ + var stop = lsb + nsb * el.len; + var rv; + var i; + var f; + /** @type {number} */ + rv = 0; + /** @type {number} */ + i = lsb; + /** @type {number} */ + f = 1; + for (; i != stop; rv = rv + octet_array_a[offset_p + i] * f, i = i + nsb, f = f * 256) { + } + if (el.bSigned && rv & Math.pow(2, el.len * 8 - 1)) { + /** @type {number} */ + rv = rv - Math.pow(2, el.len * 8); + } + return rv; + }; + + + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {number} val + * @return {undefined} + */ + m._EnInt = function(octet_array_a, offset_p, val) { + if (DEBUG) console.log("chunk-from: "+val); + /** @type {number} */ + var lsb = booleanIsBigEndian ? el.len - 1 : 0; + /** @type {number} */ + var nsb = booleanIsBigEndian ? -1 : 1; + /** @type {number} */ + var stop = lsb + nsb * el.len; + var i; + // range limit: + if (val < el.min ) { + val = el.min; + console.log("value limited to MIN:"+val); + } + if (val > el.max ) { + val = el.max; + console.log("value limited to MAX:"+val); + } + /** @type {number} */ + i = lsb; + if (DEBUG) console.log("booleanIsBigEndian:"+booleanIsBigEndian); + if (DEBUG) console.log("el.len:"+el.len); + if (DEBUG) console.log("lsb:"+lsb); + if (DEBUG) console.log("nsb:"+nsb); + if (DEBUG) console.log("i:"+i); + if (DEBUG) console.log("stop:"+stop); + for (; i != stop; ) { + + var to = JSON.stringify(val&255); + if (DEBUG) console.log("chunk as bytes: "+to); + + octet_array_a[offset_p + i] = val & 255; + i = i + nsb; + val = val >> 8; + + + } + }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @param {number} len + * @return {?} + */ + // ASCII character strings + m._DeString = function(octet_array_a, offset_p, len) { + if (DEBUG) console.log("zzz7"); + /** @type {!Array} */ + var retval = new Array(len); + /** @type {number} */ + var i = 0; + for (; i < len; retval[i] = String.fromCharCode(octet_array_a[offset_p + i]), i++) { + } + return retval.join(""); + }; + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {number} len + * @param {string} strval + * @return {undefined} + */ + m._EnString = function(octet_array_a, offset_p, len, strval) { + if (DEBUG) console.log("zzz8"); + var t; + /** @type {number} */ + if ( DEBUG ) console.log("strencode before: "+octet_array_a+"\np:"+offset_p+" len:"+len+" strval:"+strval) + var i = 0; + //if (DEBUG) console.log("strval:"+strval); +//console.trace("Here I am!") + + // we all strings to be passed in as a string of characters, or a an array or buffer of them is ok too + + if (typeof strval.charCodeAt === "function") { + for (; i < len; octet_array_a[offset_p + i] = (t = strval.charCodeAt(i)) ? t : 0, i++) { + if ( t > 255 ) console.log("ERROR ERROR ERROR ERROR ERROR ERROR - It seems u passed unicode/utf-8/etc to jspack, not 8 bit ascii. please use .toString('binary'); not .toString();"); + } + if ( DEBUG ) console.log("strencode from CHAR-string."); + + } else if (Array.isArray(strval)) { + for (; i < len; octet_array_a[offset_p + i] = (t = strval[i]) ? t : 0, i++) { + // referring directly to 't' inside this loop is bad, seems delayed by an iteration, but strval[i] is ok. + if ( strval[i] > 255 ) console.log("ERROR ERROR ERROR ERROR ERROR ERROR - It seems u passed unicode/utf-8/etc, or array data with values > 255, to jspack, not 8 bit ascii.\n(bad Array data)"+strval[i]); + } + if ( DEBUG ) console.log("strencode from ARRAY."); + + } else if (Buffer.isBuffer(strval)) { + for (; i < len; octet_array_a[offset_p + i] = (t = strval[i]) ? t : 0, i++) { + if ( strval[i] > 255 ) console.log("ERROR ERROR ERROR ERROR ERROR ERROR - It seems u passed unicode/utf-8/etc to jspack, not 8 bit ascii. \n(bad Buffer data)"+strval[i]); + } + if ( DEBUG ) console.log("strencode from Buffer."); + + } else { + console.log("ERROR encoding string _EnString: array:"+octet_array_a+" p:"+offset_p+" len:"+len+" strval:"+JSON.stringify(strval)) +} + }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Little-endian N-bit IEEE 754 floating point + m._De754 = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz9"); + var bool_s; + var exponent; + var mantissa; + var i; + var d; + var nBits; + var mantissaLen; + var exponentLen; + var eBias; + var eMax; + mantissaLen = el.mLen; + /** @type {number} */ + exponentLen = el.len * 8 - el.mLen - 1; + /** @type {number} */ + eMax = (1 << exponentLen) - 1; + /** @type {number} */ + eBias = eMax >> 1; + /** @type {number} */ + i = booleanIsBigEndian ? 0 : el.len - 1; + /** @type {number} */ + d = booleanIsBigEndian ? 1 : -1; + bool_s = octet_array_a[offset_p + i]; + /** @type {number} */ + i = i + d; + /** @type {number} */ + nBits = -7; + /** @type {number} */ + exponent = bool_s & (1 << -nBits) - 1; + /** @type {number} */ + bool_s = bool_s >> -nBits; + /** @type {number} */ + nBits = nBits + exponentLen; + for (; nBits > 0; exponent = exponent * 256 + octet_array_a[offset_p + i], i = i + d, nBits = nBits - 8) { + } + /** @type {number} */ + mantissa = exponent & (1 << -nBits) - 1; + /** @type {number} */ + exponent = exponent >> -nBits; + nBits = nBits + mantissaLen; + for (; nBits > 0; mantissa = mantissa * 256 + octet_array_a[offset_p + i], i = i + d, nBits = nBits - 8) { + } + switch(exponent) { + case 0: + /** @type {number} */ + // Zero, or denormalized number + exponent = 1 - eBias; + break; + case eMax: + // NaN, or +/-Infinity + return mantissa ? NaN : (bool_s ? -1 : 1) * Infinity; + default: + // Normalized number + mantissa = mantissa + Math.pow(2, mantissaLen); + /** @type {number} */ + exponent = exponent - eBias; + break; + } + return (bool_s ? -1 : 1) * mantissa * Math.pow(2, exponent - mantissaLen); + }; + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {number} v + * @return {undefined} + */ + m._En754 = function(octet_array_a, offset_p, v) { + if (DEBUG) console.log("zzz_10"); + var bool_s; + var exponent; + var mantissa; + var i; + var d; + var c; + var mantissaLen; + var exponentLen; + var eBias; + var eMax; + mantissaLen = el.mLen; + /** @type {number} */ + exponentLen = el.len * 8 - el.mLen - 1; + /** @type {number} */ + eMax = (1 << exponentLen) - 1; + /** @type {number} */ + eBias = eMax >> 1; + /** @type {number} */ + bool_s = v < 0 ? 1 : 0; + /** @type {number} */ + v = Math.abs(v); + if (isNaN(v) || v == Infinity) { + /** @type {number} */ + mantissa = isNaN(v) ? 1 : 0; + /** @type {number} */ + exponent = eMax; + } else { + /** @type {number} */ + exponent = Math.floor(Math.log(v) / Math.LN2);// Calculate log2 of the value + if (v * (c = Math.pow(2, -exponent)) < 1) { // Math.log() isn't 100% reliable + exponent--; + /** @type {number} */ + c = c * 2; + } + // Round by adding 1/2 the significand's LSD + if (exponent + eBias >= 1) { + /** @type {number} */ + v = v + el.rt / c; // Normalized: mLen significand digits + } else { + /** @type {number} */ + v = v + el.rt * Math.pow(2, 1 - eBias);// Denormalized: <= mLen significand digits + } + if (v * c >= 2) { + exponent++; + /** @type {number} */ + c = c / 2; // Rounding can increment the exponent + } + if (exponent + eBias >= eMax) { + // Overflow + /** @type {number} */ + mantissa = 0; + /** @type {number} */ + exponent = eMax; + } else { + if (exponent + eBias >= 1) { + // Normalized - term order matters, as Math.pow(2, 52-e) and v*Math.pow(2, 52) can overflow + /** @type {number} */ + mantissa = (v * c - 1) * Math.pow(2, mantissaLen); + /** @type {number} */ + exponent = exponent + eBias; + } else { + // Denormalized - also catches the '0' case, somewhat by chance + /** @type {number} */ + mantissa = v * Math.pow(2, eBias - 1) * Math.pow(2, mantissaLen); + /** @type {number} */ + exponent = 0; + } + } + } + /** @type {number} */ + i = booleanIsBigEndian ? el.len - 1 : 0; + /** @type {number} */ + d = booleanIsBigEndian ? -1 : 1; + for (; mantissaLen >= 8; octet_array_a[offset_p + i] = mantissa & 255, i = i + d, mantissa = mantissa / 256, mantissaLen = mantissaLen - 8) { + } + /** @type {number} */ + exponent = exponent << mantissaLen | mantissa; + exponentLen = exponentLen + mantissaLen; + for (; exponentLen > 0; octet_array_a[offset_p + i] = exponent & 255, i = i + d, exponent = exponent / 256, exponentLen = exponentLen - 8) { + } + octet_array_a[offset_p + i - d] |= bool_s * 128; + }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Convert int64 to array with 3 elements: [lowBits, highBits, unsignedFlag] + // '>>>' trick to convert signed 32bit int to unsigned int (because << always results in a signed 32bit int) + m._DeInt64 = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz_11"); + /** @type {number} */ + var lsb = booleanIsBigEndian ? 0 : 7; + /** @type {number} */ + var nsb = booleanIsBigEndian ? 1 : -1; + /** @type {number} */ + var stop = lsb + nsb * 8; + /** @type {!Array} */ + var nextIdLookup = [0, 0, !el.bSigned]; + var i; + var f; + var indexLookupKey; + /** @type {number} */ + i = lsb; + /** @type {number} */ + indexLookupKey = 1; + /** @type {number} */ + f = 0; + for (; i != stop; nextIdLookup[indexLookupKey] = (nextIdLookup[indexLookupKey] << 8 >>> 0) + octet_array_a[offset_p + i], i = i + nsb, f++, indexLookupKey = f < 4 ? 1 : 0) { + + if ( DEBUG ) console.log("jsPacking int64:"+octet_array_a[offset_p + i]); + + } + return nextIdLookup; + }; + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {!Object} v + * @return {undefined} + */ + m._EnInt64 = function(octet_array_a, offset_p, v) { + + if (v.length != 2) { //todo put this error back + console.log("ERROR ERROR: jspack needs an array of at least length TWO to pack an int64 "+v+' len:'+v.length); + } +// if (DEBUG) console.log("zzz_12 v:"+v); + /** @type {number} */ + var lsb = booleanIsBigEndian ? 0 : 7; + /** @type {number} */ + var nsb = booleanIsBigEndian ? 1 : -1; + /** @type {number} */ + var stop = lsb + nsb * 8; + var i; + var f; + var j; + var shift; + /** @type {number} */ + i = lsb; + /** @type {number} */ + j = 1; + /** @type {number} */ + f = 0; + /** @type {number} */ + shift = 24; + + for (; i != stop; octet_array_a[offset_p + i] = v[j] >> shift & 255, i = i + nsb, f++, j = f < 4 ? 1 : 0, shift = 24 - 8 * (f % 4)) { + var x = v[j] >> shift & 255 ; + var vj = v[j]; + + if ( DEBUG ) console.log('js qqqq vj:'+vj+' j:'+j+' x:'+x+' a:'+octet_array_a+' i:'+i+" offset_p:"+offset_p+" v:"+v); + } + }; + + + + // Class data + /** @type {string} */ + m._sPattern = "(\\d+)?([AxcbBhHsfdiIlLqQ])"; + + m._lenLut = {'A':1, 'x':1, 'c':1, 'b':1, 'B':1, 'h':2, 'H':2, 's':1, 'f':4, 'd':8, 'i':4, 'I':4, 'l':4, 'L':4, 'q':8, 'Q':8}; + + m._elLookUpTable = { 'A': {en:m._EnArray, de:m._DeArray}, + 's': {en:m._EnString, de:m._DeString}, + 'c': {en:m._EnChar, de:m._DeChar}, + 'b': {en:m._EnInt, de:m._DeInt, len:1, bSigned:true, min:-Math.pow(2, 7), max:Math.pow(2, 7)-1}, + 'B': {en:m._EnInt, de:m._DeInt, len:1, bSigned:false, min:0, max:Math.pow(2, 8)-1}, + 'h': {en:m._EnInt, de:m._DeInt, len:2, bSigned:true, min:-Math.pow(2, 15), max:Math.pow(2, 15)-1}, + 'H': {en:m._EnInt, de:m._DeInt, len:2, bSigned:false, min:0, max:Math.pow(2, 16)-1}, + 'i': {en:m._EnInt, de:m._DeInt, len:4, bSigned:true, min:-Math.pow(2, 31), max:Math.pow(2, 31)-1}, + 'I': {en:m._EnInt, de:m._DeInt, len:4, bSigned:false, min:0, max:Math.pow(2, 32)-1}, + 'l': {en:m._EnInt, de:m._DeInt, len:4, bSigned:true, min:-Math.pow(2, 31), max:Math.pow(2, 31)-1}, + 'L': {en:m._EnInt, de:m._DeInt, len:4, bSigned:false, min:0, max:Math.pow(2, 32)-1}, + 'f': {en:m._En754, de:m._De754, len:4, mLen:23, rt:Math.pow(2, -24)-Math.pow(2, -77)}, + 'd': {en:m._En754, de:m._De754, len:8, mLen:52, rt:0}, + 'q': {en:m._EnInt64, de:m._DeInt64, bSigned:true, len:8 }, // 64bit fields need 8 bytes.. + 'Q': {en:m._EnInt64, de:m._DeInt64, bSigned:false, len:8 }}; // quirk of longs is they come in with a length of 2 in an array + + + /** + * @param {number} num_elements_n + * @param {number} size_s + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Unpack a series of n elements of size s from array a at offset p with fxn + m._UnpackSeries = function(num_elements_n, size_s, octet_array_a, offset_p) { + if (DEBUG) console.log("zzz_13"); + var fxn = el.de; + /** @type {!Array} */ + var rv = []; + /** @type {number} */ + var o = 0; + for (; o < num_elements_n; rv.push(fxn(octet_array_a, offset_p + o * size_s)), o++) { + } + return rv; + }; + /** + * @param {number} num_elements_n + * @param {number} size_s + * @param {!Array} to_octet_array_a + * @param {number} array_a_offset_p + * @param {(Array|NodeList|null)} from_array_v + * @param {number} array_v_offset_i + * @return {undefined} + */ + // Pack a series of n elements of size s from array v at offset i to array a at offset p with fxn + + m._PackSeries = function(num_elements_n, size_s, to_octet_array_a, array_a_offset_p, from_array_v, array_v_offset_i) { + if (DEBUG) console.log("pack-series: "); + + + if ( DEBUG ) console.log('js before 0:'+0+' num_elements_n:'+num_elements_n+' size_s:'+size_s+' to_a:'+to_octet_array_a+' i:'+array_v_offset_i+" offset_p:"+array_a_offset_p+" v:"+from_array_v); + var fxn = el.en; + /** @type {number} */ + var o = 0; + for (; o < num_elements_n; o++) { + //if (DEBUG) console.log("14 called fxn with o:"+o); + var z = from_array_v[array_v_offset_i + o]; + var to = JSON.stringify(z); + var too = JSON.stringify(from_array_v); + if (DEBUG) console.log('js pre-ffff z:'+z+' to:'+to+' too:'+too+''); + // handle flattened arrays - non-array things don't have a .length + try { + if (z.length == undefined ) { + //from_array_v = [ from_array_v ] ; + if (DEBUG) console.log('Z FIX'); + }} catch (e){} + var z = from_array_v[array_v_offset_i + o]; + var to = JSON.stringify(z); + var too = JSON.stringify(from_array_v); + + // if we only have one thing to back and its got an 8 byte target len ( it's a 64bit long), and length of source array is 2 ( low and high bits ) + // we treat it as a singular thing... we use this for Q type, which gets passed in as [lowBits, hightBits] + if (( num_elements_n == 1 ) && (size_s == 8) && (from_array_v.length == 2) ) { + z = from_array_v; + if (DEBUG) console.log("js handling Q 64bit array"); + } + + + if (DEBUG) console.log('js partial z:'+z+' to:'+to+' too:'+too+' num_elements_n:'+num_elements_n+' size_s:'+size_s+' to_a:'+to_octet_array_a+' v_offset_i:'+array_v_offset_i+" a_offset_p:"+array_a_offset_p+" from_v:"+from_array_v); + + fxn(to_octet_array_a, array_a_offset_p + o * size_s, z); + + } + if (DEBUG) console.log('js after to_a:'+to_octet_array_a); + }; + + + /** + * @param {string} fmt + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Unpack the octet array a, beginning at offset p, according to the fmt string + m.Unpack = function(fmt, octet_array_a, offset_p) { + if (DEBUG) console.log("zzz_15"); + /** @type {boolean} */ + // Set the private bBE flag based on the format string - assume big-endianness + booleanIsBigEndian = fmt.charAt(0) != "<"; + /** @type {number} */ + offset_p = offset_p ? offset_p : 0; + /** @type {!RegExp} */ + var re = new RegExp(this._sPattern, "g"); + var re_match; + var repeat_count_n; + var element_size_s; + /** @type {!Array} */ + var rv = []; + + //loop over chars in the format string with regex due to optional digits + for (; re_match = re.exec(fmt);) { + /** @type {number} */ + repeat_count_n = re_match[1] == undefined || re_match[1] == "" ? 1 : parseInt(re_match[1]); + element_size_s = this._lenLut[re_match[2]]; + if (offset_p + repeat_count_n * element_size_s > octet_array_a.length) { + return undefined; + } + switch(re_match[2]) { + case "A": + case "s": + rv.push(this._elLookUpTable[re_match[2]].de(octet_array_a, offset_p, repeat_count_n)); + break; + case "c": + case "b": + case "B": + case "h": + case "H": + case "i": + case "I": + case "l": + case "L": + case "f": + case "d": + case "q": + case "Q": + el = this._elLookUpTable[re_match[2]]; + + //rv.push(this._UnpackSeries(repeat_count_n, element_size_s, octet_array_a, offset_p)); + + // unpack arrays to an actual array type within the field array result: + // https://github.com/AndreasAntener/node-jspack/commit/4f16680101303a6b4a1b0deba8cf7d20fc68213e + if (repeat_count_n > 1) { + // Field is array, unpack into separate array and push as such + var arr = []; + arr.push(this._UnpackSeries(repeat_count_n, element_size_s, octet_array_a, offset_p)); + rv.push(arr); + } else { + rv.push(this._UnpackSeries(repeat_count_n, element_size_s, octet_array_a, offset_p)); + } + + break; + } + /** @type {number} */ + offset_p = offset_p + repeat_count_n * element_size_s; + } + return Array.prototype.concat.apply([], rv); + }; + + + // cross check the list of input data matches the size of bytes we'll be assembling + // this is a slightly tweaked implementation of the previous 'PackTo' commented out below. + // it has a more-consistent approach to input and output arrays, paying particular attention to Q,q, long, etc + m.WouldPack = function(fmt, octet_array_a, offset_p, values) { + //if (DEBUG) console.log("zzz_16 fmt:"+JSON.stringify(fmt)+" values:"+JSON.stringify(values)); + // @type {boolean} / + // Set the private bBE flag based on the format string - assume big-endianness + booleanIsBigEndian = fmt.charAt(0) != "<"; + // @type {!RegExp} / + var re = new RegExp(this._sPattern, "g"); + + var m; + var n; + var s; + var values_i = 0; // current index into the values[] + + var j; + for (; m = re.exec(fmt);) { + + // leading optional prefix num or 1 + n = m[1] == undefined || m[1] == "" ? 1 : parseInt(m[1]); + + s = this._lenLut[m[2]]; + + + if (DEBUG) console.log("character: "+m[2]+" how many(n)?: "+n); + el = this._elLookUpTable[m[2]]; + + //if (DEBUG) console.log("using lookup table:"+JSON.stringify(el)); + var bytes_consumed_per_element = el["len"]; + bytes_consumed_per_element = bytes_consumed_per_element == undefined ? 1 : bytes_consumed_per_element ; // undefined means 1 + if (DEBUG) console.log("bytes_consumed_per_element:"+JSON.stringify(bytes_consumed_per_element)); + if (DEBUG) console.log("current_values_idx:"+JSON.stringify(values_i) +" values:"+JSON.stringify(values[values_i]) ) ; + + + // do per-case behaviours 'A' , 's' and 'x' are special, everything else gets the same + switch(m[2]) { + //------------------------------------------ + case "A": + case "s": + if (values_i + 1 > values.length) { + console.log("JSPACK-ERROR: values_i + 1 > values.length values_i:"+values_i+" values.length:"+values.length); + //return false; + } + if (DEBUG) console.log("all values:"+JSON.stringify(values)); + this._elLookUpTable[m[2]].en(octet_array_a, offset_p, n, values[values_i]); + // @type {number} / + values_i = values_i + 1; + break; + //------------------------------------------ + case "x": + // @type {number} / + j = 0; + for (; j < n; j++) { + // @type {number} / + octet_array_a[offset_p + j] = 0; + } + break; + //------------------------------------------ + // everything else + default: + + // if n > 1 , ie it's multiple occurrences of a 'thing' + if (n > 1 ) { + + // if we were handed an array at this idx, we need the array to be the same length as n + if (Array.isArray(values[values_i])) { + + // Value series is array, iterate through that, only increment by 1 + if ((values_i + 1) > values.length) { + if (DEBUG) console.log("JSPACK-ERROR: value series is array but (values_i + 1) > values.length. i:"+values_i+" values.length:"+values.length); + //return false; + } + if (DEBUG) console.log("(dst IS array) (source IS array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values[values_i], 0); + values_i += 1; + } + else { + if (DEBUG) console.log("ERROR: (dst IS array) (source is not array)"); + } + } + + // if n == 1, it just one of a thing + if (n == 1 ) { + + // type Q can have the source as an array when there is only 1 of them. + if (Array.isArray(values[values_i]) ) { + + if (( m[2] == 'Q' ) || ( m[2] == 'q' ) ) { + this._PackSeries(n, s, octet_array_a, offset_p, values[values_i], 0); + values_i += 1; + } + if (DEBUG) console.log("(dst is not array) (source IS array)"); + + } else { + if ((values_i + n ) > values.length) { + if (DEBUG) console.log("JSPACK-ERROR: value series NOT array but (values_i + n ) > values.length. i:"+values_i+" n:"+n+" values.length:"+values.length+" values:"+JSON.stringify(values)); + //return false; + } + if (DEBUG) console.log("(dst is not array) (source is not array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values, values_i); + values_i += n; + } + } + + if (DEBUG) console.log(""); + break; + //------------------------------------------ + } + + offset_p = offset_p + n * s; + + } + if (DEBUG) console.log("wouldpack completed, result array_a is:"+JSON.stringify(octet_array_a)); + return octet_array_a + } + + + + /** + * @param {string} fmt + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {!NodeList} values + * @return {?} + */ +/* + // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string + m.PackTo = function(fmt, octet_array_a, offset_p, values) { + if (DEBUG) console.log("zzz_16 fmt:"+JSON.stringify(fmt)+" values:"+JSON.stringify(values)); + // @type {boolean} / + // Set the private bBE flag based on the format string - assume big-endianness + booleanIsBigEndian = fmt.charAt(0) != "<"; + // @type {!RegExp} / + var re = new RegExp(this._sPattern, "g"); + var m; + var n; + var s; + // @type {number} / + var i = 0; + var j; + for (; m = re.exec(fmt);) { + // @type {number} / + n = m[1] == undefined || m[1] == "" ? 1 : parseInt(m[1]); + s = this._lenLut[m[2]]; + if (offset_p + n * s > octet_array_a.length) { + console.log("JSPACK-ERROR: offset_p + n * s > octet_array_a.length offset_p:"+offset_p+" n:"+n+" s:"+s+" octet_array_a.length:"+octet_array_a.length+" octet_array_a:"+JSON.stringify(octet_array_a)); + return false; + } + if (DEBUG) console.log("\n---------------------------------------------\n"); + if (DEBUG) console.log("handling format specifier:"+m[2]+" how many:"+n); + switch(m[2]) { + case "A": + case "s": + if (i + 1 > values.length) { + console.log("JSPACK-ERROR: i + 1 > values.length i:"+i+" values.length:"+values.length); + return false; + } + if (DEBUG) console.log("zzz_16A values:"+JSON.stringify(values)); + this._elLookUpTable[m[2]].en(octet_array_a, offset_p, n, values[i]); + // @type {number} / + i = i + 1; + break; + case "c": + case "b": + case "B": + case "h": + case "H": + case "i": + case "I": + case "l": + case "L": + case "f": + case "d": + case "q": + case "Q": + if (DEBUG) console.log("16 blerg"); + el = this._elLookUpTable[m[2]]; + if (DEBUG) console.log("using lookup table:"+JSON.stringify(el)); + //if (i + n > values.length) { return false; } + //this._PackSeries(n, s, octet_array_a, offset_p, values, i); + //i = i + n; + //added support for packing value series when they are supplied as arrays within the values array + // https://github.com/AndreasAntener/node-jspack/commit/8de80d20aa06dea15527b3073c6c8631abda0f17 + if (n > 1 && Array.isArray(values[i])) { + // Value series is array, iterate through that, only increment by 1 + if ((i + 1) > values.length) { + console.log("JSPACK-ERROR: value series is array but (i + 1) > values.length. i:"+i+" values.length:"+values.length); + return false; + } + if (DEBUG) console.log("zzz_16 option 1 (source is array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values[i], 0); + i += 1; + } else { + if ((i + n) > values.length) { + console.log("JSPACK-ERROR: value series NOT array but (i + n) > values.length. i:"+i+" n:"+n+" values.length:"+values.length+" values:"+JSON.stringify(values)); + //return false; + } + if (DEBUG) console.log("zzz_16 option 2 (source is not array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values, i); + i += n; + } + + if (DEBUG) console.log("end case"); + break; + case "x": + // @type {number} / + j = 0; + for (; j < n; j++) { + // @type {number} / + octet_array_a[offset_p + j] = 0; + } + break; + } + // @type {number} / + offset_p = offset_p + n * s; + } + console.log("pack completed, result array_a is:"+JSON.stringify(octet_array_a)); + return octet_array_a; + }; + */ + + /** + * @param {string} fmt + * @param {(Node|NodeList|null|string)} values + * @return {?} + */ + // Pack the supplied values into a new octet array, according to the fmt string + m.Pack = function(fmt, values) { + if (DEBUG) console.log("\n\n------------------------------------------------------------------------------------------------------------\n\n"); + if (DEBUG) console.log("initial unpacked values:"+JSON.stringify(values)); + if (DEBUG) console.log("initial format string:"+JSON.stringify(fmt)); + if (DEBUG) console.log("\n\nwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww\n\n"); + return this.WouldPack(fmt, new Array(this.CalcLength(fmt)), 0, values); + //if (DEBUG) console.log("\n\nmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm\n\n"); + // return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); + }; + + /** + * @param {string} fmt + * @param {(Node|NodeList|null|string)} values + * @return {?} + */ + // Pack the supplied values into a new octet array, according to the fmt string + m.oldPack = function(fmt, values) { + if (DEBUG) console.log("\n\n------------------------------------------------------------------------------------------------------------\n\n"); + if (DEBUG) console.log("initial unpacked values:"+JSON.stringify(values)); + if (DEBUG) console.log("initial format string:"+JSON.stringify(fmt)); + return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); + }; + + /** + * @param {string} fmt + * @return {?} + */ + // Determine the number of bytes represented by the format string + m.CalcLength = function(fmt) { + + /** @type {!RegExp} */ + var re = new RegExp(this._sPattern, "g"); + var m; + /** @type {number} */ + var value = 0; + for (; m = re.exec(fmt);) { + /** @type {number} */ + value = value + (m[1] == undefined || m[1] == "" ? 1 : parseInt(m[1])) * this._lenLut[m[2]]; + } + if (DEBUG) console.log("number of bytes in format string?: "+value+"\n"); + return value; + }; +} + +export default JSPack + diff --git a/Chat/MAVLink/local_modules/jspack/package.json b/Chat/MAVLink/local_modules/jspack/package.json new file mode 100644 index 00000000..f9c36896 --- /dev/null +++ b/Chat/MAVLink/local_modules/jspack/package.json @@ -0,0 +1,64 @@ +{ + "_from": "jspack@0.0.4", + "_id": "jspack@0.0.4", + "_inBundle": false, + "_integrity": "sha1-Mt01x/3LPjRWwY+7fvntC8YjgXc=", + "_location": "/jspack", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": false, + "raw": "jspack@0.0.4", + "name": "jspack", + "escapedName": "jspack", + "rawSpec": "0.0.4", + "saveSpec": null, + "fetchSpec": "0.0.4" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "local_modules/jspack", + "_shasum": "32dd35c7fdcb3e3456c18fbb7ef9ed0bc6238177", + "_spec": "jspack@0.0.4", + "_where": "/home/buzz/GCS/mavlink/pymavlink/generator/javascript", + "author": { + "name": "https://github.com/pgriess" + }, + "bugs": { + "url": "https://github.com/birchroad/node-jspack/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "JavaScript library to pack primitives to octet arrays, including int64 support, packaged for NodeJS.", + "devDependencies": { + "long": "", + "mocha": "", + "should": "", + "sinon": "" + }, + "homepage": "https://github.com/birchroad/node-jspack", + "main": "./jspack.js", + "maintainers": [ + { + "name": "Peter Magnusson", + "email": "peter@birchroad.net", + "url": "http://github.com/birchroad/node-jspack" + }, + { + "name": "Andreas Antener", + "url": "https://github.com/AndreasAntener/node-jspack" + } + ], + "name": "jspack", + "repository": { + "type": "git", + "url": "https://github.com/birchroad/node-jspack.git" + }, + "scripts": { + "pretest": "npm install", + "test": "mocha test" + }, + "version": "0.0.4" +} diff --git a/Chat/MAVLink/local_modules/jspack/test/int64.js b/Chat/MAVLink/local_modules/jspack/test/int64.js new file mode 100644 index 00000000..fc72eb49 --- /dev/null +++ b/Chat/MAVLink/local_modules/jspack/test/int64.js @@ -0,0 +1,456 @@ +// This file is MODIFIED from the original, by buzz 2020, please see README.md in the upper level folder for more details. +var should = require('should'); +var jspack = require('../jspack.js').jspack; +var Long = require('long'); + +describe('Test long integration (examples):', function() { + + // Demonstrating the use together with Long.js (https://github.com/dcodeIO/Long.js) + // + // Packing a long requires the input of a 2 part array containing the [low, high] bits + // of the specific long value. + // Unpacking a long results in a 3 part array containing [low, high, unsigned] bits and flag. + // The decoded value can be applied directly to Long.fromBits() + // + // Test number u 228290380562207 (BE: 0x00, 0x00, 0xcf, 0xa0, 0xff, 0x09, 0xff, 0x1f) + // (LE: 0x1f, 0xff, 0x09, 0xff, 0xa0, 0xcf, 0x00, 0x00) + // Test number s -228290380562207 (BE: 0xff, 0xff, 0x30, 0x5f, 0x00, 0xf6, 0x00, 0xe1) + // (LE: 0xe1, 0x00, 0xf6, 0x00, 0x5f, 0x30, 0xff, 0xff) + + it('pack Q', function() { + var buf = jspack.Pack('>Q', [[0xffe1ffff, 0xffa0]]); + buf.should.be.eql([0x00, 0x00, 0xff, 0xa0, 0xff, 0xe1, 0xff, 0xff]); + }); + + it('unpack Q', function() { + var buf = jspack.Unpack('>Q', [0x00, 0x00, 0xff, 0xa0, 0xff, 0xe1, 0xff, 0xff]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0xffe1ffff); + buf[0][1].should.be.eql(0xffa0); + buf[0][2].should.be.true; + }); + + // Test lower-case q as well. This only test the matching of the character and the unsigned bit, + // the parsing is the same as for upper-case Q (since we don't actually convert to a number). + it('pack >q (signed)', function() { + var buf = jspack.Pack('>q', [[0xffe1ffff, 0xffa0]]); + buf.should.be.eql([0x00, 0x00, 0xff, 0xa0, 0xff, 0xe1, 0xff, 0xff]); + }); + + it('unpack >> 0).toString(2); + y = ("00000000000000000000000000000000" + x).slice(-32) + y1 = y.substring(0,8); + y2 = y.substring(8,16); + y3 = y.substring(16,24); + y4 = y.substring(24,32); + return [y,y1,y2,y3,y4]; +} +function dec2bin_ws(dec) { + var str = dec2bin(dec); + var bb = str.slice(1); //1-4 skipping zero + var bbj = bb.join(' '); + return bbj; +} + +describe('ASCII Boundary tests:', function() { + + it('pack <4s correctly over the ascii 127->128->129 boundary', function() { // should work in range 0-255 if u use 'binary' encoding + + this.format = '<4s'; + + this.ascii_bytes = new Buffer.from([ 126, 127, 128, 129]).toString('binary'); // 'binary' encoding is important here, as without it values above 128 are treated as unicode. + var buf = jspack.Pack(this.format, [ this.ascii_bytes]); + body = [ 0x7e, 0x7f, 0x80, 0x81]; // expected result + buf.should.be.eql(body); + + }); + + it('long Q buzz', function() { // should work in range 0-255 if u use 'binary' encoding + +//from aoa_ssa + + this.format = '> 8) & 0xFF), this.msgId>>16]; + + this.msgId = 130; + + var v1 = ((this.msgId & 0xFF) << 8) | ((this.msgId >> 8) & 0xFF); + var v2 = this.msgId>>16; + + v1.should.be.eql(33280); + v2.should.be.eql(0); + + var orderedfields = [253,13,0,0,40,11,10,33280,0]; + + console.log("------------------------------------------------------------------------\nmavheader:"+JSON.stringify(orderedfields)); + var hdr = jspack.Pack('BBBBBBBHB',orderedfields); + + buf = [0xfd, 0x0d, 0x00, 0x00, 0x28, 0x0b, 0x0a, 0x82, 0x00, 0x00]; + + buf.should.be.eql(hdr); + }); + +}); + +describe('Q Boundary tests:', function() { + + it('unpack >Q full', function() { + var buf = jspack.Unpack('>Q', [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0xffffffff); + buf[0][1].should.be.eql(0xffffffff); + buf[0][2].should.be.true; + }); + + it('pack >Q full', function() { + var buf = jspack.Pack('>Q', [[0xffffffff, 0xffffffff]]); + buf.should.be.eql([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + }); + + it('unpack Q zero', function() { + var buf = jspack.Unpack('>Q', [0, 0, 0, 0, 0, 0, 0, 0]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0); + buf[0][1].should.be.eql(0); + buf[0][2].should.be.true; + }); + + it('pack >Q zero', function() { + var buf = jspack.Pack('>Q', [[0, 0]]); + buf.should.be.eql([0, 0, 0, 0, 0, 0, 0, 0]); + }); + + it('unpack Q one', function() { + var buf = jspack.Unpack('>Q', [1, 1, 1, 1, 1, 1, 1, 1]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0x01010101); + buf[0][1].should.be.eql(0x01010101); + buf[0][2].should.be.true; + }); + + it('pack >Q one', function() { + var buf = jspack.Pack('>Q', [[0x01010101, 0x01010101]]); + buf.should.be.eql([1, 1, 1, 1, 1, 1, 1, 1]); + }); + + it('unpack Q 0xfe', function() { + var buf = jspack.Unpack('>Q', [0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0xfefefefe); + buf[0][1].should.be.eql(0xfefefefe); + buf[0][2].should.be.true; + }); + + it('pack >Q 0xfe', function() { + var buf = jspack.Pack('>Q', [[0xfefefefe, 0xfefefefe]]); + buf.should.be.eql([0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe]); + }); + + it('unpack 53 are representable in the Number type", which is "representing the +doubleprecision 64-bit format IEEE 754 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic". +The [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) +in JavaScript is 253-1. + +Example: 264-1 is 1844674407370955**1615** but in JavaScript it evaluates to 1844674407370955**2000**. + +Furthermore, bitwise operators in JavaScript "deal only with integers in the range −231 through +231−1, inclusive, or in the range 0 through 232−1, inclusive. These operators accept any value of +the Number type but first convert each such value to one of 232 integer values." + +In some use cases, however, it is required to be able to reliably work with and perform bitwise operations on the full +64 bits. This is where long.js comes into play. + +Usage +----- + +The class is compatible with CommonJS and AMD loaders and is exposed globally as `Long` if neither is available. + +```javascript +var Long = require("long"); + +var longVal = new Long(0xFFFFFFFF, 0x7FFFFFFF); + +console.log(longVal.toString()); +... +``` + +API +--- + +### Constructor + +* new **Long**(low: `number`, high: `number`, unsigned?: `boolean`)
+ Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. See the from* functions below for more convenient ways of constructing Longs. + +### Fields + +* Long#**low**: `number`
+ The low 32 bits as a signed value. + +* Long#**high**: `number`
+ The high 32 bits as a signed value. + +* Long#**unsigned**: `boolean`
+ Whether unsigned or not. + +### Constants + +* Long.**ZERO**: `Long`
+ Signed zero. + +* Long.**ONE**: `Long`
+ Signed one. + +* Long.**NEG_ONE**: `Long`
+ Signed negative one. + +* Long.**UZERO**: `Long`
+ Unsigned zero. + +* Long.**UONE**: `Long`
+ Unsigned one. + +* Long.**MAX_VALUE**: `Long`
+ Maximum signed value. + +* Long.**MIN_VALUE**: `Long`
+ Minimum signed value. + +* Long.**MAX_UNSIGNED_VALUE**: `Long`
+ Maximum unsigned value. + +### Utility + +* Long.**isLong**(obj: `*`): `boolean`
+ Tests if the specified object is a Long. + +* Long.**fromBits**(lowBits: `number`, highBits: `number`, unsigned?: `boolean`): `Long`
+ Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is assumed to use 32 bits. + +* Long.**fromBytes**(bytes: `number[]`, unsigned?: `boolean`, le?: `boolean`): `Long`
+ Creates a Long from its byte representation. + +* Long.**fromBytesLE**(bytes: `number[]`, unsigned?: `boolean`): `Long`
+ Creates a Long from its little endian byte representation. + +* Long.**fromBytesBE**(bytes: `number[]`, unsigned?: `boolean`): `Long`
+ Creates a Long from its big endian byte representation. + +* Long.**fromInt**(value: `number`, unsigned?: `boolean`): `Long`
+ Returns a Long representing the given 32 bit integer value. + +* Long.**fromNumber**(value: `number`, unsigned?: `boolean`): `Long`
+ Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + +* Long.**fromString**(str: `string`, unsigned?: `boolean`, radix?: `number`)
+ Long.**fromString**(str: `string`, radix: `number`)
+ Returns a Long representation of the given string, written using the specified radix. + +* Long.**fromValue**(val: `*`, unsigned?: `boolean`): `Long`
+ Converts the specified value to a Long using the appropriate from* function for its type. + +### Methods + +* Long#**add**(addend: `Long | number | string`): `Long`
+ Returns the sum of this and the specified Long. + +* Long#**and**(other: `Long | number | string`): `Long`
+ Returns the bitwise AND of this Long and the specified. + +* Long#**compare**/**comp**(other: `Long | number | string`): `number`
+ Compares this Long's value with the specified's. Returns `0` if they are the same, `1` if the this is greater and `-1` if the given one is greater. + +* Long#**divide**/**div**(divisor: `Long | number | string`): `Long`
+ Returns this Long divided by the specified. + +* Long#**equals**/**eq**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value equals the specified's. + +* Long#**getHighBits**(): `number`
+ Gets the high 32 bits as a signed integer. + +* Long#**getHighBitsUnsigned**(): `number`
+ Gets the high 32 bits as an unsigned integer. + +* Long#**getLowBits**(): `number`
+ Gets the low 32 bits as a signed integer. + +* Long#**getLowBitsUnsigned**(): `number`
+ Gets the low 32 bits as an unsigned integer. + +* Long#**getNumBitsAbs**(): `number`
+ Gets the number of bits needed to represent the absolute value of this Long. + +* Long#**greaterThan**/**gt**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is greater than the specified's. + +* Long#**greaterThanOrEqual**/**gte**/**ge**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is greater than or equal the specified's. + +* Long#**isEven**(): `boolean`
+ Tests if this Long's value is even. + +* Long#**isNegative**(): `boolean`
+ Tests if this Long's value is negative. + +* Long#**isOdd**(): `boolean`
+ Tests if this Long's value is odd. + +* Long#**isPositive**(): `boolean`
+ Tests if this Long's value is positive. + +* Long#**isZero**/**eqz**(): `boolean`
+ Tests if this Long's value equals zero. + +* Long#**lessThan**/**lt**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is less than the specified's. + +* Long#**lessThanOrEqual**/**lte**/**le**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is less than or equal the specified's. + +* Long#**modulo**/**mod**/**rem**(divisor: `Long | number | string`): `Long`
+ Returns this Long modulo the specified. + +* Long#**multiply**/**mul**(multiplier: `Long | number | string`): `Long`
+ Returns the product of this and the specified Long. + +* Long#**negate**/**neg**(): `Long`
+ Negates this Long's value. + +* Long#**not**(): `Long`
+ Returns the bitwise NOT of this Long. + +* Long#**notEquals**/**neq**/**ne**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value differs from the specified's. + +* Long#**or**(other: `Long | number | string`): `Long`
+ Returns the bitwise OR of this Long and the specified. + +* Long#**shiftLeft**/**shl**(numBits: `Long | number | string`): `Long`
+ Returns this Long with bits shifted to the left by the given amount. + +* Long#**shiftRight**/**shr**(numBits: `Long | number | string`): `Long`
+ Returns this Long with bits arithmetically shifted to the right by the given amount. + +* Long#**shiftRightUnsigned**/**shru**/**shr_u**(numBits: `Long | number | string`): `Long`
+ Returns this Long with bits logically shifted to the right by the given amount. + +* Long#**subtract**/**sub**(subtrahend: `Long | number | string`): `Long`
+ Returns the difference of this and the specified Long. + +* Long#**toBytes**(le?: `boolean`): `number[]`
+ Converts this Long to its byte representation. + +* Long#**toBytesLE**(): `number[]`
+ Converts this Long to its little endian byte representation. + +* Long#**toBytesBE**(): `number[]`
+ Converts this Long to its big endian byte representation. + +* Long#**toInt**(): `number`
+ Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. + +* Long#**toNumber**(): `number`
+ Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). + +* Long#**toSigned**(): `Long`
+ Converts this Long to signed. + +* Long#**toString**(radix?: `number`): `string`
+ Converts the Long to a string written in the specified radix. + +* Long#**toUnsigned**(): `Long`
+ Converts this Long to unsigned. + +* Long#**xor**(other: `Long | number | string`): `Long`
+ Returns the bitwise XOR of this Long and the given one. + +Building +-------- + +To build an UMD bundle to `dist/long.js`, run: + +``` +$> npm install +$> npm run build +``` + +Running the [tests](./tests): + +``` +$> npm test +``` diff --git a/Chat/MAVLink/local_modules/long/index.js b/Chat/MAVLink/local_modules/long/index.js new file mode 100644 index 00000000..e16857a1 --- /dev/null +++ b/Chat/MAVLink/local_modules/long/index.js @@ -0,0 +1 @@ +module.exports = require("./src/long"); diff --git a/Chat/MAVLink/local_modules/long/package.json b/Chat/MAVLink/local_modules/long/package.json new file mode 100644 index 00000000..532202f4 --- /dev/null +++ b/Chat/MAVLink/local_modules/long/package.json @@ -0,0 +1,64 @@ +{ + "_from": "long@^4.0.0", + "_id": "long@4.0.0", + "_inBundle": false, + "_integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "_location": "/long", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": false, + "raw": "long@^4.0.0", + "name": "long", + "escapedName": "long", + "rawSpec": "^4.0.0", + "saveSpec": null, + "fetchSpec": "^4.0.0" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "local_modules/long", + "_shasum": "9a7b71cfb7d361a194ea555241c92f7468d5bf28", + "_spec": "long@^4.0.0", + "_where": "/home/buzz/GCS/mavlink/pymavlink/generator/javascript", + "author": { + "name": "Daniel Wirtz", + "email": "dcode@dcode.io" + }, + "bugs": { + "url": "https://github.com/dcodeIO/long.js/issues" + }, + "bundleDependencies": false, + "dependencies": {}, + "deprecated": false, + "description": "A Long class for representing a 64-bit two's-complement integer value.", + "devDependencies": { + "webpack": "^3.10.0" + }, + "files": [ + "index.js", + "LICENSE", + "README.md", + "src/long.js", + "dist/long.js", + "dist/long.js.map" + ], + "homepage": "https://github.com/dcodeIO/long.js#readme", + "keywords": [ + "math" + ], + "license": "Apache-2.0", + "main": "src/long.js", + "name": "long", + "repository": { + "type": "git", + "url": "git+https://github.com/dcodeIO/long.js.git" + }, + "scripts": { + "build": "webpack", + "test": "node tests" + }, + "version": "4.0.0" +} diff --git a/Chat/MAVLink/local_modules/long/src/long.js b/Chat/MAVLink/local_modules/long/src/long.js new file mode 100644 index 00000000..6eec276c --- /dev/null +++ b/Chat/MAVLink/local_modules/long/src/long.js @@ -0,0 +1,1325 @@ +// This file is MODIFIED from the original, by buzz 2020, please see README.md in the upper level folder for more details. + +module.exports = Long; + +/** + * wasm optimizations, to do native i64 multiplication and divide + */ +var wasm = null; + +try { + wasm = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 13, 2, 96, 0, 1, 127, 96, 4, 127, 127, 127, 127, 1, 127, 3, 7, 6, 0, 1, 1, 1, 1, 1, 6, 6, 1, 127, 1, 65, 0, 11, 7, 50, 6, 3, 109, 117, 108, 0, 1, 5, 100, 105, 118, 95, 115, 0, 2, 5, 100, 105, 118, 95, 117, 0, 3, 5, 114, 101, 109, 95, 115, 0, 4, 5, 114, 101, 109, 95, 117, 0, 5, 8, 103, 101, 116, 95, 104, 105, 103, 104, 0, 0, 10, 191, 1, 6, 4, 0, 35, 0, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 126, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 127, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 128, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 129, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 130, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11 + ])), {}).exports; +} catch (e) { + // no wasm support :( +} + +/** + * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. + * See the from* functions below for more convenient ways of constructing Longs. + * @exports Long + * @class A Long class for representing a 64 bit two's-complement integer value. + * @param {number} low The low (signed) 32 bits of the long + * @param {number} high The high (signed) 32 bits of the long + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @constructor + */ +function Long(low, high, unsigned) { + + /** + * The low 32 bits as a signed value. + * @type {number} + */ + this.low = low | 0; + + /** + * The high 32 bits as a signed value. + * @type {number} + */ + this.high = high | 0; + + /** + * Whether unsigned or not. + * @type {boolean} + */ + this.unsigned = !!unsigned; +} + +// The internal representation of a long is the two given signed, 32-bit values. +// We use 32-bit pieces because these are the size of integers on which +// Javascript performs bit-operations. For operations like addition and +// multiplication, we split each number into 16 bit pieces, which can easily be +// multiplied within Javascript's floating-point representation without overflow +// or change in sign. +// +// In the algorithms below, we frequently reduce the negative case to the +// positive case by negating the input(s) and then post-processing the result. +// Note that we must ALWAYS check specially whether those values are MIN_VALUE +// (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as +// a positive number, it overflows back into a negative). Not handling this +// case would often result in infinite recursion. +// +// Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from* +// methods on which they depend. + +/** + * An indicator used to reliably determine if an object is a Long or not. + * @type {boolean} + * @const + * @private + */ +Long.prototype.__isLong__; + +Object.defineProperty(Long.prototype, "__isLong__", { value: true }); + +/** + * @function + * @param {*} obj Object + * @returns {boolean} + * @inner + */ +function isLong(obj) { + return (obj && obj["__isLong__"]) === true; +} + +/** + * Tests if the specified object is a Long. + * @function + * @param {*} obj Object + * @returns {boolean} + */ +Long.isLong = isLong; + +/** + * A cache of the Long representations of small integer values. + * @type {!Object} + * @inner + */ +var INT_CACHE = {}; + +/** + * A cache of the Long representations of small unsigned integer values. + * @type {!Object} + * @inner + */ +var UINT_CACHE = {}; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromInt(value, unsigned) { + var obj, cachedObj, cache; + if (unsigned) { + value >>>= 0; + if (cache = (0 <= value && value < 256)) { + cachedObj = UINT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); + if (cache) + UINT_CACHE[value] = obj; + return obj; + } else { + value |= 0; + if (cache = (-128 <= value && value < 128)) { + cachedObj = INT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, value < 0 ? -1 : 0, false); + if (cache) + INT_CACHE[value] = obj; + return obj; + } +} + +/** + * Returns a Long representing the given 32 bit integer value. + * @function + * @param {number} value The 32 bit integer in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromInt = fromInt; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromNumber(value, unsigned) { + if (isNaN(value)) + return unsigned ? UZERO : ZERO; + if (unsigned) { + if (value < 0) + return UZERO; + if (value >= TWO_PWR_64_DBL) + return MAX_UNSIGNED_VALUE; + } else { + if (value <= -TWO_PWR_63_DBL) + return MIN_VALUE; + if (value + 1 >= TWO_PWR_63_DBL) + return MAX_VALUE; + } + if (value < 0) + return fromNumber(-value, unsigned).neg(); + return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); +} + +/** + * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * @function + * @param {number} value The number in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromNumber = fromNumber; + +/** + * @param {number} lowBits + * @param {number} highBits + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromBits(lowBits, highBits, unsigned) { + return new Long(lowBits, highBits, unsigned); +} + +/** + * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is + * assumed to use 32 bits. + * @function + * @param {number} lowBits The low 32 bits + * @param {number} highBits The high 32 bits + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromBits = fromBits; + +/** + * @function + * @param {number} base + * @param {number} exponent + * @returns {number} + * @inner + */ +var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4) + +/** + * @param {string} str + * @param {(boolean|number)=} unsigned + * @param {number=} radix + * @returns {!Long} + * @inner + */ +function fromString(str, unsigned, radix) { + if (str.length === 0) + throw Error('empty string'); + if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") + return ZERO; + if (typeof unsigned === 'number') { + // For goog.math.long compatibility + radix = unsigned, + unsigned = false; + } else { + unsigned = !! unsigned; + } + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + + var p; + if ((p = str.indexOf('-')) > 0) + throw Error('interior hyphen'); + else if (p === 0) { + return fromString(str.substring(1), unsigned, radix).neg(); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 8)); + + var result = ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i), + value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = fromNumber(pow_dbl(radix, size)); + result = result.mul(power).add(fromNumber(value)); + } else { + result = result.mul(radixToPower); + result = result.add(fromNumber(value)); + } + } + result.unsigned = unsigned; + return result; +} + +/** + * Returns a Long representation of the given string, written using the specified radix. + * @function + * @param {string} str The textual representation of the Long + * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to signed + * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 + * @returns {!Long} The corresponding Long value + */ +Long.fromString = fromString; + +/** + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromValue(val, unsigned) { + if (typeof val === 'number') + return fromNumber(val, unsigned); + if (typeof val === 'string') + return fromString(val, unsigned); + // Throws for non-objects, converts non-instanceof Long: + return fromBits(val.low, val.high, typeof unsigned === 'boolean' ? unsigned : val.unsigned); +} + +/** + * Converts the specified value to a Long using the appropriate from* function for its type. + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} + */ +Long.fromValue = fromValue; + +// NOTE: the compiler should inline these constant values below and then remove these variables, so there should be +// no runtime penalty for these. + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_16_DBL = 1 << 16; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_24_DBL = 1 << 24; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2; + +/** + * @type {!Long} + * @const + * @inner + */ +var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL); + +/** + * @type {!Long} + * @inner + */ +var ZERO = fromInt(0); + +/** + * Signed zero. + * @type {!Long} + */ +Long.ZERO = ZERO; + +/** + * @type {!Long} + * @inner + */ +var UZERO = fromInt(0, true); + +/** + * Unsigned zero. + * @type {!Long} + */ +Long.UZERO = UZERO; + +/** + * @type {!Long} + * @inner + */ +var ONE = fromInt(1); + +/** + * Signed one. + * @type {!Long} + */ +Long.ONE = ONE; + +/** + * @type {!Long} + * @inner + */ +var UONE = fromInt(1, true); + +/** + * Unsigned one. + * @type {!Long} + */ +Long.UONE = UONE; + +/** + * @type {!Long} + * @inner + */ +var NEG_ONE = fromInt(-1); + +/** + * Signed negative one. + * @type {!Long} + */ +Long.NEG_ONE = NEG_ONE; + +/** + * @type {!Long} + * @inner + */ +var MAX_VALUE = fromBits(0xFFFFFFFF|0, 0x7FFFFFFF|0, false); + +/** + * Maximum signed value. + * @type {!Long} + */ +Long.MAX_VALUE = MAX_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); + +/** + * Maximum unsigned value. + * @type {!Long} + */ +Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MIN_VALUE = fromBits(0, 0x80000000|0, false); + +/** + * Minimum signed value. + * @type {!Long} + */ +Long.MIN_VALUE = MIN_VALUE; + +/** + * @alias Long.prototype + * @inner + */ +var LongPrototype = Long.prototype; + +/** + * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. + * @returns {number} + */ +LongPrototype.toInt = function toInt() { + return this.unsigned ? this.low >>> 0 : this.low; +}; + +/** + * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). + * @returns {number} + */ +LongPrototype.toNumber = function toNumber() { + if (this.unsigned) + return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0); + return this.high * TWO_PWR_32_DBL + (this.low >>> 0); +}; + +/** + * Converts the Long to a string written in the specified radix. + * @param {number=} radix Radix (2-36), defaults to 10 + * @returns {string} + * @override + * @throws {RangeError} If `radix` is out of range + */ +LongPrototype.toString = function toString(radix) { + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + if (this.isZero()) + return '0'; + if (this.isNegative()) { // Unsigned Longs are never negative + if (this.eq(MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = fromNumber(radix), + div = this.div(radixLong), + rem1 = div.mul(radixLong).sub(this); + return div.toString(radix) + rem1.toInt().toString(radix); + } else + return '-' + this.neg().toString(radix); + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned), + rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower), + intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0, + digits = intval.toString(radix); + rem = remDiv; + if (rem.isZero()) + return digits + result; + else { + while (digits.length < 6) + digits = '0' + digits; + result = '' + digits + result; + } + } +}; + +/** + * Gets the high 32 bits as a signed integer. + * @returns {number} Signed high bits + */ +LongPrototype.getHighBits = function getHighBits() { + return this.high; +}; + +/** + * Gets the high 32 bits as an unsigned integer. + * @returns {number} Unsigned high bits + */ +LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { + return this.high >>> 0; +}; + +/** + * Gets the low 32 bits as a signed integer. + * @returns {number} Signed low bits + */ +LongPrototype.getLowBits = function getLowBits() { + return this.low; +}; + +/** + * Gets the low 32 bits as an unsigned integer. + * @returns {number} Unsigned low bits + */ +LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { + return this.low >>> 0; +}; + +/** + * Gets the number of bits needed to represent the absolute value of this Long. + * @returns {number} + */ +LongPrototype.getNumBitsAbs = function getNumBitsAbs() { + if (this.isNegative()) // Unsigned Longs are never negative + return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs(); + var val = this.high != 0 ? this.high : this.low; + for (var bit = 31; bit > 0; bit--) + if ((val & (1 << bit)) != 0) + break; + return this.high != 0 ? bit + 33 : bit + 1; +}; + +/** + * Tests if this Long's value equals zero. + * @returns {boolean} + */ +LongPrototype.isZero = function isZero() { + return this.high === 0 && this.low === 0; +}; + +/** + * Tests if this Long's value equals zero. This is an alias of {@link Long#isZero}. + * @returns {boolean} + */ +LongPrototype.eqz = LongPrototype.isZero; + +/** + * Tests if this Long's value is negative. + * @returns {boolean} + */ +LongPrototype.isNegative = function isNegative() { + return !this.unsigned && this.high < 0; +}; + +/** + * Tests if this Long's value is positive. + * @returns {boolean} + */ +LongPrototype.isPositive = function isPositive() { + return this.unsigned || this.high >= 0; +}; + +/** + * Tests if this Long's value is odd. + * @returns {boolean} + */ +LongPrototype.isOdd = function isOdd() { + return (this.low & 1) === 1; +}; + +/** + * Tests if this Long's value is even. + * @returns {boolean} + */ +LongPrototype.isEven = function isEven() { + return (this.low & 1) === 0; +}; + +/** + * Tests if this Long's value equals the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.equals = function equals(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1) + return false; + return this.high === other.high && this.low === other.low; +}; + +/** + * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.eq = LongPrototype.equals; + +/** + * Tests if this Long's value differs from the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.notEquals = function notEquals(other) { + return !this.eq(/* validates */ other); +}; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.neq = LongPrototype.notEquals; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ne = LongPrototype.notEquals; + +/** + * Tests if this Long's value is less than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThan = function lessThan(other) { + return this.comp(/* validates */ other) < 0; +}; + +/** + * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lt = LongPrototype.lessThan; + +/** + * Tests if this Long's value is less than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { + return this.comp(/* validates */ other) <= 0; +}; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lte = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.le = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is greater than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThan = function greaterThan(other) { + return this.comp(/* validates */ other) > 0; +}; + +/** + * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gt = LongPrototype.greaterThan; + +/** + * Tests if this Long's value is greater than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { + return this.comp(/* validates */ other) >= 0; +}; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gte = LongPrototype.greaterThanOrEqual; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ge = LongPrototype.greaterThanOrEqual; + +/** + * Compares this Long's value with the specified's. + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.compare = function compare(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.eq(other)) + return 0; + var thisNeg = this.isNegative(), + otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) + return -1; + if (!thisNeg && otherNeg) + return 1; + // At this point the sign bits are the same + if (!this.unsigned) + return this.sub(other).isNegative() ? -1 : 1; + // Both are positive if at least one is unsigned + return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1; +}; + +/** + * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}. + * @function + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.comp = LongPrototype.compare; + +/** + * Negates this Long's value. + * @returns {!Long} Negated Long + */ +LongPrototype.negate = function negate() { + if (!this.unsigned && this.eq(MIN_VALUE)) + return MIN_VALUE; + return this.not().add(ONE); +}; + +/** + * Negates this Long's value. This is an alias of {@link Long#negate}. + * @function + * @returns {!Long} Negated Long + */ +LongPrototype.neg = LongPrototype.negate; + +/** + * Returns the sum of this and the specified Long. + * @param {!Long|number|string} addend Addend + * @returns {!Long} Sum + */ +LongPrototype.add = function add(addend) { + if (!isLong(addend)) + addend = fromValue(addend); + + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = addend.high >>> 16; + var b32 = addend.high & 0xFFFF; + var b16 = addend.low >>> 16; + var b00 = addend.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the difference of this and the specified Long. + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.subtract = function subtract(subtrahend) { + if (!isLong(subtrahend)) + subtrahend = fromValue(subtrahend); + return this.add(subtrahend.neg()); +}; + +/** + * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}. + * @function + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.sub = LongPrototype.subtract; + +/** + * Returns the product of this and the specified Long. + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.multiply = function multiply(multiplier) { + if (this.isZero()) + return this; + if (!isLong(multiplier)) + multiplier = fromValue(multiplier); + + // use wasm support if present + if (wasm) { + var low = wasm.mul(this.low, + this.high, + multiplier.low, + multiplier.high); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (multiplier.isZero()) + return (this.unsigned?UZERO:ZERO); + if (this.eq(MIN_VALUE)) + return multiplier.isOdd() ? MIN_VALUE : (this.unsigned?UZERO:ZERO); + if (multiplier.eq(MIN_VALUE)) + return this.isOdd() ? MIN_VALUE : (this.unsigned?UZERO:ZERO); + + if (this.isNegative()) { + if (multiplier.isNegative()) + return this.neg().mul(multiplier.neg()); + else + return this.neg().mul(multiplier).neg(); + } else if (multiplier.isNegative()) + return this.mul(multiplier.neg()).neg(); + + // If both longs are small, use float multiplication + if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24)) + return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned); + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = multiplier.high >>> 16; + var b32 = multiplier.high & 0xFFFF; + var b16 = multiplier.low >>> 16; + var b00 = multiplier.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}. + * @function + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.mul = LongPrototype.multiply; + +/** + * Returns this Long divided by the specified. The result is signed if this Long is signed or + * unsigned if this Long is unsigned. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.divide = function divide(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + if (divisor.isZero()) + throw Error('division by zero'); + + // use wasm support if present + if (wasm) { + // guard against signed division overflow: the largest + // negative number / -1 would be 1 larger than the largest + // positive number, due to two's complement. + if (!this.unsigned && + this.high === -0x80000000 && + divisor.low === -1 && divisor.high === -1) { + // be consistent with non-wasm code path + return this; + } + var low = (this.unsigned ? wasm.div_u : wasm.div_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (this.isZero()) + return this.unsigned ? UZERO : ZERO; + var approx, rem, res; + if (!this.unsigned) { + // This section is only relevant for signed longs and is derived from the + // closure library as a whole. + if (this.eq(MIN_VALUE)) { + if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) + return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + else if (divisor.eq(MIN_VALUE)) + return ONE; + else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shr(1); + approx = halfThis.div(divisor).shl(1); + if (approx.eq(ZERO)) { + return divisor.isNegative() ? ONE : NEG_ONE; + } else { + rem = this.sub(divisor.mul(approx)); + res = approx.add(rem.div(divisor)); + return res; + } + } + } else if (divisor.eq(MIN_VALUE)) + return this.unsigned ? UZERO : ZERO; + if (this.isNegative()) { + if (divisor.isNegative()) + return this.neg().div(divisor.neg()); + return this.neg().div(divisor).neg(); + } else if (divisor.isNegative()) + return this.div(divisor.neg()).neg(); + res = ZERO; + } else { + // The algorithm below has not been made for unsigned longs. It's therefore + // required to take special care of the MSB prior to running it. + if (!divisor.unsigned) + divisor = divisor.toUnsigned(); + if (divisor.gt(this)) + return UZERO; + if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true + return UONE; + res = UZERO; + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + rem = this; + while (rem.gte(divisor)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2), + delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48), + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + approxRes = fromNumber(approx), + approxRem = approxRes.mul(divisor); + while (approxRem.isNegative() || approxRem.gt(rem)) { + approx -= delta; + approxRes = fromNumber(approx, this.unsigned); + approxRem = approxRes.mul(divisor); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) + approxRes = ONE; + + res = res.add(approxRes); + rem = rem.sub(approxRem); + } + return res; +}; + +/** + * Returns this Long divided by the specified. This is an alias of {@link Long#divide}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.div = LongPrototype.divide; + +/** + * Returns this Long modulo the specified. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.modulo = function modulo(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + + // use wasm support if present + if (wasm) { + var low = (this.unsigned ? wasm.rem_u : wasm.rem_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + return this.sub(this.div(divisor).mul(divisor)); +}; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.mod = LongPrototype.modulo; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.rem = LongPrototype.modulo; + +/** + * Returns the bitwise NOT of this Long. + * @returns {!Long} + */ +LongPrototype.not = function not() { + return fromBits(~this.low, ~this.high, this.unsigned); +}; + +/** + * Returns the bitwise AND of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.and = function and(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low & other.low, this.high & other.high, this.unsigned); +}; + +/** + * Returns the bitwise OR of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.or = function or(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low | other.low, this.high | other.high, this.unsigned); +}; + +/** + * Returns the bitwise XOR of this Long and the given one. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.xor = function xor(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftLeft = function shiftLeft(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned); + else + return fromBits(0, this.low << (numBits - 32), this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shl = LongPrototype.shiftLeft; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRight = function shiftRight(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned); + else + return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned); +}; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr = LongPrototype.shiftRight; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + numBits &= 63; + if (numBits === 0) + return this; + else { + var high = this.high; + if (numBits < 32) { + var low = this.low; + return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned); + } else if (numBits === 32) + return fromBits(high, 0, this.unsigned); + else + return fromBits(high >>> (numBits - 32), 0, this.unsigned); + } +}; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shru = LongPrototype.shiftRightUnsigned; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr_u = LongPrototype.shiftRightUnsigned; + +/** + * Converts this Long to signed. + * @returns {!Long} Signed long + */ +LongPrototype.toSigned = function toSigned() { + if (!this.unsigned) + return this; + return fromBits(this.low, this.high, false); +}; + +/** + * Converts this Long to unsigned. + * @returns {!Long} Unsigned long + */ +LongPrototype.toUnsigned = function toUnsigned() { + if (this.unsigned) + return this; + return fromBits(this.low, this.high, true); +}; + +/** + * Converts this Long to its byte representation. + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {!Array.} Byte representation + */ +LongPrototype.toBytes = function toBytes(le) { + return le ? this.toBytesLE() : this.toBytesBE(); +}; + +/** + * Converts this Long to its little endian byte representation. + * @returns {!Array.} Little endian byte representation + */ +LongPrototype.toBytesLE = function toBytesLE() { + var hi = this.high, + lo = this.low; + return [ + lo & 0xff, + lo >>> 8 & 0xff, + lo >>> 16 & 0xff, + lo >>> 24 , + hi & 0xff, + hi >>> 8 & 0xff, + hi >>> 16 & 0xff, + hi >>> 24 + ]; +}; + +/** + * Converts this Long to its big endian byte representation. + * @returns {!Array.} Big endian byte representation + */ +LongPrototype.toBytesBE = function toBytesBE() { + var hi = this.high, + lo = this.low; + return [ + hi >>> 24 , + hi >>> 16 & 0xff, + hi >>> 8 & 0xff, + hi & 0xff, + lo >>> 24 , + lo >>> 16 & 0xff, + lo >>> 8 & 0xff, + lo & 0xff + ]; +}; + +/** + * Creates a Long from its byte representation. + * @param {!Array.} bytes Byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {Long} The corresponding Long value + */ +Long.fromBytes = function fromBytes(bytes, unsigned, le) { + return le ? Long.fromBytesLE(bytes, unsigned) : Long.fromBytesBE(bytes, unsigned); +}; + +/** + * Creates a Long from its little endian byte representation. + * @param {!Array.} bytes Little endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesLE = function fromBytesLE(bytes, unsigned) { + return new Long( + bytes[0] | + bytes[1] << 8 | + bytes[2] << 16 | + bytes[3] << 24, + bytes[4] | + bytes[5] << 8 | + bytes[6] << 16 | + bytes[7] << 24, + unsigned + ); +}; + +/** + * Creates a Long from its big endian byte representation. + * @param {!Array.} bytes Big endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesBE = function fromBytesBE(bytes, unsigned) { + return new Long( + bytes[4] << 24 | + bytes[5] << 16 | + bytes[6] << 8 | + bytes[7], + bytes[0] << 24 | + bytes[1] << 16 | + bytes[2] << 8 | + bytes[3], + unsigned + ); +}; diff --git a/Chat/MAVLink/mavlink.js b/Chat/MAVLink/mavlink.js new file mode 100644 index 00000000..5e3e2824 --- /dev/null +++ b/Chat/MAVLink/mavlink.js @@ -0,0 +1,18848 @@ +/* +MAVLink protocol implementation for node.js (auto-generated by mavgen_javascript.py) + +Generated from: all.xml,ardupilotmega.xml,ASLUAV.xml,common.xml,development.xml,icarous.xml,minimal.xml,python_array_test.xml,standard.xml,test.xml,ualberta.xml,uAvionix.xml,loweheiser.xml,storm32.xml,AVSSUAS.xml,cubepilot.xml,csAirLink.xml + +Note: this file has been auto-generated. DO NOT EDIT +*/ + +var jspack +import("./local_modules/jspack/jspack.js").then((mod) => { + jspack = new mod.default() +}).catch((e) => { + // Discard annoying error. + // This fails in the sandbox because of CORS + // Only really want the message definitions in that case, not the parser, so its OK. +}) + +mavlink20 = function(){}; + +// Implement the CRC-16/MCRF4XX function (present in the Python version through the mavutil.py package) +mavlink20.x25Crc = function(buffer, crcIN) { + + var bytes = buffer; + var crcOUT = crcIN === undefined ? 0xffff : crcIN; + bytes.forEach(function(e) { + var tmp = e ^ (crcOUT & 0xff); + tmp = (tmp ^ (tmp << 4)) & 0xff; + crcOUT = (crcOUT >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4); + crcOUT = crcOUT & 0xffff; + }); + return crcOUT; + +} + +mavlink20.WIRE_PROTOCOL_VERSION = "2.0"; +mavlink20.PROTOCOL_MARKER_V1 = 0xFE +mavlink20.PROTOCOL_MARKER_V2 = 0xFD +mavlink20.HEADER_LEN_V1 = 6 +mavlink20.HEADER_LEN_V2 = 10 +mavlink20.HEADER_LEN = 10; + +mavlink20.MAVLINK_TYPE_CHAR = 0 +mavlink20.MAVLINK_TYPE_UINT8_T = 1 +mavlink20.MAVLINK_TYPE_INT8_T = 2 +mavlink20.MAVLINK_TYPE_UINT16_T = 3 +mavlink20.MAVLINK_TYPE_INT16_T = 4 +mavlink20.MAVLINK_TYPE_UINT32_T = 5 +mavlink20.MAVLINK_TYPE_INT32_T = 6 +mavlink20.MAVLINK_TYPE_UINT64_T = 7 +mavlink20.MAVLINK_TYPE_INT64_T = 8 +mavlink20.MAVLINK_TYPE_FLOAT = 9 +mavlink20.MAVLINK_TYPE_DOUBLE = 10 + +mavlink20.MAVLINK_IFLAG_SIGNED = 0x01 +mavlink20.MAVLINK_SIGNATURE_BLOCK_LEN = 13 + +// Mavlink headers incorporate sequence, source system (platform) and source component. +mavlink20.header = function(msgId, mlen, seq, srcSystem, srcComponent, incompat_flags=0, compat_flags=0,) { + + this.mlen = ( typeof mlen === 'undefined' ) ? 0 : mlen; + this.seq = ( typeof seq === 'undefined' ) ? 0 : seq; + this.srcSystem = ( typeof srcSystem === 'undefined' ) ? 0 : srcSystem; + this.srcComponent = ( typeof srcComponent === 'undefined' ) ? 0 : srcComponent; + this.msgId = msgId + this.incompat_flags = incompat_flags + this.compat_flags = compat_flags + +} +mavlink20.header.prototype.pack = function() { + return jspack.Pack('BBBBBBBHB', [253, this.mlen, this.incompat_flags, this.compat_flags, this.seq, this.srcSystem, this.srcComponent, ((this.msgId & 0xFF) << 8) | ((this.msgId >> 8) & 0xFF), this.msgId>>16]); +} + +// Base class declaration: mavlink.message will be the parent class for each +// concrete implementation in mavlink.messages. +mavlink20.message = function() {}; + +// Convenience setter to facilitate turning the unpacked array of data into member properties +mavlink20.message.prototype.set = function(args,verbose) { + // inspect + this.fieldnames.forEach(function(e, i) { + var num = parseInt(i,10); + if (this.hasOwnProperty(e) && isNaN(num) ){ // asking for an attribute that's non-numeric is ok unless its already an attribute we have + if ( verbose >= 1) { console.log("WARNING, overwriting an existing property is DANGEROUS:"+e+" ==>"+i+"==>"+args[i]+" -> "+JSON.stringify(this)); } + } + }, this); + + // then modify + this.fieldnames.forEach(function(e, i) { + this[e] = args[i]; + }, this); +}; + +// trying to be the same-ish as the python function of the same name +mavlink20.message.prototype.sign_packet = function( mav) { + var crypto= require('crypto'); + var h = crypto.createHash('sha256'); + + //mav.signing.timestamp is a 48bit number, or 6 bytes. + + // due to js not being able to shift numbers more than 32, we'll use this instead.. + // js stores all its numbers as a 64bit float with 53 bits of mantissa, so have room for 48 ok. + // positive shifts left, negative shifts right + function shift(number, shift) { + return number * Math.pow(2, shift); + } + + var thigh = shift(mav.signing.timestamp,-32) // 2 bytes from the top, shifted right by 32 bits + var tlow = (mav.signing.timestamp & 0xfffffff ) // 4 bytes from the bottom + + // I means unsigned 4bytes, H means unsigned 2 bytes + // first add the linkid(1 byte) and timestamp(6 bytes) that start the signature + this._msgbuf = this._msgbuf.concat(jspack.Pack(' 1) && ( (this._payload[plen-1] == 0) || (this._payload[plen-1] == null) ) ) { + plen = plen - 1; + } + this._payload = this._payload.slice(0, plen); + // signing is our first incompat flag. + var incompat_flags = 0; + if (mav.signing.sign_outgoing){ + incompat_flags |= mavlink20.MAVLINK_IFLAG_SIGNED + } + // header + this._header = new mavlink20.header(this._id, this._payload.length, mav.seq, mav.srcSystem, mav.srcComponent, incompat_flags, 0,); + // payload + this._msgbuf = this._header.pack().concat(this._payload); + // crc - for now, assume always using crc_extra = True. TODO: check/fix this. + var crc = mavlink20.x25Crc(this._msgbuf.slice(1)); + crc = mavlink20.x25Crc([crc_extra], crc); + this._msgbuf = this._msgbuf.concat(jspack.Pack(' MAV. Also used to +return a point from MAV -> GCS. + + target_system : System ID. (uint8_t) + target_component : Component ID. (uint8_t) + idx : Point index (first point is 1, 0 is for return point). (uint8_t) + count : Total number of points (for sanity checking). (uint8_t) + lat : Latitude of point. (float) + lng : Longitude of point. (float) + +*/ + mavlink20.messages.fence_point = function( ...moreargs ) { + [ this.target_system , this.target_component , this.idx , this.count , this.lat , this.lng ] = moreargs; + + this._format = ' MAV. Also used to +return a point from MAV -> GCS. + + target_system : System ID. (uint8_t) + target_component : Component ID. (uint8_t) + idx : Point index (first point is 0). (uint8_t) + count : Total number of points (for sanity checking). (uint8_t) + lat : Latitude of point. (int32_t) + lng : Longitude of point. (int32_t) + alt : Transit / loiter altitude relative to home. (int16_t) + break_alt : Break altitude relative to home. (int16_t) + land_dir : Heading to aim for when landing. (uint16_t) + flags : Configuration flags. (uint8_t) + +*/ + mavlink20.messages.rally_point = function( ...moreargs ) { + [ this.target_system , this.target_component , this.idx , this.count , this.lat , this.lng , this.alt , this.break_alt , this.land_dir , this.flags ] = moreargs; + + this._format = ' value[float]. +This allows to send a parameter to any other component (such as the +GCS) without the need of previous knowledge of possible parameter +names. Thus the same GCS can store different parameters for different +autopilots. See also https://mavlink.io/en/services/parameter.html for +a full documentation of QGroundControl and IMU code. + + target_system : System ID (uint8_t) + target_component : Component ID (uint8_t) + param_id : Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars - applications have to provide 16+1 bytes storage if the ID is stored as string (char) + param_index : Parameter index. Send -1 to use the param ID field as identifier (else the param id will be ignored) (int16_t) + +*/ + mavlink20.messages.param_request_read = function( ...moreargs ) { + [ this.target_system , this.target_component , this.param_id , this.param_index ] = moreargs; + + this._format = ' 0 indicates the interval at which it is sent. (int32_t) + +*/ + mavlink20.messages.message_interval = function( ...moreargs ) { + [ this.message_id , this.interval_us ] = moreargs; + + this._format = '= 1 && this.buf[0] != this.protocol_marker ) { + + // Strip the offending initial byte and throw an error. + var badPrefix = this.buf[0]; + this.bufInError = this.buf.slice(0,1); + this.buf = this.buf.slice(1); + this.expected_length = mavlink20.HEADER_LEN; //initially we 'expect' at least the length of the header, later parseLength corrects for this. + throw new Error("Bad prefix ("+badPrefix+")"); + } + +} + +// Determine the length. Leaves buffer untouched. +// Although the 'len' of a packet is available as of the second byte, the third byte with 'incompat_flags' lets +// us know if we have signing enabled, which affects the real-world length by the signature-block length of 13 bytes. +// once successful, 'this.expected_length' is correctly set for the whole packet. +MAVLink20Processor.prototype.parseLength = function() { + + if( this.buf.length >= 3 ) { + var unpacked = jspack.Unpack('BBB', this.buf.slice(0, 3)); + var magic = unpacked[0]; // stx ie fd or fe etc + this.expected_length = unpacked[1] + mavlink20.HEADER_LEN + 2 // length of message + header + CRC (ie non-signed length) + this.incompat_flags = unpacked[2]; + // mavlink2 only.. in mavlink1, incompat_flags var above is actually the 'seq', but for this test its ok. + if ((magic == mavlink20.PROTOCOL_MARKER_V2 ) && ( this.incompat_flags & mavlink20.MAVLINK_IFLAG_SIGNED )){ + this.expected_length += mavlink20.MAVLINK_SIGNATURE_BLOCK_LEN; + } + } + +} + +// input some data bytes, possibly returning a new message - python equiv function is called parse_char / __parse_char_legacy +MAVLink20Processor.prototype.parseChar = function(c) { + + if (c == null) { + return + } + + var m = null; + + try { + this.pushBuffer(c); + this.parsePrefix(); + this.parseLength(); + m = this.parsePayload(); + + } catch(e) { + this.log('error', e.message); + this.total_receive_errors += 1; + m = new mavlink20.messages.bad_data(this.bufInError, e.message); + this.bufInError = new Uint8Array(); + + } + + // emit a packet-specific message as well as a generic message, user/s can choose to use either or both of these. + //if(null != m) { + // this.emit(m._name, m); + // this.emit('message', m); + //} + + return m; + +} + +// continuation of python's __parse_char_legacy +MAVLink20Processor.prototype.parsePayload = function() { + + var m = null; + + // tip: this.expected_length and this.incompat_flags both already set correctly by parseLength(..) above + + // If we have enough bytes to try and read it, read it. + // shortest packet is header+checksum(2) with no payload, so we need at least that many + // but once we have a longer 'expected length' we have to read all of it. + if(( this.expected_length >= mavlink20.HEADER_LEN+2) && (this.buf.length >= this.expected_length) ) { + + // Slice off the expected packet length, reset expectation to be to find a header. + var mbuf = this.buf.slice(0, this.expected_length); + + // TODO: slicing off the buffer should depend on the error produced by the decode() function + // - if we find a well formed message, cut-off the expected_length + // - if the message is not well formed (correct prefix by accident), cut-off 1 char only + this.buf = this.buf.slice(this.expected_length); + this.expected_length = mavlink20.HEADER_LEN; // after attempting a parse, we'll next expect to find just a header. + + try { + m = this.decode(mbuf); + this.total_packets_received += 1; + } + catch(e) { + // Set buffer in question and re-throw to generic error handling + this.bufInError = mbuf; + throw e; + } + } + + return m; + +} + +// input some data bytes, possibly returning an array of new messages +MAVLink20Processor.prototype.parseBuffer = function(s) { + + // Get a message, if one is available in the stream. + var m = this.parseChar(s); + + // No messages available, bail. + if ( null === m ) { + return null; + } + + // While more valid messages can be read from the existing buffer, add + // them to the array of new messages and return them. + var ret = [m]; + while(true) { + m = this.parseChar(); + if ( null === m ) { + // No more messages left. + return ret; + } + ret.push(m); + } + +} + +//check signature on incoming message , many of the comments in this file come from the python impl +MAVLink20Processor.prototype.check_signature = function(msgbuf, srcSystem, srcComponent) { + + //timestamp_buf = msgbuf[-12:-6] + var timestamp_buf= msgbuf.slice(-12,-6); + + //link_id = msgbuf[-13] + var link_id= new Buffer.from(msgbuf.slice(-13,-12)); // just a single byte really, but returned as a buffer + link_id = link_id[0]; // get the first byte. + + //self.mav_sign_unpacker = jspack.Unpack('> 8) & 0xFF); // first-two msgid bytes + var msgIDhigh = unpacked[8]; // the 3rd msgid byte + msgId = msgIDlow | (msgIDhigh<<16); // combined result. 0 - 16777215 24bit number + } + catch(e) { + throw new Error('Unable to unpack MAVLink header: ' + e.message); + } + + // TODO allow full parsing of 1.0 inside the 2.0 parser, this is just a start + if (magic == mavlink20.PROTOCOL_MARKER_V1){ + //headerlen = 6; + + // these two are in the same place in both v1 and v2 so no change needed: + //magic = magic; + //mlen = mlen; + + // grab mavlink-v1 header position info from v2 unpacked position + seq1 = incompat_flags; + srcSystem1 = compat_flags; + srcComponent1 = seq; + msgId1 = srcSystem; + // override the v1 vs v2 offsets so we get the correct data either way... + seq = seq1; + srcSystem = srcSystem1; + srcComponent = srcComponent1; + msgId = msgId1; + // don't exist in mavlink1, so zero-them + incompat_flags = 0; + compat_flags = 0; + signature_len = 0; + // todo add more v1 here and don't just return + return; + } + + if (magic != this.protocol_marker) { + throw new Error("Invalid MAVLink prefix ("+magic+")"); + } + + // is packet supposed to be signed? + if ( incompat_flags & mavlink20.MAVLINK_IFLAG_SIGNED ){ + signature_len = mavlink20.MAVLINK_SIGNATURE_BLOCK_LEN; + } else { + signature_len = 0; + } + + // header's declared len compared to packets actual len + var actual_len = (msgbuf.length - (mavlink20.HEADER_LEN + 2 + signature_len)); + var actual_len_nosign = (msgbuf.length - (mavlink20.HEADER_LEN + 2 )); + + if ((mlen == actual_len) && (signature_len > 0)){ + var len_if_signed = mlen+signature_len; + //console.log("Packet appears signed && labeled as signed, OK. msgId=" + msgId); + + } else if ((mlen == actual_len_nosign) && (signature_len > 0)){ + + var len_if_signed = mlen+signature_len; + throw new Error("Packet appears unsigned when labeled as signed. Got actual_len "+actual_len_nosign+" expected " + len_if_signed + ", msgId=" + msgId); + + } else if( mlen != actual_len) { + throw new Error("Invalid MAVLink message length. Got " + (msgbuf.length - (mavlink20.HEADER_LEN + 2)) + " expected " + mlen + ", msgId=" + msgId); + + } + + if (!(msgId in mavlink20.map)) { + throw new Error("Unknown MAVLink message ID (" + msgId + ")"); + } + + // here's the common chunks of packet we want to work with below.. + var headerBuf= msgbuf.slice(mavlink20.HEADER_LEN); // first10 + var sigBuf = msgbuf.slice(-signature_len); // last 13 or nothing + var crcBuf1 = msgbuf.slice(-2); // either last-2 or last-2-prior-to-signature + var crcBuf2 = msgbuf.slice(-15,-13); // either last-2 or last-2-prior-to-signature + var payloadBuf = msgbuf.slice(mavlink20.HEADER_LEN, -(signature_len+2)); // the remaining bit between the header and the crc + var crcCheckBuf = msgbuf.slice(1, -(signature_len+2)); // the part uses to calculate the crc - ie between the magic and signature, + + // decode the payload + // refs: (fmt, type, order_map, crc_extra) = mavlink20.map[msgId] + var decoder = mavlink20.map[msgId]; + + // decode the checksum + var receivedChecksum = undefined; + if ( signature_len == 0 ) { // unsigned + try { + receivedChecksum = jspack.Unpack(' payloadBuf.length) { + payloadBuf = this.concat_buffer(payloadBuf, new Uint8Array(paylen - payloadBuf.length).fill(0)); + } + // Decode the payload and reorder the fields to match the order map. + try { + var t = jspack.Unpack(decoder.format, payloadBuf); + } + catch (e) { + throw new Error('Unable to unpack MAVLink payload type='+decoder.type+' format='+decoder.format+' payloadLength='+ payloadBuf +': '+ e.message); + } + + // Need to check if the message contains arrays + var args = {}; + const elementsInMsg = decoder.order_map.length; + const actualElementsInMsg = JSON.parse(JSON.stringify(t)).length; + + if (elementsInMsg == actualElementsInMsg) { + // Reorder the fields to match the order map + t.forEach(function(e, i, l) { + args[i] = t[decoder.order_map[i]] + }); + } else { + // This message contains arrays + var typeIndex = 1; + var orderIndex = 0; + var memberIndex = 0; + var tempArgs = {}; + + // Walk through the fields + for(var i = 0, size = decoder.format.length-1; i <= size; ++i) { + var order = decoder.order_map[orderIndex]; + var currentType = decoder.format[typeIndex]; + + if (isNaN(parseInt(currentType))) { + // This field is not an array check the type and add it to the args + tempArgs[orderIndex] = t[memberIndex]; + memberIndex++; + } else { + // This field is part of an array, need to find the length of the array + var arraySize = '' + var newArray = [] + while (!isNaN(decoder.format[typeIndex])) { + arraySize = arraySize + decoder.format[typeIndex]; + typeIndex++; + } + + // Now that we know how long the array is, create an array with the values + for(var j = 0, size = parseInt(arraySize); j < size; ++j){ + newArray.push(t[j+orderIndex]); + memberIndex++; + } + + // Add the array to the args object + arraySize = arraySize + decoder.format[typeIndex]; + currentType = arraySize; + tempArgs[orderIndex] = newArray; + } + orderIndex++; + typeIndex++; + } + + // Finally reorder the fields to match the order map + t.forEach(function(e, i, l) { + args[i] = tempArgs[decoder.order_map[i]] + }); + } + + // construct the message object + try { + // args at this point might look like: { '0': 6, '1': 8, '2': 0, '3': 0, '4': 3, '5': 3 } + var m = new decoder.type; // make a new 'empty' instance of the right class, + m.set(args,false); // associate ordered-field-numbers to names, after construction not during. + } + catch (e) { + throw new Error('Unable to instantiate MAVLink message of type '+decoder.type+' : ' + e.message); + } + + m._signed = sig_ok; + if (m._signed) { m._link_id = msgbuf[-13]; } + + m._msgbuf = msgbuf; + m._payload = payloadBuf + m.crc = receivedChecksum; + m._header = new mavlink20.header(msgId, mlen, seq, srcSystem, srcComponent, incompat_flags, compat_flags); + this.log(m); + return m; +} + + +// allow loading as both common.js (Node), and/or vanilla javascript in-browser +if(typeof module === "object" && module.exports) { + module.exports = {mavlink20, MAVLink20Processor}; +} + diff --git a/Chat/index.html b/Chat/index.html index f3ecc87e..becc99e8 100644 --- a/Chat/index.html +++ b/Chat/index.html @@ -4,6 +4,7 @@ Chat + @@ -92,8 +93,18 @@

Chat

- - + + + + + + + // MAVLink connection related functions + let connect_button = document.getElementById("mavlink-connect-button"); + let connect_url = document.getElementById("mavlink-connect-url").value; + let is_connected = false; // connection state + let mavlink_ws = null; // websocket object + let mavlink_sysid = 254; // system id + let mavlink_compid = MAVLink20Processor.MAV_COMP_ID_MISSIONPLANNER; // component id + let MAVLink = new MAVLink20Processor(null, mavlink_sysid, mavlink_compid); + + // toggle connection state (called by Connect/Disconnect button) + function mavlink_toggle_connect() { + if (is_connected) { + mavlink_disconnect(); + } else { + mavlink_connect(); + } + } + + // set the mavlink button connection state + function mavlink_set_connect_state(connected) { + is_connected = connected; + if (connected) { + connect_button.innerText = "Disconnect"; + } else { + connect_button.innerText = "Connect"; + } + } + + // connect to the vehicle + function mavlink_connect() { + if (mavlink_ws == null) { + // create a new websocket connection + mavlink_ws = new WebSocket(connect_url); + mavlink_ws.binaryType = "arraybuffer" + + // set up event handlers + mavlink_ws.onopen = function() { + mavlink_set_connect_state(true); + }; + mavlink_ws.onclose = function() { + mavlink_set_connect_state(false); + }; + mavlink_ws.onerror = function() { + mavlink_disconnect(); + }; + + // parse incoming message and forward + mavlink_ws.onmessage = (msg) => { + // sanity check parser has been created + if (MAVLink == null) { + return; + } + // parse message + for (const char of new Uint8Array(msg.data)) { + const mavlink_msg = MAVLink.parseChar(char) + if ((mavlink_msg != null) && (mavlink_msg._id != -1)) { + // got a message with a known ID + mavlink_msg_handler(mavlink_msg); + } + } + } + } + } + + // disconnect from the vehicle + function mavlink_disconnect() { + if (mavlink_ws != null) { + mavlink_ws.close(); + mavlink_ws = null; + } + } + + // mavlink message handler + function mavlink_msg_handler(msg) { + switch (msg._id) { + case 0: // HEARTBEAT + //alert("custom mode:" + msg.custom_mode); + //alert("Got a heartbeat: " + JSON.stringify(msg)); + break; + case 1: // SYS_STATUS + //alert("Got a system status: " + JSON.stringify(msg)); + break; + case 24: // GPS_RAW_INT + //alert("Got a GPS raw int: " + JSON.stringify(msg)); + break; + case 30: // ATTITUDE + //alert("Got an attitude: " + JSON.stringify(msg)); + break; + case 33: // GLOBAL_POSITION_INT + //alert("Got a global position int: " + JSON.stringify(msg)); + break; + case 35: // HIGHRES_IMU + //alert("Got a high resolution IMU: " + JSON.stringify(msg)); + break; + case 42: // NAMED_VALUE_FLOAT + //alert("Got a named value float: " + JSON.stringify(msg)); + break; + case 74: // VFR_HUD + //alert("Got a VFR HUD: " + JSON.stringify(msg)); + break; + case 253: // STATUSTEXT + //alert("Got a status text: " + JSON.stringify(msg)); + break; + default: + //alert("Got a message id: " + JSON.stringify(msg)); + break; + } + } + + // arm the vehicle + function mavlink_arm_vehicle() { + if (mavlink_ws == null || mavlink_ws.readyState !== WebSocket.OPEN) { + alert("Please connect to the vehicle first"); + return; + } + // construct the ARM command using COMMAND_LONG + let command_long_msg = new MAVLink20Processor.messages.COMMAND_LONG( + mavlink_sysid, // Target system ID + mavlink_compid, // Target component ID + 400, // MAV_CMD_COMPONENT_ARM_DISARM (command ID 400) + 1, // Confirmation + 1, // Param1: 1 to arm, 0 to disarm + 0, 0, 0, 0, 0, 0 // Unused parameters + ); + + // pack the message + let packed_msg = command_long_msg.pack(mavlink_sysid, mavlink_compid); + + // send the message as an ArrayBuffer over the WebSocket + if (mavlink_ws && mavlink_ws.readyState === WebSocket.OPEN) { + mavlink_ws.send(new Uint8Array(packed_msg)); + alert("Sent ARM command via MAVLink"); + } else { + alert("WebSocket is not open. Cannot send ARM command."); + } + } + + + From 3a84de69725c238c4fd7ea5e8ef3f5cecf5ba21b Mon Sep 17 00:00:00 2001 From: esatiyev Date: Mon, 30 Jun 2025 06:33:45 +0900 Subject: [PATCH 10/24] Chat: Refactor chat: update layout, add shared.js and style.css --- Chat/index.html | 2189 ++++++++++++++++++++++++++++++++--------------- Chat/shared.js | 11 + Chat/style.css | 189 ++++ 3 files changed, 1699 insertions(+), 690 deletions(-) create mode 100644 Chat/shared.js create mode 100644 Chat/style.css diff --git a/Chat/index.html b/Chat/index.html index a2d00c36..bdcc6316 100644 --- a/Chat/index.html +++ b/Chat/index.html @@ -1,27 +1,40 @@ + + Chat + + + + + + + + + -
- - - - -
- - -
- -
- - - -

Chat

- - -
-
-
- - - + } + --> + + + + +
+

ArduPilot AI Chat Control

+ +
+ +
+
+

AI Chat Interface

+ +
+ +
+
+

Welcome! I'm your ArduPilot AI assistant. You can control the drone using + natural language commands like:

+
    +
  • "Arm the drone"
  • +
  • "Take off to 10 meters"
  • +
  • "Fly north for 50 meters"
  • +
  • "Return to launch"
  • +
  • "Change to guided mode"
  • +
  • "Emergency stop"
  • +
+
+
+ +
+ + + +
+ + + +
+ + + + + + + + +
+
+ + +
+

Vehicle Telemetry

+ +
+
+
Flight Mode
+
DISCONNECTED
+
+
+
Armed State
+
DISARMED
+
+
+
Battery
+
+
-.- V
+
+
+
+
+
+
+
GPS Status
+
NO FIX
+
+
+ +
+

Attitude Indicator

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Roll
+
0.0°
+
+
+
Pitch
+
0.0°
+
+
+
Yaw
+
0.0°
+
+
+
+ +
+

Flight Mode

+
+ + +
+
+ +
+

AI Configuration

+ +
+ +
+ + +
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+

Position Tracking

+ +
+ +
+
+
Latitude
+
0.000000
+
+
+
Longitude
+
0.000000
+
+
+
Altitude
+
0.0 m
+
+
+ +
+

Pre-Flight Checks

+
+
Not connected
+
+ +
+ +
+

Quick Commands

+
+ + + + + + +
+
+ + +
+ + +
+ +
- -
- - - -
- - -
- - -
- - -
- - -
- - -
- - -
- - - - - - - - -
- -
- + + - - + + - // return function call results to the assistant - async submitToolOutputs(toolOutputs, runId, threadId) { - try { - // use the submitToolOutputsStream helper - const stream = this.client.beta.threads.runs.submitToolOutputsStream( - threadId, - runId, - { tool_outputs: toolOutputs }, - ) - for await (const event of stream) { - this.emit("event", event) - } - } catch (error) { - console.error("Error submitting tool outputs:", error) - } + - - - recordButton.addEventListener('click', () => { - if (state.isRecording) { - stopRecording(); - } else { - startRecording().catch(err => { - console.error("Failed to start recording:", err); - });; - } - }); - - - + + \ No newline at end of file diff --git a/Chat/shared.js b/Chat/shared.js new file mode 100644 index 00000000..054120ab --- /dev/null +++ b/Chat/shared.js @@ -0,0 +1,11 @@ +// shared.js +export let MAVLink = null; +export let mavlink_ws = null; + +export function setMAVLink(instance) { + MAVLink = instance; +} + +export function setMavlinkWS(ws) { + mavlink_ws = ws; +} diff --git a/Chat/style.css b/Chat/style.css new file mode 100644 index 00000000..5bfdcbf4 --- /dev/null +++ b/Chat/style.css @@ -0,0 +1,189 @@ +.toast { + position: fixed; + bottom: 20px; + right: 20px; + color: white; + padding: 10px 20px; + border-radius: 5px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + z-index: 1000; + opacity: 1; + transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out; +} + +.toast-warning { + background-color: #f39c12; +} + +.toast-info { + background-color: #3498db; +} + +.toast-success { + background-color: #2ecc71; +} + +.toast-error { + background-color: #e74c3c; +} + +.toast.fade-out { + opacity: 0; + transform: translateY(20px); +} + +#map { + height: 300px; + z-index: 1; +} + +.attitude-indicator { + background: linear-gradient(to bottom, #7bb6fa 0%, #1e88e5 100%); + position: relative; + overflow: hidden; +} + +.attitude-ball { + position: absolute; + width: 80%; + height: 80%; + background: rgba(0, 0, 0, 0.2); + border-radius: 50%; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.attitude-horizon { + position: absolute; + width: 100%; + height: 2px; + background-color: white; + top: 50%; + transform: translateY(-50%); +} + +.drone-marker { + background: #e74c3c; + border-radius: 50%; + width: 24px; + height: 24px; + position: relative; + display: flex; + justify-content: center; + align-items: center; +} + +.drone-marker:after { + content: ""; + position: absolute; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 12px solid white; + top: -10px; + left: 6px; +} + +.chat-message-user { + background-color: #ebf8ff; + border-radius: 0.5rem; + padding: 0.75rem; + margin-bottom: 0.5rem; + align-self: flex-end; + max-width: 80%; +} + +.chat-message-bot { + background-color: #f3f4f6; + border-radius: 0.5rem; + padding: 0.75rem; + margin-bottom: 0.5rem; + align-self: flex-start; + max-width: 80%; +} + +.battery-indicator { + height: 20px; + background: linear-gradient(to right, #e74c3c, #f39c12, #2ecc71); + border-radius: 3px; + overflow: hidden; +} + +.battery-level { + height: 100%; + background-color: rgba(0, 0, 0, 0.2); + transition: width 0.5s ease; +} + +.command-history { + max-height: 100px; + overflow-y: auto; + border-top: 1px solid #eee; + padding-top: 5px; +} + +.command-history-item { + padding: 2px 5px; + cursor: pointer; + border-radius: 3px; +} + +.command-history-item:hover { + background-color: #f0f0f0; +} + +#chat-messages { + max-height: 750px; + /* Set a fixed height for the chat container */ + overflow-y: auto; + /* Enable vertical scrolling */ + padding-right: 10px; +} + +#chat-messages::-webkit-scrollbar { + width: 8px; +} + +#chat-messages::-webkit-scrollbar-thumb { + background-color: #cbd5e0; + /* Light gray */ + border-radius: 4px; +} + +#chat-messages::-webkit-scrollbar-thumb:hover { + background-color: #a0aec0; + /* Darker gray on hover */ +} + +/* Chat message bubbles */ +.user-text { + align-self: flex-end; + background-color: #2563eb; /* blue-600 */ + color: #fff; + padding: 10px 16px; + border-radius: 18px 18px 4px 18px; + max-width: 70%; + margin: 4px 0; + font-size: 1rem; + box-shadow: 0 1px 2px rgba(0,0,0,0.07); +} + +.assistant-text { + align-self: flex-start; + background-color: #f3f4f6; /* gray-100 */ + color: #222; + padding: 10px 16px; + border-radius: 18px 18px 18px 4px; + max-width: 70%; + margin: 4px 0; + font-size: 1rem; + box-shadow: 0 1px 2px rgba(0,0,0,0.07); +} + +/* Chat container flex direction */ +#chatBox { + display: flex; + flex-direction: column; +} \ No newline at end of file From 817791afded44ee761aa6a7921fa2a27a99d4c03 Mon Sep 17 00:00:00 2001 From: esatiyev Date: Thu, 3 Jul 2025 02:22:22 +0900 Subject: [PATCH 11/24] Chat: add wakeup timers, fix UI bug - Added and Fixed functions to set, get, delete, and check wakeup timers - Implemented get_mode_mapping function - Fixed minor UI design issue - Updated handleTranscription to add FormData field language=en to enforce English in Whisper transcription --- Chat/index.html | 298 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 245 insertions(+), 53 deletions(-) diff --git a/Chat/index.html b/Chat/index.html index bdcc6316..89cb6d81 100644 --- a/Chat/index.html +++ b/Chat/index.html @@ -92,7 +92,7 @@

AI Chat Interface

-->
-
+

Welcome! I'm your ArduPilot AI assistant. You can control the drone using natural language commands like:

@@ -632,10 +632,10 @@

Quick Commands

// global variable to store wakeup messages let wakeup_schedule = []; - // window.onload = function () { - // initialize the check wakeup: - // check_wakeup_timers(); - // }; + window.onload = function () { + // initialize the check wakeup: + check_wakeup_timers(); + }; // add text to chat box function add_text_to_chat(text, role = "assistant") { @@ -695,6 +695,12 @@

Quick Commands

return; } + // sanity check message + if (!message || message.trim() === "") { + add_text_to_debug("send_to_assistant: message is empty"); + return; + } + // show the message in chat as system or wakeup message add_text_to_chat(message, "user"); // use "system" or custom style class // debugmu? @@ -828,11 +834,11 @@

Quick Commands

case "get_parameter": // Get a vehicle parameter's value. The full list of available parameters and their values is available using the get_all_parameters function // fallthrough + add_text_to_debug("get_parameter args: " + JSON.stringify(args)); return get_parameter(args); case "get_wakeup_timers": // Retrieves a list of all active wakeup timers. You can optionally provide a message parameter to filter timers by their associated messages. When specifying the message parameter, you can use regular expressions (regex) to match patterns within the timer messages. This is useful when you want to find timers with specific keywords or patterns in their messages. For example, to retrieve all timers containing the word 'hello', you can use the regex '.*hello.*', where the dot-star (.*) pattern matches any character sequence. // fallthrough - add_text_to_debug("get_wakeup_timers: name: " + name); add_text_to_debug("get_wakeup_timers: args: " + JSON.stringify(args)); const wakeup_response = get_wakeup_timers(args); return wakeup_response; @@ -846,6 +852,7 @@

Quick Commands

return send_mavlink_set_position_target_global_int(args); case "get_vehicle_state": // Get the vehicle state including armed status and (flight) mode + add_text_to_debug("get_vehicle_state args: " + JSON.stringify(args)); return get_vehicle_state(); case "get_location_plus_offset": // Calculate the latitude and longitude given an existing latitude and longitude and distances (in meters) North and East @@ -873,8 +880,6 @@

Quick Commands

case "delete_wakeup_timers": // Delete all active wakeup timers. You can optionally provide a message parameter to filter which timers will be deleted based on their message. When specifying the message parameter, you can use regular expressions (regex) to match patterns within the timer messages. This is useful when you want to delete timers with specific keywords or patterns in their message. For example, to delete all timers containing the word 'hello', you can use the regex '.*hello.*', where the dot-star (.*) pattern matches any character sequence. // fallthrough - add_text_to_debug("delete_wakeup_timers: name: " + name); - add_text_to_debug("delete_wakeup_timers: args: " + JSON.stringify(args)); return delete_wakeup_timers(args); case "set_parameter": // Set a vehicle parameter's value. The full list of parameters is available using the get_all_parameters function @@ -885,31 +890,19 @@

Quick Commands

return "Unknown function: " + name; case "get_current_datetime": // Get the current date and time, e.g. 'Saturday, June 24, 2023 6:14:14 PM + add_text_to_debug("get_current_datetime called"); return getFormattedDate(); case "get_mode_mapping": // Get a list of mode names to mode numbers available for this vehicle. If the name or number parameter is provided only that mode's name and number will be returned. If neither name nor number is provided the full list of available modes will be returned // fallthrough - return { - "STABILIZE": 0, - "ACRO": 1, - "ALT_HOLD": 2, - "AUTO": 3, - "GUIDED": 4, - "LOITER": 5, - "RTL": 6, - "CIRCLE": 7, - "LAND": 9, - "DRIFT": 11, - "POSHOLD": 16 - }; - + const mode_mapping = get_mode_mapping(args); + add_text_to_debug("get_mode_mapping response: " + JSON.stringify(mode_mapping)); + return mode_mapping; case "get_all_parameters": // Get all available parameter names and values // fallthrough case "set_wakeup_timer": // Set a timer to wake you up in a specified number of seconds in the future. This allows taking actions in the future. The wakeup message will appear with the user role but will look something like WAKEUP:. Multiple wakeup messages are supported - - add_text_to_debug("set_wakeup_timer function: " + name); return set_wakeup_timer(args); case "get_available_mavlink_messages": return get_available_mavlink_messages(); @@ -966,6 +959,170 @@

Quick Commands

return "unknown"; } + // return a mapping of mode names to numbers for the current vehicle type + function get_mode_mapping(args) { + if (typeof args === "string") { + try { + args = JSON.parse(args); + } catch (e) { + add_text_to_debug("get_mode_mapping: ERROR parsing args string"); + args = {}; + } + } + + args = args || {}; + + // get name and/or number arguments + let mode_name = args.name ?? null; + if (mode_name != null) { + mode_name = mode_name.toUpperCase(); + } + + let mode_number = args.number ?? null; + if (mode_number != null) { + mode_number = parseInt(mode_number, 10); + } + + // prepare list of modes + let mode_list = []; + let mode_mapping = {}; + + const vehicle_type = get_vehicle_type(); + switch (vehicle_type) { + case "Heli": + case "Blimp": + case "Copter": + mode_mapping = { + "STABILIZE": mavlink20.COPTER_MODE_STABILIZE, + "ACRO": mavlink20.COPTER_MODE_ACRO, + "ALT_HOLD": mavlink20.COPTER_MODE_ALT_HOLD, + "AUTO": mavlink20.COPTER_MODE_AUTO, + "GUIDED": mavlink20.COPTER_MODE_GUIDED, + "LOITER": mavlink20.COPTER_MODE_LOITER, + "RTL": mavlink20.COPTER_MODE_RTL, + "CIRCLE": mavlink20.COPTER_MODE_CIRCLE, + "LAND": mavlink20.COPTER_MODE_LAND, + "DRIFT": mavlink20.COPTER_MODE_DRIFT, + "SPORT": mavlink20.COPTER_MODE_SPORT, + "FLIP": mavlink20.COPTER_MODE_FLIP, + "AUTOTUNE": mavlink20.COPTER_MODE_AUTOTUNE, + "POSHOLD": mavlink20.COPTER_MODE_POSHOLD, + "BRAKE": mavlink20.COPTER_MODE_BRAKE, + "THROW": mavlink20.COPTER_MODE_THROW, + "AVOID_ADSB": mavlink20.COPTER_MODE_AVOID_ADSB, + "GUIDED_NOGPS": mavlink20.COPTER_MODE_GUIDED_NOGPS, + "SMART_RTL": mavlink20.COPTER_MODE_SMART_RTL, + "FLOWHOLD": mavlink20.COPTER_MODE_FLOWHOLD, + "FOLLOW": mavlink20.COPTER_MODE_FOLLOW, + "ZIGZAG": mavlink20.COPTER_MODE_ZIGZAG, + "SYSTEMID": mavlink20.COPTER_MODE_SYSTEMID, + "AUTOROTATE": mavlink20.COPTER_MODE_AUTOROTATE, + "AUTO_RTL": mavlink20.COPTER_MODE_AUTO_RTL + }; + break; + case "Plane": + mode_mapping = { + "MANUAL": mavlink20.PLANE_MODE_MANUAL, + "CIRCLE": mavlink20.PLANE_MODE_CIRCLE, + "STABILIZE": mavlink20.PLANE_MODE_STABILIZE, + "TRAINING": mavlink20.PLANE_MODE_TRAINING, + "ACRO": mavlink20.PLANE_MODE_ACRO, + "FLY_BY_WIRE_A": mavlink20.PLANE_MODE_FLY_BY_WIRE_A, + "FLY_BY_WIRE_B": mavlink20.PLANE_MODE_FLY_BY_WIRE_B, + "CRUISE": mavlink20.PLANE_MODE_CRUISE, + "AUTOTUNE": mavlink20.PLANE_MODE_AUTOTUNE, + "AUTO": mavlink20.PLANE_MODE_AUTO, + "RTL": mavlink20.PLANE_MODE_RTL, + "LOITER": mavlink20.PLANE_MODE_LOITER, + "TAKEOFF": mavlink20.PLANE_MODE_TAKEOFF, + "AVOID_ADSB": mavlink20.PLANE_MODE_AVOID_ADSB, + "GUIDED": mavlink20.PLANE_MODE_GUIDED, + "INITIALIZING": mavlink20.PLANE_MODE_INITIALIZING, + "QSTABILIZE": mavlink20.PLANE_MODE_QSTABILIZE, + "QHOVER": mavlink20.PLANE_MODE_QHOVER, + "QLOITER": mavlink20.PLANE_MODE_QLOITER, + "QLAND": mavlink20.PLANE_MODE_QLAND, + "QRTL": mavlink20.PLANE_MODE_QRTL, + "QAUTOTUNE": mavlink20.PLANE_MODE_QAUTOTUNE, + "QACRO": mavlink20.PLANE_MODE_QACRO, + "THERMAL": mavlink20.PLANE_MODE_THERMAL + }; + break; + case "Boat": + case "Rover": + mode_mapping = { + "MANUAL": mavlink20.ROVER_MODE_MANUAL, + "ACRO": mavlink20.ROVER_MODE_ACRO, + "STEERING": mavlink20.ROVER_MODE_STEERING, + "HOLD": mavlink20.ROVER_MODE_HOLD, + "LOITER": mavlink20.ROVER_MODE_LOITER, + "FOLLOW": mavlink20.ROVER_MODE_FOLLOW, + "SIMPLE": mavlink20.ROVER_MODE_SIMPLE, + "AUTO": mavlink20.ROVER_MODE_AUTO, + "RTL": mavlink20.ROVER_MODE_RTL, + "SMART_RTL": mavlink20.ROVER_MODE_SMART_RTL, + "GUIDED": mavlink20.ROVER_MODE_GUIDED, + "INITIALIZING": mavlink20.ROVER_MODE_INITIALIZING + }; + break; + case "Sub": + mode_mapping = { + "STABILIZE": mavlink20.SUB_MODE_STABILIZE, + "ACRO": mavlink20.SUB_MODE_ACRO, + "ALT_HOLD": mavlink20.SUB_MODE_ALT_HOLD, + "AUTO": mavlink20.SUB_MODE_AUTO, + "GUIDED": mavlink20.SUB_MODE_GUIDED, + "CIRCLE": mavlink20.SUB_MODE_CIRCLE, + "SURFACE": mavlink20.SUB_MODE_SURFACE, + "POSHOLD": mavlink20.SUB_MODE_POSHOLD, + "MANUAL": mavlink20.SUB_MODE_MANUAL + }; + break; + case "Tracker": + mode_mapping = { + "MANUAL": mavlink20.TRACKER_MODE_MANUAL, + "STOP": mavlink20.TRACKER_MODE_STOP, + "SCAN": mavlink20.TRACKER_MODE_SCAN, + "SERVO_TEST": mavlink20.TRACKER_MODE_SERVO_TEST, + "AUTO": mavlink20.TRACKER_MODE_AUTO, + "INITIALIZING": mavlink20.TRACKER_MODE_INITIALIZING + }; + break; + default: + // maybe we don't know the vehicle type + add_text_to_debug("get_mode_mapping: unknown vehicle type: " + vehicle_type); + return `get_mode_mapping: failed to retrieve mode mapping: unknown vehicle type: ${vehicle_type}`; + } + + // handle request for all modes + if (mode_name === null && mode_number === null) { + for (let mname in mode_mapping) { + let mnumber = mode_mapping[mname]; + mode_list.push({ "name": mname.toUpperCase(), "number": mnumber }); + } + } + // handle request using mode name + else if (mode_name !== null) { + for (let mname in mode_mapping) { + if (mname.toUpperCase() === mode_name) { + mode_list.push({ "name": mname.toUpperCase(), "number": mode_mapping[mname] }); + } + } + } + // handle request using mode number + else if (mode_number !== null) { + for (let mname in mode_mapping) { + let mnumber = mode_mapping[mname]; + if (mnumber === mode_number) { + mode_list.push({ "name": mname.toUpperCase(), "number": mnumber }); + } + } + } + + // return list of modes + return mode_list; + } + // get a vehicle parameter's value function get_parameter(args) { // check if the parameter name is provided @@ -1170,6 +1327,7 @@

Quick Commands

// get the current time and date as a string. E.g. 'Saturday, June 24, 2023 6:14:14 PM' function getFormattedDate() { + add_text_to_debug("getFormattedDate called"); const options = { weekday: 'long', year: 'numeric', @@ -1185,7 +1343,6 @@

Quick Commands

// set a wakeup timer function set_wakeup_timer(args) { - // check required arguments are specified if (typeof args === "string") { try { args = JSON.parse(args); @@ -1195,69 +1352,102 @@

Quick Commands

} } - const seconds = args.seconds !== undefined ? args.seconds : -1; + // check required arguments are specified + const seconds = args.seconds ?? -1; if (seconds < 0) { return "set_wakeup_timer: seconds not specified"; } - const message = args.message !== undefined ? args.message : null; + const message = args.message ?? null; if (message === null) { return "set_wakeup_timer: message not specified"; } // add timer to wakeup schedule const triggerTime = Date.now() + seconds * 1000; // seconds → milliseconds → match Date.now() + add_text_to_debug("set_wakeup_timer: triggerTime: " + new Date(triggerTime).toLocaleString()); + add_text_to_debug("current time: " + new Date(Date.now()).toLocaleString()); wakeup_schedule.push({ time: triggerTime, message: message }); - + add_text_to_debug("wakeup_schedule: " + JSON.stringify(wakeup_schedule)); return "set_wakeup_timer: wakeup timer set"; } // get wake timers function get_wakeup_timers(args) { - // check message argument, default to null meaning all - const message = args.message ?? null; - // prepare list of matching timers - const matching_timers = []; - - // handle simple case of all timers - if (message === null) { - matching_timers = wakeup_schedule; + if (typeof args === "string") { + try { + args = JSON.parse(args); + } catch (e) { + add_text_to_debug("ERROR set_wakeup_timer: Could not parse args JSON"); + return "Invalid arguments: JSON parse error"; + } } - // handle regex in message - else if (contains_regex(message)) { - const pattern = new RegExp(message, "i"); // ignore case - for (const wakeup_timer of wakeup_schedule) { - if (pattern.test(wakeup_timer.message)) { - matching_timers.push(wakeup_timer); + try { + + // check message argument, default to null meaning all + const message = args.message ?? null; + // prepare list of matching timers + let matching_timers = []; + + // handle simple case of all timers + if (message === null) { + matching_timers = wakeup_schedule; + add_text_to_debug("get_wakeup_timers: returning all timers"); + } + + // handle regex in message + else if (contains_regex(message)) { + const pattern = new RegExp(message, "i"); // ignore case + for (const wakeup_timer of wakeup_schedule) { + if (pattern.test(wakeup_timer.message)) { + matching_timers.push(wakeup_timer); + } } + add_text_to_debug("get_wakeup_timers: returning timers matching regex: " + message); } - } - // handle case of a specific message - else { - for (const wakeup_timer of wakeup_schedule) { - if (wakeup_timer.message === message) { - matching_timers.push(wakeup_timer); + // handle case of a specific message + else { + for (const wakeup_timer of wakeup_schedule) { + if (wakeup_timer.message === message) { + matching_timers.push(wakeup_timer); + } } + add_text_to_debug("get_wakeup_timers: returning timers matching message: " + message); } - } - // return matching timers - return matching_timers; + // return matching timers + return matching_timers; + + } catch (e) { + add_text_to_debug("ERROR get_wakeup_timers: " + e); + return "Invalid arguments: JSON parse error"; + } } // delete wake timers function delete_wakeup_timers(args) { + if (typeof args === "string") { + try { + args = JSON.parse(args); + } catch (e) { + add_text_to_debug("ERROR set_wakeup_timer: Could not parse args JSON"); + return "Invalid arguments: JSON parse error"; + } + } + // check message argument, default to all const message = args.message ?? null; // find matching timers let numDeleted = 0; + add_text_to_debug("delete_wakeup_timers: message: " + message); // handle simple case of deleting all timers if (message === null) { numDeleted = wakeup_schedule.length; wakeup_schedule.length = 0; + add_text_to_debug("delete_wakeup_timers: deleted all timers"); } // handle regex in message else if (contains_regex(message)) { @@ -1268,6 +1458,7 @@

Quick Commands

numDeleted++; } } + add_text_to_debug("delete_wakeup_timers: deleted timers matching regex: " + message); } // handle simple case of a single message else { @@ -1277,6 +1468,7 @@

Quick Commands

numDeleted++; } } + add_text_to_debug("delete_wakeup_timers: deleted timers matching message: " + message); } // return number deleted and remaining @@ -1566,8 +1758,6 @@

Quick Commands

// Access the global OpenAI instance safely const openai = window.openai; - add_text_to_chat("OpenAI key: " + openai.apiKey, "assistant"); - // add text to chat box function add_text_to_chat(text, role = "assistant") { let div_class = role === "assistant" ? "assistant-text" : "user-text"; @@ -1734,6 +1924,8 @@

Quick Commands

const formData = new FormData(); formData.append("file", blob, "recording.webm"); formData.append("model", "whisper-1"); + // update this static language option with dynamic in future , let user choose language + formData.append("language", "en"); const resp = await fetch("https://api.openai.com/v1/audio/transcriptions", { method: "POST", From 2384c04a77e1873bbf6fa7234a5e9c81a61bfc97 Mon Sep 17 00:00:00 2001 From: esatiyev Date: Tue, 15 Jul 2025 04:19:00 +0900 Subject: [PATCH 12/24] Chat: Refactor style.css: Remove unused styles --- Chat/style.css | 167 +------------------------------------------------ 1 file changed, 1 insertion(+), 166 deletions(-) diff --git a/Chat/style.css b/Chat/style.css index 5bfdcbf4..b20deff8 100644 --- a/Chat/style.css +++ b/Chat/style.css @@ -1,163 +1,4 @@ -.toast { - position: fixed; - bottom: 20px; - right: 20px; - color: white; - padding: 10px 20px; - border-radius: 5px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - z-index: 1000; - opacity: 1; - transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out; -} - -.toast-warning { - background-color: #f39c12; -} - -.toast-info { - background-color: #3498db; -} - -.toast-success { - background-color: #2ecc71; -} - -.toast-error { - background-color: #e74c3c; -} - -.toast.fade-out { - opacity: 0; - transform: translateY(20px); -} - -#map { - height: 300px; - z-index: 1; -} - -.attitude-indicator { - background: linear-gradient(to bottom, #7bb6fa 0%, #1e88e5 100%); - position: relative; - overflow: hidden; -} - -.attitude-ball { - position: absolute; - width: 80%; - height: 80%; - background: rgba(0, 0, 0, 0.2); - border-radius: 50%; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.attitude-horizon { - position: absolute; - width: 100%; - height: 2px; - background-color: white; - top: 50%; - transform: translateY(-50%); -} - -.drone-marker { - background: #e74c3c; - border-radius: 50%; - width: 24px; - height: 24px; - position: relative; - display: flex; - justify-content: center; - align-items: center; -} - -.drone-marker:after { - content: ""; - position: absolute; - width: 0; - height: 0; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 12px solid white; - top: -10px; - left: 6px; -} - -.chat-message-user { - background-color: #ebf8ff; - border-radius: 0.5rem; - padding: 0.75rem; - margin-bottom: 0.5rem; - align-self: flex-end; - max-width: 80%; -} - -.chat-message-bot { - background-color: #f3f4f6; - border-radius: 0.5rem; - padding: 0.75rem; - margin-bottom: 0.5rem; - align-self: flex-start; - max-width: 80%; -} - -.battery-indicator { - height: 20px; - background: linear-gradient(to right, #e74c3c, #f39c12, #2ecc71); - border-radius: 3px; - overflow: hidden; -} - -.battery-level { - height: 100%; - background-color: rgba(0, 0, 0, 0.2); - transition: width 0.5s ease; -} - -.command-history { - max-height: 100px; - overflow-y: auto; - border-top: 1px solid #eee; - padding-top: 5px; -} - -.command-history-item { - padding: 2px 5px; - cursor: pointer; - border-radius: 3px; -} - -.command-history-item:hover { - background-color: #f0f0f0; -} - -#chat-messages { - max-height: 750px; - /* Set a fixed height for the chat container */ - overflow-y: auto; - /* Enable vertical scrolling */ - padding-right: 10px; -} - -#chat-messages::-webkit-scrollbar { - width: 8px; -} - -#chat-messages::-webkit-scrollbar-thumb { - background-color: #cbd5e0; - /* Light gray */ - border-radius: 4px; -} - -#chat-messages::-webkit-scrollbar-thumb:hover { - background-color: #a0aec0; - /* Darker gray on hover */ -} - -/* Chat message bubbles */ +/* Chat messages */ .user-text { align-self: flex-end; background-color: #2563eb; /* blue-600 */ @@ -180,10 +21,4 @@ margin: 4px 0; font-size: 1rem; box-shadow: 0 1px 2px rgba(0,0,0,0.07); -} - -/* Chat container flex direction */ -#chatBox { - display: flex; - flex-direction: column; } \ No newline at end of file From 382501325f5fbefc9658eb77fa5229f86e1b03c8 Mon Sep 17 00:00:00 2001 From: esatiyev Date: Tue, 15 Jul 2025 04:20:17 +0900 Subject: [PATCH 13/24] Chat: Implement PARAM_VALUE handling, add parameter functions, minor UI change - Added case 22 in mavlink_msg_handler for handling PARAM_VALUE messages - Converted handle_function_call to async - Added functions for use in handle_function_call: - get_parameter - get_all_parameters - get_parameter_description - set_parameter - get_mavlink_message - get_available_mavlink_messages - Made small UI adjustments - Made small UI adjustments --- Chat/index.html | 750 ++++++++++++++++++++++++++++-------------------- 1 file changed, 442 insertions(+), 308 deletions(-) diff --git a/Chat/index.html b/Chat/index.html index 89cb6d81..ed5159c5 100644 --- a/Chat/index.html +++ b/Chat/index.html @@ -17,71 +17,12 @@ - - - - -

ArduPilot AI Chat Control

-
+
@@ -93,7 +34,7 @@

AI Chat Interface

-
+

Welcome! I'm your ArduPilot AI assistant. You can control the drone using natural language commands like:

    @@ -107,17 +48,21 @@

    AI Chat Interface

-
+
- -
-