A modern PLC (Programmable Logic Controller) implementation in TypeScript/Deno with support for Modbus, OPC UA, and Sparkplug MQTT protocols.
- 🔌 Multi-protocol support:
- Modbus TCP/IP
- OPC UA
- Sparkplug MQTT
- 🚀 Built with TypeScript and Deno for type safety and modern development
- 📊 GraphQL API for easy integration
- ⚡ High-performance asynchronous I/O
- 🔄 Automatic reconnection handling
- 📝 Comprehensive logging and error handling
# Add to your deps.ts or import_map.json
import * as tentacle from "joyautomation/tentacle@$VERSION";Here's a simple example that creates a PLC with Modbus and MQTT:
import { createPlc } from "joyautomation/tentacle";
// Define your PLC configuration
const config = {
tasks: {
readModbus: {
interval: 1000, // Run every second
variables: ["temperature", "pressure"],
},
},
variables: {
temperature: {
source: {
type: "modbus",
register: 40001,
registerType: "HOLDING_REGISTER",
format: "FLOAT32",
},
},
pressure: {
source: {
type: "modbus",
register: 40003,
registerType: "HOLDING_REGISTER",
format: "FLOAT32",
},
},
},
mqtt: {
main: {
serverUrl: "mqtt://localhost:1883",
groupId: "Sparkplug B/Group",
edgeNode: "Node1",
clientId: "tentacle-plc1",
},
},
sources: {
modbus1: {
type: "modbus",
host: "192.168.1.100",
port: 502,
unitId: 1,
},
},
};
// Create and start the PLC
const plc = await createPlc(config);
// Access the GraphQL API
const server = await createGraphQLServer(plc);
server.listen(4000);The PLC exposes a GraphQL API for monitoring and control. Here are some example queries:
# Get PLC status
query {
plc {
runtime {
tasks {
name
lastRun
nextRun
error
}
variables {
name
value
quality
timestamp
}
mqtt {
connected
lastMessageSent
}
sources {
name
connected
error
}
}
}
}Tasks are periodic operations that read or write variables:
tasks: {
name: string;
interval: number;
variables: string[];
enabled?: boolean;
}Variables represent data points that can be read from or written to sources:
variables: {
name: string;
source: {
type: "modbus" | "opcua";
// Modbus-specific config
register?: number;
registerType?: ModbusRegisterType;
format?: ModbusFormat;
// OPC UA-specific config
nodeId?: string;
};
}Sources define the connections to external systems:
sources: {
name: string;
type: "modbus" | "opcua";
// Modbus-specific config
host?: string;
port?: number;
unitId?: number;
// OPC UA-specific config
endpointUrl?: string;
}Tentacle uses the Result type from @joyautomation/dark-matter for consistent error handling:
import { type Result } from "@joyautomation/dark-matter";
const result = await plc.writeVariable("temperature", 23.5);
if (!result.success) {
console.error(`Failed to write: ${result.error}`);
}Contributions are welcome! Please read our Contributing Guidelines for details.
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.