Files
SequencerIO/arnold/__pycache__/terminator_io.cpython-311.pyc

250 lines
37 KiB
Plaintext
Raw Normal View History

2026-03-02 17:48:55 -05:00
<EFBFBD>
2026-03-03 17:25:38 -05:00
_<>igp<00><01>@<00>dZddlmZddlZddlZddlZddlmZddlm Z ddl
2026-03-02 17:48:55 -05:00
m Z ddl m Z ddlmZe r
d d
2026-03-03 17:25:38 -05:00
lmZmZmZeje<15><00>ZeGd <0B>d <0C><00><00><00>ZGd <0A>d<0E><00>ZGd<0F>d<10><00>ZGd<11>dej<00><00>ZGd<13>d<14><00>ZdS)u<>
2026-03-02 17:48:55 -05:00
arnold/terminator_io.py — AutomationDirect Terminator I/O driver.
Encapsulates everything that touches a physical T1H-EBC100 controller:
- Modbus TCP connection management (pymodbus, auto-reconnect)
- Signal state cache (thread-safe)
- Background fast-poll thread (reads both coils and registers each cycle)
2026-03-03 17:25:38 -05:00
Dual-connection architecture
----------------------------
Each TerminatorIO maintains TWO independent Modbus TCP connections to
the same EBC100:
_read_client — used exclusively by the poll thread (FC02, FC04)
_write_client — used exclusively by write callers (FC05, FC06, FC15, FC16)
Each connection has its own lock and connection state. This eliminates
lock contention between the poll thread and output writes, reducing
write latency from 535 ms (old shared-lock design) to 519 ms
(just sleep jitter + Modbus round-trip, no lock wait).
The EBC100 accepts multiple simultaneous TCP connections on port 502
and processes them independently.
2026-03-02 17:48:55 -05:00
Key hardware quirks documented here:
- The EBC100 uses a UNIFIED flat coil address space across all digital
modules in physical slot order. FC02 (read discrete inputs) and
FC01/FC05/FC15 (read/write coils) share the same sequential offsets.
If slot 1 and slot 2 are 8-pt input modules (addresses 0-7, 8-15),
a 16-pt output module in slot 3 starts at coil address 16 — NOT 0.
- The EBC100 maintains TWO independent flat address spaces:
coil space (1-bit) — digital modules: FC01/FC02/FC05/FC15
register space (16-bit) — analog + temperature: FC03/FC04/FC06/FC16
A digital module advances only the coil offset; an analog module
advances only the register offset. They do not interfere.
- FC02 (read discrete inputs) returns input bits starting at address 0.
Because input modules always appear first in the unified coil scheme,
the FC02 bit index equals modbus_address for every digital input signal.
- FC04 (read input registers) returns 16-bit values for analog/temperature
input modules, starting at register address 0 in the register space.
- The EBC100 never raises Modbus exception code 2 (illegal address) for
out-of-range reads — it silently returns zeros. Module presence cannot
be auto-detected via protocol errors; use the config 'modules' list.
- The EBC100 responds to any Modbus unit/slave ID over TCP — the unit_id
field is echoed back but not used for routing. Set it to 1 (default).
- FC05 write_coil echoes back True for any address, even unmapped ones.
There is no write-error feedback for out-of-range output addresses.
- The device has no unsolicited push capability. Polling is mandatory.
Public API
----------
TerminatorIO(device: DeviceConfig)
2026-03-03 17:25:38 -05:00
.connect() -> bool # connects both read and write clients
.connect_reader() -> bool # connect read client only (poll thread)
.connect_writer() -> bool # connect write client only
2026-03-02 17:48:55 -05:00
.disconnect()
2026-03-03 17:25:38 -05:00
.read_inputs() -> list[bool] | None # bulk FC02, read client
.read_registers(address, count) -> list[int] | None # bulk FC04, read client
.write_output(address, value) -> bool # FC05, write client
.write_outputs(address, values) -> bool # FC15, write client
.write_register(address, value) -> bool # FC06, write client
.write_registers(address, values) -> bool # FC16, write client
2026-03-02 17:48:55 -05:00
.connected: bool
.status() -> dict
SignalState dataclass: name, value (bool|int), updated_at, stale
IORegistry(config) multi-device coordinator
.start() connect + start all poll threads
.stop() stop all poll threads + disconnect
.get(signal) -> SignalState | None
.get_value(signal) -> bool | int | None
.snapshot() -> dict[str, SignalState]
.poll_stats() -> list[dict]
.driver_status() -> list[dict]
<EFBFBD>)<01> annotationsN)<01> dataclass)<01> TYPE_CHECKING)<01>ModbusTcpClient)<01>ModbusException)<01>ExceptionResponse<73>)<03>Config<69> DeviceConfig<69> LogicalIOc<01><<00>eZdZUded<ded<ded<dZded <d
S) <0B> SignalState<74>str<74>namez
bool | int<6E>value<75>float<61>
updated_atF<EFBFBD>bool<6F>staleN)<05>__name__<5F>
2026-03-03 17:25:38 -05:00
__module__<EFBFBD> __qualname__<5F>__annotations__r<00><00><00>//home/noise/Code/arnold/arnold/terminator_io.pyrrjsD<00><00><00><00><00><00><00><13>O<EFBFBD>O<EFBFBD>O<EFBFBD><1A><1A><1A><1A><15><15><15><15><1C>E<EFBFBD><1C><1C><1C><1C><1C>rrc<01>B<00>eZdZdZdd<08>Zdd
<EFBFBD>Zdd <0B>Zedd <0A><04><00>ZdS)<13> _ModbusConnz<6E>
A single Modbus TCP connection with independent lock and state.
TerminatorIO creates two of these: one for reads, one for writes.
Each can connect, reconnect, and operate without blocking the other.
<20>device<63>'DeviceConfig'<27>roler<00>return<72>Nonec<01><><00>||_||_tj<00><00>|_d|_d|_d|_d|_dS)NFr<00>) <09>_device<63>_role<6C> threading<6E>Lock<63>lock<63>_client<6E> connected<65>connect_attempts<74>
last_error)<03>selfrr!s r<00>__init__z_ModbusConn.__init__~s@<00><00><1D><04> <0C><1B><04>
<EFBFBD> <20>~<7E>'<27>'<27><04> <09>/3<><04> <0C>$<24><04><0E> !<21><04><1D> "<22><04><0F><0F>rrc<01>N<00>|j<00>+ |j<00><00><00>n#t$rYnwxYwt|jj|jjdd<03><04><00>|_|xjdz c_|j<00><00><00>}||_ |rBt<00> d|jj |j |jj|jj<00><00>nRd|jj<00>d|jj<00><00>|_t<00>d|jj |j |j<00><00>|S) z9Open (or reopen) the TCP connection. Call with lock held.N<>r )<04>host<73>port<72>timeout<75>retriesz%s %s connected to %s:%dzTCP connect failed to <20>:z%s %s connect failed: %s)r+<00>close<73> Exceptionrr&r3r4r-<00>connectr,<00>log<6F>info<66>idr'r.<00>warning)r/<00>oks rr:z_ModbusConn.connect<63>s><00><00> <0F><<3C> #<23> <15><14> <0C>"<22>"<22>$<24>$<24>$<24>$<24><><1C> <15> <15> <15><14><04> <15><><EFBFBD><EFBFBD>'<27><15><1C>"<22><15><1C>"<22><15><15> 
2026-03-02 17:48:55 -05:00
<EFBFBD>
<EFBFBD>
2026-03-03 17:25:38 -05:00
<EFBFBD><04> <0C> <0A><1D><1D><11>"<22><1D><1D> <11>\<5C> !<21> !<21> #<23> #<23><02><1B><04><0E> <0A> F<01> <0F>H<EFBFBD>H<EFBFBD>/<2F><19>\<5C>_<EFBFBD>d<EFBFBD>j<EFBFBD><19>\<5C>&<26><04> <0C>(9<> ;<3B> ;<3B> ;<3B> ;<3B>
Q<01><14><1C>):<3A>P<>P<>T<EFBFBD>\<5C>=N<>P<>P<> <11>O<EFBFBD> <10>K<EFBFBD>K<EFBFBD>2<><1C> <0C><0F><14><1A>T<EFBFBD>_<EFBFBD> F<01> F<01> F<01><11> <09> <00>#<00>
0<03>0c<01><><00>|jr+ |j<00><00><00>n#t$rYnwxYwd|_d|_dS)NF)r+r8r9r,<00>r/s rr8z_ModbusConn.close<73>sZ<00><00> <0F><<3C> <15> <15><14> <0C>"<22>"<22>$<24>$<24>$<24>$<24><><1C> <15> <15> <15><14><04> <15><><EFBFBD><EFBFBD><1E><04><0E><1D><04> <0C> <0C> r@<00>ModbusTcpClient | Nonec<01><00>|jS<00>N)r+rBs r<00>clientz_ModbusConn.client<6E>s
<00><00><13>|<7C>rN)rr r!rr"r#<00>r"r<00>r"r#)r"rC) rrr<00>__doc__r0r:r8<00>propertyrFrrrrrvsz<00><00><00><00><00><00><08><08>#<23>#<23>#<23>#<23><12><12><12><12>:<1E><1E><1E><1E><0E><1C><1C><1C><0E>X<EFBFBD><1C><1C>rrc<01><><00>eZdZdZd'd<06>Zd(d<08>Zd(d <09>Zd(d
<EFBFBD>Zd)d <0B>Ze d(d <0C><04><00>Z
d*d<0E>Z d+d<14>Z d,d<16>Z d-d<17>Zd.d<19>Zd/d<1A>Zd0d<1D>Zd1d<1E>Zd2d<1F>Zd3d <20>Zd4d"<22>Zd5d#<23>Zd6d%<25>Zd&S)7<> TerminatorIOax
Modbus TCP driver for a single T1H-EBC100 controller.
Uses two independent TCP connections:
- _reader: for poll thread reads (FC02, FC04). Lock held only during reads.
- _writer: for output writes (FC05, FC06, FC15, FC16). Lock held only during writes.
Since each connection has its own lock, writes never block behind reads
and vice versa.
rr r"r#c<01>h<00>||_t|d<01><00>|_t|d<02><00>|_dS)N<>reader<65>writer)rr<00>_reader<65>_writer)r/rs rr0zTerminatorIO.__init__<5F>s/<00><00><1D><04> <0B>"<22>6<EFBFBD>8<EFBFBD>4<>4<><04> <0C>"<22>6<EFBFBD>8<EFBFBD>4<>4<><04> <0C> <0C> rrc<01>Z<00>|<00><00><00>}|<00><00><00>}|o|S)zCOpen both read and write connections. Returns True if both succeed.)<02>connect_reader<65>connect_writer)r/<00>r<>ws rr:zTerminatorIO.connect<63>s-<00><00> <10> <1F> <1F> !<21> !<21><01> <10> <1F> <1F> !<21> !<21><01><10>w<EFBFBD>Q<EFBFBD>rc<01><><00>|jj5|j<00><00><00>cddd<01><00>S#1swxYwYdS)z/Open the read connection (used by poll thread).N)rPr*r:rBs rrSzTerminatorIO.connect_reader<65><00><><00><00> <11>\<5C> <1E> *<2A> *<2A><17><<3C>'<27>'<27>)<29>)<29> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A><><EFBFBD><EFBFBD> *<2A> *<2A> *<2A> *<2A> *<2A> *<2A> <00>3<03>7<07>7c<01><><00>|jj5|j<00><00><00>cddd<01><00>S#1swxYwYdS)z6Open the write connection (used by sequencer/API/TUI).N)rQr*r:rBs rrTzTerminatorIO.connect_writer<65>rXrYc<01><><00>|jj5|j<00><00><00>ddd<00><00>n #1swxYwY|jj5|j<00><00><00>ddd<00><00>dS#1swxYwYdSrE)rPr*r8rQrBs r<00>
disconnectzTerminatorIO.disconnect<63>s<><00><00> <11>\<5C> <1E> !<21> !<21> <10>L<EFBFBD> <1E> <1E> <20> <20> <20> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21><><EFBFBD><EFBFBD> !<21> !<21> !<21> !<21> <11>\<5C> <1E> !<21> !<21> <10>L<EFBFBD> <1E> <1E> <20> <20> <20> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21> !<21><><EFBFBD><EFBFBD> !<21> !<21> !<21> !<21> !<21> !s<00>3<03>7<07>7<07>
A1<03>1A5<07>8A5c<01>2<00>|jjp |jjSrE)rPr,rQrBs rr,zTerminatorIO.connected<65>s<00><00><13>|<7C>%<25>?<3F><14><1C>)?<3F>?r<00>list[bool] | Nonec<01><><00>|j<00><00><00>}|dkrgS|jj5|<00>|jd|<01><02><00>cddd<03><00>S#1swxYwYdS)u
2026-03-02 17:48:55 -05:00
Read all discrete input points in one FC02 request.
Returns a flat list of bool ordered by slot then point (matching
the unified address scheme), or None on comms error.
2026-03-03 17:25:38 -05:00
Uses the read connection — never blocks write callers.
r<00><02>address<73>countN)r<00>total_input_pointsrPr*<00>_fc02)r/<00>totals r<00> read_inputszTerminatorIO.read_inputs<74>s<><00><00><15> <0B>.<2E>.<2E>0<>0<><05> <10>A<EFBFBD>:<3A>:<3A><15>I<EFBFBD> <11>\<5C> <1E> D<01> D<01><17>:<3A>:<3A>d<EFBFBD>l<EFBFBD>A<EFBFBD>U<EFBFBD>:<3A>C<>C<> D<01> D<01> D<01> D<01> D<01> D<01> D<01> D<01> D<01> D<01> D<01> D<01><><EFBFBD><EFBFBD> D<01> D<01> D<01> D<01> D<01> Ds<00>A<03>A<07>A<07>connrra<00>intrbc<01>t<00>td<01><00>D<00>]&}|js|<01><00><00>sdS |j<00>|||jj<00><02><00>}|<05><00><00>st|t<00><00>r.t<00> d|jj |<05><00>d|_<00><>t|jd|<03><00><00>cS#tt t"f$rP}t<00> d|jj |dz|<06><00>d|_t%jd<07><00>Yd}~<06><01> d}~wwxYwdS)Nr2<00>rarb<00> device_idz%s FC02 error: %sFz#%s FC02 read error (attempt %d): %sr <><E79A99><EFBFBD><EFBFBD><EFBFBD>?)<14>ranger,r:rF<00>read_discrete_inputsr<00>unit_id<69>isError<6F>
isinstancerr;r>r=<00>list<73>bitsr<00>ConnectionError<6F>OSError<6F>time<6D>sleep<65>r/rgrarb<00>attempt<70>rr<72>excs rrdzTerminatorIO._fc02<30>sH<00><00><1C>Q<EFBFBD>x<EFBFBD>x<EFBFBD> !<21> !<21>G<EFBFBD><17>><3E> <20><1B>|<7C>|<7C>~<7E>~<7E> <20><1F>4<EFBFBD>4<EFBFBD> !<21><19>[<5B>5<>5<>#<23>5<EFBFBD>"<22>k<EFBFBD>1<>6<><12><12><02><16>:<3A>:<3A><<3C><<3C><1D>:<3A>b<EFBFBD>2C<32>#D<>#D<><1D><17>K<EFBFBD>K<EFBFBD> 3<>T<EFBFBD>[<5B>^<5E>R<EFBFBD>H<>H<>H<>%*<2A>D<EFBFBD>N<EFBFBD><1C><1B>B<EFBFBD>G<EFBFBD>F<EFBFBD>U<EFBFBD>F<EFBFBD>O<EFBFBD>,<2C>,<2C>,<2C>,<2C>,<2C><>#<23>_<EFBFBD>g<EFBFBD>><3E> !<21> !<21> !<21><13> <0B> <0B>A<> <20>K<EFBFBD>N<EFBFBD>G<EFBFBD>a<EFBFBD>K<EFBFBD><13>><3E>><3E>><3E>!&<26><04><0E><14>
2026-03-02 17:48:55 -05:00
<EFBFBD>4<EFBFBD> <20> <20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD>  !<21><><EFBFBD><EFBFBD>
2026-03-03 17:25:38 -05:00
<14>t<EFBFBD><00>A=C<02>0C<02>D5<05>%AD0<05>0D5<05>list[int] | Nonec<01><><00>|dkrgS|jj5|<00>|j||<02><00>cddd<02><00>S#1swxYwYdS)u<>
2026-03-02 17:48:55 -05:00
Read contiguous 16-bit input registers via FC04.
2026-03-03 17:25:38 -05:00
Uses the read connection — never blocks write callers.
rN)rPr*<00>_fc04)r/rarbs r<00>read_registerszTerminatorIO.read_registerss<><00><00> <11>A<EFBFBD>:<3A>:<3A><15>I<EFBFBD> <11>\<5C> <1E> <<3C> <<3C><17>:<3A>:<3A>d<EFBFBD>l<EFBFBD>G<EFBFBD>U<EFBFBD>;<3B>;<3B> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C><><EFBFBD><EFBFBD> <<3C> <<3C> <<3C> <<3C> <<3C> <s<00>><03>A<07>Ac<01>t<00>td<01><00>D<00>]&}|js|<01><00><00>sdS |j<00>|||jj<00><02><00>}|<05><00><00>st|t<00><00>r.t<00> d|jj |<05><00>d|_<00><>t|jd|<03><00><00>cS#tt t"f$rP}t<00> d|jj |dz|<06><00>d|_t%jd<07><00>Yd}~<06><01> d}~wwxYwdS)Nr2rjz%s FC04 error: %sFz#%s FC04 read error (attempt %d): %sr rl)rmr,r:rF<00>read_input_registersrrorprqrr;r>r=rr<00> registersrrtrurvrwrxs rrzTerminatorIO._fc04sI<00><00><1C>Q<EFBFBD>x<EFBFBD>x<EFBFBD> !<21> !<21>G<EFBFBD><17>><3E> <20><1B>|<7C>|<7C>~<7E>~<7E> <20><1F>4<EFBFBD>4<EFBFBD> !<21><19>[<5B>5<>5<>#<23>5<EFBFBD>"<22>k<EFBFBD>1<>6<><12><12><02><16>:<3A>:<3A><<3C><<3C><1D>:<3A>b<EFBFBD>2C<32>#D<>#D<><1D><17>K<EFBFBD>K<EFBFBD> 3<>T<EFBFBD>[<5B>^<5E>R<EFBFBD>H<>H<>H<>%*<2A>D<EFBFBD>N<EFBFBD><1C><1B>B<EFBFBD>L<EFBFBD><16>%<25><16>0<>1<>1<>1<>1<>1<><31>#<23>_<EFBFBD>g<EFBFBD>><3E> !<21> !<21> !<21><13> <0B> <0B>A<> <20>K<EFBFBD>N<EFBFBD>G<EFBFBD>a<EFBFBD>K<EFBFBD><13>><3E>><3E>><3E>!&<26><04><0E><14>
2026-03-02 17:48:55 -05:00
<EFBFBD>4<EFBFBD> <20> <20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD>  !<21><><EFBFBD><EFBFBD>
2026-03-03 17:25:38 -05:00
<14>tr|rc<01><><00>|jj5|<00>|j||<02><00>cddd<01><00>S#1swxYwYdS)uz
2026-03-02 17:48:55 -05:00
Write a single coil via FC05.
2026-03-03 17:25:38 -05:00
Uses the write connection — never blocked by poll thread reads.
N)rQr*<00>_fc05<30>r/rars r<00> write_outputzTerminatorIO.write_output7s<><00><00> <12>\<5C> <1E> <<3C> <<3C><17>:<3A>:<3A>d<EFBFBD>l<EFBFBD>G<EFBFBD>U<EFBFBD>;<3B>;<3B> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C><><EFBFBD><EFBFBD> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <00>6<03>:<07>:c<01><><00>td<01><00>D<00>]3}|js|<01><00><00>sdS |j<00>|||jj<00><03><00>}|<05><00><00>st|t<00><00>r/t<00> d|jj ||<05><00>d|_<00><>t<00> d|jj ||<03><00>dS#ttt f$rP}t<00> d|jj |dz|<06><00>d|_t#jd <09><00>Yd}~<06><01>-d}~wwxYwdS)
Nr2F<>rarrkz%s FC05 error addr=%d: %sz%s coil[%d] = %sTz$%s FC05 write error (attempt %d): %sr rl)rmr,r:rF<00>
write_coilrrorprqrr;r>r=<00>debugrrtrurvrw<00>r/rgrarryrzr{s rr<>zTerminatorIO._fc05@sW<00><00><1C>Q<EFBFBD>x<EFBFBD>x<EFBFBD> !<21> !<21>G<EFBFBD><17>><3E> !<21><1B>|<7C>|<7C>~<7E>~<7E>!<21> <20>5<EFBFBD>5<EFBFBD> !<21><19>[<5B>+<2B>+<2B>#<23>5<EFBFBD>"<22>k<EFBFBD>1<>,<2C><12><12><02><16>:<3A>:<3A><<3C><<3C><1D>:<3A>b<EFBFBD>2C<32>#D<>#D<><1D><17>K<EFBFBD>K<EFBFBD> ;<3B> $<24> <0B><0E><07><12>=<3D>=<3D>=<3D>%*<2A>D<EFBFBD>N<EFBFBD><1C><13> <09> <09>,<2C>d<EFBFBD>k<EFBFBD>n<EFBFBD>g<EFBFBD>u<EFBFBD>M<>M<>M<><1B>t<EFBFBD>t<EFBFBD><74>#<23>_<EFBFBD>g<EFBFBD>><3E> !<21> !<21> !<21><13> <0B> <0B>B<> <20>K<EFBFBD>N<EFBFBD>G<EFBFBD>a<EFBFBD>K<EFBFBD><13>><3E>><3E>><3E>!&<26><04><0E><14>
2026-03-02 17:48:55 -05:00
<EFBFBD>4<EFBFBD> <20> <20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD>  !<21><><EFBFBD><EFBFBD>
<15>u<EFBFBD><00>A>C<02>1'C<02>E<05>2AD=<05>=E<05>values<65>
2026-03-03 17:25:38 -05:00
list[bool]c<01><><00>|jj5|<00>|j||<02><00>cddd<01><00>S#1swxYwYdS)z@Write multiple contiguous coils via FC15. Uses write connection.N)rQr*<00>_fc15<31>r/rar<>s r<00> write_outputszTerminatorIO.write_outputsXs<><00><00> <11>\<5C> <1E> =<3D> =<3D><17>:<3A>:<3A>d<EFBFBD>l<EFBFBD>G<EFBFBD>V<EFBFBD><<3C><<3C> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D><><EFBFBD><EFBFBD> =<3D> =<3D> =<3D> =<3D> =<3D> =r<>c<01>@<00>td<01><00>D<00>] }|js|<01><00><00>sdS |j<00>|||jj<00><03><00>}|<05><00><00>st|t<00><00>r/t<00> d|jj ||<05><00>d|_<00><>dS#tttf$rP}t<00> d|jj |dz|<06><00>d|_t!jd<08><00>Yd}~<06><01>d}~wwxYwdS) Nr2F<>rar<>rkz%s FC15 error addr=%d: %sTz$%s FC15 write error (attempt %d): %sr rl)rmr,r:rF<00> write_coilsrrorprqrr;r>r=rrtrurvrw<00>r/rgrar<>ryrzr{s rr<>zTerminatorIO._fc15]s:<00><00><1C>Q<EFBFBD>x<EFBFBD>x<EFBFBD> !<21> !<21>G<EFBFBD><17>><3E> !<21><1B>|<7C>|<7C>~<7E>~<7E>!<21> <20>5<EFBFBD>5<EFBFBD> !<21><19>[<5B>,<2C>,<2C>#<23>F<EFBFBD>"<22>k<EFBFBD>1<>-<2D><12><12><02><16>:<3A>:<3A><<3C><<3C><1D>:<3A>b<EFBFBD>2C<32>#D<>#D<><1D><17>K<EFBFBD>K<EFBFBD> ;<3B> $<24> <0B><0E><07><12>=<3D>=<3D>=<3D>%*<2A>D<EFBFBD>N<EFBFBD><1C><1B>t<EFBFBD>t<EFBFBD><74>#<23>_<EFBFBD>g<EFBFBD>><3E> !<21> !<21> !<21><13> <0B> <0B>B<> <20>K<EFBFBD>N<EFBFBD>G<EFBFBD>a<EFBFBD>K<EFBFBD><13>><3E>><3E>><3E>!&<26><04><0E><14>
<EFBFBD>4<EFBFBD> <20> <20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD>  !<21><><EFBFBD><EFBFBD>
<15>u<EFBFBD><00>A>B4<02>4D<05> AD<05>Dc<01><><00>|jj5|<00>|j||<02><00>cddd<01><00>S#1swxYwYdS)u<>
2026-03-02 17:48:55 -05:00
Write a single 16-bit holding register via FC06.
2026-03-03 17:25:38 -05:00
Uses the write connection — never blocked by poll thread reads.
N)rQr*<00>_fc06r<36>s r<00>write_registerzTerminatorIO.write_registerxs<><00><00>
<12>\<5C> <1E> <<3C> <<3C><17>:<3A>:<3A>d<EFBFBD>l<EFBFBD>G<EFBFBD>U<EFBFBD>;<3B>;<3B> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C> <<3C><><EFBFBD><EFBFBD> <<3C> <<3C> <<3C> <<3C> <<3C> <r<>c<01><><00>td<01><00>D<00>]3}|js|<01><00><00>sdS |j<00>|||jj<00><03><00>}|<05><00><00>st|t<00><00>r/t<00> d|jj ||<05><00>d|_<00><>t<00> d|jj ||<03><00>dS#ttt f$rP}t<00> d|jj |dz|<06><00>d|_t#jd <09><00>Yd}~<06><01>-d}~wwxYwdS)
Nr2Fr<46>z%s FC06 error addr=%d: %sz%s reg[%d] = %dTz$%s FC06 write error (attempt %d): %sr rl)rmr,r:rFr<>rrorprqrr;r>r=r<>rrtrurvrwr<>s rr<>zTerminatorIO._fc06<30>sW<00><00><1C>Q<EFBFBD>x<EFBFBD>x<EFBFBD> !<21> !<21>G<EFBFBD><17>><3E> !<21><1B>|<7C>|<7C>~<7E>~<7E>!<21> <20>5<EFBFBD>5<EFBFBD> !<21><19>[<5B>/<2F>/<2F>#<23>5<EFBFBD>"<22>k<EFBFBD>1<>0<><12><12><02><16>:<3A>:<3A><<3C><<3C><1D>:<3A>b<EFBFBD>2C<32>#D<>#D<><1D><17>K<EFBFBD>K<EFBFBD> ;<3B> $<24> <0B><0E><07><12>=<3D>=<3D>=<3D>%*<2A>D<EFBFBD>N<EFBFBD><1C><13> <09> <09>+<2B>T<EFBFBD>[<5B>^<5E>W<EFBFBD>e<EFBFBD>L<>L<>L<><1B>t<EFBFBD>t<EFBFBD><74>#<23>_<EFBFBD>g<EFBFBD>><3E> !<21> !<21> !<21><13> <0B> <0B>B<> <20>K<EFBFBD>N<EFBFBD>G<EFBFBD>a<EFBFBD>K<EFBFBD><13>><3E>><3E>><3E>!&<26><04><0E><14>
<EFBFBD>4<EFBFBD> <20> <20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD>  !<21><><EFBFBD><EFBFBD>
<15>ur<75><00> list[int]c<01><><00>|jj5|<00>|j||<02><00>cddd<01><00>S#1swxYwYdS)z[Write multiple contiguous 16-bit holding registers via FC16.
Uses write connection.N)rQr*<00>_fc16r<36>s r<00>write_registerszTerminatorIO.write_registers<72>s<><00><00><12>\<5C> <1E> =<3D> =<3D><17>:<3A>:<3A>d<EFBFBD>l<EFBFBD>G<EFBFBD>V<EFBFBD><<3C><<3C> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D> =<3D><><EFBFBD><EFBFBD> =<3D> =<3D> =<3D> =<3D> =<3D> =r<>c<01>@<00>td<01><00>D<00>] }|js|<01><00><00>sdS |j<00>|||jj<00><03><00>}|<05><00><00>st|t<00><00>r/t<00> d|jj ||<05><00>d|_<00><>dS#tttf$rP}t<00> d|jj |dz|<06><00>d|_t!jd<08><00>Yd}~<06><01>d}~wwxYwdS) Nr2Fr<46>z%s FC16 error addr=%d: %sTz$%s FC16 write error (attempt %d): %sr rl)rmr,r:rFr<>rrorprqrr;r>r=rrtrurvrwr<>s rr<>zTerminatorIO._fc16<31>s:<00><00><1C>Q<EFBFBD>x<EFBFBD>x<EFBFBD> !<21> !<21>G<EFBFBD><17>><3E> !<21><1B>|<7C>|<7C>~<7E>~<7E>!<21> <20>5<EFBFBD>5<EFBFBD> !<21><19>[<5B>0<>0<>#<23>F<EFBFBD>"<22>k<EFBFBD>1<>1<><12><12><02><16>:<3A>:<3A><<3C><<3C><1D>:<3A>b<EFBFBD>2C<32>#D<>#D<><1D><17>K<EFBFBD>K<EFBFBD> ;<3B> $<24> <0B><0E><07><12>=<3D>=<3D>=<3D>%*<2A>D<EFBFBD>N<EFBFBD><1C><1B>t<EFBFBD>t<EFBFBD><74>#<23>_<EFBFBD>g<EFBFBD>><3E> !<21> !<21> !<21><13> <0B> <0B>B<> <20>K<EFBFBD>N<EFBFBD>G<EFBFBD>a<EFBFBD>K<EFBFBD><13>><3E>><3E>><3E>!&<26><04><0E><14>
2026-03-02 17:48:55 -05:00
<EFBFBD>4<EFBFBD> <20> <20> <20> <20> <20> <20> <20> <20><><EFBFBD><EFBFBD><EFBFBD>  !<21><><EFBFBD><EFBFBD>
2026-03-03 17:25:38 -05:00
<15>ur<75><00>dictc <01><><00>|jj|jj|jj|j|jj|jj|jj|jj|jjpd|jjpdd<01>
S)N)
rkr3r4r,<00>reader_connected<65>writer_connected<65>reader_connect_attempts<74>writer_connect_attempts<74>last_reader_error<6F>last_writer_error) rr=r3r4r,rPrQr-r.rBs r<00>statuszTerminatorIO.status<75>sg<00><00> $<24> <0B><0E> $<24> <0B> 0<> $<24> <0B> 0<> $<24><0E> $<24> <0C> 6<> $<24> <0C> 6<>'+<2B>|<7C>'D<>'+<2B>|<7C>'D<>!%<25><1C>!8<>!@<40>D<EFBFBD>!%<25><1C>!8<>!@<40>D<EFBFBD> 
<EFBFBD> 
<EFBFBD>
rN)rr r"r#rGrH)r"r^)rgrrarhrbrhr"r^)rarhrbrhr"r})rgrrarhrbrhr"r})rarhrrr"r)rgrrarhrrr"r)rarhr<>r<>r"r)rgrrarhr<>r<>r"r)rarhrrhr"r)rgrrarhrrhr"r)rarhr<>r<>r"r)rgrrarhr<>r<>r"r<00>r"r<>)rrrrIr0r:rSrTr\rJr,rfrdr<>rr<>r<>r<>r<>r<>r<>r<>r<>r<>rrrrLrL<00>s<><00><00><00><00><00><00> <08> <08>5<>5<>5<>5<><17><17><17><17> *<2A>*<2A>*<2A>*<2A>
*<2A>*<2A>*<2A>*<2A>
!<21>!<21>!<21>!<21> <0E>@<01>@<01>@<01><0E>X<EFBFBD>@<01> D<01> D<01> D<01> D<01><14><14><14><14>4 <<3C> <<3C> <<3C> <<3C><14><14><14><14>4<<3C><<3C><<3C><<3C><15><15><15><15>0=<3D>=<3D>=<3D>=<3D>
<15><15><15><15>6<<3C><<3C><<3C><<3C><15><15><15><15>0=<3D>=<3D>=<3D>=<3D> <15><15><15><15>6 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
rrLc<01>\<00><00>eZdZdZd<15>fd <0A> Zedd<0F><04><00>Zdd<10>Zdd<11>Zdd<12>Z dd<14>Z
<EFBFBD>xZ S)<19> _PollThreadu<64>
2026-03-02 17:48:55 -05:00
Reads all input points from one EBC100 at poll_interval_ms, updates the
shared signal cache. Daemon thread — exits when the process does.
2026-03-03 17:25:38 -05:00
Each poll cycle reads BOTH address spaces via the driver's read connection:
- FC02 (coil space): digital input signals -> list[bool]
- FC04 (register space): analog/temperature input signals -> list[int]
The read connection has its own lock, so poll reads never block output
writes (which use the separate write connection).
<20>driverrL<00>digital_signals<6C>list['LogicalIO']<5D>analog_signals<6C>cache<68>dict[str, SignalState]r*<00>threading.Lockr"r#c<01><00><01>t<00><00><00>d|jj<00><00>d<02><03><00>||_||_||_||_||_tj
<00><00>|_ d|_ d|_ d|_d|_dS)Nzpoll-T)r<00>daemonrg)<10>superr0rr=<00>_driver<65>_digital_signals<6C>_analog_signals<6C>_cache<68>_lockr(<00>Event<6E>_stop<6F>
poll_count<EFBFBD> error_count<6E> _achieved_hz<68> _last_poll_ts)r/r<>r<>r<>r<>r*<00> __class__s <20>rr0z_PollThread.__init__<5F>s<><00><><00> <0E><07><07><18><18>8<>f<EFBFBD>m<EFBFBD>&6<>8<>8<><14><18>F<>F<>F<> &<26><04> <0C> /<2F><04><1D> .<2E><04><1C> %<25><04> <0B> $<24><04>
2026-03-02 17:48:55 -05:00
<EFBFBD>"<22><1F>*<2A>*<2A><04>
2026-03-03 17:25:38 -05:00
<EFBFBD><1C><04><0F><1C><04><18>#&<26><04><19>+/<2F><04><1A><1A>rrhc<01>T<00>t|j<00><00>t|j<00><00>zSrE)<03>lenr<6E>r<>rBs r<00>_total_signalsz_PollThread._total_signals<6C>s#<00><00><12>4<EFBFBD>(<28>)<29>)<29>C<EFBFBD><04>0D<30>,E<>,E<>E<>Erc<01>8<00>|j<00><00><00>dSrE)r<><00>setrBs r<00>stopz_PollThread.stop<6F>s<00><00> <0C>
2026-03-02 17:48:55 -05:00
<EFBFBD><0E><0E><18><18><18><18>rc <01><00>|jjjdz }t<00>d|jjj|jjjt |j<00><00>t |j<00><00><00><00>|j<00> <00><00>tj <00><00>}d}|j <00> <00><00><00>stj <00><00>}|<00><00><00>|dz }|xjdz c_tj <00><00>|z
}tj <00><00>|z
}|dkrU||z |_t<00>d|jjj|j|j<00><00>tj <00><00>}d}||z
2026-03-03 17:25:38 -05:00
}|dkr|j <00>|<07><00>|j <00> <00><00><00><01>t<00>d|jjj<00><00>|j<00><00><00>dS)Ng@<40>@zIPoll thread started: %s %.0f ms interval %d digital + %d analog signalsrr g@z%s %.1f polls/s errors=%dzPoll thread stopped: %s)r<>r<00>poll_interval_msr;r<r=r<>r<>r<>rSrv<00> monotonicr<63><00>is_set<65>_cycler<65>r<>r<>r<><00>waitr\)r/<00>interval<61>rate_t0<74>
rate_polls<EFBFBD>t0<74>elapsed<65>windowr<77>s r<00>runz_PollThread.run<75>s<><00><00><17><<3C>&<26>7<>&<26>@<40><08> <0B><08><08>\<5C><15><1C>$<24>'<27><15><1C>$<24>5<><14>T<EFBFBD>*<2A>+<2B>+<2B><14>T<EFBFBD>)<29>*<2A>*<2A>  ,<2C> ,<2C> ,<2C> <0A> <0C>#<23>#<23>%<25>%<25>%<25><19>^<5E>%<25>%<25><07><16>
2026-03-02 17:48:55 -05:00
<EFBFBD><16>*<2A>#<23>#<23>%<25>%<25> &<26><15><1E>!<21>!<21>B<EFBFBD> <10>K<EFBFBD>K<EFBFBD>M<EFBFBD>M<EFBFBD>M<EFBFBD> <16>Q<EFBFBD> <1E>J<EFBFBD> <10>O<EFBFBD>O<EFBFBD>q<EFBFBD> <20>O<EFBFBD>O<EFBFBD><1A>n<EFBFBD>&<26>&<26><12>+<2B>G<EFBFBD><1A>^<5E>%<25>%<25><07>/<2F>F<EFBFBD><15><13>}<7D>}<7D>$.<2E><16>$7<><04>!<21><13> <09> <09>7<><1E>,<2C>-<2D>0<><1E>+<2B>T<EFBFBD>-=<3D>?<3F>?<3F>?<3F>"<22>^<5E>-<2D>-<2D><07><1E>
<EFBFBD><1B>g<EFBFBD>%<25>D<EFBFBD><13>a<EFBFBD>x<EFBFBD>x<EFBFBD><14>
<EFBFBD><0F><0F><04>%<25>%<25>%<25>)<17>*<2A>#<23>#<23>%<25>%<25> &<26>, <0C><08><08>*<2A>D<EFBFBD>L<EFBFBD>,?<3F>,B<>C<>C<>C<> <0C> <0C><1F><1F>!<21>!<21>!<21>!<21>!rc
<01><><00>|js |jsdSd}i}tj<00><00>}|j<00>r|j<00><00><00>}|<04>^d}|jD]S}|j<00>|j<00><00>}t|j|r|j
nd|r|j n|d<02><03><00>||j<<00>Tn<54>|jD]<5D>}|j t|<04><00>kr9t|jt||j <00><00>|d<01><03><00>||j<<00>St<00>d|jjj|j|j t|<04><00><00><00><00><>|j<00>r9|jj<00><00><00>}|j<00>d|<07><06><00>}|<08>^d}|jD]S}|j<00>|j<00><00>}t|j|r|j
2026-03-03 17:25:38 -05:00
nd|r|j n|d<02><03><00>||j<<00>Tn<54>|jD]<5D>}|j t|<08><00>kr9t|jt+||j <00><00>|d<01><03><00>||j<<00>St<00>d|jjj|j|j t|<08><00><00><00><00><>|r|xjdz c_||_|j5|j<00>|<02><00>ddd<00><00>dS#1swxYwYdS) NFT)rrrrz+%s signal %r addr %d out of range (%d bits)rr`z/%s signal %r reg addr %d out of range (%d regs)r )r<>r<>rvr<>r<>rfr<><00>getrrrr<00>modbus_addressr<73>rr;r>rr=<00>total_analog_input_channelsr<73>rhr<>r<>r<><00>update) r/<00> had_error<6F>updates<65>nowrs<00>sig<69>existing<6E>
total_regs<EFBFBD>regss rr<>z_PollThread._cycles|<00><00><13>$<24> <13>T<EFBFBD>-A<> <13> <12>F<EFBFBD><19> <09>*,<2C><07><12>n<EFBFBD><1E><1E><03> <10> <20> C<01><17><<3C>+<2B>+<2B>-<2D>-<2D>D<EFBFBD><13>|<7C> <20> <09><1F>0<><16><16>C<EFBFBD>#<23>{<7B><EFBFBD><EFBFBD>s<EFBFBD>x<EFBFBD>8<>8<>H<EFBFBD>(3<> <20>X<EFBFBD>08<30>C<>h<EFBFBD>n<EFBFBD>n<EFBFBD>e<EFBFBD>:B<>#K<>8<EFBFBD>#6<>#6<><03>"<22> )<16>)<16>)<16>G<EFBFBD>C<EFBFBD>H<EFBFBD>%<25>%<25><16> <20>0<> C<01> C<01>C<EFBFBD><1A>)<29>C<EFBFBD><04>I<EFBFBD>I<EFBFBD>5<>5<>,7<>!$<24><18>"&<26>t<EFBFBD>C<EFBFBD>,><3E>'?<3F>"@<40>"@<40>'*<2A>"'<27> -<1A>-<1A>-<1A><07><03><08>)<29>)<29><1C> <0B> <0B>$Q<>$(<28>L<EFBFBD>$7<>$:<3A>C<EFBFBD>H<EFBFBD>$'<27>$6<><03>D<EFBFBD> <09> <09>C<01>C<01>C<01>C<01>
2026-03-02 17:48:55 -05:00
<10> <1F> C<01><1D><1C>,<2C>H<>H<>J<>J<>J<EFBFBD><17><<3C>.<2E>.<2E>q<EFBFBD>
2026-03-03 17:25:38 -05:00
<EFBFBD>.<2E>K<>K<>D<EFBFBD><13>|<7C> <20> <09><1F>/<2F><16><16>C<EFBFBD>#<23>{<7B><EFBFBD><EFBFBD>s<EFBFBD>x<EFBFBD>8<>8<>H<EFBFBD>(3<> <20>X<EFBFBD>08<30>?<3F>h<EFBFBD>n<EFBFBD>n<EFBFBD>a<EFBFBD>:B<>#K<>8<EFBFBD>#6<>#6<><03>"<22> )<16>)<16>)<16>G<EFBFBD>C<EFBFBD>H<EFBFBD>%<25>%<25><16> <20>/<2F> C<01> C<01>C<EFBFBD><1A>)<29>C<EFBFBD><04>I<EFBFBD>I<EFBFBD>5<>5<>,7<>!$<24><18>"%<25>d<EFBFBD>3<EFBFBD>+=<3D>&><3E>"?<3F>"?<3F>'*<2A>"'<27> -<1A>-<1A>-<1A><07><03><08>)<29>)<29><1C> <0B> <0B>$U<>$(<28>L<EFBFBD>$7<>$:<3A>C<EFBFBD>H<EFBFBD>$'<27>$6<><03>D<EFBFBD> <09> <09>C<01>C<01>C<01>C<01> <15> "<22> <10> <1C> <1C><01> !<21> <1C> <1C> <20><04><1A> <11>Z<EFBFBD> (<28> (<28> <10>K<EFBFBD> <1E> <1E>w<EFBFBD> '<27> '<27> '<27> (<28> (<28> (<28> (<28> (<28> (<28> (<28> (<28> (<28> (<28> (<28> (<28><><EFBFBD><EFBFBD> (<28> (<28> (<28> (<28> (<28> (s<00>)K<03>K<07>Kr<>c<01><><00>|jjj|j|jt |jd<01><00>t d|jjjz d<01><00>|j|<00> <00><00>d<03>S)Nr i<>)rkr<>r<><00> achieved_hz<68> target_hz<68> last_poll_ts<74>running)
r<EFBFBD>rr=r<>r<><00>roundr<64>r<>r<><00>is_aliverBs r<00>statsz_PollThread.statscsb<00><00> <20>L<EFBFBD>/<2F>2<> <20>O<EFBFBD> <20>,<2C>!<21>$<24>"3<>Q<EFBFBD>7<>7<>!<21>$<24><14><1C>)<<3C>)M<>"M<>q<EFBFBD>Q<>Q<> <20>.<2E> <20>M<EFBFBD>M<EFBFBD>O<EFBFBD>O<EFBFBD>
2026-03-02 17:48:55 -05:00
<EFBFBD>
<EFBFBD>
2026-03-03 17:25:38 -05:00
r) r<>rLr<>r<>r<>r<>r<>r<>r*r<>r"r#)r"rhrHr<>) rrrrIr0rJr<>r<>r<>r<>r<><00> __classcell__)r<>s@rr<>r<><00>s<><00><><00><00><00><00><00>
<08>
<08>0<>0<>0<>0<>0<>0<>*<0E>F<01>F<01>F<01><0E>X<EFBFBD>F<01><19><19><19><19>%"<22>%"<22>%"<22>%"<22>NE(<28>E(<28>E(<28>E(<28>N 
2026-03-02 17:48:55 -05:00
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
<EFBFBD> 
2026-03-03 17:25:38 -05:00
rr<>c<01>b<00>eZdZdZdd<06>Zdd<07>Zdd<08>Zdd <0C>Zdd<0E>Zdd<10>Z dd<12>Z
2026-03-02 17:48:55 -05:00
d d<15>Z d!d<17>Z d!d<18>Z dS)"<22>
IORegistrya
Owns all TerminatorIO drivers and poll threads for the full config.
Usage:
registry = IORegistry(config)
registry.start() # connect + begin polling
...
val = registry.get_value("my_signal")
registry.stop()
2026-03-03 17:25:38 -05:00
<20>config<69>'Config'r"r#c<01><><00><06>||_i|_tj<00><00>|_i|_g|_|jD]<5D><>t<00><06><00>}||j<00>j <t<00>fd<01>|j D<00><00>d<02><00><03><00>}t<00>fd<04>|j D<00><00>d<05><00><03><00>}t||||j|j<00><00>}|j<00> |<05><00><00><>dS)Nc3<01>h<00>K<00>|],}|j<00>jkr|jdkr|jdk<00>(|V<00><00>-dS)<03>input<75>coilN<6C>rr=<00> direction<6F> modbus_space<63><03>.0<EFBFBD>srs <20>r<00> <genexpr>z&IORegistry.__init__.<locals>.<genexpr><3E>sY<00><><00><00><00>/<2F>/<2F>q<EFBFBD><15>H<EFBFBD><06> <09>)<29>)<29><16>[<5B>G<EFBFBD>+<2B>+<2B><16>^<5E>v<EFBFBD>-<2D>-<2D><13>.<2E>-<2D>-<2D>-<2D>/<2F>/rc<01><00>|jSrE<00>r<><00>r<>s r<00><lambda>z%IORegistry.__init__.<locals>.<lambda><3E><00> <00><00>a<EFBFBD>.<2E>r)<01>keyc3<01>h<00>K<00>|],}|j<00>jkr|jdkr|jdk<00>(|V<00><00>-dS)r<><00>registerNr<4E>r<>s <20>rr<>z&IORegistry.__init__.<locals>.<genexpr><3E>sY<00><><00><00><00>3<>3<>q<EFBFBD><15>H<EFBFBD><06> <09>)<29>)<29><16>[<5B>G<EFBFBD>+<2B>+<2B><16>^<5E>z<EFBFBD>1<>1<><13>2<>1<>1<>1<>3<>3rc<01><00>|jSrEr<>r<>s rr<>z%IORegistry.__init__.<locals>.<lambda><3E>rr)<0E>_configr<67>r(r)r<><00>_drivers<72>_pollers<72>devicesrLr=<00>sorted<65>
logical_ior<EFBFBD><00>append)r/r<>r<><00>digital_inputs<74> analog_inputs<74>pollerrs @rr0zIORegistry.__init__s.<00><><00><1E><04> <0C>02<30><04> <0B>"<22><1E>)<29>)<29><04>
<EFBFBD>24<32><04> <0A>13<31><04> <0A><1C>n<EFBFBD> )<29> )<29>F<EFBFBD>!<21>&<26>)<29>)<29>F<EFBFBD>'-<2D>D<EFBFBD>M<EFBFBD>&<26>)<29> $<24>$<24>/<2F>/<2F>/<2F>/<2F>F<EFBFBD>-<2D>/<2F>/<2F>/<2F>/<2F>.<2E> <0E><0E><0E>N<EFBFBD>#<23>3<>3<>3<>3<>F<EFBFBD>-<2D>3<>3<>3<>/<2F>.<2E> <0E><0E><0E>M<EFBFBD>!<21><16><0E> <0A><14> <0B>T<EFBFBD>Z<EFBFBD><0E><0E>F<EFBFBD> <11>M<EFBFBD> <20> <20><16> (<28> (<28> (<28> (<28>/ )<29> )rc<01>B<00>|jD]}|<01><00><00><00>dS)zFStart all poll threads (each connects its read client on first cycle).N)r<00>start<72>r/<00>ps rrzIORegistry.start<72>s,<00><00><15><1D> <16> <16>A<EFBFBD> <0A>G<EFBFBD>G<EFBFBD>I<EFBFBD>I<EFBFBD>I<EFBFBD>I<EFBFBD> <16> rc<01><><00>|jD]}|<01><00><00><00>|jD]}|<01>d<01><02><00><00>dS)z1Stop all poll threads and disconnect all drivers.<2E>)r5N)rr<><00>joinrs rr<>zIORegistry.stop<6F>sT<00><00><15><1D> <15> <15>A<EFBFBD> <0A>F<EFBFBD>F<EFBFBD>H<EFBFBD>H<EFBFBD>H<EFBFBD>H<EFBFBD><15><1D> <1E> <1E>A<EFBFBD> <0A>F<EFBFBD>F<EFBFBD>1<EFBFBD>F<EFBFBD> <1D> <1D> <1D> <1D> <1E> r<00> signal_namer<00>SignalState | Nonec<01>x<00>|j5|j<00>|<01><00>cddd<00><00>S#1swxYwYdSrE)r<>r<>r<>)r/rs rr<>zIORegistry.get<65>s<00><00> <11>Z<EFBFBD> 0<> 0<><17>;<3B>?<3F>?<3F>;<3B>/<2F>/<2F> 0<> 0<> 0<> 0<> 0<> 0<> 0<> 0<> 0<> 0<> 0<> 0<><30><EFBFBD><EFBFBD> 0<> 0<> 0<> 0<> 0<> 0s <00>/<03>3<07>3<07>bool | int | Nonec<01><><00>|j5|j<00>|<01><00>}|<02>|jndcddd<00><00>S#1swxYwYdSrE)r<>r<>r<>r<00>r/rr<>s r<00> get_valuezIORegistry.get_value<75><00><><00><00> <11>Z<EFBFBD> 6<> 6<><14> <0B><0F><0F> <0B>,<2C>,<2C>A<EFBFBD><1F>m<EFBFBD>1<EFBFBD>7<EFBFBD>7<EFBFBD><14> 6<> 6<> 6<> 6<> 6<> 6<> 6<> 6<> 6<> 6<> 6<> 6<><36><EFBFBD><EFBFBD> 6<> 6<> 6<> 6<> 6<> 6<> <00>%:<03>><07>>rc<01><><00>|j5|j<00>|<01><00>}|<02>|jndcddd<00><00>S#1swxYwYdS)NT)r<>r<>r<>rrs r<00>is_stalezIORegistry.is_stale<6C>rrr<>c<01>l<00>|j5t|j<00><00>cddd<01><00>S#1swxYwYdS)z&Shallow copy of the full signal cache.N)r<>r<>r<>rBs r<00>snapshotzIORegistry.snapshot<6F>s{<00><00> <11>Z<EFBFBD> %<25> %<25><17><04> <0B>$<24>$<24> %<25> %<25> %<25> %<25> %<25> %<25> %<25> %<25> %<25> %<25> %<25> %<25><><EFBFBD><EFBFBD> %<25> %<25> %<25> %<25> %<25> %s <00>)<03>-<07>-rk<00>TerminatorIO | Nonec<01>6<00>|j<00>|<01><00>SrE)rr<>)r/rks rr<>zIORegistry.driver<65>s<00><00><13>}<7D> <20> <20><19>+<2B>+<2B>+r<00>
list[dict]c<01>H<00>d<01>|j<00><00><00>D<00><00>S)Nc<01>6<00>g|]}|<01><00><00><00><02>Sr)r<>)r<><00>ds r<00>
<listcomp>z,IORegistry.driver_status.<locals>.<listcomp><3E>s <00><00>;<3B>;<3B>;<3B>q<EFBFBD><01><08><08>
2026-03-02 17:48:55 -05:00
<EFBFBD>
2026-03-03 17:25:38 -05:00
<EFBFBD>;<3B>;<3B>;r)rr<>rBs r<00> driver_statuszIORegistry.driver_status<75>s$<00><00>;<3B>;<3B>D<EFBFBD>M<EFBFBD>$8<>$8<>$:<3A>$:<3A>;<3B>;<3B>;<3B>;rc<01>$<00>d<01>|jD<00><00>S)Nc<01>6<00>g|]}|<01><00><00><00><02>Sr)r<>)r<>rs rr)z)IORegistry.poll_stats.<locals>.<listcomp><3E>s <00><00>1<>1<>1<>a<EFBFBD><01><07><07> <09> <09>1<>1<>1r)rrBs r<00>
poll_statszIORegistry.poll_stats<74>s<00><00>1<>1<>4<EFBFBD>=<3D>1<>1<>1<>1rN)r<>r<>r"r#rH)rrr"r)rrr"r)rrr"r)r"r<>)rkrr"r#)r"r%)rrrrIr0rr<>r<>rr r"r<>r*r-rrrr<>r<>ss<><00><00><00><00><00><00> <08> <08> )<29> )<29> )<29> )<29>L<16><16><16><16>
2026-03-02 17:48:55 -05:00
<1E><1E><1E><1E>0<>0<>0<>0<>6<>6<>6<>6<>
6<>6<>6<>6<>
2026-03-03 17:25:38 -05:00
%<25>%<25>%<25>%<25>,<2C>,<2C>,<2C>,<2C><<3C><<3C><<3C><<3C>2<>2<>2<>2<>2<>2rr<>)rI<00>
__future__r<00>loggingr(rv<00> dataclassesr<00>typingr<00>pymodbus.clientr<00>pymodbus.exceptionsr<00> pymodbus.pdurr<>r
r r <00> getLoggerrr;rrrL<00>Threadr<64>r<>rrr<00><module>r7s<><00><01>Q<04>Q<04>f#<23>"<22>"<22>"<22>"<22>"<22><0E><0E><0E><0E><10><10><10><10> <0B> <0B> <0B> <0B>!<21>!<21>!<21>!<21>!<21>!<21> <20> <20> <20> <20> <20> <20>+<2B>+<2B>+<2B>+<2B>+<2B>+<2B>/<2F>/<2F>/<2F>/<2F>/<2F>/<2F>*<2A>*<2A>*<2A>*<2A>*<2A>*<2A><10>8<>7<>7<>7<>7<>7<>7<>7<>7<>7<>7<><17>g<EFBFBD><17><08>!<21>!<21><03> <0B><1D><1D><1D><1D><1D><1D><1D> <0B><19><1D>9<1C>9<1C>9<1C>9<1C>9<1C>9<1C>9<1C>9<1C>@O
<EFBFBD>O
<EFBFBD>O
<EFBFBD>O
<EFBFBD>O
<EFBFBD>O
<EFBFBD>O
<EFBFBD>O
<EFBFBD>l`
<EFBFBD>`
<EFBFBD>`
<EFBFBD>`
<EFBFBD>`
<EFBFBD>)<29>"<22>`
<EFBFBD>`
<EFBFBD>`
<EFBFBD>Nd2<>d2<>d2<>d2<>d2<>d2<>d2<>d2<>d2<>d2r