colors = ({
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
red: "#E74C3C",
green: "#27AE60",
purple: "#9B59B6",
darkTeal: "#1ABC9C",
lightNavy: "#34495E",
yellow: "#F39C12",
blue: "#3498DB",
darkGray: "#34495E"
})
// Function code definitions
functionCodes = [
{ code: 0x01, name: "Read Coils", description: "Read discrete output status (bits)", type: "read", dataType: "coil", write: false },
{ code: 0x02, name: "Read Discrete Inputs", description: "Read discrete input status (bits)", type: "read", dataType: "discrete", write: false },
{ code: 0x03, name: "Read Holding Registers", description: "Read holding register values (16-bit)", type: "read", dataType: "holding", write: false },
{ code: 0x04, name: "Read Input Registers", description: "Read input register values (16-bit)", type: "read", dataType: "input", write: false },
{ code: 0x05, name: "Write Single Coil", description: "Write single discrete output", type: "write", dataType: "coil", write: true },
{ code: 0x06, name: "Write Single Register", description: "Write single holding register", type: "write", dataType: "holding", write: true },
{ code: 0x0F, name: "Write Multiple Coils", description: "Write multiple discrete outputs", type: "write", dataType: "coil", write: true },
{ code: 0x10, name: "Write Multiple Registers", description: "Write multiple holding registers", type: "write", dataType: "holding", write: true }
]
viewof selectedFunction = Inputs.select(functionCodes.map(f => `FC${f.code.toString(16).toUpperCase().padStart(2, '0')} - ${f.name}`), {
value: "FC03 - Read Holding Registers",
label: "Select Function Code"
})
parsedFunction = {
const match = selectedFunction.match(/FC([0-9A-F]{2})/);
if (match) {
const code = parseInt(match[1], 16);
return functionCodes.find(f => f.code === code) || functionCodes[0];
}
return functionCodes[0];
}
functionCodeTable = {
const width = 900;
const height = 520;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", "100%")
.attr("height", height)
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("background", colors.white)
.style("border", `1px solid ${colors.lightGray}`)
.style("border-radius", "8px");
// Title
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Modbus Function Codes");
// Headers
const headers = ["Code", "Name", "Description", "Type", "Request Format", "Response Format"];
const colWidths = [60, 150, 200, 60, 200, 200];
const startX = 15;
const headerY = 60;
let x = startX;
headers.forEach((header, i) => {
svg.append("text")
.attr("x", x + 5)
.attr("y", headerY)
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text(header);
x += colWidths[i];
});
// Header line
svg.append("line")
.attr("x1", startX)
.attr("y1", headerY + 8)
.attr("x2", width - startX)
.attr("y2", headerY + 8)
.attr("stroke", colors.navy)
.attr("stroke-width", 2);
// Function code rows
const rowHeight = 50;
const rowData = [
{ code: "01", name: "Read Coils", desc: "Read 1-2000 contiguous coils", type: "R",
req: "Addr(2) + Qty(2)", resp: "ByteCnt(1) + Data(n)" },
{ code: "02", name: "Read Discrete Inputs", desc: "Read 1-2000 discrete inputs", type: "R",
req: "Addr(2) + Qty(2)", resp: "ByteCnt(1) + Data(n)" },
{ code: "03", name: "Read Holding Regs", desc: "Read 1-125 holding registers", type: "R",
req: "Addr(2) + Qty(2)", resp: "ByteCnt(1) + Data(n*2)" },
{ code: "04", name: "Read Input Regs", desc: "Read 1-125 input registers", type: "R",
req: "Addr(2) + Qty(2)", resp: "ByteCnt(1) + Data(n*2)" },
{ code: "05", name: "Write Single Coil", desc: "Write single coil ON/OFF", type: "W",
req: "Addr(2) + Value(2)", resp: "Echo request" },
{ code: "06", name: "Write Single Reg", desc: "Write single 16-bit register", type: "W",
req: "Addr(2) + Value(2)", resp: "Echo request" },
{ code: "0F", name: "Write Multiple Coils", desc: "Write 1-1968 coils", type: "W",
req: "Addr(2)+Qty(2)+Cnt(1)+Data", resp: "Addr(2) + Qty(2)" },
{ code: "10", name: "Write Multiple Regs", desc: "Write 1-123 registers", type: "W",
req: "Addr(2)+Qty(2)+Cnt(1)+Data", resp: "Addr(2) + Qty(2)" }
];
rowData.forEach((row, i) => {
const y = headerY + 25 + i * rowHeight;
const isSelected = `FC${row.code}` === selectedFunction.substring(0, 4);
// Row background
svg.append("rect")
.attr("x", startX)
.attr("y", y - 12)
.attr("width", width - startX * 2)
.attr("height", rowHeight - 5)
.attr("rx", 4)
.attr("fill", isSelected ? colors.teal : (i % 2 === 0 ? colors.lightGray : colors.white))
.attr("opacity", isSelected ? 0.2 : 0.5);
let x = startX;
// Code
svg.append("text")
.attr("x", x + 5)
.attr("y", y + 5)
.attr("font-size", "12px")
.attr("font-weight", "bold")
.attr("font-family", "monospace")
.attr("fill", isSelected ? colors.teal : colors.navy)
.text(`0x${row.code}`);
x += colWidths[0];
// Name
svg.append("text")
.attr("x", x + 5)
.attr("y", y + 5)
.attr("font-size", "10px")
.attr("font-weight", isSelected ? "bold" : "normal")
.attr("fill", colors.navy)
.text(row.name);
x += colWidths[1];
// Description
svg.append("text")
.attr("x", x + 5)
.attr("y", y + 5)
.attr("font-size", "9px")
.attr("fill", colors.gray)
.text(row.desc);
x += colWidths[2];
// Type
svg.append("rect")
.attr("x", x + 5)
.attr("y", y - 8)
.attr("width", 25)
.attr("height", 18)
.attr("rx", 4)
.attr("fill", row.type === "R" ? colors.blue : colors.orange);
svg.append("text")
.attr("x", x + 17)
.attr("y", y + 5)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", colors.white)
.text(row.type);
x += colWidths[3];
// Request format
svg.append("text")
.attr("x", x + 5)
.attr("y", y + 5)
.attr("font-size", "9px")
.attr("font-family", "monospace")
.attr("fill", colors.darkGray)
.text(row.req);
x += colWidths[4];
// Response format
svg.append("text")
.attr("x", x + 5)
.attr("y", y + 5)
.attr("font-size", "9px")
.attr("font-family", "monospace")
.attr("fill", colors.darkGray)
.text(row.resp);
// Row separator
if (i < rowData.length - 1) {
svg.append("line")
.attr("x1", startX)
.attr("y1", y + rowHeight / 2 - 5)
.attr("x2", width - startX)
.attr("y2", y + rowHeight / 2 - 5)
.attr("stroke", colors.lightGray)
.attr("stroke-width", 0.5);
}
});
// Legend
const legendY = height - 35;
svg.append("text")
.attr("x", startX)
.attr("y", legendY)
.attr("font-size", "10px")
.attr("fill", colors.gray)
.text("Format: Field(bytes) | R = Read, W = Write | All values Big-Endian (MSB first)");
return svg.node();
}