DiscoverRequest
low_limitLower bound of the device-instance range to scan. Requires high_limit.
high_limitUpper bound of the device-instance range to scan. Requires low_limit.
addressTarget one specific device (directed Who-Is) instead of broadcasting - a local ip[:port] or a routed net:mac.
networkBroadcast on this one remote network (net:*) instead of globally.
global_broadcastfalse keeps the Who-Is to this subnet (a local broadcast), as before Baler#126.
timeoutSeconds to wait for I-Am responses. Defaults to bacpypes3's own Who-Is timeout (3s) if omitted.
Device
device_instanceaddressThe address the device responded from, as every other call takes it: a BACnet/IP address (no port when it's the standard 47808), or for a device behind a router net:mac - its network number and its MAC on that network (5:12 for MS/TP MAC 12 on network 5, 7:10.0.1.20 for a BACnet/IP device on a routed IP network).
vendor_idnetworkThe device's network number when it's behind a router; null on this subnet.
macThe device's MAC on its network (the IP address on this subnet).
max_apduLargest APDU the device accepts, from its I-Am (480 is typical of MS/TP).
LearnedPoint
object_identifierThe real BACnet object identifier - what create-a-connection needs, not a display name.
labelobject-name if the device reports one, otherwise falls back to the object_identifier itself.
Current value at learn time - a snapshot, not a live feed. null if the object has none (e.g. a network-port) or the property read failed.
unitsPointRef
object_identifierlabelMQTT topic segment for this point - freely renameable from what /learn suggested.
unitsThe BACnet units /learn reported for this point (pass it through). Published as the point's Haystack unit tag.
descriptionThe description /learn reported. Its words help tag the point (rules/bacnet_to_haystack_tags.yaml).
unitAn explicit Haystack unit symbol for the point's unit tag, overriding units - for a device that reports none or the wrong one.
poll_intervalSeconds between reads of this specific point, overriding the connection-level default. Each point runs its own independent poll loop - a fast-changing point and a slow one on the same device don't have to share a cadence.
Enables change-of-value filtering for this point, matching Haystack's own hisCollectCov semantics: true publishes on any change; a number is a numeric tolerance the value must move by before it's published (non-numeric values always compare by equality regardless). Omit to publish every poll unconditionally (the default).
his_collect_cov_rate_limitNever publish COV-triggered changes faster than this many seconds apart, even if the value keeps changing. Only meaningful alongside his_collect_cov.
his_collect_intervalAlternative to his_collect_cov - publish at most this often regardless of whether the value changed, independent of poll_interval (matches Haystack's hisCollectInterval).
subscribe_covOverrides the connection's subscribe_cov for this point - e.g. false for a point whose device floods notifications. (Not the same thing as his_collect_cov, which filters what gets published, however the value arrived.)
trend_logThe controller's TrendLog object that logs this point (e.g. trend-log,3). After Windrow was down, or couldn't reach the device, the readings it missed are read back from that log with ReadRange and published on the point's topic with their own timestamps, not retained (so the live value stays the retained one). Goes back at most the connection's backfill_window.
TimeValue
timevalueWhat the schedule writes then: a number, active/inactive for a binary target, or null (relinquish).
SchedulePeriod
datedate_rangecalendarAlarmEvent
objectdeviceevent_typefrom_stateto_statenotify_typepriorityack_requiredmessagenotification_classdevice_timestampThe device's own timestamp: a date-time, a time of day, or #n for a sequence number.
tsWhen Windrow received it (unix seconds).
CreateConnectionRequest
device_addressip[:port] on this subnet, or net:mac for a device behind a router (5:12) - as /discover reports it. A broadcast address is a 400.
device_instancetopic_prefixMQTT topic prefix every point on this connection publishes under, as {topic_prefix}/{label}.
device_nameName of the device this connection polls - what it announces itself as (windrow.announce), or a DNS host name. When set, Windrow re-points the connection's address when the device announces (or resolves to) a new one. Empty clears it.
poll_intervalSeconds between poll cycles.
read_multipleRead with ReadPropertyMultiple. Reads are batched per device, across every connection on it: points on one device tick together and go out as one request per read_multiple_max objects. A device that rejects RPM is read with one ReadProperty per point from then on; one whose answer doesn't fit an APDU gets smaller batches. false keeps one ReadProperty per point for this connection.
read_multiple_maxMost objects per ReadPropertyMultiple to this device. Default: what fits the device's max APDU (from its I-Am), at most WINDROW_RPM_MAX_POINTS (50). With several connections on one device the smallest value wins.
subscribe_covSubscribeCOV for every point (a point's own subscribe_cov overrides it), so a change publishes as soon as the device notifies. While a point's subscription is live it is still polled, every cov_poll_interval, in case a notification is lost; if the subscription fails it's polled at its normal interval and re-subscribed with back-off, and an object the device won't do COV for is just polled.
cov_lifetimeSeconds each COV subscription asks the device for. Renewed at half that.
cov_poll_intervalSeconds between the safety-net polls of a point whose COV subscription is live.
forward_alarmsForward the device's intrinsic-reporting alarms (Baler#164). Windrow adds itself to the recipient-list of every notification-class object on the device (this changes the controller's configuration, hence opt-in), re-checks that every WINDROW_ALARM_REFRESH seconds (default 3600), and reads the active alarms at start. Each event notification is published retained on {topic_prefix}/{label}/alarm for a point of this connection, or {topic_prefix}/alarms/{type}-{instance} for another object, as the usual {point, value, ts} envelope whose value is the event (see AlarmEvent). Turning it off or deleting the connection removes Windrow's recipient entries again.
backfill_windowMost seconds of history a trend-log backfill reads back for this connection's points with trend_log; 0 turns backfill off.
Optional - the login this device's own UI/gateway needs, if any. Write-only on create; a created/listed Connection always shows this redacted (see RedactedCredential).
Connection
iddevice_addressip[:port] on this subnet, or net:mac for a device behind a router (5:12) - as /discover reports it. A broadcast address is a 400.
device_instancetopic_prefixMQTT topic prefix every point on this connection publishes under, as {topic_prefix}/{label}.
Optional - the login this device's own UI/gateway needs, if any. Write-only on create; a created/listed Connection always shows this redacted (see RedactedCredential).
Listings only: whether this connection is actually reading, since the process started. Kept in memory and never stored on the record; it resets when the connection is updated. Times are unix seconds.
device_nameName of the device this connection polls - what it announces itself as (windrow.announce), or a DNS host name. When set, Windrow re-points the connection's address when the device announces (or resolves to) a new one. Empty clears it.
poll_intervalSeconds between poll cycles.
read_multipleRead with ReadPropertyMultiple. Reads are batched per device, across every connection on it: points on one device tick together and go out as one request per read_multiple_max objects. A device that rejects RPM is read with one ReadProperty per point from then on; one whose answer doesn't fit an APDU gets smaller batches. false keeps one ReadProperty per point for this connection.
read_multiple_maxMost objects per ReadPropertyMultiple to this device. Default: what fits the device's max APDU (from its I-Am), at most WINDROW_RPM_MAX_POINTS (50). With several connections on one device the smallest value wins.
subscribe_covSubscribeCOV for every point (a point's own subscribe_cov overrides it), so a change publishes as soon as the device notifies. While a point's subscription is live it is still polled, every cov_poll_interval, in case a notification is lost; if the subscription fails it's polled at its normal interval and re-subscribed with back-off, and an object the device won't do COV for is just polled.
cov_lifetimeSeconds each COV subscription asks the device for. Renewed at half that.
cov_poll_intervalSeconds between the safety-net polls of a point whose COV subscription is live.
forward_alarmsForward the device's intrinsic-reporting alarms (Baler#164). Windrow adds itself to the recipient-list of every notification-class object on the device (this changes the controller's configuration, hence opt-in), re-checks that every WINDROW_ALARM_REFRESH seconds (default 3600), and reads the active alarms at start. Each event notification is published retained on {topic_prefix}/{label}/alarm for a point of this connection, or {topic_prefix}/alarms/{type}-{instance} for another object, as the usual {point, value, ts} envelope whose value is the event (see AlarmEvent). Turning it off or deleting the connection removes Windrow's recipient entries again.
backfill_windowMost seconds of history a trend-log backfill reads back for this connection's points with trend_log; 0 turns backfill off.
PollStatus
statewaiting = nothing polled yet; partial = some points failing; error = every point failing.
last_poll_atlast_success_atlast_value_atWhen a value was last published.
last_errorlast_error_atfailing_countfailing_pointsLabels of the points whose last read failed (at most 20).
StartJobRequest
jobJob
idjobstatestarted_atunix seconds
finished_atmodbus_discover only - hosts probed so far.
resultWhen done - what the synchronous route returns. Left out of the listing.
errorModbusTestReadRequest
hostregisterportdevice_idModbus unit id.
Optional - a point in its stored object form (type, function, byte/word order...), as in a connection's points. Without it, register is read as one unsigned holding register.
ModbusTestReadResult
hostportdevice_idregistervalueThe decoded value (a number or boolean).
NodeStatus
announcedDevices that have announced themselves (windrow.announce), ever.
announced_onlineOf those, how many announced within the last 3 intervals.
announce_rejectedAnnouncements refused since start (outside --announce-allow, bad signature, malformed).
onlinesite_idinstance_idversionstarted_attsbacnet_connectionsbacnet_networkBACnet mode and, for a foreign device, its BBMD registration (see /bacnet/network).
bacnet_requestsBACnet requests sent since startup by kind
modbus_connectionsdevicespending_devicesnetworksSubnets this node sits on (its BACnet --address and any --modbus-scan-cidr), as CIDRs.
SetCredentialRequest
usernamepasswordStored encrypted at rest (Fernet, keyed by the WINDROW_CREDENTIAL_KEY env var) - never returned in plaintext again after this call's response.
CredentialResponse
usernamepasswordShown once, here only - every later GET of the owning record redacts this to null.
RedactedCredential
usernamepasswordAlways null in any GET/list response - see CredentialResponse for the one-time-shown plaintext.
ModbusDiscoverRequest
cidrSubnet to sweep, in CIDR notation. No default - Modbus periodic/on-demand discovery only ever scans a subnet the operator explicitly supplied.
portconcurrencyMax simultaneous TCP-connect attempts, bounded so a large sweep doesn't exhaust local ephemeral ports/file descriptors.
timeoutSeconds to wait for each host's TCP connect.
ModbusDevice
hostportvendorFrom Modbus function 0x2B/0x0E (Read Device Identification) - null if the device doesn't implement it, which is common and not an error.
product_coderevisionModbusPoints
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = integer | |
| type = object · requires: register |
Holding register address (0-based), read as uint16.
ModbusPoint
register0-based protocol address, not the 1-based "Modicon" number some register maps print: holding register 40001 is register: 0, input register 30010 is function: input, register: 9, coil 00001 is function: coil, register: 0. A 32-bit type spans register and register + 1.
functionRead with FC3 (holding), FC4 (input registers), FC1 (coils) or FC2 (discrete inputs).
typeRegister types only.
word_order32-bit types only. big = high word in the lower register (Modbus convention); little = word-swapped.
byte_orderByte order within each 16-bit register. little = byte-swapped.
scaleoffsetunitHaystack unit, published as the point's unit tag. Don't also name this point in the connection's units.
writableHolding registers and coils only. Tags the point writable so Baler accepts a pointWrite to it, and lets POST /write / point_write command it (FC6 for one register, FC16 for two, FC5 for a coil).
CreateModbusConnectionRequest
hosttopic_prefixMQTT topic prefix every point on this connection publishes under, as {topic_prefix}/{label}.
{label: point} - Modbus has no self-describing object list, so these are supplied directly from the vendor's register map. Each point is either a bare register address (the original form: one holding register read as an unsigned 16-bit integer) or a ModbusPoint object naming its function code, data type, word/byte order, scale/offset, unit and writability. Both forms can be mixed in one connection.
device_nameName of the device this connection polls - what it announces itself as (windrow.announce), or a DNS host name. When set, Windrow re-points the connection's address when the device announces (or resolves to) a new one. Empty clears it.
portdevice_idModbus unit/slave id.
poll_intervalSeconds between poll cycles - one shared interval for every point on this connection (unlike BACnet connections, Modbus connections don't support a per-point poll_interval).
Optional Haystack unit symbol per point label (a register is just a number). Published as each point's unit tag on {topic_prefix}/{label}/tags. A point object can carry its own unit instead; naming one point in both is a 400.
ModbusConnection
idhosttopic_prefixMQTT topic prefix every point on this connection publishes under, as {topic_prefix}/{label}.
{label: point} - Modbus has no self-describing object list, so these are supplied directly from the vendor's register map. Each point is either a bare register address (the original form: one holding register read as an unsigned 16-bit integer) or a ModbusPoint object naming its function code, data type, word/byte order, scale/offset, unit and writability. Both forms can be mixed in one connection.
null if no credential has been set for this device/connection.
Listings only: whether this connection is actually reading, since the process started. Kept in memory and never stored on the record; it resets when the connection is updated. Times are unix seconds.
Read-only. The priority levels commanded on each writable point through POST /write, as {label: {level: value}} (levels as strings). Modbus devices have no priority array, so Windrow keeps this one and writes the highest level's value. Absent when nothing is commanded.
device_nameName of the device this connection polls - what it announces itself as (windrow.announce), or a DNS host name. When set, Windrow re-points the connection's address when the device announces (or resolves to) a new one. Empty clears it.
portdevice_idModbus unit/slave id.
poll_intervalSeconds between poll cycles - one shared interval for every point on this connection (unlike BACnet connections, Modbus connections don't support a per-point poll_interval).
Optional Haystack unit symbol per point label (a register is just a number). Published as each point's unit tag on {topic_prefix}/{label}/tags. A point object can carry its own unit instead; naming one point in both is a 400.
UpdateConnectionRequest
device_nameName of the device this connection polls - what it announces itself as (windrow.announce), or a DNS host name. When set, Windrow re-points the connection's address when the device announces (or resolves to) a new one. Empty clears it.
device_addressRe-point the connection (also done automatically for a named device that moves).
Replaces every point. Labels must be unique.
poll_intervaltopic_prefixNo +/#; a trailing / is dropped.
read_multipleRead with ReadPropertyMultiple. Reads are batched per device, across every connection on it: points on one device tick together and go out as one request per read_multiple_max objects. A device that rejects RPM is read with one ReadProperty per point from then on; one whose answer doesn't fit an APDU gets smaller batches. false keeps one ReadProperty per point for this connection.
read_multiple_maxMost objects per ReadPropertyMultiple to this device. Default: what fits the device's max APDU (from its I-Am), at most WINDROW_RPM_MAX_POINTS (50). With several connections on one device the smallest value wins.
subscribe_covSubscribeCOV for every point (a point's own subscribe_cov overrides it), so a change publishes as soon as the device notifies. While a point's subscription is live it is still polled, every cov_poll_interval, in case a notification is lost; if the subscription fails it's polled at its normal interval and re-subscribed with back-off, and an object the device won't do COV for is just polled.
cov_lifetimeSeconds each COV subscription asks the device for. Renewed at half that.
cov_poll_intervalSeconds between the safety-net polls of a point whose COV subscription is live.
forward_alarmsForward the device's intrinsic-reporting alarms (Baler#164). Windrow adds itself to the recipient-list of every notification-class object on the device (this changes the controller's configuration, hence opt-in), re-checks that every WINDROW_ALARM_REFRESH seconds (default 3600), and reads the active alarms at start. Each event notification is published retained on {topic_prefix}/{label}/alarm for a point of this connection, or {topic_prefix}/alarms/{type}-{instance} for another object, as the usual {point, value, ts} envelope whose value is the event (see AlarmEvent). Turning it off or deleting the connection removes Windrow's recipient entries again.
backfill_windowMost seconds of history a trend-log backfill reads back for this connection's points with trend_log; 0 turns backfill off.
UpdateModbusConnectionRequest
device_nameName of the device this connection polls - what it announces itself as (windrow.announce), or a DNS host name. When set, Windrow re-points the connection's address when the device announces (or resolves to) a new one. Empty clears it.
{label: point} - Modbus has no self-describing object list, so these are supplied directly from the vendor's register map. Each point is either a bare register address (the original form: one holding register read as an unsigned 16-bit integer) or a ModbusPoint object naming its function code, data type, word/byte order, scale/offset, unit and writability. Both forms can be mixed in one connection.
Replaces the {label: unit} map.
poll_intervaltopic_prefixhostportdevice_idAddDeviceRequest
Decision Table
| Variant | Matching Criteria |
|---|---|
| type = object · requires: protocol, device_address, device_instance | |
| type = object · requires: protocol, host |
protocoldevice_addressdevice_instanceDeviceRecord
idprotocolfirst_seen_atUnix epoch seconds.
last_seen_atUnix epoch seconds.
statusdevice_instanceBACnet only.
addressBACnet only.
vendor_idBACnet only.
hostModbus only.
portModbus only.
vendorModbus only.
product_codeModbus only.
revisionModbus only.
device_idModbus only, and only on a device added by address (POST /devices) - a sweep can't learn the unit id.
connection_idSet once a matching connection exists for this device.
credentialnull if no credential has been set for this device/connection.
default_credential_flagSet if the best-effort default-credential check found one that worked. Always null for BACnet devices (no HTTP-login concept applies - see /devices's description).
default_credential_checked_at
