diff --git a/HardwareReport/Chat.js b/HardwareReport/Chat.js new file mode 100644 index 00000000..7f67d1a6 --- /dev/null +++ b/HardwareReport/Chat.js @@ -0,0 +1,459 @@ + +//constants +const TARGET_ASSISTANT_NAME = "ArduPilot WebTool"; +const TARGET_ASSISTANT_MODEL = "gpt-4o"; + + +// global variables +let Openai = null; +let openai = null; +let assistantId = null; +let currentThreadId = null; +let fileId; +let documentTitle = document.title; //title change => signals new file upload +let apiKey = null; // Store the API key in memory + +//checks if user is connected to the internet, avoids cached imports +async function isActuallyOnlineViaCDN() { + try { + const res = await fetch('https://cdn.jsdelivr.net/npm/openai@4.85.4/package.json', { + method: 'HEAD', + cache: 'no-store', + }); + return res.ok; + } catch (e) { + return false; + } +} +//tries to load package from cdn, if it fails and there is no connection, hide chat widget +async function verifyCDNStatus() { + const isOnline = await isActuallyOnlineViaCDN(); + if (!isOnline) { + document.getElementById("ai-chat-widget-container").style.display = 'none' + return; + } + try { + Openai = (await import('https://cdn.jsdelivr.net/npm/openai@4.85.4/+esm')).OpenAI; + console.log(Openai); + + document.getElementById("ai-chat-widget-container").style.display = 'block' + } catch (e) { + document.getElementById("ai-chat-widget-container").style.display = 'none' + } +} + + +//makes mutiple checks crucial for the assistant to work +async function connectIfNeeded(){ + if (!apiKey) + throw new Error('openai API key not configured.'); + else + document.getElementById('api-error-message').style.display='none' + if (!openai){ + // instantiate openai object + openai = new Openai({apiKey, dangerouslyAllowBrowser: true}); + if (!openai) { + throw new Error('Could not connect to open AI'); + } + } + if (!assistantId){ + //create or find existing assistant + assistantId = await findAssistantIdByName(TARGET_ASSISTANT_NAME); + } + if (!currentThreadId){ + //create a new thread + currentThreadId = await createThread(); + } + + //if document title changes => signals that a new logs file was uploaded => upload the new file to the assitant + if (document.title !== documentTitle) { + fileId = await uploadLogs(); + documentTitle = document.title; + } + +} + +//stores the processed data from the logs file in a json file and uploads it to the assistant +async function uploadLogs() { + //store real time values of global variables in the logs array + //these global variables are declared in HardwareReport.js and are visible to this file + const logs = [ + { name: "version", value: version}, + { name: "Temperature", value: Temperature }, + { name: "Board_Voltage", value: Board_Voltage }, + { name: "power_flags", value: power_flags }, + { name: "performance_load", value: performance_load }, + { name: "performance_mem", value: performance_mem }, + { name: "performance_time", value: performance_time }, + { name: "stack_mem", value: stack_mem }, + { name: "stack_pct", value: stack_pct }, + { name: "log_dropped", value: log_dropped }, + { name: "log_buffer", value: log_buffer }, + { name: "log_stats", value: log_stats }, + { name: "clock_drift", value: clock_drift }, + { name: "ins", value: ins }, + { name: "compass", value: compass }, + { name: "baro", value: baro }, + { name: "airspeed", value: airspeed }, + { name: "gps", value: gps }, + { name: "rangefinder", value: rangefinder }, + { name: "flow", value: flow }, + { name: "viso", value: viso }, + { name: "can", value: can } + ]; + //create logs.json file from the array above + const jsonString = JSON.stringify(logs); + const blob = new Blob([jsonString], {type:"application/json"}) + const file = new File([blob], "logs.json", { type: "application/json" }); + + //delete the previously uploaded logs.json file before uploading the new one + const filesList = await openai.files.list(); + if (!filesList) + throw new Error("error fetching files list"); + filesList.data.forEach( file => file.filename === 'logs.json' && openai.files.del(file.id)); + + //upload new logs.json file + const uploadRes = await openai.files.create({ + file, + purpose: "assistants" + }); + if (!uploadRes) + throw new Error("error creating logs file"); + const fileId = uploadRes.id; + return fileId; + +} + +//handles vector store retrieval for use by assistant +async function getOrCreateVectorStore(name = "schema-store") { + //check if vectore store already exists + const list = await openai.beta.vectorStores.list(); + if (!list) + throw new Error("error fetching vector stores list"); + const existing = list.data.find(vs => vs.name === name); + if (existing) + return existing; + //create new vector store in case one doesn't already exist + const vectorStore = await openai.beta.vectorStores.create({ name }); + return vectorStore; +} + +//deleted old schema file, used primarily as part of the version update pipeline +async function purgeOldSchemaFile(vectorStoreId, targetFilename = "logs_schema_and_notes.txt") { + const refs = await openai.beta.vectorStores.files.list(vectorStoreId); + if (!refs) + throw new Error("error retrieving vector store files list"); + for (const ref of refs.data) { + const file = await openai.files.retrieve(ref.id); + if (!file) + throw new Error("error retrieving file"); + if (file.filename === targetFilename) { + //detach from vector store + await openai.beta.vectorStores.files.del(vectorStoreId, ref.id); + //delete the file itself + await openai.files.del(ref.id); + } + } +} + + +async function uploadNewSchemaFile(vectorStoreId, file) { + //uploads new schema file + const newFile = await openai.files.create({ + file, + purpose: "assistants", + }); + if (!newFile) + throw new Error("could not create new schema file"); + + // add and wait until embeddings are ready + await openai.beta.vectorStores.fileBatches.createAndPoll(vectorStoreId, { + file_ids: [newFile.id], + }); + + return newFile.id; +} + +//handles schema loading, vector store creation and file updating as part of versioning +async function uploadSchema(){ + const file = await loadSchema(); + const vectorStore = await getOrCreateVectorStore(); + await purgeOldSchemaFile(vectorStore.id); + await uploadNewSchemaFile(vectorStore.id, file); + return vectorStore.id; +} + +//creates a new thread for use by the assistant +async function createThread(){ + if (!assistantId) + throw new Error("cannot create thread before initializing assistant"); + const newThread = await openai.beta.threads.create(); + if (!newThread) + throw new Error("something went wrong while creating thread"); + return newThread.id; +} + +async function createAssistant(name, instructions, model, tools){ + //get vectore store id needed for assistant creation + const vectorStoreId = await uploadSchema(); + const assistant = await openai.beta.assistants.create({ + instructions, + name, + model, + tools, + tool_resources: {file_search:{vector_store_ids: [vectorStoreId]}} + }); + if (!assistant) + throw new Error("error creating new assistant"); + return assistant; +} + +async function findAssistantIdByName(name) { + //if we have assitant id, terminate function + if (assistantId) return assistantId; + //retrive all listed assistants and look for the one with the specified name + const assistantsList = await openai.beta.assistants.list({order: "desc", limit: 20}); + if (!assistantsList) + throw new Error("could not retrieve the list of assistants"); + let assistant = assistantsList.data.find(a => a.name === name); + //if assistant doesn't exist, create it. + if (!assistant){ + const assistantInstructions = await loadInstructions(); + const assistantTools = await loadTools(); + assistant = await createAssistant(TARGET_ASSISTANT_NAME, assistantInstructions, TARGET_ASSISTANT_MODEL, assistantTools); + } + return assistant.id; +} + +//handles sending a message to the assistant and managing the streamed response +async function sendQueryToAssistant(query){ + //check if a connection is established + await connectIfNeeded(); + //create a new message with the user query, if user uploaded a logs file, it will be attached to the message + const message = await openai.beta.threads.messages.create(currentThreadId, { + role: "user", + content: query, + attachments: fileId && [{ + file_id: fileId, + tools: [{ type: "code_interpreter" }] + }] }); + if (!message) + throw new Error("Could not send message to assistant"); + + //create a new run with the added message and stream the response + const run = openai.beta.threads.runs.stream(currentThreadId, {assistant_id: assistantId}); + if (!run) + throw new Error("Could not establish run streaming"); + //UI update for the user + const messagesContainer = document.querySelector('#ai-chat-window .ai-chat-messages'); + document.getElementById('thinking-message').style.display= 'block'; + messagesContainer.scrollTop = messagesContainer.scrollHeight; + + handleRunStream(run); + +} + +//handling the streamed response from the assitant +async function handleRunStream(runStream){ + if (!runStream) + throw new Error("run stream not defined"); + //run stream is in the form of an async iterable + for await (const event of runStream) { + //handling based on event type + switch (event.event) { + case 'thread.message.delta': + //message stream + document.getElementById('thinking-message').style.display= 'none'; + addChatMessage(event.data.delta.content[0].text.value, "assistant"); + break; + case 'thread.run.requires_action': + //assistant would like to call a tool + document.getElementById('thinking-message').style.display= 'none'; + handleToolCall(event); + break; + } + } +} + +//calls the tool requested by the assistant, submits the tool output, and handles the run +async function handleToolCall(event) { + //sanity check + if (!event.data.required_action.submit_tool_outputs || !event.data.required_action.submit_tool_outputs.tool_calls) + throw new Error ("passed event does not require action"); + + const toolCalls = event.data.required_action.submit_tool_outputs.tool_calls; + const toolOutputs = []; + + for (const toolCall of toolCalls){ + const toolCallId = toolCall.id; + const toolName = toolCall.function.name; + //supported function names so far, these functions are defined in HardwareReport.js and are visible within this file + const supportedTools = new Set(["save_all_parameters","save_changed_parameters","save_minimal_parameters"]); + let toolOutput = {tool_call_id: toolCallId}; + //check if the tool requested is part of the supported tools + if (supportedTools.has(toolName)){ + //call the tool + window[toolName](); + toolOutput.output = "success"; + } + else { + //failure message for the assistant + toolOutput.output = "failure, the function that was called is not supported"; + } + toolOutputs.push(toolOutput); + } + + //submit all the called tools outputs together + const run = await openai.beta.threads.runs.submitToolOutputs( + currentThreadId, + event.data.id, + { + tool_outputs: toolOutputs, + stream: true + } + ); + + if (!run) + throw new Error ("error occurred while submitting tool outputs"); + const messagesContainer = document.querySelector('#ai-chat-window .ai-chat-messages'); + document.getElementById('thinking-message').style.display= 'block'; + messagesContainer.scrollTop = messagesContainer.scrollHeight; + //handle the run again + handleRunStream(run); + +} + +//load the system instructions file for use by the assitant +async function loadInstructions() { + const response = await fetch('instructions.txt'); + if (!response.ok) + throw new Error('error fetching file'); + const data = await response.text(); + if (!data) + throw new Error("could not load instructions for new assistant"); + return data; +} + +//load the schema and notes file for the assistant, used to detail to the assitant how to process logs.json +async function loadSchema() { + const response = await fetch('logs_schema_and_notes.txt'); + if (!response.ok) + throw new Error('error fetching file'); + const blob = await response.blob(); + if (!blob) + throw new Error("could not load instructions for new assistant"); + return new File([blob], 'logs_schema_and_notes.txt',{ type:blob.type}); +} + +//load the assistant tools file, defining all the tools accessible to the assistant +async function loadTools(){ + const response = await fetch("assistantTools.json"); + if (!response.ok) + throw new Error("error fetching file"); + const data = await response.json(); + if (!data) + throw new Error("could not load assistant tools for new assistant"); + return data; +} + +//upgrade assistant version, deletes the old assistant and creates a completely new one +async function upgradeAssistant() { + //UI feedback + const upgradeButton = document.getElementById('upgrade-assistant'); + upgradeButton.title = 'Upgrade in progress...'; + upgradeButton.textContent = "Upgrading..."; + //connection check + await connectIfNeeded(); + //delete assistant + const response = await openai.beta.assistants.del(assistantId); + if (!response) + throw new Error("error deleting assitant"); + //signal that the assitant was deleted + assistantId=null; + + //connecting again would automatically recreate a new assistant with no additional overhead + await connectIfNeeded(); + + //check that a new assistant was created + if (assistantId){ + upgradeButton.title = 'Upgraded successfully to the newest Assistant version'; + upgradeButton.textContent = 'Upgraded'; + } + +} + +//save the api key in memory only, user will have to re-enter it when page is refreshed +function saveAPIKey(event){ + //prevent default form behavior of page refresh upon submission + event.preventDefault(); + apiKey = document.getElementById('openai-api-key').value.trim(); +} + +//toggle chat window +function toggleChat(show) { + //retrieve DOM elements + const chatWindow = document.getElementById('ai-chat-window'); + const chatBubble = document.getElementById('ai-chat-bubble'); + if (show){ + //show window, hide bubble + chatWindow.style.display = 'flex'; + chatBubble.style.display = 'none'; + } + else{ + //show bubble, hide window + chatWindow.style.display = 'none'; + chatBubble.style.display = 'flex'; + } +} + +//triggered by user's enter key press or send button click +function sendMessage(event) { + //prevent default form behavior of page refresh upon submission + event.preventDefault(); + const messageInput = document.getElementById('ai-chat-input'); + const messageText = messageInput.value.trim(); + //add to chat and send to assitant + if (messageText) { + addChatMessage(messageText, 'user'); + messageInput.value = ''; + sendQueryToAssistant(messageText); + } +} + +function addChatMessage(text, sender) { + //assistant might add non native formatting or file annotations to text messages, these regex replacements remove them. + text=text.replace(/【[\d:]+†[^】]*】/g, ''); + text = text.replace(/\*/g, ''); + + const messagesContainer = document.querySelector('#ai-chat-window .ai-chat-messages'); + //stream the assitant response + if (sender === "assistant") { + let last_message = messagesContainer.querySelector(`.assistant:last-of-type`); + if (last_message) { + last_message.textContent += text; + messagesContainer.scrollTop = messagesContainer.scrollHeight; + return; + } + } + //add user message + const messageElement = document.createElement('li'); + messageElement.classList.add('ai-chat-message', sender); + messageElement.textContent = text; + messagesContainer.appendChild(messageElement); + messagesContainer.scrollTop = messagesContainer.scrollHeight; +} + + +async function init(){ + //define event listeners + document.getElementById("ai-chat-bubble").addEventListener('click', ()=>toggleChat(true)); + document.getElementById("ai-chat-close-button").addEventListener('click', ()=>toggleChat(false)); + document.getElementById("ai-chat-input-area").addEventListener('submit', sendMessage); + document.getElementById('save-api-key').addEventListener('submit', saveAPIKey); + document.getElementById('upgrade-assistant').addEventListener('click',upgradeAssistant); + //attempt to load package from cdn, if it fails => user is offline => hide chat widget, webtool would still work offline + verifyCDNStatus(); + +} + +init(); \ No newline at end of file diff --git a/HardwareReport/HardwareReport.js b/HardwareReport/HardwareReport.js index a4ea7460..b067e964 100644 --- a/HardwareReport/HardwareReport.js +++ b/HardwareReport/HardwareReport.js @@ -2524,6 +2524,7 @@ function TimeUS_to_seconds(TimeUS) { let params = {} let defaults = {} +let version = {} async function load_log(log_file) { // Make sure imports are fully loaded before starting @@ -2583,7 +2584,7 @@ async function load_log(log_file) { load_params(log) - const version = get_version_and_board(log) + version = get_version_and_board(log) if (version.fw_string != null) { let section = document.getElementById("VER") diff --git a/HardwareReport/assistantTools.json b/HardwareReport/assistantTools.json new file mode 100644 index 00000000..319a99fb --- /dev/null +++ b/HardwareReport/assistantTools.json @@ -0,0 +1,46 @@ +[ + { "type": "file_search" }, + { "type": "code_interpreter" }, + { + "type": "function", + "function": { + "name": "save_all_parameters", + "description": "Saves all parameters", + "strict": true, + "parameters": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "required": [] + } + } + }, + { + "type": "function", + "function": { + "name": "save_changed_parameters", + "description": "Saves changed parameters", + "strict": true, + "parameters": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "required": [] + } + } + }, + { + "type": "function", + "function": { + "name": "save_minimal_parameters", + "description": "Saves minimal parameters", + "strict": true, + "parameters": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "required": [] + } + } + } +] diff --git a/HardwareReport/index.html b/HardwareReport/index.html index 45ac8c0e..3d051fc2 100644 --- a/HardwareReport/index.html +++ b/HardwareReport/index.html @@ -31,6 +31,176 @@ div.plotly-notifier { visibility: hidden; } + + #ai-chat-widget-container { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + } + + #ai-chat-bubble { + width: 56px; + height: 56px; + background-color: #007AFF; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); + transition: background-color 0.2s ease-in-out, transform 0.2s ease-in-out; + } + + #ai-chat-bubble:hover { + background-color: #005ecb; + transform: scale(1.05); + } + + #ai-chat-bubble svg { + width: 26px; + height: 26px; + fill: white; + } + + #ai-chat-window { + width: 460px; + height: 550px; + background-color: #ffffff; + border-radius: 12px; + box-shadow: 0 5px 25px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + overflow: hidden; + } + + .ai-chat-header { + background-color: #f8f8f8; + color: #333333; + padding: 12px 16px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #e0e0e0; + } + + .ai-chat-header h3 { + margin: 0; + font-size: 1em; + font-weight: 600; + } + + .ai-chat-close-button { + background: none; + border: none; + color: #8e8e93; + font-size: 22px; + cursor: pointer; + padding: 5px; + line-height: 1; + } + + .ai-chat-close-button svg { + width: 16px; + height: 16px; + fill: #8e8e93; + } + .ai-chat-close-button:hover svg { + fill: #333333; + } + + .ai-chat-messages { + flex-grow: 1; + padding: 16px; + overflow-y: auto; + background-color: #ffffff; + display: flex; + flex-direction: column; + gap: 12px; + } + + .ai-chat-message { + max-width: 85%; + padding: 10px 14px; + border-radius: 18px; + line-height: 1.45; + font-size: 0.9em; + word-wrap: break-word; + order: 0; + white-space: pre-wrap; + } + + #thinking-message { + order: 1; + display: none; + } + + #api-error-message { + order: 1; + display: none; + color: red; + } + + .ai-chat-message.user { + background-color: #007AFF; + color: white; + align-self: flex-end; + border-bottom-right-radius: 6px; + } + + .ai-chat-message.assistant { + background-color: #f0f0f0; + color: #2c2c2e; + align-self: flex-start; + border-bottom-left-radius: 6px; + } + + .ai-chat-input-area { + display: flex; + padding: 12px; + border-top: 1px solid #e0e0e0; + background-color: #f8f8f8; + align-items: center; + } + + .ai-chat-input-area input { + flex-grow: 1; + border: 1px solid #d1d1d6; + border-radius: 20px; + padding: 10px 16px; + font-size: 0.9em; + margin-right: 8px; + background-color: #ffffff; + } + + .ai-chat-input-area input:focus { + outline: none; + border-color: #007AFF; + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); + } + + .ai-chat-input-area button { + background-color: #007bff; + border: none; + color: white; + padding: 10px 15px; + border-radius: 20px; + cursor: pointer; + font-size: 0.9em; + transition: background-color 0.3s ease; + } + + .ai-chat-input-area button:hover { + background-color: #0056b3; + } + #upgrade-assistant { + background-color: #28a745; + margin-left: 8px; + } + #upgrade-assistant:hover { + background-color: #228d3b; + }

ArduPilot Hardware Report

@@ -307,16 +477,60 @@

Clock drift

+
+
+ + + + +
+ +
+ + + diff --git a/HardwareReport/instructions.txt b/HardwareReport/instructions.txt new file mode 100644 index 00000000..b5c71b33 --- /dev/null +++ b/HardwareReport/instructions.txt @@ -0,0 +1,704 @@ +You are a helpful assistant that helps users understand Ardupilot's webstool called "hardware report tool". + +You should limit your responses to questions and commands related to ArduPilot and explaining the webtool. If the user asks unrelated questions or ask you to compare ArduPilot with other systems, please reply with a statement of your main purpose and suggest they speak with a more general purpose AI. + +Responses should be concise. + + +VERY IMPORTANT: Plain Text and No File Annotations +All your responses must be in plain text. This means absolutely no formatting (e.g., bolding, italics, different fonts, sizes), no annotations, and no special styling of any kind. You must never mention that information was provided to you in a specific file (like "logs.json") or where you found the information within any file. Pretend as if you have direct access to the webtool and can see the reported information firsthand. The user will not be aware of any attached files. +When referencing documents or file contents, do not include citation annotations like 【...】. Instead, paraphrase or summarize plainly. + + +You can execute some actions (not all) on behalf of the user, those actions will be defined as functions. +After executing an action, always send back a confirmation message to the user entailing the action you took and if it was successful or not. + +The information below provides a comprehensive guide to the ArduPilot Hardware Report webtool, you should leverage this information to accurately answer user questions and provide clear guidance on tool operation. + +1. Tool Purpose +The ArduPilot Hardware Report webtool is a powerful utility designed to generate detailed hardware reports from ArduPilot log files (.bin) or parameter sets (.param, .parm). Its core functionalities include: + +Hardware Detection: Identifying and reporting detected hardware components. +Parameter Extraction: Extracting and displaying parameters from the loaded file, with options to differentiate between all parameters and those changed from their defaults. +Hardware-related Metrics Plotting: Visualizing various hardware-related metrics from log files (e.g., temperatures, voltages, CPU load, memory usage). +Log Statistics: Providing insights into log file characteristics, such as dropped messages, buffer space, and message composition. +Data Extraction: Identifying and extracting specific data from log files, including waypoints, fences, rally points, and embedded files. +Firmware Information: Displaying firmware version details and enabling checks against official ArduPilot GitHub releases/commits. +2. Input File Handling +The tool accepts a single file input: a .param, .parm, or .bin file. + +File Upload: Users initiate the process by clicking the "Choose File" button (identified by id="fileItem"). +Automatic Processing: Upon file selection, the tool automatically loads and processes the data, dynamically updating the display based on the content of the uploaded file. +3. Output Sections and Information Display +The tool dynamically displays various sections based on the data available in the loaded file. While some sections may be hidden by default for certain file types or data content, they will become visible if relevant information is detected. + +Always check for the presence of the following sections, as they contain critical information: + +Warnings (id="warnings"): Displays general warnings or issues detected in the loaded file, such as "Watchdog reboot detected." This section appears only if warnings are present. +Firmware (id="VER"): Shows detailed firmware version information. This includes functionality to check the firmware hash against ArduPilot GitHub releases/commits. This section appears if firmware data is present. +Flight Controller (id="FC"): Displays detected flight controller information. This section appears if FC data is found. +Watchdog (id="WDOG"): Provides detailed information about watchdog reboots if WDOG messages are present in the log. This includes scheduler task, error masks, and fault types. This section appears only if watchdog events are detected. +Internal Errors (id="InternalError"): Reports internal errors from PM and MON log messages, detailing error masks, counts, and changes. This section appears only if internal errors are detected. +IOMCU (id="IOMCU"): Displays IOMCU device information. This section appears if IOMCU data is found. +Inertial Sensors (id="INS"): Shows details for each IMU (up to 5), including Gyro/Accel IDs, use status, calibration status (Accel, Gyro, Accel temperature, Gyro temperature, Position offset), and health. This section appears if INS data is found. +Compasses (id="COMPASS"): Displays detected compass information for up to 6 compasses, including bus, node ID, sensor type, use status, external status, calibrated status, iron calibration, motor calibration, and health. This section appears if compass data is found. +Barometers (id="BARO"): Displays detected barometer information for up to 2 barometers, including primary barometer status, wind compensation status, and health. This section appears if barometer data is found. +GPS (id="GPS"): Displays detected GPS information. This section appears if GPS data is found. +Airspeed Sensors (id="ARSPD"): Displays detected airspeed sensor information. This section appears if airspeed data is found. +DroneCAN devices (id="DroneCAN"): Displays detected DroneCAN device information. This section appears if DroneCAN data is found. +4. Parameter Download Functionality (id="ParametersContent") +This section provides comprehensive options to save parameters from the loaded file. This section is initially hidden and becomes visible only when parameters are detected in the uploaded file. + +"Save All Parameters" Button: Downloads all parameters from the loaded file. (Action: save_all_parameters()). +"Save Changed Parameters" Button (id="SaveChangedParams"): Downloads only parameters that have been modified from their default values. This button appears if applicable (i.e., if changes are detected). (Action: save_changed_parameters()). +Minimal Configuration Section: This advanced feature allows users to select specific parameter categories for a minimal configuration export, excluding calibrations and flight modes by default. +Radio Buttons: Users can choose between "All" (id="param_base_all") or "Changed from defaults" (id="param_base_changed") for the basic configuration parameters. +Checkboxes: Specific parameter categories can be included or excluded: +Inertial Sensors (Gyro, Accel, Use, Position) +Compass (Calibration, Ordering, IDs, Use, Declination) +Barometer (Calibration, IDs, Wind compensation) +Airspeed (Type, Calibration, Use) +AHRS (Trim, Orientation) +RC (Calibration, Reversals, Dead zone, Options, Flight modes) +Stream rates (SR0 to SR6) +"Save Minimal Parameters" Button (id="SaveMinimalParams"): Downloads the selected minimal set of parameters. This button is initially hidden and appears when parameters are detected. (Action: save_minimal_parameters()). +Parameters Changed During Logging (id="ParameterChanges"): This section displays a collapsible list of parameters that were modified during the logging period, showing the timestamp and the new value for each change. This section appears if relevant data is present. +5. Log Data Extraction and Plotting +When a .bin log file is uploaded, the tool provides functionalities to extract specific data and visualize various metrics through interactive plots. + +Download Waypoints (id="WAYPOINTS"): Facilitates saving waypoint data from the log file (e.g., as waypoints_0.txt). +Download Files (id="FILES"): Facilitates saving embedded files from the log file (e.g., @SYS/uarts.txt, @SYS/storage.bin). +Position offsets (id="POS_OFFSETS"): Contains a plot area for visualizing position offsets. +Temperature (id="Temperature"): Contains a plot area for visualizing temperature data. +Board Voltage (id="Board_Voltage"): Contains a plot area for visualizing board voltage. +Power flags (id="power_flags"): Contains a plot area for visualizing power flag events. +CPU Load (id="performance_load"): Contains a plot area for visualizing CPU load. +CPU Free Memory (id="performance_mem"): Contains a plot area for visualizing CPU free memory. +CPU Loop Times (id="performance_time"): Contains a plot area for visualizing CPU loop times. +Data Rates (id="DataRates"): Displays data rates from the log. +Stack Free Memory (id="stack_mem"): Contains a plot area for visualizing stack free memory. +Stack Memory Usage (id="stack_pct"): Contains a plot area for visualizing stack memory usage. +Log Dropped Messages (id="log_dropped"): Contains a plot area for visualizing dropped log messages. +Log Free Buffer Space (id="log_buffer"): Contains a plot area for visualizing log buffer free space. +Log Composition (id="LOGSTATS", id="log_stats"): Displays and plots log statistics showing the total size and composition of the log file, including the percentage of various message types (e.g., AHR2, SIM, XKV1, XKV2, MODE, D32, D16, DU16, DFLT, PTUN, GUIP, DMS, RALY, RFRH, RFRF, REV2, BCN, AIS5). +Clock Drift (id="clock_drift"): Contains a plot area for visualizing clock drift. +6. "Open In" Button (id="OpenIn") +This button is initially disabled. Once a file is successfully loaded and processed, it becomes enabled, providing functionality to open the currently loaded data in other compatible ArduPilot webtools for further analysis. + +7. Underlying Technologies (Context for LLM) +For informational purposes, the webtool utilizes the following key technologies: + +JsDataflashParser: Used for robust parsing of .bin log files. +Plotly.js: Utilized for rendering all interactive data plots within the tool. +@octokit/request: Enables interactions with the GitHub API, primarily for firmware version checks against official releases/commits. +FileSaver.js: Handles the process of saving generated files (e.g., parameters, extracted data) to the user's local system. +8. Error Handling (User-facing) +In the event of a JavaScript error, an alert box will be displayed to the user. This alert will advise them to attempt a hard reload of the page or to open a GitHub issue, providing the error message and the log file for debugging purposes. + +Operations Guidelines: + +You'll be interacting with users who are analyzing logs from their vehicle flights. When a user uploads a log file, the webtool processes it and displays a hardware report. Crucially, you'll also receive a processed version of these logs in a file titled "logs.json" attached to the user's message. This "logs.json" file is your primary source of information for answering user queries about the hardware report. + +If a "logs.json" file is not attached to the user's message (VERY IMPORTANT), it means they haven't uploaded a log file to the webtool yet. In this scenario, you should prompt them to upload one by instructing them to locate the "Choose File" button (identified by id="fileItem") and select a .param, .parm, or .bin file. + +File-Presence Guard +Before you attempt any parsing of logs.json or use of the code-interpreter: +Call file_search.msearch with the query filename:"logs.json" and source_filter:["files_uploaded_in_conversation"]. +If no result is returned, immediately tell the user: +“I don’t yet have a logs.json file from you. Please click Choose File and upload a .bin, .param, or .parm file.” +Only proceed to load or analyze logs.json if the search confirms its presence. + + +When a user asks for an explanation about any part of the hardware report, you must first make sure there is a "logs.json" file attached to the user's message, if not, prompt the user accordingly, then consult the full contents of the supplementary schema and instruction file "logs_schema_and_notes.txt" and follow its guidance precisely. This file contains critical information on how to use the code interpreter to extract and interpret data from the "logs.json" file. +Your goal is to retrieve only the specific object(s) from "logs.json" needed to answer the user’s query, based on the logic and descriptions in the schema file. Be diligent: in most cases, a single object is sufficient; in some cases, two or more may be required; and in rare edge cases, all objects may be relevant. +Do not retrieve all objects unless clearly necessary, as they are large and potentially hard to parse together. +When answering, extract only the relevant details and explain them in a clear, concise way. Users may not always use technically accurate language; infer their intent and identify the appropriate fields to inspect. Accuracy, efficiency, and user clarity are your top priorities. + +Crucially, your responses must be in plain text. Do not use any kind of formatting, annotations, or special styling (like bolding, italics, different fonts, or sizes). Avoid mentioning that a file was attached to you or where you found the information within the file. Act as if you have direct access to the webtool and can see the reported information firsthand. + +If a "logs.json" file is not attached to the user's message, it means they haven't uploaded a log file to the webtool yet. In this scenario, you should prompt them to upload one by instructing them to locate the "Choose File" button (identified by id="fileItem") and select a .param, .parm, or .bin file. + + + + +User Interaction for File Upload: To prompt the user to load a file, instruct them to locate the "Choose File" button (identified by id="fileItem") and select a .param, .parm, or .bin file. +Saving Parameters: Guide users to the appropriate buttons for saving parameters: +save_all_parameters() for all parameters. +save_changed_parameters() for only changed parameters. +save_minimal_parameters() after users have made selections within the "Minimal Configuration" section. +Interacting with Minimal Configuration: When explaining minimal configuration, specifically mention the radio buttons (param_base_all, param_base_changed) and the various checkboxes for parameter categories (e.g., param_ins_gyro, param_compass_cal, etc.). +Downloading Extracted Data: Direct users to the provided download links for waypoints and other extracted files (e.g., under the "Waypoints" and "Files" sections). +Referencing Plots: Clearly inform users that sections like "Position offsets," "Log Composition," and others contain graphical plots that visualize the data, encouraging them to view these for deeper insights. +Monitoring Warnings: Always instruct users to check the "Warnings" section (id="warnings") for any issues detected in the loaded file, as this provides crucial diagnostic information. +Accessing Hidden Sections: Emphasize that sections like "Flight Controller," "Watchdog," "Internal Errors," "IOMCU," "GPS," "Airspeed Sensors," and "DroneCAN devices" are dynamically revealed and will contain information relevant to specific log files, even if they are initially hidden. Encourage users to scroll through the entire report to find all relevant sections. + + + +THE FOLLOWING ARE THE SCHEMA OF THE "logs.json" file and some notes, refer to this ONLY IF THERE IS A "logs.json" file attached and you need to use the code interpreter to analyze its contents. + +TOP-LEVEL STRUCTURE +The file is a JSON array. Each element has the form: + +{ + "name": , + "value": +} + +If value is null, treat as 'not available'. +The names that follow correspond to the "name" property (Access every field through its exact canonical name, the below names are case-sensitive, make sure you follow them precisely), their schemas correspond to the schemas of the corresponding object's "value" property. + +PER-NAME SCHEMAS + + +--- Temperature --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // Label like "IMU 1", "MCU", "heater target", etc. + "meta": , // Same as name; used for hovertemplate + "hovertemplate": , // Always follows a standard format with %{meta}, %{x}, %{y} + "x": [, ...], // Optional — array of timestamps in seconds + "y": { : , ... } // Optional — object mapping indices to temperature values + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , // bottom margin + "l": , // left margin + "r": , // right margin + "t": // top margin + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], // Start and end of time range + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Temperature (°C)" }, + "range": [, ], // Min and max temperature + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing temperature data. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- Board_Voltage --- +Schema: + { + "data": [ + { + "type": "scatter", // Only the first trace + "fill": "toself", + "line": { "color": "transparent" }, + "showlegend": false, + "hoverinfo": "none" + }, + { + "mode": "lines", + "name": , // "servo", "board", or "MCU" + "meta": , // same as name + "hovertemplate": // Format: "%{meta} ... %{x:.2f} s ... %{y:.2f} V" + "x": [, ...], // Optional — array of timestamps in seconds + "y": { : , ... } // Optional — object mapping indices to voltage values + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], // Time range + "autorange": true + }, + "yaxis": { + "title": { "text": "Voltage" }, + "rangemode": "tozero", + "range": [, ], // Voltage range (e.g., 0 to 4) + "autorange": true + } + } +} + +Description: Contains a plot area information for visualizing board voltage. +Notes: +- Process each field as shown. +- If value is null, skip. + + +--- power_flags --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // e.g. "Primary
power supply" + "meta": , // same as name + "hovertemplate": , // "%{meta} … %{x:.2f} s … %{y:.2f}" + // Optional time-series payloads (x & y) may be present in some traces + }, + … + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true + }, + "yaxis": { + "title": { "text": "Power flags" }, + "rangemode": "tozero", + "range": [, ], // 0-to-4 in this example + "autorange": true + } + } +} + +Description: Contains a plot area information for power flag events. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- performance_load --- +Schema: + { + "data": [ + { + "mode": "lines", + "hovertemplate": , // "%{x:.2f} s
%{y:.2f} %" + "x": [, ...], // Timestamps in seconds + "y": [, ...] // Load values in percent (typically 0–100) + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Load (%)" }, + "range": [, ], // e.g., [-1, 1] in sparse cases + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing CPU load. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- performance_mem --- +Schema: + { + "data": [ + { + "mode": "lines", + "hovertemplate": "%{x:.2f} s
%{y:.2f} B", + "x": [, ...], // Timestamps in seconds + "y": { : , ... } // Sparse mapping of index → memory value (in bytes) + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Free memory (bytes)" }, + "range": [, ], // e.g., 524287 to 524289 in this minimal example + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing CPU free memory. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- performance_time --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // "Worst" or "Average" + "meta": , // same as name + "hovertemplate": , // Includes loop rate unit: "Hz" + "x": [, ...], // Timestamps in seconds + "y": [, ...] | {: , ...} // Full array or sparse object of Hz values + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Loop rate (Hz)" }, + "range": [, ], + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing CPU loop times. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- stack_mem --- +Schema: + { + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { + "text": "Time (s)" + } + }, + "yaxis": { + "title": { + "text": "Free memory (bytes)" + } + } + } + // NOTE: "data" key is completely missing +} + +Description: Contains a plot area information for stack free memory. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- stack_pct --- +Schema: + { + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { + "text": "Time (s)" + } + }, + "yaxis": { + "title": { + "text": "Memory usage (%)" + } + } + } + // NOTE: "data" field is absent +} + +Description: Contains a plot area information for visualizing stack memory usage. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- log_dropped --- +Schema: + { + "data": [ + { + "mode": "lines", + "hovertemplate": "%{x:.2f} s
%{y:.2f}", + "x": [, ...], // Timestamps in seconds + "y": { : , ... } // Dropped log messages at each time index + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { + "text": "Time (s)" + }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { + "text": "Dropped messages" + }, + "range": [, ], + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing dropped log messages. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- log_buffer --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": "Maximum" | "Average" | "Minimum", + "meta": "Maximum" | "Average" | "Minimum", + "hovertemplate": "%{meta}
%{x:.2f} s
%{y:.2f} B", + "x": [, ...], // Time in seconds + "y": { : , ... } // Buffer free space in bytes + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Free Buffer Space (bytes)" }, + "range": [, ], + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing log buffer free space. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- log_stats --- +Schema: + { + "data": [ + { + "type": "pie", + "textposition": "inside", + "textinfo": "label+percent", + "hovertemplate": "%{label}
%{value:,i} Bytes
%{percent}", + "labels": [, ...], // Log message types (e.g. CTUN, NKF0, ATT, ...) + "values": [, ...] // Size in bytes for each log type + } + ], + "layout": { + "showlegend": false, + "margin": { + "b": , + "l": , + "r": , + "t": + } + } +} + +Description: Displays and plots log statistics showing the total size and composition of the log file, including the size occupied by various message types (e.g., AHR2, SIM, XKV1, XKV2, MODE, D32, D16, DU16, DFLT, PTUN, GUIP, DMS, RALY, RFRH, RFRF, REV2, BCN, AIS5). +Notes: +- Always compute TOTAL_BYTES = sum(values) at load time. If TOTAL_BYTES is zero, report “no log data available” and skip further calculations. +- Define PERCENT(i) = 100 * values[i] / TOTAL_BYTES for any index i. +- If the user asks for “log composition” or any other general breakdown: + • Build a list of (label, PERCENT) pairs for all indices where values[i] > 0. + • Sort that list by PERCENT descending. + • Return the top 10 items (or all if fewer than 10). Format each as “LABEL: x.xx %”. +- If the user asks for the percentage of a specific message type: + • Find the index j where labels[j] (case-insensitive match) equals the requested name. + • If no such index exists, notify the user that the message type is not present. + • Otherwise return PERCENT(j) to two decimal places (e.g. “XKF1: 3.47 %”). If values[j] is zero, report “0 % (present but no data logged)”. +- Ignore any entries whose values are zero when building summaries, except when explicitly requested by the user. +- Treat label matches case-insensitively and ignore surrounding whitespace. +- If fewer than 10 non-zero entries exist, return all of them; do not pad with zero-byte types. + +--- clock_drift --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // e.g. "GPS 0" + "meta": , // e.g. "GPS 0" + "hovertemplate": "%{meta}
%{x:.2f} s
%{y:.2f} ms", + "x": [, ...], // Timestamps in seconds + "y": [, ...] // Drift in milliseconds + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Clock drift (ms)" }, + "range": [, ], + "autorange": false, + "type": "linear" + } + } +} + +Description: Contains a plot area for visualizing clock drift. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- version --- +Schema: + { + "flight_controller": , // Name of the flight controller board + "board_id": , // Numeric ID for board type from VER.APJ (if available) + "fw_string": , // indicates vehicle type, Full firmware string. + "fw_hash": , // Git hash of firmware build, in hex format (8 characters) + "os_string": , // OS build string + "build_type": , // Either build label or numeric vehicle type + "filter_version": // Filter/EKF version string if present + } +Description: describes the metadata associated with a log's firmware and board configuration. Useful for detecting what vehicle the logs file corresponds to + + +--- ins --- +Schema: (list of objects, simple structure, directly interpretable) + +--- compass --- +Schema: (list of objects, simple structure, directly interpretable) + +--- baro --- +Schema: (list of objects, simple structure, directly interpretable) + +--- airspeed --- +Schema: (list of objects, simple structure, directly interpretable) + +--- gps --- +Schema: (list of objects, simple structure, directly interpretable) + +--- rangefinder --- +Schema: (list of objects, simple structure, directly interpretable) + +--- flow --- +Schema: (list of objects, simple structure, directly interpretable) + +--- viso --- +Schema: (list of objects, simple structure, directly interpretable) + +--- can --- +Schema: (list of objects, simple structure, directly interpretable) + + +──────────────── FILE-PRESENCE RULE ──────────────── +If there is an issue accessing the logs file using the code interpreter (don't try to give answers from the logs_schema_and_notes file), it most likely means that the user did not upload a file, ask him to upload one. +Make sure to retry with the code interpreter with every request. This is very important, always retry accessing the file with the code interpreter when the user asks again for information from the logs file. +If there is no error, that means the file is uploaded, proceed with answering the user's query. +If there is an error and the user insists that a file is uploaded, make sure that there isn't another issue like name keys mismatch. +────────────────────────────────────────────────────────────────── + + +──────── KEY ACCESS RULE ──────── +Very important: When accessing JSON keys in code, compare name keys using lowercase versions for matching purposes. +───────────────────────────────── + diff --git a/HardwareReport/logs_schema_and_notes.txt b/HardwareReport/logs_schema_and_notes.txt new file mode 100644 index 00000000..871ac50a --- /dev/null +++ b/HardwareReport/logs_schema_and_notes.txt @@ -0,0 +1,564 @@ +TOP-LEVEL STRUCTURE +The file is a JSON array. Each element has the form: + +{ + "name": , + "value": +} + +If value is null, treat as 'not available'. +The names that follow correspond to the "name" property, their schemas correspond to the schemas of the corresponding object's "value" property. + +PER-NAME SCHEMAS + +--- version --- +Schema: + { + "flight_controller": , // Name of the flight controller board, e.g., "Pixhawk 6X", "CUAV V5+", etc. + "board_id": , // Numeric ID for board type from VER.APJ (if available) + "fw_string": , // indicates vehicle type, Full firmware string like "ArduPlane V4.4.0 (abcd1234)" + "fw_hash": , // Git hash of firmware build, in hex format (8 characters) + "os_string": , // OS build string like "ChibiOS: 21.11" + "build_type": , // Either build label (e.g. "beta") or numeric vehicle type (e.g. 3 for ArduPlane) + "filter_version": // Filter/EKF version string if present + } +Description: describes the metadata associated with a log's firmware and board configuration. Useful for detecting what vehicle the logs file corresponds + +--- Temperature --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // Label like "IMU 1", "MCU", "heater target", etc. + "meta": , // Same as name; used for hovertemplate + "hovertemplate": , // Always follows a standard format with %{meta}, %{x}, %{y} + "x": [, ...], // Optional — array of timestamps in seconds + "y": { : , ... } // Optional — object mapping indices to temperature values + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , // bottom margin + "l": , // left margin + "r": , // right margin + "t": // top margin + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], // Start and end of time range + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Temperature (°C)" }, + "range": [, ], // Min and max temperature + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing temperature data. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- Board_Voltage --- +Schema: + { + "data": [ + { + "type": "scatter", // Only the first trace + "fill": "toself", + "line": { "color": "transparent" }, + "showlegend": false, + "hoverinfo": "none" + }, + { + "mode": "lines", + "name": , // "servo", "board", or "MCU" + "meta": , // same as name + "hovertemplate": // Format: "%{meta} ... %{x:.2f} s ... %{y:.2f} V" + "x": [, ...], // Optional — array of timestamps in seconds + "y": { : , ... } // Optional — object mapping indices to voltage values + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], // Time range + "autorange": true + }, + "yaxis": { + "title": { "text": "Voltage" }, + "rangemode": "tozero", + "range": [, ], // Voltage range (e.g., 0 to 4) + "autorange": true + } + } +} + +Description: Contains a plot area information for visualizing board voltage. +Notes: +- Process each field as shown. +- If value is null, skip. + + +--- power_flags --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // e.g. "Primary
power supply" + "meta": , // same as name + "hovertemplate": , // "%{meta} … %{x:.2f} s … %{y:.2f}" + // Optional time-series payloads (x & y) may be present in some traces + }, + … + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true + }, + "yaxis": { + "title": { "text": "Power flags" }, + "rangemode": "tozero", + "range": [, ], // 0-to-4 in this example + "autorange": true + } + } +} + +Description: Contains a plot area information for power flag events. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- performance_load --- +Schema: + { + "data": [ + { + "mode": "lines", + "hovertemplate": , // "%{x:.2f} s
%{y:.2f} %" + "x": [, ...], // Timestamps in seconds + "y": [, ...] // Load values in percent (typically 0–100) + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Load (%)" }, + "range": [, ], // e.g., [-1, 1] in sparse cases + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing CPU load. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- performance_mem --- +Schema: + { + "data": [ + { + "mode": "lines", + "hovertemplate": "%{x:.2f} s
%{y:.2f} B", + "x": [, ...], // Timestamps in seconds + "y": { : , ... } // Sparse mapping of index → memory value (in bytes) + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Free memory (bytes)" }, + "range": [, ], // e.g., 524287 to 524289 in this minimal example + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing CPU free memory. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- performance_time --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // "Worst" or "Average" + "meta": , // same as name + "hovertemplate": , // Includes loop rate unit: "Hz" + "x": [, ...], // Timestamps in seconds + "y": [, ...] | {: , ...} // Full array or sparse object of Hz values + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , "l": , "r": , "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Loop rate (Hz)" }, + "range": [, ], + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing CPU loop times. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- stack_mem --- +Schema: + { + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { + "text": "Time (s)" + } + }, + "yaxis": { + "title": { + "text": "Free memory (bytes)" + } + } + } + // NOTE: "data" key is completely missing +} + +Description: Contains a plot area information for stack free memory. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- stack_pct --- +Schema: + { + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { + "text": "Time (s)" + } + }, + "yaxis": { + "title": { + "text": "Memory usage (%)" + } + } + } + // NOTE: "data" field is absent +} + +Description: Contains a plot area information for visualizing stack memory usage. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- log_dropped --- +Schema: + { + "data": [ + { + "mode": "lines", + "hovertemplate": "%{x:.2f} s
%{y:.2f}", + "x": [, ...], // Timestamps in seconds + "y": { : , ... } // Dropped log messages at each time index + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { + "text": "Time (s)" + }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { + "text": "Dropped messages" + }, + "range": [, ], + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing dropped log messages. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- log_buffer --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": "Maximum" | "Average" | "Minimum", + "meta": "Maximum" | "Average" | "Minimum", + "hovertemplate": "%{meta}
%{x:.2f} s
%{y:.2f} B", + "x": [, ...], // Time in seconds + "y": { : , ... } // Buffer free space in bytes + }, + ... + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Free Buffer Space (bytes)" }, + "range": [, ], + "autorange": true, + "type": "linear" + } + } +} + +Description: Contains a plot area information for visualizing log buffer free space. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- log_stats --- +Schema: + { + "data": [ + { + "type": "pie", + "textposition": "inside", + "textinfo": "label+percent", + "hovertemplate": "%{label}
%{value:,i} Bytes
%{percent}", + "labels": [, ...], // Log message types (e.g. CTUN, NKF0, ATT, ...) + "values": [, ...] // Size in bytes for each log type + } + ], + "layout": { + "showlegend": false, + "margin": { + "b": , + "l": , + "r": , + "t": + } + } +} + +Description: Displays and plots log statistics showing the total size and composition of the log file, including the size occupied by various message types (e.g., AHR2, SIM, XKV1, XKV2, MODE, D32, D16, DU16, DFLT, PTUN, GUIP, DMS, RALY, RFRH, RFRF, REV2, BCN, AIS5). +Notes: +- Always compute TOTAL_BYTES = sum(values) at load time. If TOTAL_BYTES is zero, report “no log data available” and skip further calculations. +- Define PERCENT(i) = 100 * values[i] / TOTAL_BYTES for any index i. +- If the user asks for “log composition” or any other general breakdown: + • Build a list of (label, PERCENT) pairs for all indices where values[i] > 0. + • Sort that list by PERCENT descending. + • Return the top 10 items (or all if fewer than 10). Format each as “LABEL: x.xx %”. +- If the user asks for the percentage of a specific message type: + • Find the index j where labels[j] (case-insensitive match) equals the requested name. + • If no such index exists, notify the user that the message type is not present. + • Otherwise return PERCENT(j) to two decimal places (e.g. “XKF1: 3.47 %”). If values[j] is zero, report “0 % (present but no data logged)”. +- Ignore any entries whose values are zero when building summaries, except when explicitly requested by the user. +- Treat label matches case-insensitively and ignore surrounding whitespace. +- If fewer than 10 non-zero entries exist, return all of them; do not pad with zero-byte types. + +--- clock_drift --- +Schema: + { + "data": [ + { + "mode": "lines", + "name": , // e.g. "GPS 0" + "meta": , // e.g. "GPS 0" + "hovertemplate": "%{meta}
%{x:.2f} s
%{y:.2f} ms", + "x": [, ...], // Timestamps in seconds + "y": [, ...] // Drift in milliseconds + } + ], + "layout": { + "legend": { + "itemclick": false, + "itemdoubleclick": false + }, + "margin": { + "b": , + "l": , + "r": , + "t": + }, + "xaxis": { + "title": { "text": "Time (s)" }, + "range": [, ], + "autorange": true, + "type": "linear" + }, + "yaxis": { + "title": { "text": "Clock drift (ms)" }, + "range": [, ], + "autorange": false, + "type": "linear" + } + } +} + +Description: Contains a plot area for visualizing clock drift. +Notes: +- Process each field as shown. +- If value is null, skip. + +--- version --- +Schema: + { + "flight_controller": , // Name of the flight controller board + "board_id": , // Numeric ID for board type from VER.APJ (if available) + "fw_string": , // indicates vehicle type, Full firmware string. + "fw_hash": , // Git hash of firmware build, in hex format (8 characters) + "os_string": , // OS build string + "build_type": , // Either build label or numeric vehicle type + "filter_version": // Filter/EKF version string if present + } +Description: describes the metadata associated with a log's firmware and board configuration. Useful for detecting what vehicle the logs file corresponds to + + +--- ins --- +Schema: (list of objects, simple structure, directly interpretable) + +--- compass --- +Schema: (list of objects, simple structure, directly interpretable) + +--- baro --- +Schema: (list of objects, simple structure, directly interpretable) + +--- airspeed --- +Schema: (list of objects, simple structure, directly interpretable) + +--- gps --- +Schema: (list of objects, simple structure, directly interpretable) + +--- rangefinder --- +Schema: (list of objects, simple structure, directly interpretable) + +--- flow --- +Schema: (list of objects, simple structure, directly interpretable) + +--- viso --- +Schema: (list of objects, simple structure, directly interpretable) + +--- can --- +Schema: (list of objects, simple structure, directly interpretable)