bacnetip

package
v0.0.0-...-a9c5517 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

Documentation

Index

Constants

View Source
const (
	OBJECT_TYPE          = "objectType"
	OBJECT_INSTANCE      = "objectInstance"
	PROPERTY_IDENTIFIERS = "propertyIdentifiers"
	PROPERTY_IDENTIFIER  = "propertyIdentifier"
	ARRAY_INDEX          = "arrayIndex"
	WRITE_PRIORITY       = "writePriority"
)

Variables

This section is empty.

Functions

func AwaitableSegmenter

func AwaitableSegmenter(_ context.Context)

AwaitableSegmenter is a thin context-aware wrapper for callers that want to honor ctx cancellation while waiting for segment acks. Reserved for future use; not consumed by the current Reader/Writer flow.

func NewDriver

func NewDriver(_options ...options.WithOption) plc4go.PlcDriver

func NewInboundReassembler

func NewInboundReassembler(invokeId, windowSize uint8) *inboundReassembler

NewInboundReassembler creates a fresh reassembler tracking the given invoke id. windowSize is the actual window size we want to advertise back to the publisher in each SegmentAck (often capped to 1 for simplicity until a real device shows up that benefits from larger windows).

func NewOutboundSegmenter

func NewOutboundSegmenter(invokeId uint8, payload []byte, maxApdu uint16, windowSize uint8) *outboundSegmenter

NewOutboundSegmenter prepares a segmenter for the given serialized request payload. maxApdu is the peer's MaxApduLengthAccepted in bytes; we subtract a conservative 20-byte overhead allowance for APDU/NPDU/BVLC headers.

func One

func One(filteredOptionMap map[string][]any, key string) (any, error)

func OneInt

func OneInt(filteredOptionMap map[string][]any, key string) (int, error)

func OneString

func OneString(filteredOptionMap map[string][]any, key string) (string, error)

func OneUint

func OneUint(filteredOptionMap map[string][]any, key string) (uint, error)

Types

type BacNetPlcTag

type BacNetPlcTag interface {
	apiModel.PlcTag

	GetObjectId() objectId
	GetProperties() []property
}

type Configuration

type Configuration struct {
	// LocalDeviceId is the BACnet device-instance number this driver claims when issuing
	// IAm and identifying itself in confirmed-request initiators. Range 0..4194303.
	LocalDeviceId uint32

	// LocalNetworkNumber is the BACnet network number for this driver's NPDU source
	// address. 0 means "same network as the destination" (i.e. local IP segment).
	LocalNetworkNumber uint16

	// MaxApduLengthAccepted is the largest APDU we will accept from peers, in octets.
	// Default 1476 (the largest fragment that fits a standard Ethernet MTU). Used to
	// derive MaxApduLengthAccepted enum constants at runtime.
	MaxApduLengthAccepted uint16

	// SegmentationSupported declares this driver's segmentation capabilities. One of
	// "segmented-both", "segmented-transmit", "segmented-receive", "no-segmentation".
	SegmentationSupported string

	// MaxSegmentsAccepted is the largest number of APDU segments we will accept in a
	// segmented response. Default 16.
	MaxSegmentsAccepted uint8

	// VendorId is the BACnet Vendor Id this driver advertises. Apache PLC4X uses 0x4D4D
	// ("MM") by convention until a real allocation is registered with ASHRAE.
	VendorId uint16

	// ForeignDeviceBBMD, when non-empty (host:port), enables BBMD foreign-device
	// registration at connect time so the driver can interoperate across IP subnets.
	ForeignDeviceBBMD string

	// ForeignDeviceTTL is the BBMD registration TTL in seconds. The driver re-registers
	// at TTL/2 to keep the foreign-device entry alive.
	ForeignDeviceTTL uint16

	// ApduTimeoutMs is the per-request timeout in milliseconds before the transaction
	// manager surfaces a timeout error.
	ApduTimeoutMs uint32

	// ApduRetries is the number of transparent retransmits attempted on confirmed
	// services before failing the request.
	ApduRetries uint8

	// CovLifetimeSeconds is the default SubscribeCOV lifetime (and refresh interval will
	// be lifetime/2). 0 means "indefinite" per BACnet spec.
	CovLifetimeSeconds uint32

	// DiscoveryTimeoutSeconds bounds the WhoIs broadcast wait time. Replaces the older
	// hardcoded 60-second wait inherited from the alpha discoverer.
	DiscoveryTimeoutSeconds uint32

	// StaticDevices lets users seed the DeviceInfoCache with peers that won't respond to
	// WhoIs (e.g. routed devices on a network with no BBMD). Comma-separated entries
	// of the form "<deviceId>@<network>:<host>:<port>".
	StaticDevices string

	// RemoteNetwork is the BACnet network number of this connection's target device
	// when it sits behind a BACnet router (ASHRAE 135 clause 6). The connection's
	// transport host is then the ROUTER's IP; every outgoing NPDU carries a
	// destination specifier (DNET=RemoteNetwork, DADR=RemoteAddress, hop count 255)
	// so the router forwards it onto that network, and replies arrive with the
	// device's source specifier mirrored back. 0 (default) means the target is on
	// the local segment and NPDUs stay specifier-free.
	RemoteNetwork uint16

	// RemoteAddress is the target device's MAC address on RemoteNetwork, required
	// when RemoteNetwork is set. For BACnet/IP-to-BACnet/IP routing use
	// "<ip>:<port>" (encoded as the 6-byte B/IP DADR); for other datalinks a hex
	// string ("0x0C") supplies the raw MAC octets.
	RemoteAddress string

	// PeerMaxApduLengthAccepted is the target device's MaxApduLengthAccepted in
	// octets, as the device declared it in its I-Am. When a confirmed request's
	// APDU would exceed this, the driver sends it as a segmented request
	// (ASHRAE 135 clause 5.4) — provided PeerSegmentationSupported allows it.
	// 0 (default) means unknown: requests are never segmented and an oversized
	// request fails fast instead of provoking an abort from the device.
	PeerMaxApduLengthAccepted uint16

	// PeerSegmentationSupported is the target device's segmentation capability
	// from its I-Am: "segmented-both", "segmented-transmit", "segmented-receive"
	// or "no-segmentation". Segmented requests are only sent when the peer can
	// RECEIVE segments ("segmented-both"/"segmented-receive"). Empty (default)
	// means unknown and is treated as "no-segmentation".
	PeerSegmentationSupported string
}

Configuration captures driver-level options parsed from the connection URL. String/uint/bool fields are populated reflectively from driverOptions keyed by title-cased field name (e.g. "LocalDeviceId").

func ParseFromOptions

func ParseFromOptions(log zerolog.Logger, optionsMap map[string][]string) (Configuration, error)

ParseFromOptions populates a Configuration from the connection-URL query options, applying defaults for any field the user did not set explicitly. Field names are matched case-insensitively so users can write "localDeviceId", "LocalDeviceId", or "localdeviceid" interchangeably in the connection string.

func (*Configuration) Serialize

func (d *Configuration) Serialize() ([]byte, error)

func (*Configuration) SerializeWithWriteBuffer

func (d *Configuration) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error

func (*Configuration) String

func (d *Configuration) String() string

type Connection

type Connection struct {
	_default.DefaultConnection
	// contains filtered or unexported fields
}

func NewConnection

func NewConnection(messageCodec spi.MessageCodec, tagHandler spi.PlcTagHandler, tm transactions.RequestTransactionManager, connectionOptions map[string][]string, _options ...options.WithOption) *Connection

func (*Connection) ActiveSubscriptionCount

func (c *Connection) ActiveSubscriptionCount() int

ActiveSubscriptionCount reports how many COV subscription handles are currently registered across this connection's subscribers. The connection cache consults it (as an optional capability) to exempt subscription- carrying connections from idle reaping: their server-side COV registrations and refresh timers live on this connection and would be destroyed by a reap.

func (*Connection) Close

func (c *Connection) Close() error

func (*Connection) Connect

func (c *Connection) Connect(ctx context.Context) error

func (*Connection) GetConnection

func (c *Connection) GetConnection() plc4go.PlcConnection

func (*Connection) GetConnectionId

func (c *Connection) GetConnectionId() string

func (*Connection) GetMessageCodec

func (c *Connection) GetMessageCodec() spi.MessageCodec

func (*Connection) GetMetadata

func (c *Connection) GetMetadata() apiModel.PlcConnectionMetadata

func (*Connection) GetTracer

func (c *Connection) GetTracer() tracer.Tracer

func (*Connection) IsTraceEnabled

func (c *Connection) IsTraceEnabled() bool

func (*Connection) ReadRequestBuilder

func (c *Connection) ReadRequestBuilder() apiModel.PlcReadRequestBuilder

func (*Connection) String

func (c *Connection) String() string

func (*Connection) SubscriptionRequestBuilder

func (c *Connection) SubscriptionRequestBuilder() apiModel.PlcSubscriptionRequestBuilder

func (*Connection) UnsubscriptionRequestBuilder

func (c *Connection) UnsubscriptionRequestBuilder() apiModel.PlcUnsubscriptionRequestBuilder

func (*Connection) WriteRequestBuilder

func (c *Connection) WriteRequestBuilder() apiModel.PlcWriteRequestBuilder

type DeviceInfo

type DeviceInfo struct {
	// DeviceId is the BACnet device-instance number (0..4194303).
	DeviceId uint32

	// Address is the UDP endpoint reachable on the local IP segment that hosts
	// (or routes to) this device.
	Address *net.UDPAddr

	// SourceNetwork is the BACnet network number the device is on. 0 means
	// "local network" (no routing needed).
	SourceNetwork uint16

	// SourceAddress is the per-network MAC-equivalent address used in the NPDU
	// DADR field when routing across networks. For BACnet/IP devices on a
	// remote network it's typically the device's IPv4 address + port (6 bytes).
	SourceAddress []byte

	// SegmentationSupported is what the device declared in its IAm. We honor
	// this when deciding whether to send a segmented request to it.
	SegmentationSupported model.BACnetSegmentation

	// MaxApdu is the device's MaxApduLengthAccepted (already converted to bytes).
	MaxApdu uint16

	// VendorId is the BACnet Vendor Id from IAm.
	VendorId uint16

	// LastSeen is set whenever we receive any APDU from this device, used for
	// staleness pruning if/when we add a TTL.
	LastSeen time.Time
}

DeviceInfo captures everything we need to know to address a remote BACnet device for routed Read/Write/Subscribe requests. Most fields are populated from IAm responses (during discovery or in response to WhoIs); the seed entries from Configuration.StaticDevices supply Address + SourceNetwork only.

type DeviceInfoCache

type DeviceInfoCache struct {
	// contains filtered or unexported fields
}

DeviceInfoCache is the in-memory routing table. Safe for concurrent use.

func NewDeviceInfoCache

func NewDeviceInfoCache() *DeviceInfoCache

func (*DeviceInfoCache) All

func (c *DeviceInfoCache) All() []DeviceInfo

All returns a copy of every entry. Useful for diagnostics; tests use it to assert eventual-consistency without exposing the internal map.

func (*DeviceInfoCache) Get

func (c *DeviceInfoCache) Get(deviceId uint32) *DeviceInfo

Get returns a snapshot of the DeviceInfo for the given device id, or nil if the device isn't known. The returned pointer is a copy — callers may not mutate the cache through it.

func (*DeviceInfoCache) LoadStatic

func (c *DeviceInfoCache) LoadStatic(spec string) error

LoadStatic seeds the cache from a Configuration.StaticDevices string. The format is comma-separated entries of the form "<deviceId>@<network>:<host>:<port>". Whitespace around separators is tolerated.

"1234@0:10.0.0.5:47808,42@1:10.0.0.6:47808"

Entries that fail to parse are skipped and counted in the returned error; the cache always contains every entry that did parse cleanly.

func (*DeviceInfoCache) Put

func (c *DeviceInfoCache) Put(info DeviceInfo)

Put inserts or overwrites a device-info entry. LastSeen is bumped to now.

func (*DeviceInfoCache) Touch

func (c *DeviceInfoCache) Touch(deviceId uint32)

Touch updates only the LastSeen timestamp for the given device id. Used by the message-receive path to refresh entries we've already seen.

type Discoverer

type Discoverer struct {
	// contains filtered or unexported fields
}

func NewDiscoverer

func NewDiscoverer(_options ...options.WithOption) *Discoverer

func (*Discoverer) Close

func (d *Discoverer) Close() error

func (*Discoverer) Discover

func (d *Discoverer) Discover(ctx context.Context, callback func(event apiModel.PlcDiscoveryItem), discoveryOptions ...options.WithDiscoveryOption) error

func (*Discoverer) SetDiscoveryTimeout

func (d *Discoverer) SetDiscoveryTimeout(t time.Duration)

SetDiscoveryTimeout overrides the default 5-second window for discovery. 0 falls back to the default. Intended to be called from Driver setup once Configuration.DiscoveryTimeoutSeconds is known.

type Driver

type Driver struct {
	_default.DefaultDriver
	// contains filtered or unexported fields
}

func (*Driver) Close

func (d *Driver) Close() error

func (*Driver) Discover

func (d *Driver) Discover(ctx context.Context, callback func(event apiModel.PlcDiscoveryItem), discoveryOptions ...options.WithDiscoveryOption) error

func (*Driver) GetConnection

func (d *Driver) GetConnection(ctx context.Context, transportUrl url.URL, transports map[string]transports.Transport, driverOptions map[string][]string) (plc4go.PlcConnection, error)

func (*Driver) SetAwaitDisconnectComplete

func (d *Driver) SetAwaitDisconnectComplete(awaitComplete bool)

func (*Driver) SetAwaitSetupComplete

func (d *Driver) SetAwaitSetupComplete(awaitComplete bool)

func (*Driver) SupportsDiscovery

func (d *Driver) SupportsDiscovery() bool

type DriverContext

type DriverContext struct {
	// contains filtered or unexported fields
}

DriverContext carries the resolved Configuration plus values derived from it (enum mappings, lifecycle flags) into the Connection layer. It is passed by value because all fields are small.

func NewDriverContext

func NewDriverContext(configuration Configuration) DriverContext

func (*DriverContext) Serialize

func (d *DriverContext) Serialize() ([]byte, error)

func (*DriverContext) SerializeWithWriteBuffer

func (d *DriverContext) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error

func (*DriverContext) String

func (d *DriverContext) String() string

type InvokeIdGenerator

type InvokeIdGenerator struct {
	// contains filtered or unexported fields
}

type MessageCodec

type MessageCodec struct {
	_default.DefaultCodec
	// contains filtered or unexported fields
}

func NewMessageCodec

func NewMessageCodec(transportInstance transports.TransportInstance, _options ...options.WithOption) *MessageCodec

func (*MessageCodec) GetCodec

func (m *MessageCodec) GetCodec() spi.MessageCodec

func (*MessageCodec) Receive

func (m *MessageCodec) Receive(ctx context.Context) (spi.Message, error)

func (*MessageCodec) Send

func (m *MessageCodec) Send(ctx context.Context, interactionInfo string, message spi.Message) error

func (*MessageCodec) Serialize

func (d *MessageCodec) Serialize() ([]byte, error)

func (*MessageCodec) SerializeWithWriteBuffer

func (d *MessageCodec) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error

func (*MessageCodec) String

func (d *MessageCodec) String() string

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

func NewReader

func NewReader(invokeIdGenerator *InvokeIdGenerator, messageCodec spi.MessageCodec, tm transactions.RequestTransactionManager, driverContext DriverContext, routedDest *routedDestination, _options ...options.WithOption) *Reader

func (*Reader) Read

func (m *Reader) Read(ctx context.Context, readRequest apiModel.PlcReadRequest) <-chan apiModel.PlcReadRequestResult

func (*Reader) ToPlc4xReadResponse

func (m *Reader) ToPlc4xReadResponse(apdu readWriteModel.APDU, readRequest apiModel.PlcReadRequest) (apiModel.PlcReadResponse, error)

type RequestTransaction

type RequestTransaction interface {
	transactions.RequestTransaction
}

type Subscriber

type Subscriber struct {
	// contains filtered or unexported fields
}

Subscriber drives BACnet COV (Change-of-Value) subscriptions: it issues SubscribeCOV requests on Subscribe, refreshes them at lifetime/2 via a background ticker, fans incoming UnconfirmedCOVNotification / ConfirmedCOVNotification APDUs out to registered consumers, and tears everything down on Unsubscribe.

func NewSubscriber

func NewSubscriber(connection *Connection, _options ...options.WithOption) *Subscriber

func (*Subscriber) HandleConfirmedCOVNotification

HandleConfirmedCOVNotification is invoked by Connection's message loop when a ConfirmedCOVNotification APDU arrives. It looks up the SubscriptionHandle by subscriber-process-id and fans out a SubscriptionEvent to every consumer registered against that handle. The caller is responsible for sending the APDUSimpleAck reply back to the publisher.

func (*Subscriber) HandleUnconfirmedCOVNotification

HandleUnconfirmedCOVNotification mirrors HandleConfirmedCOVNotification for the unconfirmed variant. No reply is sent.

func (*Subscriber) Serialize

func (d *Subscriber) Serialize() ([]byte, error)

func (*Subscriber) SerializeWithWriteBuffer

func (d *Subscriber) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error

func (*Subscriber) String

func (d *Subscriber) String() string

func (*Subscriber) Subscribe

func (m *Subscriber) Subscribe(ctx context.Context, subscriptionRequest apiModel.PlcSubscriptionRequest) <-chan apiModel.PlcSubscriptionRequestResult

Subscribe issues one BACnet SubscribeCOV request per requested tag. The per-tag lifetime is taken from Connection.configuration.CovLifetimeSeconds; a value of 0 means "indefinite" per BACnet semantics.

func (*Subscriber) Unregister

func (m *Subscriber) Unregister(registration apiModel.PlcConsumerRegistration)

func (*Subscriber) Unsubscribe

func (m *Subscriber) Unsubscribe(ctx context.Context, unsubscriptionRequest apiModel.PlcUnsubscriptionRequest) <-chan apiModel.PlcUnsubscriptionRequestResult

Unsubscribe sends a SubscribeCOV with lifetime=0 (BACnet cancel semantics) for each handle in the unsubscription request, then drops the handle from the routing map so further notifications are silently ignored.

type SubscriptionEvent

type SubscriptionEvent struct {
	*spiModel.DefaultPlcSubscriptionEvent
	// contains filtered or unexported fields
}

SubscriptionEvent is the payload delivered to a consumer when a COV notification arrives. address carries the BACnet origin (device-id and object-id) so the caller can disambiguate notifications when one consumer is registered on multiple subscriptions.

func NewSubscriptionEvent

func NewSubscriptionEvent(
	tags map[string]apiModel.PlcTag,
	types map[string]apiModel.PlcSubscriptionType,
	intervals map[string]time.Duration,
	responseCodes map[string]apiModel.PlcResponseCode,
	address map[string]string,
	sources map[string]string,
	values map[string]apiValues.PlcValue,
	_options ...options.WithOption,
) SubscriptionEvent

func (SubscriptionEvent) GetAddress

func (m SubscriptionEvent) GetAddress(name string) string

func (SubscriptionEvent) GetSource

func (m SubscriptionEvent) GetSource(name string) string

type SubscriptionHandle

type SubscriptionHandle struct {
	*spiModel.DefaultPlcSubscriptionHandle
	// contains filtered or unexported fields
}

SubscriptionHandle tracks an active BACnet COV subscription. Returned to the caller as the api/model.PlcSubscriptionHandle and used internally by the Subscriber to route incoming notifications and to refresh subscriptions before their lifetime expires.

func NewSubscriptionHandle

func NewSubscriptionHandle(
	subscriber *Subscriber,
	tagName string,
	tag any,
	subscriptionType apiModel.PlcSubscriptionType,
	interval time.Duration,
) *SubscriptionHandle

type TagHandler

type TagHandler struct {
	// contains filtered or unexported fields
}

func NewTagHandler

func NewTagHandler() TagHandler

func (TagHandler) ParseQuery

func (m TagHandler) ParseQuery(_ string) (apiModel.PlcQuery, error)

func (TagHandler) ParseTag

func (m TagHandler) ParseTag(tagString string) (apiModel.PlcTag, error)

type ValueHandler

type ValueHandler struct{}

ValueHandler maps user-supplied Go values (the third arg to AddTagAddress in a write request) onto plc4go PlcValues. Because BACnet properties have many possible underlying types and our plcTag reports PlcValueType_Struct, the default handler's struct path bails out — we override NewPlcValue directly to dispatch on the user's Go type.

We do NOT embed DefaultValueHandler. Embedded-method dispatch in Go is static: DefaultValueHandler.parseType would call DefaultValueHandler's own ParseStructType, not our override. Implementing the spi.PlcValueHandler interface (just NewPlcValue) directly keeps control of the dispatch.

func NewValueHandler

func NewValueHandler() ValueHandler

func (ValueHandler) NewPlcValue

func (h ValueHandler) NewPlcValue(_ apiModel.PlcTag, value any) (apiValues.PlcValue, error)

NewPlcValue wraps a raw Go primitive into the matching PlcValue. The Writer's ValueEncoder later inverts this onto a BACnet ApplicationTag, so here we only care about preserving the type information losslessly.

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer issues BACnet WriteProperty / WritePropertyMultiple confirmed-service requests and waits for SimpleAck / Error / Reject / Abort responses.

func NewWriter

func NewWriter(
	invokeIdGenerator *InvokeIdGenerator,
	messageCodec spi.MessageCodec,
	tm transactions.RequestTransactionManager,
	driverContext DriverContext,
	routedDest *routedDestination,
	_options ...options.WithOption,
) *Writer

func (*Writer) Write

func (m *Writer) Write(ctx context.Context, writeRequest apiModel.PlcWriteRequest) <-chan apiModel.PlcWriteRequestResult

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL