Initial commit

This commit is contained in:
2026-07-03 16:31:37 +07:00
commit 899c7c637d
1939 changed files with 641750 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
color: var(--bs-secondary-color);
text-align: end;
}
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
text-align: start;
}
.monaco-editor-container {
height: 100% !important;
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>RobotNet10.RobotApp.Script</name>
</assembly>
<members>
<member name="T:RobotNet10.RobotApp.Script.IRobot">
<summary>
Điều khiển AMR
</summary>
</member>
<member name="M:RobotNet10.RobotApp.Script.IRobot.MoveTo(System.String)">
<summary>
</summary>
<param name="name"></param>
</member>
</members>
</doc>

View File

@@ -0,0 +1,446 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>RobotNet10.Script</name>
</assembly>
<members>
<member name="T:RobotNet10.Script.ILogger">
<summary>
Xuất thông tin ghi log cho các thành phần trong RobotNet.
</summary>
</member>
<member name="M:RobotNet10.Script.ILogger.LogInfo(System.String)">
<summary>
Xuất thông tin ghi log với mức độ thông tin.
</summary>
<param name="message"></param>
</member>
<member name="M:RobotNet10.Script.ILogger.LogWarning(System.String)">
<summary>
Xuất thông tin ghi log với mức độ cảnh báo.
</summary>
<param name="message"></param>
</member>
<member name="M:RobotNet10.Script.ILogger.LogError(System.String)">
<summary>
Xuất thông tin ghi log với mức độ lỗi.
</summary>
<param name="message"></param>
</member>
<member name="M:RobotNet10.Script.ILogger.GetLog">
<summary>
</summary>
<returns></returns>
</member>
<member name="T:RobotNet10.Script.IO.ICcLinkIeConnection">
<summary>
Interface for CC-Link IE connection operations.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.ICcLinkIeConnection.IpAddress">
<summary>
Gets the IP address of the CC-Link IE device.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.ICcLinkIeConnection.StationNumber">
<summary>
Gets the station number.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.ICcLinkIeConnection.IsConnected">
<summary>
Gets whether the connection is currently connected.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.ICcLinkIeConnection.ConnectAsync">
<summary>
Connects to the CC-Link IE device.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.ICcLinkIeConnection.DisconnectAsync">
<summary>
Disconnects from the CC-Link IE device.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.ICcLinkIeConnection.ReadAsync(System.Int32,System.Int32)">
<summary>
Reads data from the CC-Link IE device.
</summary>
<param name="address">The memory address.</param>
<param name="length">The number of words to read.</param>
<returns>The read data as ushort array.</returns>
</member>
<member name="M:RobotNet10.Script.IO.ICcLinkIeConnection.WriteAsync(System.Int32,System.UInt16[])">
<summary>
Writes data to the CC-Link IE device.
</summary>
<param name="address">The memory address.</param>
<param name="data">The data to write.</param>
</member>
<member name="T:RobotNet10.Script.IO.IHttpConnection">
<summary>
Interface for HTTP connection operations.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IHttpConnection.BaseUrl">
<summary>
Gets the base URL of the HTTP connection.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IHttpConnection.IsConnected">
<summary>
Gets whether the connection is currently connected.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IHttpConnection.ConnectAsync">
<summary>
Connects to the HTTP endpoint.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IHttpConnection.DisconnectAsync">
<summary>
Disconnects from the HTTP endpoint.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IHttpConnection.GetAsync(System.String,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Sends a GET request to the specified path.
</summary>
<param name="path">The path relative to BaseUrl.</param>
<param name="headers">Optional headers to include in the request.</param>
<returns>The response content as string.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IHttpConnection.PostAsync(System.String,System.String,System.String,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Sends a POST request to the specified path.
</summary>
<param name="path">The path relative to BaseUrl.</param>
<param name="content">The content to send.</param>
<param name="contentType">The content type (default: application/json).</param>
<param name="headers">Optional headers to include in the request.</param>
<returns>The response content as string.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IHttpConnection.PutAsync(System.String,System.String,System.String,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Sends a PUT request to the specified path.
</summary>
<param name="path">The path relative to BaseUrl.</param>
<param name="content">The content to send.</param>
<param name="contentType">The content type (default: application/json).</param>
<param name="headers">Optional headers to include in the request.</param>
<returns>The response content as string.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IHttpConnection.DeleteAsync(System.String,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Sends a DELETE request to the specified path.
</summary>
<param name="path">The path relative to BaseUrl.</param>
<param name="headers">Optional headers to include in the request.</param>
<returns>The response content as string.</returns>
</member>
<member name="T:RobotNet10.Script.IO.IModbusTcpConnection">
<summary>
Interface for ModbusTCP connection operations.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IModbusTcpConnection.IpAddress">
<summary>
Gets the IP address of the ModbusTCP server.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IModbusTcpConnection.Port">
<summary>
Gets the port of the ModbusTCP server.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IModbusTcpConnection.SlaveId">
<summary>
Gets the slave ID (unit identifier).
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IModbusTcpConnection.IsConnected">
<summary>
Gets whether the connection is currently connected.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.ConnectAsync">
<summary>
Connects to the ModbusTCP server.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.DisconnectAsync">
<summary>
Disconnects from the ModbusTCP server.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.ReadHoldingRegistersAsync(System.UInt16,System.UInt16)">
<summary>
Reads holding registers.
</summary>
<param name="startAddress">The starting address.</param>
<param name="numberOfPoints">The number of registers to read.</param>
<returns>Array of register values.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.ReadInputRegistersAsync(System.UInt16,System.UInt16)">
<summary>
Reads input registers.
</summary>
<param name="startAddress">The starting address.</param>
<param name="numberOfPoints">The number of registers to read.</param>
<returns>Array of register values.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.ReadCoilsAsync(System.UInt16,System.UInt16)">
<summary>
Reads coils.
</summary>
<param name="startAddress">The starting address.</param>
<param name="numberOfPoints">The number of coils to read.</param>
<returns>Array of coil values.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.ReadDiscreteInputsAsync(System.UInt16,System.UInt16)">
<summary>
Reads discrete inputs.
</summary>
<param name="startAddress">The starting address.</param>
<param name="numberOfPoints">The number of inputs to read.</param>
<returns>Array of input values.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.WriteSingleCoilAsync(System.UInt16,System.Boolean)">
<summary>
Writes a single coil.
</summary>
<param name="coilAddress">The coil address.</param>
<param name="value">The value to write.</param>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.WriteMultipleCoilsAsync(System.UInt16,System.Boolean[])">
<summary>
Writes multiple coils.
</summary>
<param name="startAddress">The starting address.</param>
<param name="values">The values to write.</param>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.WriteSingleRegisterAsync(System.UInt16,System.UInt16)">
<summary>
Writes a single holding register.
</summary>
<param name="registerAddress">The register address.</param>
<param name="value">The value to write.</param>
</member>
<member name="M:RobotNet10.Script.IO.IModbusTcpConnection.WriteMultipleRegistersAsync(System.UInt16,System.UInt16[])">
<summary>
Writes multiple holding registers.
</summary>
<param name="startAddress">The starting address.</param>
<param name="values">The values to write.</param>
</member>
<member name="T:RobotNet10.Script.IO.IOpcUaConnection">
<summary>
Interface for OPC UA connection operations.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IOpcUaConnection.EndpointUrl">
<summary>
Gets the endpoint URL of the OPC UA server.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IOpcUaConnection.IsConnected">
<summary>
Gets whether the connection is currently connected.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.ConnectAsync">
<summary>
Connects to the OPC UA server.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.ConnectAsync(System.String,System.String)">
<summary>
Connects to the OPC UA server with username and password.
</summary>
<param name="username">The username.</param>
<param name="password">The password.</param>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.DisconnectAsync">
<summary>
Disconnects from the OPC UA server.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.ReadNodeAsync(System.String)">
<summary>
Reads a node value by node ID.
</summary>
<param name="nodeId">The node ID string (e.g., "ns=2;s=MyVariable").</param>
<returns>The read value as object.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.ReadNodesAsync(System.String[])">
<summary>
Reads multiple node values by node IDs.
</summary>
<param name="nodeIds">Array of node ID strings.</param>
<returns>Dictionary of node ID to value.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.WriteNodeAsync(System.String,System.Object)">
<summary>
Writes a value to a node by node ID.
</summary>
<param name="nodeId">The node ID string.</param>
<param name="value">The value to write.</param>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.WriteNodesAsync(System.Collections.Generic.Dictionary{System.String,System.Object})">
<summary>
Writes multiple values to nodes.
</summary>
<param name="values">Dictionary of node ID to value.</param>
</member>
<member name="M:RobotNet10.Script.IO.IOpcUaConnection.BrowseNodesAsync(System.String)">
<summary>
Browses the node tree starting from the specified node.
</summary>
<param name="nodeId">The starting node ID (null for root).</param>
<returns>Array of child node IDs.</returns>
</member>
<member name="T:RobotNet10.Script.IO.IProfiNetConnection">
<summary>
Interface for ProfiNet connection operations.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IProfiNetConnection.IpAddress">
<summary>
Gets the IP address of the ProfiNet device.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IProfiNetConnection.Slot">
<summary>
Gets the slot number.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IProfiNetConnection.Subslot">
<summary>
Gets the subslot number.
</summary>
</member>
<member name="P:RobotNet10.Script.IO.IProfiNetConnection.IsConnected">
<summary>
Gets whether the connection is currently connected.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IProfiNetConnection.ConnectAsync">
<summary>
Connects to the ProfiNet device.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IProfiNetConnection.DisconnectAsync">
<summary>
Disconnects from the ProfiNet device.
</summary>
</member>
<member name="M:RobotNet10.Script.IO.IProfiNetConnection.ReadAsync(System.Int32,System.Int32)">
<summary>
Reads data from the ProfiNet device.
</summary>
<param name="index">The index to read from.</param>
<param name="length">The number of bytes to read.</param>
<returns>The read data as byte array.</returns>
</member>
<member name="M:RobotNet10.Script.IO.IProfiNetConnection.WriteAsync(System.Int32,System.Byte[])">
<summary>
Writes data to the ProfiNet device.
</summary>
<param name="index">The index to write to.</param>
<param name="data">The data to write.</param>
</member>
<member name="T:RobotNet10.Script.MissionAttribute">
<summary>
Khai báo một nhiệm vụ trong hệ thống RobotNet.
</summary>
<param name="totalScore"></param>
<param name="isMultipleRun"></param>
<param name="autoStart"></param>
</member>
<member name="M:RobotNet10.Script.MissionAttribute.#ctor(System.Int32,System.Boolean,System.Boolean)">
<summary>
Khai báo một nhiệm vụ trong hệ thống RobotNet.
</summary>
<param name="totalScore"></param>
<param name="isMultipleRun"></param>
<param name="autoStart"></param>
</member>
<member name="P:RobotNet10.Script.MissionAttribute.TotalScore">
<summary>
Tổng điểm của nhiệm vụ.
</summary>
</member>
<member name="P:RobotNet10.Script.MissionAttribute.IsMultipleRun">
<summary>
Cho phép nhiệm vụ này có thể chạy nhiều lần hay không.
</summary>
</member>
<member name="P:RobotNet10.Script.MissionAttribute.AutoStart">
<summary>
Tự động khởi động nhiệm vụ khi MissionManager chuyển sang trạng thái Running.
</summary>
</member>
<member name="T:RobotNet10.Script.MissionStatus">
<summary>
Trạng thái trả về mỗi step trong quá trình thực hiện nhiệm vụ.
</summary>
<param name="Score"></param>
<param name="Message"></param>
</member>
<member name="M:RobotNet10.Script.MissionStatus.#ctor(System.Int32,System.String)">
<summary>
Trạng thái trả về mỗi step trong quá trình thực hiện nhiệm vụ.
</summary>
<param name="Score"></param>
<param name="Message"></param>
</member>
<member name="P:RobotNet10.Script.MissionStatus.Score">
<summary></summary>
</member>
<member name="P:RobotNet10.Script.MissionStatus.Message">
<summary></summary>
</member>
<member name="T:RobotNet10.Script.TaskAttribute">
<summary>
Thuộc tính để đánh dấu một phương thức là một tác vụ định kỳ trong hệ thống RobotNet.
</summary>
<param name="interval"></param>
<param name="autoStart"></param>
</member>
<member name="M:RobotNet10.Script.TaskAttribute.#ctor(System.Int32,System.Boolean)">
<summary>
Thuộc tính để đánh dấu một phương thức là một tác vụ định kỳ trong hệ thống RobotNet.
</summary>
<param name="interval"></param>
<param name="autoStart"></param>
</member>
<member name="P:RobotNet10.Script.TaskAttribute.Interval">
<summary>
Thời gian định kỳ để thực hiện tác vụ, tính bằng giây.
</summary>
</member>
<member name="P:RobotNet10.Script.TaskAttribute.AutoStart">
<summary>
Cho phép tác vụ này tự động bắt đầu khi hệ thống khởi động hay không.
</summary>
</member>
<member name="T:RobotNet10.Script.VariableAttribute">
<summary>
Khai báo biến được phép đọc giá trị từ ngoài hệ thống RobotNet Script
</summary>
<param name="publicWrite"></param>
</member>
<member name="M:RobotNet10.Script.VariableAttribute.#ctor(System.Boolean)">
<summary>
Khai báo biến được phép đọc giá trị từ ngoài hệ thống RobotNet Script
</summary>
<param name="publicWrite"></param>
</member>
<member name="P:RobotNet10.Script.VariableAttribute.PublicWrite">
<summary>
Khai báo biến được phép ghi giá trị từ ngoài hệ thống RobotNet Script
</summary>
</member>
</members>
</doc>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,135 @@
// Tim781S LiDAR Visualization JavaScript
let canvas, ctx;
let centerX, centerY;
let scale = 20; // pixels per meter
let maxDisplay = 20; // max display range in meters
function initLidarCanvas() {
canvas = document.getElementById('lidarCanvas');
if (!canvas) {
console.error('LiDAR canvas not found');
return;
}
ctx = canvas.getContext('2d');
centerX = canvas.width / 2;
centerY = canvas.height / 2;
// Draw initial grid
drawGrid();
console.log('LiDAR canvas initialized');
}
function drawGrid() {
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw background
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw grid circles (range rings)
ctx.strokeStyle = '#2a2a2a';
ctx.lineWidth = 1;
for (let r = 5; r <= maxDisplay; r += 5) {
ctx.beginPath();
ctx.arc(centerX, centerY, r * scale, 0, 2 * Math.PI);
ctx.stroke();
// Draw range labels
ctx.fillStyle = '#666';
ctx.font = '12px monospace';
ctx.fillText(r + 'm', centerX + 5, centerY - r * scale + 15);
}
// Draw axes
ctx.strokeStyle = '#444';
ctx.lineWidth = 2;
// X axis
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(canvas.width, centerY);
ctx.stroke();
// Y axis
ctx.beginPath();
ctx.moveTo(centerX, 0);
ctx.lineTo(centerX, canvas.height);
ctx.stroke();
// Draw robot position (center circle)
ctx.fillStyle = '#00ff88';
ctx.beginPath();
ctx.arc(centerX, centerY, 5, 0, 2 * Math.PI);
ctx.fill();
// Draw forward direction indicator
ctx.strokeStyle = '#00ff88';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(centerX, centerY - 30);
ctx.stroke();
}
function drawLidarScan(points) {
if (!ctx || !Array.isArray(points)) return;
// Redraw grid first
drawGrid();
// Draw scan points
points.forEach(point => {
const angle = point.angle;
const range = point.range;
// Convert polar to Cartesian (LiDAR coordinate frame)
// In LiDAR frame: 0° = forward (up on screen)
// We need to rotate 90° so forward matches up on screen
const x = centerX + (range * scale * Math.sin(angle));
const y = centerY - (range * scale * Math.cos(angle));
// Color based on distance (closer = brighter green, farther = darker)
const intensity = 1.0 - (range / maxDisplay);
const r = Math.floor(0 * intensity);
const g = Math.floor(255 * intensity);
const b = Math.floor(136 * intensity);
// Draw point
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI);
ctx.fill();
});
// Draw connection lines (optional - creates solid appearance)
if (points.length > 1) {
ctx.strokeStyle = 'rgba(0, 255, 136, 0.3)';
ctx.lineWidth = 1;
ctx.beginPath();
points.forEach((point, index) => {
const angle = point.angle;
const range = point.range;
const x = centerX + (range * scale * Math.sin(angle));
const y = centerY - (range * scale * Math.cos(angle));
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
}
}
// Make functions available globally
window.initLidarCanvas = initLidarCanvas;
window.drawLidarScan = drawLidarScan;

View File

@@ -0,0 +1,379 @@
// Velocity Chart Renderer
// Renders line charts for velocity data comparison (OdomVel vs CmdVel)
window.velocityChartRenderer = {
linearChartCtx: null,
angularChartCtx: null,
linearChartData: { odom: [], cmd: [], times: [] },
angularChartData: { odom: [], cmd: [], times: [] },
initLinearVelocityChart: function(canvas) {
if (!canvas || typeof canvas.getContext !== 'function') return;
try {
this.linearChartCtx = canvas.getContext('2d');
this.linearChartData = { odom: [], cmd: [], times: [] };
} catch (e) {
console.error('Error initializing linear velocity chart:', e);
}
},
initAngularVelocityChart: function(canvas) {
if (!canvas || typeof canvas.getContext !== 'function') return;
try {
this.angularChartCtx = canvas.getContext('2d');
this.angularChartData = { odom: [], cmd: [], times: [] };
} catch (e) {
console.error('Error initializing angular velocity chart:', e);
}
},
updateLinearVelocityChart: function(canvas, data) {
if (!canvas || !data || data.length === 0) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight || 150;
// Clear canvas
ctx.clearRect(0, 0, width, height);
if (data.length === 0) return;
// Calculate min/max values for scaling
let dataMinVal = Math.min(...data.map(d => Math.min(d.odomVel, d.cmdVel)));
let dataMaxVal = Math.max(...data.map(d => Math.max(d.odomVel, d.cmdVel)));
// Set grid spacing to 0.1 m/s
const gridStep = 0.1;
// Round min/max to nearest 0.1 and add padding
let minVal = Math.floor(dataMinVal / gridStep) * gridStep;
let maxVal = Math.ceil(dataMaxVal / gridStep) * gridStep;
// Ensure at least some range
if (minVal === maxVal) {
minVal -= gridStep;
maxVal += gridStep;
}
// Add extra padding
minVal -= gridStep;
maxVal += gridStep;
// Calculate time range
const maxTime = Math.max(...data.map(d => d.time));
const minTime = Math.min(...data.map(d => d.time));
const timeRange = maxTime - minTime || 1;
// Padding
const padding = { top: 20, right: 20, bottom: 30, left: 50 };
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// Draw axes
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
// X axis
ctx.beginPath();
ctx.moveTo(padding.left, height - padding.bottom);
ctx.lineTo(width - padding.right, height - padding.bottom);
ctx.stroke();
// Y axis
ctx.beginPath();
ctx.moveTo(padding.left, padding.top);
ctx.lineTo(padding.left, height - padding.bottom);
ctx.stroke();
// Draw grid lines with 0.1 m/s spacing
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 0.5;
// Horizontal grid lines at 0.1 m/s intervals
const valueRange = maxVal - minVal;
const numSteps = Math.round(valueRange / gridStep);
// Determine label step based on range to avoid overcrowding
// If range is large, show labels less frequently
let labelStep = 1; // Show every label by default
if (valueRange > 1.0) {
labelStep = 2; // Show every 2nd label if range > 1.0
}
if (valueRange > 2.0) {
labelStep = 5; // Show every 5th label if range > 2.0
}
if (valueRange > 5.0) {
labelStep = 10; // Show every 10th label if range > 5.0
}
for (let i = 0; i <= numSteps; i++) {
const value = minVal + i * gridStep;
const y = padding.top + chartHeight - ((value - minVal) / valueRange) * chartHeight;
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(width - padding.right, y);
ctx.stroke();
// Y axis labels - only show if it's a multiple of labelStep
if (i % labelStep === 0) {
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'right';
ctx.fillText(value.toFixed(2), padding.left - 5, y + 3);
}
}
// Vertical grid lines (time)
const xSteps = 5;
for (let i = 0; i <= xSteps; i++) {
const x = padding.left + (chartWidth / xSteps) * i;
ctx.beginPath();
ctx.moveTo(x, padding.top);
ctx.lineTo(x, height - padding.bottom);
ctx.stroke();
// X axis labels
const time = minTime + (timeRange / xSteps) * i;
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.fillText(time.toFixed(1) + 's', x, height - padding.bottom + 15);
}
// Draw OdomVel line (blue - #2196f3)
if (data.length >= 1) {
ctx.strokeStyle = '#2196f3'; // Blue
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#2196f3';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
// Draw CmdVel line (green - #4caf50)
if (data.length >= 1) {
ctx.strokeStyle = '#4caf50'; // Green
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#4caf50';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
},
updateAngularVelocityChart: function(canvas, data) {
if (!canvas || !data || data.length === 0) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight || 150;
// Clear canvas
ctx.clearRect(0, 0, width, height);
if (data.length === 0) return;
// Calculate min/max values for scaling
let dataMinVal = Math.min(...data.map(d => Math.min(d.odomVel, d.cmdVel)));
let dataMaxVal = Math.max(...data.map(d => Math.max(d.odomVel, d.cmdVel)));
// Set grid spacing to 0.1 rad/s
const gridStep = 0.1;
// Round min/max to nearest 0.1 and add padding
let minVal = Math.floor(dataMinVal / gridStep) * gridStep;
let maxVal = Math.ceil(dataMaxVal / gridStep) * gridStep;
// Ensure at least some range
if (minVal === maxVal) {
minVal -= gridStep;
maxVal += gridStep;
}
// Add extra padding
minVal -= gridStep;
maxVal += gridStep;
// Calculate time range
const maxTime = Math.max(...data.map(d => d.time));
const minTime = Math.min(...data.map(d => d.time));
const timeRange = maxTime - minTime || 1;
// Padding
const padding = { top: 20, right: 20, bottom: 30, left: 50 };
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// Draw axes
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
// X axis
ctx.beginPath();
ctx.moveTo(padding.left, height - padding.bottom);
ctx.lineTo(width - padding.right, height - padding.bottom);
ctx.stroke();
// Y axis
ctx.beginPath();
ctx.moveTo(padding.left, padding.top);
ctx.lineTo(padding.left, height - padding.bottom);
ctx.stroke();
// Draw grid lines with 0.1 rad/s spacing
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 0.5;
// Horizontal grid lines at 0.1 rad/s intervals
const valueRange = maxVal - minVal;
const numSteps = Math.round(valueRange / gridStep);
// Determine label step based on range to avoid overcrowding
// If range is large, show labels less frequently
let labelStep = 1; // Show every label by default
if (valueRange > 1.0) {
labelStep = 2; // Show every 2nd label if range > 1.0
}
if (valueRange > 2.0) {
labelStep = 5; // Show every 5th label if range > 2.0
}
if (valueRange > 5.0) {
labelStep = 10; // Show every 10th label if range > 5.0
}
for (let i = 0; i <= numSteps; i++) {
const value = minVal + i * gridStep;
const y = padding.top + chartHeight - ((value - minVal) / valueRange) * chartHeight;
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(width - padding.right, y);
ctx.stroke();
// Y axis labels - only show if it's a multiple of labelStep
if (i % labelStep === 0) {
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'right';
ctx.fillText(value.toFixed(1), padding.left - 5, y + 3);
}
}
// Vertical grid lines (time)
const xSteps = 5;
for (let i = 0; i <= xSteps; i++) {
const x = padding.left + (chartWidth / xSteps) * i;
ctx.beginPath();
ctx.moveTo(x, padding.top);
ctx.lineTo(x, height - padding.bottom);
ctx.stroke();
// X axis labels
const time = minTime + (timeRange / xSteps) * i;
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.fillText(time.toFixed(1) + 's', x, height - padding.bottom + 15);
}
// Draw OdomVel line (blue - #2196f3)
if (data.length >= 1) {
ctx.strokeStyle = '#2196f3'; // Blue
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#2196f3';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
// Draw CmdVel line (green - #4caf50)
if (data.length >= 1) {
ctx.strokeStyle = '#4caf50'; // Green
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#4caf50';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
}
};

File diff suppressed because it is too large Load Diff