A portable Modbus RTU, ASCII, and TCP protocol stack with device simulation and gateway primitives for MoonBit.
moon update
moon check --target all --deny-warn
moon test --target all --deny-warn
moon run cmd/main///|
let request = @moonbit-modbus.read_holding(1, 0, 4)
///|
let rtu_bytes = @moonbit-modbus.encode_rtu(request)
///|
let parsed = @moonbit-modbus.decode_rtu(rtu_bytes)///|
let parser = @moonbit-modbus.IncrementalParser::new(@moonbit-modbus.rtu_mode())
///|
let frames = parser.feed(rtu_bytes)# 展示请求编码、虚拟设备处理和响应格式化
moon run --target native cmd/main
# 运行 1000 次 CRC、RTU、TCP 和设备读工作负载
moon run --target native cmd/benchmoon fmt --check
moon info
moon check --target all --deny-warn
moon test --target all --deny-warnfn AddressAllocator::allocate(self : AddressAllocator, length : Int) -> Result[AddressRange, ModbusError]fn AddressAllocator::release(self : AddressAllocator, range : AddressRange) -> Result[Unit, ModbusError]pub(all) struct AddressRange {
start : UInt16
length : Int
}fn AddressRange::slice(self : AddressRange, offset : Int, length : Int) -> Result[AddressRange, ModbusError]fn AddressRange::split(self : AddressRange, segment_length : Int) -> Result[Array[AddressRange], ModbusError]pub(all) struct AlarmMonitor {
threshold : Threshold
state : ThresholdState
transitions : Int
last_value : Int?
}pub(all) struct AuditEntry {
sequence : Int
direction : String
mode : Mode
transaction_id : UInt16
summary : FrameSummary
outcome : String
}pub(all) struct BenchmarkResult {
name : String
iterations : Int
successful : Int
failed : Int
bytes : Int
}fn BitVector::pack_range(self : BitVector, start : Int, quantity : Int) -> Result[Array[Byte], ModbusError]fn BitVector::unpack_into(self : BitVector, start : Int, quantity : Int, bytes : Array[Byte]) -> Result[Unit, ModbusError]pub struct Client {
mode : Mode
config : ClientConfig
next_transaction : UInt16
pending : Array[PendingExchange]
}fn Client::accept(self : Client, request : ClientRequest, bytes : Array[Byte]) -> Result[Frame, ModbusError]fn Client::accept_frame(self : Client, request : ClientRequest, transaction_id : UInt16, response : Frame) -> Result[Frame, ModbusError]pub(all) struct ClientConfig {
timeout_ticks : Int
retries : Int
max_pending : Int
strict_unit : Bool
}fn CoilBank::read(self : CoilBank, address : UInt16, quantity : Int) -> Result[Array[Bool], ModbusError]fn CoilBank::write(self : CoilBank, address : UInt16, values : Array[Bool]) -> Result[Unit, ModbusError]pub(all) struct ConformanceResult {
name : String
passed : Bool
detail : String
}pub(all) struct ConformanceVector {
name : String
mode : Mode
bytes : Array[Byte]
expected_function : Byte?
expected_error : ModbusError?
}fn ConformanceVector::invalid(name : String, mode : Mode, bytes : Array[Byte], expected_error : ModbusError) -> ConformanceVectorfn ConformanceVector::valid(name : String, mode : Mode, bytes : Array[Byte], expected_function : Byte) -> ConformanceVectorpub(all) struct Counter {
name : String
value : Int
}pub(all) struct Crc16State {
value : UInt16
}pub struct Device {
unit_id : Byte
memory : DeviceMemory
file_store : FileRecordStore
server_id : Array[Byte]
event_count : UInt16
busy : Bool
exception_count : Int
}fn Device::new(unit_id : Byte, coil_capacity? : Int, register_capacity? : Int) -> Result[Device, ModbusError]pub(all) struct DeviceHealth {
unit_id : Byte
event_count : UInt16
exception_count : Int
busy : Bool
}pub(all) struct DeviceIdObject {
object_id : Byte
value : String
}pub(all) struct DeviceIdentification {
category : DeviceIdCategory
conformity : Byte
more_follows : Bool
next_object_id : Byte
objects : Array[DeviceIdObject]
}pub struct DeviceIdentityCatalog {
category : DeviceIdCategory
objects : Array[DeviceIdObject]
conformity : Byte
}fn DeviceIdentityCatalog::add(self : DeviceIdentityCatalog, object : DeviceIdObject) -> Result[Unit, ModbusError]fn DeviceIdentityCatalog::response(self : DeviceIdentityCatalog, request : Frame) -> Result[Frame, ModbusError]pub(all) struct DeviceMemory {
coils : CoilBank
discrete_inputs : CoilBank
input_registers : RegisterBank
holding_registers : RegisterBank
}fn DeviceMemory::load_discrete(self : DeviceMemory, address : UInt16, values : Array[Bool]) -> Result[Unit, ModbusError]fn DeviceMemory::load_holding(self : DeviceMemory, address : UInt16, values : Array[UInt16]) -> Result[Unit, ModbusError]fn DeviceMemory::load_input(self : DeviceMemory, address : UInt16, values : Array[UInt16]) -> Result[Unit, ModbusError]fn DeviceMemory::new(coil_capacity? : Int, register_capacity? : Int) -> Result[DeviceMemory, ModbusError]fn EncodedAdu::new(mode : Mode, transaction_id : UInt16, frame : Frame) -> Result[EncodedAdu, ModbusError]pub(all) struct EndpointHealth {
unit_id : Byte
ready : Bool
event_count : UInt16
exceptions : Int
requests : Int
responses : Int
errors : Int
}pub(all) struct EndpointRuntime {
config : EndpointConfig
state : EndpointState
opened_at : Int
last_error : ModbusError?
reconnects : Int
frames_in : Int
frames_out : Int
}fn ExchangeEnvelope::new(source : String, destination : String, adu : EncodedAdu) -> ExchangeEnvelopepub(all) struct FileRecordReference {
reference_type : Byte
file_number : UInt16
record_number : UInt16
record_length : UInt16
}fn FileRecordReference::new(file_number : UInt16, record_number : UInt16, record_length : UInt16, reference_type? : Byte) -> Result[FileRecordReference, ModbusError]fn FileRecordStore::get(self : FileRecordStore, reference : FileRecordReference) -> Result[Array[UInt16], ModbusError]fn FileRecordStore::put(self : FileRecordStore, record : FileRecordWrite) -> Result[Unit, ModbusError]fn FileRecordWrite::new(reference : FileRecordReference, values : Array[UInt16]) -> Result[FileRecordWrite, ModbusError]fn FrameSequence::validate(self : FrameSequence, allow_broadcast : Bool) -> Result[Unit, ModbusError]pub struct FrameStream {
mode : Mode
parser : IncrementalParser
queue : ByteQueue
chunks : Int
frames : Int
}fn FrameStream::new(mode : Mode, role? : ParserRole, max_buffer? : Int, max_frame? : Int) -> Result[FrameStream, ModbusError]pub(all) struct FrameSummary {
unit_id : Byte
function : Byte
data_length : Int
exception : Bool
}fn FrameTextLog::record_wire(self : FrameTextLog, direction : String, mode : Mode, transaction_id : UInt16, frame : Frame) -> Unitpub(all) enum FunctionCode {
ReadCoils
ReadDiscreteInputs
ReadHoldingRegisters
ReadInputRegisters
WriteSingleCoil
WriteSingleRegister
ReadExceptionStatus
Diagnostics
GetCommEventCounter
GetCommEventLog
ReportServerId
WriteMultipleCoils
WriteMultipleRegisters
ReportServerIdExtended
ReadFileRecord
WriteFileRecord
MaskWriteRegister
ReadWriteMultipleRegisters
ReadFifoQueue
EncapsulatedInterface
Unknown(Byte)
} derive(Eq, Debug)pub(all) struct FunctionDescriptor {
function : Byte
name : String
request_min : Int
request_max : Int
response_variable : Bool
writable : Bool
description : String
}pub(all) struct GatewayRule {
source_unit : Byte
target_unit : Byte
function : Byte
source_start : UInt16
target_start : UInt16
span : Int
enabled : Bool
}fn GatewayRule::new(source_unit : Byte, target_unit : Byte, function : Byte, source_start : UInt16, target_start : UInt16, span : Int) -> Result[GatewayRule, ModbusError]pub struct IncrementalParser {
mode : Mode
role : ParserRole
buffer : Array[Byte]
max_frame : Int
frames_seen : Int
errors_seen : Int
}fn IncrementalParser::feed(self : IncrementalParser, input : Array[Byte]) -> Result[Array[Frame], ModbusError]fn IncrementalParser::feed_byte(self : IncrementalParser, byte : Byte) -> Result[Array[Frame], ModbusError]fn IncrementalParser::set_max_frame(self : IncrementalParser, max_frame : Int) -> Result[Unit, ModbusError]pub(all) struct PendingExchange {
transaction_id : UInt16
frame : Frame
attempts : Int
created_at : Int
last_sent_at : Int
state : ExchangeState
}pub(all) struct PlanSummary {
requests : Int
total_registers : Int
total_coils : Int
total_bytes : Int
}fn PollJob::new(id : Int, name : String, request : Frame, interval? : Int, timeout? : Int, retries? : Int) -> Result[PollJob, ModbusError]fn PollPlan::mark_success(self : PollPlan, id : Int, now : Int, response : Frame) -> Result[Unit, ModbusError]pub(all) struct ProtocolLimits {
max_adu : Int
max_pdu : Int
max_registers : Int
max_coils : Int
max_buffer : Int
}pub(all) struct ProtocolMetrics {
requests : Int
responses : Int
exceptions : Int
errors : Int
bytes_in : Int
bytes_out : Int
active : Int
latency : LatencyHistogram
}fn ProtocolMetrics::record_request(self : ProtocolMetrics, bytes : Int) -> Result[Unit, ModbusError]fn ProtocolMetrics::record_response(self : ProtocolMetrics, bytes : Int, latency : Int, exception : Bool) -> Result[Unit, ModbusError]fn RegisterBank::new(start : UInt16, length : Int, writable? : Bool) -> Result[RegisterBank, ModbusError]fn RegisterBank::read(self : RegisterBank, address : UInt16, quantity : Int) -> Result[Array[UInt16], ModbusError]fn RegisterBank::set(self : RegisterBank, address : UInt16, value : UInt16) -> Result[Unit, ModbusError]fn RegisterBank::write(self : RegisterBank, address : UInt16, values : Array[UInt16]) -> Result[Unit, ModbusError]fn RegisterBank::write_mask(self : RegisterBank, address : UInt16, and_mask : UInt16, or_mask : UInt16) -> Result[UInt16, ModbusError]fn RegisterCursor::read_f32(self : RegisterCursor, order : RegisterWordOrder) -> Result[Float, ModbusError]fn RegisterCursor::read_i32(self : RegisterCursor, order : RegisterWordOrder) -> Result[Int, ModbusError]fn RegisterCursor::read_u32(self : RegisterCursor, order : RegisterWordOrder) -> Result[UInt, ModbusError]fn RegisterCursor::read_u64(self : RegisterCursor, order : RegisterWordOrder) -> Result[UInt64, ModbusError]pub(all) struct RegisterField {
name : String
address : UInt16
kind : RegisterFieldKind
writable : Bool
scale : Float
}fn RegisterField::new(name : String, address : UInt16, kind : RegisterFieldKind, writable? : Bool, scale? : Float) -> Result[RegisterField, ModbusError]fn RegisterSchema::read(self : RegisterSchema, name : String, bank : RegisterBank) -> Result[RegisterValue, ModbusError]fn RegisterSchema::read_all(self : RegisterSchema, bank : RegisterBank) -> Result[Array[(String, RegisterValue)], ModbusError]fn RegisterSchema::validate_against(self : RegisterSchema, bank : RegisterBank) -> Result[Unit, ModbusError]fn RegisterSchema::write(self : RegisterSchema, name : String, value : RegisterValue, bank : RegisterBank) -> Result[Unit, ModbusError]pub(all) enum RegisterValue {
U16(UInt16)
I16(Int)
U32(UInt)
I32(Int)
F32(Float)
U64(UInt64)
Bits(Array[Bool])
}fn RegisterWriter::push_f32(self : RegisterWriter, value : Float, order : RegisterWordOrder) -> Result[Unit, ModbusError]fn RegisterWriter::push_u32(self : RegisterWriter, value : UInt, order : RegisterWordOrder) -> Result[Unit, ModbusError]fn RegisterWriter::push_u64(self : RegisterWriter, value : UInt64, order : RegisterWordOrder) -> Result[Unit, ModbusError]pub(all) struct SerialConfig {
path : String
baud : Int
data_bits : Int
stop_bits : Int
parity : Byte
mode : Mode
}fn SerialConfig::new(path : String, baud : Int, mode : Mode, data_bits? : Int, stop_bits? : Int, parity? : Byte) -> Result[SerialConfig, ModbusError]pub struct Server {
config : ServerConfig
devices : Array[Device]
parser : IncrementalParser
metrics : ProtocolMetrics
audit : AuditLog
}pub(all) struct ServerConfig {
mode : Mode
max_devices : Int
max_frame : Int
allow_broadcast : Bool
max_pending_bytes : Int
}pub struct Simulation {
device : Device
mode : Mode
fault : FaultMode
clock : Int
metrics : ProtocolMetrics
audit : AuditLog
}fn Simulation::request(self : Simulation, transaction_id : UInt16, request : Frame) -> SimulationResultpub(all) struct TcpConfig {
host : String
port : Int
connect_timeout : Int
keep_alive : Bool
}pub(all) struct Threshold {
low : Int
high : Int
}pub(all) struct TransportCapabilities {
mode : Mode
max_adu : Int
supports_broadcast : Bool
preserves_transaction_id : Bool
}fn VirtualLink::round_trip(self : VirtualLink, device : Device, transaction_id : UInt16, request : Frame) -> Result[DeviceResponse, ModbusError]fn VirtualLink::send_request(self : VirtualLink, transaction_id : UInt16, frame : Frame) -> Result[Unit, ModbusError]fn VirtualLink::send_response(self : VirtualLink, transaction_id : UInt16, frame : Frame) -> Result[Unit, ModbusError]fn bytes_to_register(bytes : Array[Byte], offset : Int, order : RegisterByteOrder) -> Result[UInt16, ModbusError]fn bytes_to_registers(bytes : Array[Byte], order : RegisterByteOrder) -> Result[Array[UInt16], ModbusError]fn crc16_update(crc : UInt16, byte : Byte) -> UInt16fn decode_comm_event_counter(request : Frame, response : Frame) -> Result[(UInt16, UInt16), ModbusError]fn decode_device_identification(request : Frame, response : Frame) -> Result[DeviceIdentification, ModbusError]fn decode_diagnostics(request : Frame, response : Frame) -> Result[(UInt16, Array[Byte]), ModbusError]fn decode_file_record_response(request : Frame, response : Frame) -> Result[Array[Array[UInt16]], ModbusError]fn decode_file_record_write_response(request : Frame, response : Frame) -> Result[Unit, ModbusError]fn decode_multiple_write(request : Frame, response : Frame) -> Result[(UInt16, UInt16), ModbusError]fn decode_read_write_response(request : Frame, response : Frame) -> Result[Array[UInt16], ModbusError]fn decode_register_response(request : Frame, response : Frame) -> Result[Array[UInt16], ModbusError]fn diagnostics(unit_id : Byte, subfunction : UInt16, data : Array[Byte]) -> Result[Frame, ModbusError]fn encapsulated_interface(unit_id : Byte, mei_type : Byte, payload : Array[Byte]) -> Result[Frame, ModbusError]fn encode_device_identification(unit_id : Byte, category : DeviceIdCategory, conformity : Byte, more_follows : Bool, next_object_id : Byte, objects : Array[DeviceIdObject]) -> Result[Frame, ModbusError]fn float_words(high : UInt16, low : UInt16) -> UInt64fn function_name(function : Byte) -> Stringfn gateway_function_supported(function : Byte) -> Boolfn is_broadcast(unit_id : Byte) -> Boolfn is_valid_unit_id(unit_id : Byte, broadcast? : Bool) -> Boolfn lrc_from_sum(sum : Byte) -> Bytefn mask_write_register(unit_id : Byte, address : UInt16, and_mask : UInt16, or_mask : UInt16) -> Result[Frame, ModbusError]fn ordinary_function(function : Byte) -> Bytefn plan_read_coils(unit_id : Byte, address : UInt16, quantity : Int, discrete? : Bool) -> Result[Array[PlannedRequest], ModbusError]fn plan_read_registers(unit_id : Byte, address : UInt16, quantity : Int, function? : Byte) -> Result[Array[PlannedRequest], ModbusError]fn plan_write_registers(unit_id : Byte, address : UInt16, values : Array[UInt16]) -> Result[Array[PlannedRequest], ModbusError]fn read_device_identification(unit_id : Byte, category : DeviceIdCategory, object_id : Byte) -> Result[Frame, ModbusError]fn read_discrete_inputs(unit_id : Byte, address : UInt16, quantity : UInt16) -> Result[Frame, ModbusError]fn read_file_record(unit_id : Byte, references : Array[FileRecordReference]) -> Result[Frame, ModbusError]fn read_holding_checked(unit_id : Byte, address : UInt16, quantity : UInt16) -> Result[Frame, ModbusError]fn read_holding_registers(unit_id : Byte, address : UInt16, quantity : UInt16) -> Result[Frame, ModbusError]fn read_input_registers(unit_id : Byte, address : UInt16, quantity : UInt16) -> Result[Frame, ModbusError]fn read_quantity_limit(function : Byte) -> Intfn read_write_multiple_registers(unit_id : Byte, read_address : UInt16, read_quantity : UInt16, write_address : UInt16, write_values : Array[UInt16]) -> Result[Frame, ModbusError]fn registers_to_f32(values : Array[UInt16], word_order : RegisterWordOrder) -> Result[Float, ModbusError]fn registers_to_i32(values : Array[UInt16], word_order : RegisterWordOrder) -> Result[Int, ModbusError]fn registers_to_u32(values : Array[UInt16], word_order : RegisterWordOrder) -> Result[UInt, ModbusError]fn registers_to_u64(values : Array[UInt16], word_order : RegisterWordOrder) -> Result[UInt64, ModbusError]fn signed_register(value : UInt16) -> Intfn supported_function(function : Byte) -> Boolfn try_encode_mode(mode : Mode, transaction_id : UInt16, frame : Frame) -> Result[Array[Byte], ModbusError]fn valid_address_range(address : UInt16, quantity : Int) -> Boolfn write_file_record(unit_id : Byte, records : Array[FileRecordWrite]) -> Result[Frame, ModbusError]fn write_multiple_coils(unit_id : Byte, address : UInt16, values : Array[Bool]) -> Result[Frame, ModbusError]fn write_multiple_registers(unit_id : Byte, address : UInt16, values : Array[UInt16]) -> Result[Frame, ModbusError]fn write_quantity_limit(function : Byte) -> Intfn write_single_register_checked(unit_id : Byte, address : UInt16, value : UInt16) -> Result[Frame, ModbusError]A portable Modbus RTU, ASCII, and TCP protocol stack with device simulation and gateway primitives for MoonBit.