docs: Update documentation for improved clarity and consistency

Revised various sections across multiple documentation files to reflect
updated methods (`run_plc` replacing manual setup with `cycleloop`) and
adjust for new default parameters (e.g., `autorefresh`). Enhanced
descriptions for timers, Cycletools usage, and new method explanations.
Removed outdated or redundant examples and updated system requirements.

Signed-off-by: Sven Sager <akira@narux.de>
This commit is contained in:
2026-02-17 12:05:58 +01:00
parent bae85e1b09
commit 3294c5e980
7 changed files with 71 additions and 314 deletions

View File

@@ -36,7 +36,6 @@ Gateway modules provide generic IOs (like ``Input_1``, ``Output_1``, etc.) that
rpi.io.Input_1.replace_io(
"temperature", # New IO name
"h", # struct format: signed short
defaultvalue=0 # Default value
)
# Use the custom IO by its new name
@@ -70,6 +69,7 @@ Common format codes for ``replace_io`` (see `Python struct format characters <ht
* ``'i'`` - signed int (-2147483648 to 2147483647)
* ``'I'`` - unsigned int (0 to 4294967295)
* ``'f'`` - float (32-bit)
* ``'d'`` - float (64-bit)
Multiple Custom IOs
-------------------
@@ -82,8 +82,8 @@ Define multiple custom IOs programmatically by replacing generic gateway IOs:
# Replace multiple gateway IOs with custom definitions
# Assuming a gateway module with Input_1, Input_2, Output_1, Output_2
rpi.io.Input_1.replace_io("temperature", "h", defaultvalue=0)
rpi.io.Input_2.replace_io("humidity", "h", defaultvalue=0)
rpi.io.Input_1.replace_io("temperature", "h")
rpi.io.Input_2.replace_io("humidity", "h")
rpi.io.Output_1.replace_io("setpoint", "h", defaultvalue=700)
rpi.io.Output_2.replace_io("control_word", "H", defaultvalue=0)
@@ -126,12 +126,10 @@ Create an INI-style configuration file (``replace_ios.conf``):
[temperature]
replace = Input_1
frm = h
defaultvalue = 0
[humidity]
replace = Input_2
frm = h
defaultvalue = 0
[setpoint]
replace = Output_1
@@ -181,7 +179,9 @@ This is useful for:
Watchdog Management
===================
The hardware watchdog monitors your program and resets the system if it stops responding.
The hardware watchdog monitors your program and resets the system if it stops responding. If you
use the software watchdog will will only works if you use RevPiPyControl as runtime for your python
program.
How the Watchdog Works
-----------------------
@@ -197,8 +197,6 @@ Cyclic Watchdog Toggle
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct):
# Toggle every 10 cycles (200ms @ 20ms)
if ct.flank10c:
@@ -207,7 +205,7 @@ Cyclic Watchdog Toggle
# Your control logic
ct.io.output.value = ct.io.input.value
rpi.cycleloop(main_cycle)
revpimodio2.run_plc(main_cycle)
Event-Driven Watchdog Toggle
-----------------------------
@@ -234,134 +232,6 @@ Event-Driven Watchdog Toggle
rpi.handlesignalend()
rpi.mainloop()
Conditional Watchdog
--------------------
Enable watchdog only when system is operational:
.. code-block:: python
def machine_with_watchdog(ct):
if ct.first:
ct.var.state = "IDLE"
ct.var.watchdog_enabled = False
# Enable watchdog only in RUNNING state
if ct.var.state == "RUNNING":
if not ct.var.watchdog_enabled:
ct.var.watchdog_enabled = True
print("Watchdog enabled")
# Toggle watchdog
if ct.flank10c:
ct.core.wd_toggle()
else:
ct.var.watchdog_enabled = False
# State machine logic
if ct.var.state == "IDLE":
if ct.io.start_button.value:
ct.var.state = "RUNNING"
elif ct.var.state == "RUNNING":
ct.io.motor.value = True
if ct.io.stop_button.value:
ct.var.state = "IDLE"
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(machine_with_watchdog)
Combining Paradigms
===================
Combine cyclic and event-driven programming for optimal results.
Cyclic Control with Event UI
-----------------------------
Use cyclic for time-critical control, events for user interface:
.. code-block:: python
import revpimodio2
import threading
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def cyclic_control(ct: revpimodio2.Cycletools):
"""Fast control loop."""
if ct.first:
ct.var.setpoint = 50.0
ct.var.running = False
if ct.var.running:
# Fast control logic
error = ct.var.setpoint - ct.io.sensor.value
if error > 5:
ct.io.actuator.value = True
elif error < -5:
ct.io.actuator.value = False
def on_setpoint_change(ioname, iovalue):
"""Event handler for user setpoint changes."""
print(f"New setpoint: {iovalue}")
# Access ct.var from event requires thread-safe approach
# In practice, use shared data structure or message queue
def on_start(ioname, iovalue):
print("System started")
def on_stop(ioname, iovalue):
print("System stopped")
# Register user events
rpi.io.start_button.reg_event(on_start, edge=revpimodio2.RISING)
rpi.io.stop_button.reg_event(on_stop, edge=revpimodio2.RISING)
rpi.io.setpoint_input.reg_event(on_setpoint_change, delay=100)
# Run cyclic loop in background
threading.Thread(
target=lambda: rpi.cycleloop(cyclic_control),
daemon=True
).start()
# Run event loop in main thread
rpi.handlesignalend()
rpi.mainloop()
Event Triggers with Cyclic Processing
--------------------------------------
Use events to trigger actions, cyclic for processing:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def cyclic_processor(ct):
"""Process work queue."""
if ct.first:
ct.var.work_queue = []
# Process queued work
if ct.var.work_queue:
item = ct.var.work_queue.pop(0)
process_item(item)
def on_new_item(ioname, iovalue):
"""Queue work from events."""
# Note: Accessing ct.var from events requires synchronization
# This is a simplified example
print(f"New item queued from {ioname}")
rpi.io.trigger1.reg_event(on_new_item, edge=revpimodio2.RISING)
rpi.io.trigger2.reg_event(on_new_item, edge=revpimodio2.RISING)
rpi.cycleloop(cyclic_processor)
Performance Optimization
========================
@@ -487,23 +357,19 @@ Track and handle I/O errors:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.maxioerrors = 10 # Exception after 10 errors
maxioerrors = 10 # Exception after 10 errors
def main_cycle(ct):
# Check error count periodically
if ct.flank20c:
if rpi.ioerrors > 5:
print(f"Warning: {rpi.ioerrors} I/O errors detected")
if rpi.core.ioerrorcount > maxioerrors:
print(f"Warning: {rpi.core.ioerrorcount} I/O errors detected")
ct.io.warning_led.value = True
# Normal logic
ct.io.output.value = ct.io.input.value
try:
rpi.cycleloop(main_cycle)
except RuntimeError as e:
print(f"I/O error threshold exceeded: {e}")
revpimodio2.run_plc(main_cycle)
Best Practices
==============
@@ -600,35 +466,6 @@ Document complex logic:
# State machine implementation
# ...
Testing
-------
Test your code thoroughly:
.. code-block:: python
def test_temperature_control(ct):
"""Test temperature control logic."""
if ct.first:
ct.var.cooling_active = False
ct.var.test_temp = 20.0
# Simulate temperature increase
if ct.var.test_temp < 80:
ct.var.test_temp += 0.5
# Test control logic
temp = ct.var.test_temp
if temp > 75 and not ct.var.cooling_active:
assert ct.io.cooling.value == True
ct.var.cooling_active = True
if temp < 65 and ct.var.cooling_active:
assert ct.io.cooling.value == False
ct.var.cooling_active = False
Logging
-------
@@ -676,6 +513,7 @@ Always validate external inputs:
"""Validate setpoint range."""
if 0 <= iovalue <= 100:
rpi.io.setpoint.value = iovalue
rpi.io.error_led.value = False
else:
print(f"Invalid setpoint: {iovalue}")
rpi.io.error_led.value = True

View File

@@ -78,10 +78,11 @@ Adjust cycle time to match your needs:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi = revpimodio2.RevPiModIO()
rpi.cycletime = 100 # Set to 100ms
rpi.autorefresh_all()
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements.
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements. Default values will fit most needs.
Error Handling
--------------
@@ -90,10 +91,10 @@ Configure I/O error threshold:
.. code-block:: python
rpi.maxioerrors = 10 # Raise exception after 10 errors
maxioerrors = 10 # Raise exception after 10 errors
# Check error count
if rpi.ioerrors > 5:
if rpi.core.ioerrorcount > maxioerrors:
print("Warning: I/O errors detected")
Core Objects

View File

@@ -42,8 +42,6 @@ Simple Cycle Loop
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct: revpimodio2.Cycletools):
"""Execute each cycle."""
if ct.io.start_button.value:
@@ -51,10 +49,17 @@ Simple Cycle Loop
if ct.io.stop_button.value:
ct.io.motor.value = False
rpi.cycleloop(main_cycle)
revpimodio2.run_plc(main_cycle)
# .run_plc is a shortcut for:
# rpi = revpimodio2.RevPiModIO(autorefresh=True)
# rpi.handlesignalend()
# rpi.cycleloop(main_cycle)
The ``main_cycle`` function is called repeatedly at the configured cycle time (typically 20-50ms).
**Info:** ``rpi.handlesignalend()``
Understanding Cycle Time
-------------------------
@@ -68,10 +73,9 @@ Adjust cycle time to match your needs:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycletime = 100 # 100ms = 10 Hz
revpimodio2.run_plc(main_cycle, cycletime=100) # 100ms = 10 Hz
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements.
**Important:** Faster cycle times consume more CPU but will detect fast changes of input values.
Cycletools Object
=================
@@ -109,8 +113,7 @@ Use ``ct.first`` and ``ct.last`` for setup and teardown:
ct.io.motor.value = False
print(f"Total cycles: {ct.var.counter}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(main_cycle)
revpimodio2.run_plc(main_cycle)
Persistent Variables
====================
@@ -155,6 +158,7 @@ Detect input changes efficiently without storing previous values:
print("Button released!")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.handlesignalend()
rpi.cycleloop(main_cycle)
Edge types:
@@ -176,17 +180,16 @@ Toggle flags alternate between True/False at regular intervals:
.. code-block:: python
def main_cycle(ct):
# Blink LED - flag5c alternates every 5 cycles
ct.io.blink_led.value = ct.flag5c
# Blink LED - flag5c alternates every cycle
ct.io.blink_led.value = ct.flag1c
# Different blink rates
ct.io.fast_blink.value = ct.flag2c # Every 2 cycles
ct.io.fast_blink.value = ct.flag5c # Every 5 cycles
ct.io.slow_blink.value = ct.flag20c # Every 20 cycles
**Available toggle flags:**
* ``ct.flag1c`` - Every cycle
* ``ct.flag2c`` - Every 2 cycles
* ``ct.flag5c`` - Every 5 cycles
* ``ct.flag10c`` - Every 10 cycles
* ``ct.flag20c`` - Every 20 cycles
@@ -218,12 +221,12 @@ Flank flags are True for exactly one cycle at regular intervals:
Timers
======
RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts.
RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts or milliseconds.
On-Delay Timer (TON/TONC)
--------------------------
Output becomes True only after input is continuously True for specified cycles:
Output becomes True only after input is continuously True for specified cycles (use ton with milliseconds value instead of cycles):
.. code-block:: python
@@ -253,7 +256,7 @@ Output becomes True only after input is continuously True for specified cycles:
Off-Delay Timer (TOF/TOFC)
---------------------------
Output stays True for specified cycles after input goes False:
Output stays True for specified cycles or milliseconds after input goes False (use tof with milliseconds value instead of cycles):
.. code-block:: python
@@ -280,7 +283,7 @@ Output stays True for specified cycles after input goes False:
Pulse Timer (TP/TPC)
--------------------
Generates a one-shot pulse of specified duration:
Generates a one-shot pulse of specified duration (use tp with milliseconds value instead of cycles):
.. code-block:: python
@@ -305,25 +308,6 @@ Generates a one-shot pulse of specified duration:
* Acknowledgment pulses
* Retriggerable delays
Converting Time to Cycles
--------------------------
Calculate cycles from desired time:
.. code-block:: python
# At 20ms cycle time:
# 1 second = 50 cycles
# 100ms = 5 cycles
# 2 seconds = 100 cycles
def main_cycle(ct):
cycle_time_ms = rpi.cycletime
desired_time_ms = 1500 # 1.5 seconds
cycles_needed = int(desired_time_ms / cycle_time_ms)
ct.set_tonc("my_delay", cycles_needed)
State Machines
==============
@@ -345,28 +329,29 @@ Simple State Machine
ct.io.yellow_led.value = False
ct.io.red_led.value = False
# After 100 cycles (2s @ 20ms), go to yellow
ct.set_tonc("green_time", 100)
if ct.get_tonc("green_time"):
# After 2 seconds, go to yellow
ct.set_ton("green_time", 2000)
if ct.get_ton("green_time"):
ct.var.state = "YELLOW"
elif ct.var.state == "YELLOW":
ct.io.green_led.value = False
ct.io.yellow_led.value = True
ct.set_tonc("yellow_time", 25) # 500ms
if ct.get_tonc("yellow_time"):
ct.set_ton("yellow_time", 500)
if ct.get_ton("yellow_time"):
ct.var.state = "RED"
elif ct.var.state == "RED":
ct.io.yellow_led.value = False
ct.io.red_led.value = True
ct.set_tonc("red_time", 150) # 3s
if ct.get_tonc("red_time"):
ct.set_ton("red_time", 3000)
if ct.get_ton("red_time"):
ct.var.state = "GREEN"
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.handlesignalend()
rpi.cycleloop(traffic_light)
Complex State Machine
@@ -396,8 +381,8 @@ Complex State Machine
ct.io.yellow_led.value = True
# 2-second startup delay
ct.set_tonc("startup", 100)
if ct.get_tonc("startup"):
ct.set_ton("startup", 2000)
if ct.get_ton("startup"):
ct.var.state = "RUNNING"
print("Running")
@@ -422,8 +407,8 @@ Complex State Machine
# State: STOPPING - Controlled shutdown
elif ct.var.state == "STOPPING":
# Coast motor for 1 second
ct.set_tofc("coast", 50)
ct.io.motor.value = ct.get_tofc("coast")
ct.set_tof("coast", 1000)
ct.io.motor.value = ct.get_tof("coast")
if not ct.io.motor.value:
ct.var.state = "IDLE"
@@ -432,7 +417,7 @@ Complex State Machine
# State: ERROR - Fault condition
elif ct.var.state == "ERROR":
ct.io.motor.value = False
ct.io.red_led.value = ct.flag2c # Blink red
ct.io.red_led.value = ct.flag5c # Blink red
if ct.changed(ct.io.ack_button, edge=revpimodio2.RISING):
if not ct.io.error_sensor.value:
@@ -442,8 +427,7 @@ Complex State Machine
if ct.last:
print(f"Total production: {ct.var.production_count}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(machine_controller)
revpimodio.run_plc(machine_controller)
Practical Examples
==================
@@ -476,14 +460,17 @@ Temperature monitoring with hysteresis control:
# Warning if too hot
if temp > 85:
ct.io.warning_led.value = ct.flag2c # Blink
ct.core.a1green.value = False
ct.core.a1red.value = ct.flag5c # Blink
else:
ct.core.a1green.value = ct.flag5c # Blink
ct.core.a1red.value = False
# Emergency shutdown
if temp > 95:
ct.io.emergency_shutdown.value = True
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(temperature_monitor)
revpimodio2.run_plc(temperature_monitor)
Production Counter
------------------
@@ -520,8 +507,7 @@ Count production items with start/stop control:
print(f"Final count: {ct.var.total_count}")
ct.var.total_count = 0
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(production_counter)
revpimodio2.run_plc(production_counter)
Best Practices
==============
@@ -544,7 +530,7 @@ Minimize processing time in each cycle:
**Guidelines:**
* Avoid blocking operations (network, file I/O)
* Use flank flags for expensive operations
* Use flank flags for expensive operations or even Threads
* Keep cycle time ≥20ms for stability
Use Appropriate Cycle Time

View File

@@ -94,29 +94,6 @@ Register callbacks for IO value changes:
* ``revpimodio2.FALLING`` - True to False transition
* ``revpimodio2.BOTH`` - Any change (default)
Lambda Functions
----------------
Use lambda for simple callbacks:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Simple lambda callback
rpi.io.button.reg_event(
lambda name, val: print(f"Button: {val}")
)
# Lambda with edge filter
rpi.io.start_button.reg_event(
lambda name, val: print("Started!"),
edge=revpimodio2.RISING
)
rpi.handlesignalend()
rpi.mainloop()
Multiple Events
---------------
@@ -212,12 +189,15 @@ Debouncing with Edge Detection
Timer Events
============
Execute callbacks at regular intervals independent of IO changes:
The timer is started when the IO value changes and executes the passed
function - even if the IO value has changed in the meantime. If the
timer has not expired and the condition is met again, the timer is NOT
reset to the delay value or started a second time.
.. code-block:: python
def periodic_task(ioname, iovalue):
"""Called every 500ms."""
"""Called after 500ms."""
print(f"Periodic task: {iovalue}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
@@ -255,8 +235,8 @@ Timer Event Parameters
**Parameters:**
* ``interval`` - Milliseconds between calls
* ``prefire`` - If True, trigger immediately on registration
* ``interval`` - Delay in milliseconds.
* ``prefire`` - If True, trigger immediately after starting the mainloop.
Threaded Events
===============
@@ -425,29 +405,6 @@ Sensor Logging
rpi.handlesignalend()
rpi.mainloop()
Periodic Status Report
----------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def status_report(ioname, iovalue):
"""Print system status every 10 seconds."""
print("=== Status Report ===")
print(f"Temperature: {rpi.core.temperature.value}°C")
print(f"CPU Frequency: {rpi.core.frequency.value} MHz")
print(f"IO Errors: {rpi.core.ioerrorcount.value}")
print()
# Status report every 10 seconds
rpi.io.dummy.reg_timerevent(status_report, 10000, prefire=True)
rpi.handlesignalend()
rpi.mainloop()
Threaded Data Processing
-------------------------
@@ -515,19 +472,6 @@ For slow operations, use threaded events:
rpi.io.trigger.reg_event(slow_task, as_thread=True)
Use Debouncing
--------------
Always debounce mechanical inputs:
.. code-block:: python
# Good - Debounced button
rpi.io.button.reg_event(callback, delay=30)
# Poor - No debounce (may trigger multiple times)
rpi.io.button.reg_event(callback)
Handle Errors Gracefully
-------------------------
@@ -543,18 +487,6 @@ Protect callbacks from exceptions:
print(f"Error in callback: {e}")
rpi.io.output.value = False # Safe state
Check IO Existence
------------------
Verify IOs exist before registering events:
.. code-block:: python
if "optional_button" in rpi.io:
rpi.io.optional_button.reg_event(callback)
else:
print("Optional button not configured")
Clean Up Threads
----------------

View File

@@ -24,13 +24,12 @@ Quick Example
**Cyclic Programming**::
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main(ct):
if ct.io.button.value:
ct.io.led.value = True
rpi.cycleloop(main)
revpimodio2.run_plc(main)
**Event-Driven Programming**::

View File

@@ -5,7 +5,7 @@ Installation
System Requirements
===================
* Python 3.7 or higher
* Python 3.2 or higher
* Revolution Pi hardware (Core, Core3, Connect, Compact, Flat)
* piCtory configuration tool
@@ -24,6 +24,9 @@ Log out and log back in for the group change to take effect.
Installing RevPiModIO
=====================
RevPiModIO is preinstalled on your Revolution Pi. It is distributed as debian package and will be
updated by `apt`.
Using pip
---------

View File

@@ -65,8 +65,6 @@ For continuous operation, use a cyclic loop:
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct: revpimodio2.Cycletools):
"""Called every cycle (default: 20-50ms)."""
@@ -91,7 +89,7 @@ For continuous operation, use a cyclic loop:
print("Program stopped")
# Run cyclic loop
rpi.cycleloop(main_cycle)
revpimodio2.run_plc(main_cycle)
Event-Driven Program
--------------------