docs: add comprehensive documentation structure and API reference

Created topic-based documentation:
- basics.rst: core concepts and fundamental usage
- cyclic_programming.rst: PLC-style programming with Cycletools
- event_programming.rst: event-driven patterns and callbacks
- advanced.rst: gateway IOs, replace_io_file, watchdog management
- installation.rst and quickstart.rst: getting started guides

Added complete API reference in docs/api/:
- All device classes including ModularBaseConnect_4_5 and GatewayMixin
- I/O, helper, and main class documentation

Enhanced Sphinx configuration with RTD theme and improved autodoc settings.
Removed auto-generated modules.rst and revpimodio2.rst.
This commit is contained in:
Nicolai Buchwitz
2026-02-03 14:59:39 +01:00
committed by Sven Sager
parent b35edac694
commit 2eac69b7bd
15 changed files with 3513 additions and 102 deletions

584
docs/event_programming.rst Normal file
View File

@@ -0,0 +1,584 @@
====================
Event Programming
====================
Event-driven programming uses callbacks triggered by hardware state changes.
.. contents:: Contents
:local:
:depth: 2
When to Use Event-Driven Programming
=====================================
**Event-driven programming is ideal for:**
* Handling user interactions
* Processing occasional events
* Background tasks
* System integration
* Low CPU usage requirements
**Advantages:**
* Consumes CPU only when events occur
* Natural for user interfaces
* Simple asynchronous operation
* Efficient for sporadic events
**Considerations:**
* Non-deterministic timing
* Must handle concurrent events carefully
* Less intuitive for continuous control
Basic Structure
===============
Simple Event Handler
--------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def on_button_change(ioname, iovalue):
"""Called when button changes."""
print(f"{ioname} = {iovalue}")
rpi.io.led.value = iovalue
# Register event
rpi.io.button.reg_event(on_button_change)
# Run main loop
rpi.handlesignalend()
rpi.mainloop()
The callback function receives:
* ``ioname`` - Name of the IO that changed
* ``iovalue`` - New value of the IO
Event Registration
==================
Value Change Events
-------------------
Register callbacks for IO value changes:
.. code-block:: python
def on_change(ioname, iovalue):
print(f"{ioname} changed to {iovalue}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Any change
rpi.io.sensor.reg_event(on_change)
# Rising edge only
rpi.io.button.reg_event(on_change, edge=revpimodio2.RISING)
# Falling edge only
rpi.io.button.reg_event(on_change, edge=revpimodio2.FALLING)
rpi.handlesignalend()
rpi.mainloop()
**Edge types:**
* ``revpimodio2.RISING`` - False to True transition
* ``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
---------------
Register multiple callbacks on one IO:
.. code-block:: python
def on_press(ioname, iovalue):
print("Pressed!")
def on_release(ioname, iovalue):
print("Released!")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Different callbacks for different edges
rpi.io.button.reg_event(on_press, edge=revpimodio2.RISING)
rpi.io.button.reg_event(on_release, edge=revpimodio2.FALLING)
rpi.handlesignalend()
rpi.mainloop()
Or register one callback on multiple IOs:
.. code-block:: python
def any_sensor_changed(ioname, iovalue):
print(f"{ioname} changed to {iovalue}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Same callback for multiple sensors
for sensor in ["sensor1", "sensor2", "sensor3"]:
if sensor in rpi.io:
rpi.io[sensor].reg_event(any_sensor_changed)
rpi.handlesignalend()
rpi.mainloop()
Debouncing
==========
Add debounce delays to filter noise and false triggers:
.. code-block:: python
def on_stable_press(ioname, iovalue):
"""Called only after button is stable for 50ms."""
print(f"Confirmed: {iovalue}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# 50ms debounce delay
rpi.io.noisy_button.reg_event(on_stable_press, delay=50)
rpi.handlesignalend()
rpi.mainloop()
**How debouncing works:**
1. IO value changes
2. RevPiModIO waits for ``delay`` milliseconds
3. If value is still changed, callback is triggered
4. If value changed back, callback is not triggered
**Typical debounce times:**
* Mechanical switches: 20-50ms
* Relays: 10-20ms
* Analog sensors: 100-500ms
Debouncing with Edge Detection
-------------------------------
.. code-block:: python
def on_confirmed_press(ioname, iovalue):
"""Called only for stable button press."""
print("Confirmed press")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Rising edge with 30ms debounce
rpi.io.button.reg_event(
on_confirmed_press,
edge=revpimodio2.RISING,
delay=30
)
rpi.handlesignalend()
rpi.mainloop()
Timer Events
============
Execute callbacks at regular intervals independent of IO changes:
.. code-block:: python
def periodic_task(ioname, iovalue):
"""Called every 500ms."""
print(f"Periodic task: {iovalue}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Execute every 500ms
rpi.io.dummy.reg_timerevent(periodic_task, 500)
rpi.handlesignalend()
rpi.mainloop()
Timer Event Parameters
----------------------
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def blink_led(ioname, iovalue):
"""Toggle LED every 500ms."""
rpi.io.blink_led.value = not rpi.io.blink_led.value
def log_temperature(ioname, iovalue):
"""Log temperature every 5 seconds."""
temp = rpi.io.temperature.value
print(f"Temperature: {temp}°C")
# Blink every 500ms, trigger immediately
rpi.io.blink_led.reg_timerevent(blink_led, 500, prefire=True)
# Log every 5 seconds, don't trigger immediately
rpi.io.temperature.reg_timerevent(log_temperature, 5000, prefire=False)
rpi.handlesignalend()
rpi.mainloop()
**Parameters:**
* ``interval`` - Milliseconds between calls
* ``prefire`` - If True, trigger immediately on registration
Threaded Events
===============
Use threaded events for long-running operations that would block the main loop:
.. code-block:: python
def long_task(eventcallback: revpimodio2.EventCallback):
"""Threaded handler for time-consuming tasks."""
print(f"Starting task for {eventcallback.ioname}")
for i in range(10):
# Check if stop requested
if eventcallback.exit.is_set():
print("Task cancelled")
return
# Interruptible wait (1 second)
eventcallback.exit.wait(1)
print(f"Progress: {(i+1)*10}%")
print("Task complete")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Register as threaded event
rpi.io.trigger.reg_event(
long_task,
as_thread=True,
edge=revpimodio2.RISING
)
rpi.handlesignalend()
rpi.mainloop()
EventCallback Object
--------------------
Threaded callbacks receive an ``EventCallback`` object with:
* ``eventcallback.ioname`` - Name of the IO
* ``eventcallback.iovalue`` - Value that triggered event
* ``eventcallback.exit`` - ``threading.Event`` for cancellation
**Important:** Always check ``eventcallback.exit.is_set()`` periodically to allow graceful shutdown.
Interruptible Sleep
-------------------
Use ``eventcallback.exit.wait()`` instead of ``time.sleep()`` for interruptible delays:
.. code-block:: python
def background_task(eventcallback: revpimodio2.EventCallback):
"""Long task with interruptible waits."""
while not eventcallback.exit.is_set():
# Do some work
process_data()
# Wait 5 seconds or until exit requested
if eventcallback.exit.wait(5):
break # Exit was requested
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.io.trigger.reg_event(background_task, as_thread=True)
rpi.handlesignalend()
rpi.mainloop()
Unregistering Events
====================
Remove event callbacks when no longer needed:
.. code-block:: python
def my_callback(ioname, iovalue):
print(f"{ioname} = {iovalue}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Register event
rpi.io.button.reg_event(my_callback)
# Unregister specific callback
rpi.io.button.unreg_event(my_callback)
# Unregister all events for this IO
rpi.io.button.unreg_event()
Practical Examples
==================
LED Toggle on Button Press
---------------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def toggle_led(ioname, iovalue):
"""Toggle LED on button press."""
rpi.io.led.value = not rpi.io.led.value
print(f"LED: {rpi.io.led.value}")
rpi.io.button.reg_event(toggle_led, edge=revpimodio2.RISING)
rpi.handlesignalend()
rpi.mainloop()
Multiple Button Handler
------------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def on_start(ioname, iovalue):
print("Starting motor...")
rpi.io.motor.value = True
rpi.core.A1 = revpimodio2.GREEN
def on_stop(ioname, iovalue):
print("Stopping motor...")
rpi.io.motor.value = False
rpi.core.A1 = revpimodio2.RED
def on_emergency(ioname, iovalue):
print("EMERGENCY STOP!")
rpi.io.motor.value = False
rpi.io.alarm.value = True
rpi.core.A1 = revpimodio2.RED
# Register different buttons
rpi.io.start_button.reg_event(on_start, edge=revpimodio2.RISING)
rpi.io.stop_button.reg_event(on_stop, edge=revpimodio2.RISING)
rpi.io.emergency_stop.reg_event(on_emergency, edge=revpimodio2.RISING)
rpi.handlesignalend()
rpi.mainloop()
Sensor Logging
--------------
.. code-block:: python
import revpimodio2
from datetime import datetime
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def log_sensor_change(ioname, iovalue):
"""Log sensor changes with timestamp."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"{timestamp} - {ioname}: {iovalue}")
# Log all sensor changes
for io_name in ["sensor1", "sensor2", "temperature"]:
if io_name in rpi.io:
rpi.io[io_name].reg_event(log_sensor_change)
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
-------------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def process_batch(eventcallback: revpimodio2.EventCallback):
"""Process data batch in background thread."""
print(f"Starting batch processing...")
batch_size = 100
for i in range(batch_size):
if eventcallback.exit.is_set():
print("Processing cancelled")
return
# Simulate processing
eventcallback.exit.wait(0.1)
if i % 10 == 0:
print(f"Progress: {i}/{batch_size}")
print("Batch processing complete")
rpi.io.done_led.value = True
# Trigger on button press
rpi.io.start_batch.reg_event(
process_batch,
as_thread=True,
edge=revpimodio2.RISING
)
rpi.handlesignalend()
rpi.mainloop()
Best Practices
==============
Keep Callbacks Fast
-------------------
Event callbacks should complete quickly:
.. code-block:: python
# Good - Fast callback
def good_callback(ioname, iovalue):
rpi.io.output.value = iovalue
# Poor - Blocking callback
def poor_callback(ioname, iovalue):
time.sleep(5) # Blocks event loop!
rpi.io.output.value = iovalue
For slow operations, use threaded events:
.. code-block:: python
def slow_task(eventcallback):
# Long operation in separate thread
process_data()
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
-------------------------
Protect callbacks from exceptions:
.. code-block:: python
def safe_callback(ioname, iovalue):
try:
result = risky_operation(iovalue)
rpi.io.output.value = result
except Exception as e:
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
----------------
Threaded events are automatically cleaned up on exit, but you can manually unregister:
.. code-block:: python
def long_task(eventcallback):
while not eventcallback.exit.is_set():
work()
eventcallback.exit.wait(1)
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Register
rpi.io.trigger.reg_event(long_task, as_thread=True)
# Later: unregister to stop thread
rpi.io.trigger.unreg_event(long_task)
See Also
========
* :doc:`basics` - Core concepts and configuration
* :doc:`cyclic_programming` - Cyclic programming patterns
* :doc:`advanced` - Combining paradigms and advanced topics
* :doc:`api/helper` - EventCallback API reference