Skip to content

Troubleshooting

This page collects the failure modes that come up first in the field — serial mis-config, factory protocol defaults, SKU-vs-protocol mismatches, and the typed errors the library raises when something is off. The CLI tools in watlowlib.cli cover most of the investigation paths.

"I just wired up a controller — what protocol is it speaking?"

Every EZ-ZONE PM ships from the factory in Standard Bus at 38400 8-N-1, address 1. Switching to Modbus RTU requires either an SKU that includes the Modbus stack (comms position-8 ∈ 1, 2, B, D, E, F) and a front-panel mode flip, or a protocol-mode write through change_protocol_mode(...).

First-contact paths:

from watlowlib import ProtocolKind, open_device

# Standard Bus is the right first guess.
async with await open_device("/dev/ttyUSB0", protocol=ProtocolKind.STDBUS, address=1) as ctl:
    info = await ctl.identify()

# Modbus RTU on a controller you've already flipped.
async with await open_device(
    "/dev/ttyUSB0",
    protocol=ProtocolKind.MODBUS_RTU,
    address=1,
    serial_settings=SerialSettings(port="/dev/ttyUSB0", baudrate=9600),
) as ctl:
    info = await ctl.identify()

# Auto — Std Bus probe → Modbus probe → fail.
async with await open_device("/dev/ttyUSB0", protocol=ProtocolKind.AUTO) as ctl:
    print(ctl.session.protocol, ctl.info.part_number.raw)

AUTO is conservative: it never sweeps baud or parity. Wider port discovery lives in watlow-discover.

Per-protocol factory defaults

Protocol Baud Framing Address Notes
Standard Bus 38400 8-N-1 1 Built-in on every EZ-ZONE PM. Address parameter is [Ad;S] (1–16).
Modbus RTU 9600 8-N-1 1 Optional comms stack. Address parameter is [Ad;m] (1–247).

Both share the same EIA-485 hardware port — they're a software-mode flip, not separate connectors.

watlow-discover

watlow-discover /dev/ttyUSB0 --protocol stdbus --baud 38400
watlow-discover /dev/ttyUSB0 --protocol modbus --baud 9600 --range 1-16

Probes a port across address ranges and prints the DiscoveryResult for each address. Like open_device(protocol=AUTO), discovery is conservative — never sweeps baud or parity.

If watlow-discover finds nothing:

  1. Verify the port path. On Linux, ls /dev/ttyUSB* and lsusb confirm the adapter is attached.
  2. Verify permission. The user needs read/write on the device, usually via sudo usermod -aG dialout $USER.
  3. Verify the bus wiring — A/B polarity, common ground, end-of-line termination on long runs.
  4. Try the other protocol's defaults (table above).
  5. As a last resort, use watlow-diag tap to passively watch the line while toggling the front-panel protocol switch.

watlow-read

watlow-read /dev/ttyUSB0 --address 1 --param process_value

Open, identify, read one parameter, exit. The fastest sanity check that a controller is alive and decoded correctly.

SKU-vs-protocol mismatches

EZ-ZONE PM SKUs encode the comms options as position 8 of the part-number string — the first character of the trailing options block. PM3R1CA-AAAAAAA has comms code A: Standard Bus only, no Modbus stack. Position-8 codes that do carry Modbus are 1, 2, B, D, E, F.

Symptom Likely cause Fix
change_protocol_mode(MODBUS_RTU) "succeeds" but the controller never answers Modbus frames afterwards Std-Bus-only SKU (comms position-8 = A); the parameter write lands but the device has no Modbus stack to start Flip back to Std Bus and use Modbus only on a comms-equipped SKU. The library now gates this on Capability.HAS_MODBUS and raises WatlowCapabilityError instead.
identify() reports protocol_mismatch=True EEPROM parameter 17009 disagrees with the active wire protocol Either flip the device to match (change_protocol_mode) or open with the protocol the device is actually serving.
WatlowTimeoutError on every read after a change_protocol_mode write Verifier raced the framing change — verify pass uses the target protocol's defaults but inherits the open serial settings Re-open the controller with the target protocol's serial settings (38400 for Std Bus, 9600 for Modbus).

Common typed errors

Error Meaning What to do
WatlowConnectionError Transport open failed Check port path, permissions, cable.
WatlowTimeoutError No reply within timeout Wrong protocol? Wrong baud? Wrong address?
WatlowFrameError Std Bus framing or CRC invalid Line noise, electrical fault, or running Std Bus parser against a Modbus stream.
WatlowProtocolUnsupportedError Active protocol has no variant for this command Switch protocol, or check the command's variant table.
WatlowNoSuchObjectError / WatlowNoSuchAttributeError / WatlowNoSuchInstanceError Std Bus device returned a "no such" code Parameter or instance not present on this SKU; check info.capabilities and info.loops.
WatlowModbusIllegalDataAddressError Modbus device returned exception code 02 Register not implemented for this SKU; same gap as the Std Bus "no such" codes.
WatlowModbusIllegalDataValueError Modbus device returned exception code 03 Bad argument — clamp to the documented range.
WatlowConfirmationRequiredError PERSISTENT op without confirm=True Add confirm=True if the operation is intentional — see Safety.
WatlowCapabilityError Pre-flight SKU / capability gate refused the call Check info.capabilities against the operation's requirement; the device may not have shipped the option.
WatlowFirmwareError Controller's firmware is below the command's min_firmware Update firmware or use an alternate path.
WatlowValidationError Pre-flight host-side validation refused the call Bad parameter name, instance out of range, or value outside the parsed range.
WatlowSinkDependencyError Sink module imported without its optional backend pip install 'watlowlib[parquet]' / [postgres].

Offline decode with watlow-decode

When a wire-trace dump shows up in a bug report or RE session, decode it without hardware:

watlow-decode --stdbus "55 FF 05 10 00 00 06 E8 01 03 01 04 01 01 E3 99"
watlow-decode --modbus-rtu --address 1 --hex "01 03 01 68 00 02 44 1A"

watlow-decode understands the Std Bus BACnet MS/TP outer frame, the Watlow attribute payload (read/write request/response shapes), and the Modbus function-code + register-address layout. Output is JSON-friendly; pipe to jq for filtering.

Diagnostics CLI (watlow-diag)

Reverse-engineering tools live under the watlow-diag namespace. They are deliberately separate from the stable CLI because they can write to the device:

Subcommand Purpose
watlow-diag snapshot Dump every read-only parameter the controller answers for a given identified family.
watlow-diag stream Continuous sample stream for jitter / framing analysis.
watlow-diag sweep Walk a parameter-id range and record responses.
watlow-diag argfuzz Probe a parameter's argument space. Destructive ops gated.
watlow-diag tap Passively watch the line and decode in real time.
watlow-diag detect Identify framing on an unknown-protocol line.

Destructive subcommands require an explicit --i-understand-this-is-destructive flag. Never invoked from normal discovery or open_device.

Forced protocol switch

If the controller is in the wrong mode and the front panel isn't practical:

watlow-configure switch-protocol /dev/ttyUSB0 --address 1 --to modbus_rtu --confirm

The CLI thinly wraps maintenance.change_protocol_mode(...), gated on the SKU's comms-code check and confirm=True. See Safety.

Capturing for a bug report

Useful artefacts:

  • The raw command and reply bytes — watlow-diag tap for a passive capture, or the controller's info if identify() succeeds.
  • info snapshot — part number, family, capabilities, configured vs active protocol.
  • watlow-diag snapshot output — read-only parameter survey.
  • The CLI version banner: python -c "import watlowlib; print(watlowlib.__version__)".

See also