Skip to content

Latest commit

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Documentation

BACnet Protocol Stack

A Go implementation of the BACnet/IP protocol stack for building automation and control systems.

Features

  • BACnet/IP Protocol: Full support for BACnet/IP communication
  • Device Discovery: Who-Is and I-Am services for network device discovery
  • Object Access: ReadProperty, ReadMultipleProperty, WriteProperty, WriteMultipleProperty
  • Network Management: What-Is-Network-Number, Who-Is-Router-To-Network
  • Transaction Management: TSM (Transaction State Machine) for confirmed services
  • Concurrency: Thread-safe design with connection pooling
  • BACnet Server: Full server-side implementation with object store, automatic WhoIs→IAm responses, and confirmed service handling
  • Room Simulator: cmd/room-simulator — YABE-friendly virtual room device (see ROOM_SIMULATOR.md)

Installation

go get github.com/anviod/bacnet

Edge / cross-compile (static, no cgo): ./scripts/cross-build.sh or make cross, e.g. CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build ./... (ARMv7+).

Quick Start

Basic Device Discovery

package main
import (
"fmt""log""github.com/anviod/bacnet""github.com/anviod/bacnet/btypes"
)
funcmain() {
// Create a BACnet clientclient, err:=bacnet.NewClient(&bacnet.ClientBuilder{
Ip: "192.168.1.100",
SubnetCIDR: 24,
Port: 47808, // Default BACnet port (0xBAC0)
})
iferr!=nil {
log.Fatal(err)
}
deferclient.Close()
// Start the client message loopgoclient.ClientRun()
// Discover all devices on the networkdevices, err:=client.WhoIs(&bacnet.WhoIsOpts{
Low: 0,
High: 4194304, // Max BACnet device ID
})
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Discovered %d devices\n", len(devices))
for_, dev:=rangedevices {
fmt.Printf("Device ID: %d, IP: %s:%d\n", dev.DeviceID, dev.Ip, dev.Port)
}
}

Data Collection Flow (采集流程)

The BACnet data collection process consists of six key steps:

Step 1: Client Initialization

Before any communication can occur, a BACnet client must be created with appropriate network configuration.

client, err:=bacnet.NewClient(&bacnet.ClientBuilder{
Ip: "192.168.1.100", // Local IP addressSubnetCIDR: 24, // Subnet mask (e.g., /24)Port: 47808, // BACnet port (default: 47808)
})
iferr!=nil {
log.Fatal(err)
}
deferclient.Close()

Configuration Options:

  • Ip: Local IP address to bind to
  • Interface: Network interface name (alternative to Ip)
  • SubnetCIDR: Subnet CIDR notation (e.g., 24 for 255.255.255.0)
  • Port: BACnet UDP port (default: 47808 = 0xBAC0)
  • MaxPDU: Maximum PDU size (default: 1476)

Step 2: Start Message Loop

The client message loop must be started in a goroutine to handle incoming messages:

goclient.ClientRun()

Important Notes:

  • Must be called before making any requests
  • Runs continuously until the client is closed
  • Handles message decoding and routing

Step 3: Device Discovery (WhoIs)

Discover BACnet devices on the network using the WhoIs service:

devices, err:=client.WhoIs(&bacnet.WhoIsOpts{
Low: 0, // Device ID lower boundHigh: 4194304, // Device ID upper bound (max)
})

Discovery Options:

  • Low: Lower bound of device ID range (0 to 4194304)
  • High: Upper bound of device ID range
  • GlobalBroadcast: Use global broadcast address (0xFFFF)
  • Destination: Specific target address for unicast discovery

Best Practices:

  • Use narrow ID ranges for targeted discovery to reduce network traffic
  • Avoid using full range (0-4194304) on large networks
  • Cache discovered devices to avoid repeated discovery

Step 4: Object Discovery

Retrieve all objects from a discovered device:

scannedDevice, err:=client.Objects(devices[0])
iferr!=nil {
log.Printf("Failed to scan objects: %v", err)
return
}
// Access specific object typesaiObjects:=scannedDevice.Objects[btypes.AnalogInput]
biObjects:=scannedDevice.Objects[btypes.BinaryInput]
aoObjects:=scannedDevice.Objects[btypes.AnalogOutput]
boObjects:=scannedDevice.Objects[btypes.BinaryOutput]

Supported Object Types:

  • AnalogInput (0): Analog input points (e.g., temperature sensors)
  • AnalogOutput (1): Analog output points (e.g., valves, dampers)
  • AnalogValue (2): Analog value objects
  • BinaryInput (3): Binary input points (e.g., contact sensors)
  • BinaryOutput (4): Binary output points (e.g., relays)
  • BinaryValue (5): Binary value objects
  • Device (8): BACnet device objects
  • MultiStateInput (13): Multi-state input points
  • MultiStateOutput (14): Multi-state output points
  • TrendLog (20): Trend log objects

Step 5: Data Reading

Read property values from device objects.

Read Single Property

result, err:=client.ReadProperty(device, btypes.PropertyData{
Object: btypes.Object{
ID: btypes.ObjectID{
Type: btypes.AnalogInput,
Instance: 1,
},
Properties: []btypes.Property{
{
Type: btypes.PropPresentValue,
ArrayIndex: btypes.ArrayAll,
},
},
},
})

Read Multiple Properties (Batch)

For better performance, use ReadMultiProperty to read multiple properties in one request:

result, err:=client.ReadMultiProperty(device, btypes.MultiplePropertyData{
Objects: []btypes.Object{
{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
Properties: []btypes.Property{
{Type: btypes.PropPresentValue},
{Type: btypes.PropUnits},
{Type: btypes.PropDescription},
},
},
{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 2},
Properties: []btypes.Property{
{Type: btypes.PropPresentValue},
},
},
},
})

Common Properties:

  • PropPresentValue (85): Current value of the object
  • PropUnits (117): Engineering units
  • PropDescription (28): Object description
  • PropObjectName (77): Object name
  • PropObjectType (79): Object type
  • PropObjectIdentifier (75): Object identifier
  • PropObjectList (76): List of objects in device

Step 6: Data Writing

Write values to device objects.

err:=client.WriteProperty(device, btypes.PropertyData{
Object: btypes.Object{
ID: btypes.ObjectID{
Type: btypes.AnalogOutput,
Instance: 1,
},
Properties: []btypes.Property{
{
Type: btypes.PropPresentValue,
ArrayIndex: btypes.ArrayAll,
Data: float64(25.5),
Priority: btypes.Normal,
},
},
},
})

Write Priority Levels:

  • LifeSafety (3): Life safety operations
  • CriticalEquipment (2): Critical equipment control
  • Urgent (1): Urgent operations
  • Normal (0): Normal operations

Advanced Usage

Complete Integration Flow

funccompleteIntegration(client bacnet.Client) error {
// Step 1: Discover devicesdevices, err:=client.WhoIs(&bacnet.WhoIsOpts{
Low: 0,
High: 4194304,
})
iferr!=nil {
returnfmt.Errorf("whois failed: %v", err)
}
iflen(devices) ==0 {
returnfmt.Errorf("no devices found")
}
device:=devices[0]
fmt.Printf("Found device: ID=%d, IP=%s:%d\n", device.DeviceID, device.Ip, device.Port)
// Step 2: Scan objectsscannedDevice, err:=client.Objects(device)
iferr!=nil {
returnfmt.Errorf("object scan failed: %v", err)
}
// Step 3: Find target pointaiObjects:=scannedDevice.Objects[btypes.AnalogInput]
targetPoint, ok:=aiObjects[1]
if!ok {
returnfmt.Errorf("target point not found")
}
fmt.Printf("Found target point: %s\n", targetPoint.Name)
// Step 4: Read present valueresult, err:=client.ReadProperty(device, btypes.PropertyData{
Object: btypes.Object{
ID: btypes.ObjectID{
Type: btypes.AnalogInput,
Instance: 1,
},
Properties: []btypes.Property{
{Type: btypes.PropPresentValue},
},
},
})
iferr!=nil {
returnfmt.Errorf("read property failed: %v", err)
}
fmt.Printf("Present Value: %v\n", result.Object.Properties[0].Data)
// Step 5: Write to AnalogValuewriteErr:=client.WriteProperty(device, btypes.PropertyData{
Object: btypes.Object{
ID: btypes.ObjectID{
Type: btypes.AnalogValue,
Instance: 1,
},
Properties: []btypes.Property{
{
Type: btypes.PropPresentValue,
ArrayIndex: btypes.ArrayAll,
Data: float64(25.5),
Priority: btypes.Normal,
},
},
},
})
ifwriteErr!=nil {
returnfmt.Errorf("write property failed: %v", writeErr)
}
fmt.Println("Write successful")
returnnil
}

Read with Timeout

Use timeout variants for better control over request timing:

result, err:=client.ReadPropertyWithTimeout(device, propertyData, 5*time.Second)

Error Handling Patterns

funcsafeReadProperty(client bacnet.Client, device btypes.Device, objID btypes.ObjectID) (interface{}, error) {
result, err:=client.ReadProperty(device, btypes.PropertyData{
Object: btypes.Object{
ID: objID,
Properties: []btypes.Property{
{Type: btypes.PropPresentValue},
},
},
})
iferr!=nil {
// Handle specific error typesifstrings.Contains(err.Error(), "timeout") {
returnnil, fmt.Errorf("device %d did not respond", device.DeviceID)
}
ifstrings.Contains(err.Error(), "no such object") {
returnnil, fmt.Errorf("object %s not found", objID.Type)
}
returnnil, err
}
iflen(result.Object.Properties) ==0 {
returnnil, fmt.Errorf("no properties returned")
}
returnresult.Object.Properties[0].Data, nil
}

API Reference

Client Interface

typeClientinterface {
io.CloserIsRunning() boolClientRun()
// Device DiscoveryWhoIs(wh*WhoIsOpts) ([]btypes.Device, error)
IAm(dest btypes.Address, iam btypes.IAm) error// Network ManagementWhatIsNetworkNumber() []*btypes.AddressWhoIsRouterToNetwork() (resp*[]btypes.Address)
// Object AccessObjects(dev btypes.Device) (btypes.Device, error)
ReadProperty(dest btypes.Device, rp btypes.PropertyData) (btypes.PropertyData, error)
ReadMultiProperty(dev btypes.Device, rp btypes.MultiplePropertyData) (btypes.MultiplePropertyData, error)
WriteProperty(dest btypes.Device, wp btypes.PropertyData) errorWriteMultiProperty(dev btypes.Device, wp btypes.MultiplePropertyData) error// Timeout variantsReadPropertyWithTimeout(dest btypes.Device, rp btypes.PropertyData, timeout time.Duration) (btypes.PropertyData, error)
ReadMultiPropertyWithTimeout(dev btypes.Device, rp btypes.MultiplePropertyData, timeout time.Duration) (btypes.MultiplePropertyData, error)
// COV SubscriptionSubscribeCOV(device btypes.Device, data btypes.SubscribeCOVData) errorCancelSubscribeCOV(device btypes.Device, processIDuint32, objectID btypes.ObjectID) errorWaitCOVNotification(processIDFilterint64, timeout time.Duration) (btypes.COVNotification, error)
}

WhoIs Options

typeWhoIsOptsstruct {
Lowint// Device ID lower bound (0 to 4194304)Highint// Device ID upper boundGlobalBroadcastbool// Use global broadcast (0xFFFF)NetworkNumberuint16// Target network numberDestination*btypes.Address// Specific destination (optional)
}

Configuration

ClientBuilder Options

typeClientBuilderstruct {
DataLink datalink.DataLink// Custom data link (optional)Interfacestring// Network interface name (e.g., "eth0")Ipstring// IP addressPortint// BACnet port (default: 47808)SubnetCIDRint// Subnet CIDR (e.g., 24 for /24)MaxPDUuint16// Maximum PDU size (default: 1476)
}

Constants

// ProtocolconstDefaultPort=0xBAC0// 47808constMaxAPDU=1476// NetworkconstGlobalBroadcast=0xFFFFconstDefaultHopCount=255// Prioritiesconst (
LifeSafety=3CriticalEquipment=2Urgent=1Normal=0
)

Best Practices & Recommendations

Network Considerations

  1. Port Binding:

    • Default BACnet port is 47808 (0xBAC0)
    • Use different ports for testing to avoid conflicts
    • Bind to 0.0.0.0 to listen on all interfaces
  2. IP Address Binding:

    • Avoid binding to the target device's IP address
    • For multi-subnet environments, configure subnet CIDR properly
  3. Broadcast Behavior:

    • WhoIs uses broadcast by default
    • Use Destination for unicast requests
    • Broadcast may not work across VLANs or subnets

Performance Optimization

  1. Batch Operations:

    • Use ReadMultiProperty for reading multiple properties
    • Reduce network round-trips
    • Limit batch size based on device's MaxAPDU setting
  2. Concurrency:

    • Client is thread-safe for concurrent operations
    • TSM limits concurrent confirmed transactions (default: 10)
    • Consider rate limiting for high-frequency operations
  3. Memory Management:

    • Use buffer pool for efficient memory usage
    • Release resources promptly with client.Close()

Error Handling

  1. Timeout Handling:

    • Use ReadPropertyWithTimeout for explicit timeout control
    • Confirmed services include retry logic with exponential backoff
    • Implement application-level retry for critical operations
  2. Common Errors:

    • timeout: Device did not respond within timeout
    • invalid argument: Invalid object type or property ID
    • no such object: Requested object does not exist
    • access denied: Insufficient permissions for write operations

Common Issues & Troubleshooting

Issue 1: No Devices Discovered

Possible Causes:

  • Incorrect IP address or subnet configuration
  • Firewall blocking BACnet port (47808)
  • Devices on different VLAN/subnet
  • Client not running (ClientRun() not called)

Solutions:

  • Verify network configuration
  • Check firewall rules
  • Use Wireshark to monitor BACnet traffic
  • Ensure ClientRun() is called before WhoIs()

Issue 2: ReadProperty Fails with Timeout

Possible Causes:

  • Device not responding
  • Incorrect device address
  • Network connectivity issues
  • Device busy or overloaded

Solutions:

  • Verify device is reachable via ping
  • Check device address (some devices use different ports for confirmed services)
  • Increase timeout value
  • Implement retry logic

Issue 3: WriteProperty Returns "Access Denied"

Possible Causes:

  • Insufficient permissions
  • Write protection enabled on device
  • Incorrect priority level

Solutions:

  • Check device configuration for write permissions
  • Verify priority level (use appropriate priority)
  • Contact device manufacturer for access rights

Issue 4: High Network Traffic

Possible Causes:

  • Frequent WhoIs requests with full ID range
  • Large batch operations exceeding MTU
  • Broadcast storms

Solutions:

  • Use targeted WhoIs with narrow ID ranges
  • Limit batch size to stay within MaxAPDU
  • Implement device discovery caching

BACnet Server (服务端)

The server package provides a complete BACnet/IP server implementation. It can act as a virtual BACnet device, responding to client requests with real data.

中文说明

server 包提供了完整的 BACnet/IP 服务端实现。它可以作为虚拟 BACnet 设备运行,响应客户端请求并返回真实数据。

Quick Start - Server

package main
import (
"log""os""os/signal""github.com/anviod/bacnet/server""github.com/anviod/bacnet/btypes"
)
funcmain() {
// Create server configurationcfg:=&server.DeviceConfig{
DeviceID: 1000, // BACnet device instance IDDeviceName: "My BACnet Server", // BACnet device nameVendorID: 999, // Vendor identifierIp: "0.0.0.0", // Bind to all interfacesPort: 47808, // BACnet portSubnetCIDR: 24, // Subnet mask
}
// Create the serversrv, err:=server.NewServer(cfg)
iferr!=nil {
log.Fatal(err)
}
defersrv.Close()
// Add a temperature sensor objectsrv.AddObject(btypes.Object{
ID: btypes.ObjectID{
Type: btypes.AnalogInput,
Instance: 1,
},
Properties: []btypes.Property{
{
Type: btypes.PROP_PRESENT_VALUE,
ArrayIndex: btypes.ArrayAll,
Data: float64(23.5), // Current temperature: 23.5°C
},
{
Type: btypes.PROP_OBJECT_NAME,
ArrayIndex: btypes.ArrayAll,
Data: "Room Temperature",
},
{
Type: btypes.PROP_Propertybtypes,
ArrayIndex: btypes.ArrayAll,
Data: uint32(62), // Degrees Celsius
},
{
Type: btypes.PROP_DESCRIPTION,
ArrayIndex: btypes.ArrayAll,
Data: "Office room temperature sensor",
},
},
})
// Add a binary output object (relay control)srv.AddObject(btypes.Object{
ID: btypes.ObjectID{
Type: btypes.BinaryOutput,
Instance: 1,
},
Properties: []btypes.Property{
{
Type: btypes.PROP_PRESENT_VALUE,
ArrayIndex: btypes.ArrayAll,
Data: uint32(0), // Off
},
{
Type: btypes.PROP_OBJECT_NAME,
ArrayIndex: btypes.ArrayAll,
Data: "Fan Control",
},
},
})
// Handle graceful shutdownc:=make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// Start the server in a goroutinegofunc() {
log.Println("BACnet server starting...")
iferr:=srv.Serve(); err!=nil {
log.Printf("Server error: %v", err)
}
}()
log.Printf("BACnet server started. Device ID: %d, Name: %s", srv.GetDeviceID(), cfg.DeviceName)
log.Println("Press Ctrl+C to stop")
// Update values dynamically (simulate sensor readings)// go func() {// for {// time.Sleep(5 * time.Second)// srv.SetProperty(// btypes.AnalogInput, 1,// btypes.PROP_PRESENT_VALUE,// float64(20.0 + rand.Float64()*10.0),// )// }// }()<-clog.Println("Shutting down server...")
}

Server Features

1. Object Management

The server maintains a thread-safe object store for BACnet objects:

// Add an objectsrv.AddObject(btypes.Object{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
})
// Get an objectobj, found:=srv.GetObject(btypes.AnalogInput, 1)
// Remove an objectsrv.RemoveObject(btypes.AnalogInput, 1)
// Set a property valuesrv.SetProperty(btypes.AnalogInput, 1, btypes.PROP_PRESENT_VALUE, float64(25.0))
// Get a property valuevalue, found:=srv.GetProperty(btypes.AnalogInput, 1, btypes.PROP_PRESENT_VALUE)

2. Automatic WhoIs → IAm Response

When a BACnet client sends a WhoIs broadcast, the server automatically responds with an IAm message if its device ID falls within the requested range:

// Client sends: WhoIs(0, 4194304) → all devices// Server responds: IAm(DeviceID=1000, ...)// Client sends: WhoIs(2000, 3000) → devices in range// Server (DeviceID=1000) does NOT respond (out of range)

3. Supported Confirmed Services

ServiceDescriptionStatus
ReadProperty读取单个属性值Supported
WriteProperty写入单个属性值Supported
ReadPropertyMultiple批量读取多个属性Supported
WritePropertyMultiple批量写入多个属性Supported

4. COV (Change of Value) Subscription

// Client subscribes to COV notificationserr:=client.SubscribeCOV(device, btypes.SubscribeCOVData{
ProcessID: 1,
ObjectID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
IssueConfirmed: false, // false = UnconfirmedCOVNotificationLifetime: 0, // 0 = permanent subscription
})
// Wait for COV notificationnotification, err:=client.WaitCOVNotification(1, 30*time.Second)
// notification.MonitoredObjectIdentifier → monitored object// notification.ListOfValues[0].Value → new value// Cancel subscriptionerr=client.CancelSubscribeCOV(device, 1, btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1})

5. BACnet Error Responses

The server returns proper BACnet error responses for invalid requests:

Error ConditionError ClassError Code
Unknown objectObjectErrorUnknownObject
Unknown propertyPropertyErrorUnknownProperty
Write access deniedPropertyErrorWriteAccessDenied
Invalid request tagServicesErrorInvalidTag
Missing parameterServicesErrorMissingRequiredParameter
Unsupported serviceServicesErrorServiceRequestDenied

6. Device Object Properties

The server automatically maintains the standard BACnet Device object properties:

PropertyValue
ObjectIdentifierConfigured device ID
ObjectNameConfigured device name
VendorIdentifierConfigured vendor ID
VendorName"BACnet-Go"
ModelName"BACnet-Go Server"
FirmwareRevision"1.0.0"
ProtocolVersion1
ProtocolRevision24
ProtocolServicesSupportedBitString (ReadProperty, WriteProperty, etc.)
ProtocolObjectTypesSupportedBitString (AI, AO, AV, BI, BO, BV, etc.)
MaxAPDUAccepted1476
DatabaseRevisionAuto-incremented on changes

7. Default Object Properties

When adding an object without specifying properties, the server creates default properties:

// Adding an object with no properties gets defaultssrv.AddObject(btypes.Object{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
})
// Automatically creates: ObjectIdentifier, ObjectName, ObjectType,// PresentValue, StatusFlags, EventState, Reliability, OutOfService, Units

Client ↔ Server Interaction Example

// —— Server Side ——srv, _:=server.NewServer(&server.DeviceConfig{
DeviceID: 1000, DeviceName: "TestServer", VendorID: 999,
Ip: "0.0.0.0", Port: 47808, SubnetCIDR: 24,
})
gosrv.Serve()
defersrv.Close()
srv.AddObject(btypes.Object{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
Properties: []btypes.Property{
{Type: btypes.PROP_PRESENT_VALUE, ArrayIndex: btypes.ArrayAll, Data: float64(42.5)},
},
})
// —— Client Side ——client, _:=bacnet.NewClient(&bacnet.ClientBuilder{
Ip: "192.168.1.100", SubnetCIDR: 24, Port: 47808,
})
goclient.ClientRun()
deferclient.Close()
// Discover the serverdevices, _:=client.WhoIs(&bacnet.WhoIsOpts{Low: 0, High: 4194304})
// Read property from the serverresult, _:=client.ReadProperty(devices[0], btypes.PropertyData{
Object: btypes.Object{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
Properties: []btypes.Property{
{Type: btypes.PROP_PRESENT_VALUE, ArrayIndex: btypes.ArrayAll},
},
},
})
// result.Object.Properties[0].Data == float64(42.5)// Write property to the serverclient.WriteProperty(devices[0], btypes.PropertyData{
Object: btypes.Object{
ID: btypes.ObjectID{Type: btypes.AnalogInput, Instance: 1},
Properties: []btypes.Property{
{Type: btypes.PROP_PRESENT_VALUE, ArrayIndex: btypes.ArrayAll, Data: float64(99.9)},
},
},
})

Server API Reference

typeServerinterface {
// LifecycleServe() errorClose() errorIsRunning() bool// Object ManagementAddObject(obj btypes.Object) errorRemoveObject(objType btypes.ObjectType, instance btypes.ObjectInstance) errorGetObject(objType btypes.ObjectType, instance btypes.ObjectInstance) (*btypes.Object, bool)
SetProperty(objType btypes.ObjectType, instance btypes.ObjectInstance, propType btypes.PropertyType, datainterface{}) errorGetProperty(objType btypes.ObjectType, instance btypes.ObjectInstance, propType btypes.PropertyType) (interface{}, bool)
GetObjectStore() *ObjectStoreGetDeviceID() btypes.ObjectInstance
}

DeviceConfig

typeDeviceConfigstruct {
DeviceID btypes.ObjectInstance// Device instance ID (0-4194304)DeviceNamestring// BACnet device nameVendorIDuint32// Vendor identifierInterfacestring// Network interface (e.g., "eth0")Ipstring// IP address to bindPortint// BACnet port (default: 47808)SubnetCIDRint// Subnet CIDR (e.g., 24)MaxPDUuint16// Maximum PDU sizeMaxSegmentsuint// Maximum segmentsSegmentation segmentation.SegmentedType// Segmentation support
}

Supported Object Types

Object TypeIDDescription
AnalogInput0模拟输入 (e.g., temperature sensors)
AnalogOutput1模拟输出 (e.g., valves, dampers)
AnalogValue2模拟值
BinaryInput3二进制输入 (e.g., contact sensors)
BinaryOutput4二进制输出 (e.g., relays)
BinaryValue5二进制值
Device8BACnet 设备对象
MultiStateValue19多状态值

Testing

# Run all tests
go test ./...
# Run server tests
go test ./server/...
# Run server tests with coverage
go test -cover ./server/...
# Run specific test
go test -v ./network/...
# Run acceptance tests
go test -v -run Acceptance
# Run real device integration test
go test. -run TestRealDeviceAcceptanceFlow -count=1 -v

License

MIT License

References

About

BACnet协议栈支持Client/Server双模式,完整实现Who-Is/I-Am设备发现、Read/WriteProperty属性读写及点位建模扩展。高并发跨平台,适用于边缘网关。Supports dual Client/Server modes, full device discovery (Who-Is/I-Am), property read/write, and flexible point modeling. High-concurrency, cross-platform.

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages