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

711
docs/advanced.rst Normal file
View File

@@ -0,0 +1,711 @@
========
Advanced
========
Advanced features, patterns, and best practices for RevPiModIO.
.. contents:: Contents
:local:
:depth: 2
Custom IOs (Gateway Modules)
=============================
Gateway modules (ModbusTCP, Profinet, etc.) allow defining custom IOs dynamically.
Understanding Gateway IOs
--------------------------
Gateway modules provide raw memory regions that you can map to custom IOs with specific data types and addresses.
Defining Custom IOs
-------------------
Use the :py:meth:`~revpimodio2.io.MemIO.replace_io` method to define custom IOs on gateway modules.
Gateway modules provide generic IOs (like ``Input_1``, ``Output_1``, etc.) that you can replace with custom definitions:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Replace a gateway IO with custom definition
# Gateway IOs have default names like Input_1, Output_1, etc.
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
temp = rpi.io.temperature.value / 10.0 # Scale to degrees
print(f"Temperature: {temp}°C")
rpi.exit()
**Parameters:**
* ``name`` - Name for the new IO (will be accessible via ``rpi.io.name``)
* ``frm`` - Struct format character (see `format codes <https://docs.python.org/3/library/struct.html#format-characters>`_ below)
* ``defaultvalue`` - Optional: Default value for the IO
* ``byteorder`` - Optional: Byte order (``'little'`` or ``'big'``), default is ``'little'``
* ``bit`` - Optional: Bit position for boolean IOs (0-7)
* ``event`` - Optional: Register event callback on creation
* ``delay`` - Optional: Event debounce delay in milliseconds
* ``edge`` - Optional: Event edge trigger (RISING, FALLING, or BOTH)
**Note:** The memory address is inherited from the IO being replaced (e.g., ``Input_1``). The new IO uses the same address in the process image.
Struct Format Codes
-------------------
Common format codes for ``replace_io`` (see `Python struct format characters <https://docs.python.org/3/library/struct.html#format-characters>`_ for complete reference):
* ``'b'`` - signed byte (-128 to 127)
* ``'B'`` - unsigned byte (0 to 255)
* ``'h'`` - signed short (-32768 to 32767)
* ``'H'`` - unsigned short (0 to 65535)
* ``'i'`` - signed int (-2147483648 to 2147483647)
* ``'I'`` - unsigned int (0 to 4294967295)
* ``'f'`` - float (32-bit)
Multiple Custom IOs
-------------------
Define multiple custom IOs programmatically by replacing generic gateway IOs:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# 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.Output_1.replace_io("setpoint", "h", defaultvalue=700)
rpi.io.Output_2.replace_io("control_word", "H", defaultvalue=0)
# Use all custom IOs by their new names
temp = rpi.io.temperature.value / 10.0
humidity = rpi.io.humidity.value / 10.0
print(f"Temp: {temp}°C, Humidity: {humidity}%")
# Write to output registers
rpi.io.setpoint.value = 750 # 75.0°C
rpi.io.control_word.value = 0x0001 # Enable bit
rpi.exit()
Using Configuration Files
--------------------------
For complex IO configurations, use the ``replace_io_file`` parameter to load custom IOs from a file:
.. code-block:: python
# Load custom IOs from configuration file
rpi = revpimodio2.RevPiModIO(
autorefresh=True,
replace_io_file="replace_ios.conf"
)
# Custom IOs are now available
temp = rpi.io.temperature.value / 10.0
print(f"Temperature: {temp}°C")
rpi.exit()
**Configuration File Format:**
Create an INI-style configuration file (``replace_ios.conf``):
.. code-block:: ini
[temperature]
replace = Input_1
frm = h
defaultvalue = 0
[humidity]
replace = Input_2
frm = h
defaultvalue = 0
[setpoint]
replace = Output_1
frm = h
defaultvalue = 700
[control_word]
replace = Output_2
frm = H
byteorder = big
**Configuration Parameters:**
* ``replace`` - Name of the gateway IO to replace (required)
* ``frm`` - Struct format character (required)
* ``bit`` - Bit position for boolean IOs (0-7)
* ``byteorder`` - Byte order: ``little`` or ``big`` (default: ``little``)
* ``wordorder`` - Word order for multi-word values
* ``defaultvalue`` - Default value for the IO
* ``bmk`` - Internal designation/bookmark
* ``export`` - Export flag for RevPiPyLoad/RevPiPyControl
**Exporting Configuration:**
Export your current custom IOs to a file:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Define custom IOs by replacing gateway IOs
rpi.io.Input_1.replace_io("temperature", "h", defaultvalue=0)
rpi.io.Input_2.replace_io("humidity", "h", defaultvalue=0)
# Export to configuration file
rpi.export_replaced_ios("my_config.conf")
rpi.exit()
This is useful for:
* Sharing IO configurations across multiple programs
* Integration with RevPiPyLoad and RevPiPyControl
* Version control of IO definitions
* Declarative IO configuration
Watchdog Management
===================
The hardware watchdog monitors your program and resets the system if it stops responding.
How the Watchdog Works
-----------------------
The watchdog requires periodic toggling. If not toggled within the timeout period, the system resets.
**Important:** Only enable the watchdog when your program logic is working correctly.
Cyclic Watchdog Toggle
-----------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct):
# Toggle every 10 cycles (200ms @ 20ms)
if ct.flank10c:
ct.core.wd_toggle()
# Your control logic
ct.io.output.value = ct.io.input.value
rpi.cycleloop(main_cycle)
Event-Driven Watchdog Toggle
-----------------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def toggle_wd(ioname, iovalue):
"""Toggle watchdog every 500ms."""
rpi.core.wd_toggle()
# Register timer event for watchdog
rpi.core.wd.reg_timerevent(toggle_wd, 500, prefire=True)
# Your event handlers
def on_button(ioname, iovalue):
rpi.io.led.value = iovalue
rpi.io.button.reg_event(on_button)
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
========================
Keep Cycle Logic Fast
---------------------
Minimize processing time in each cycle:
.. code-block:: python
def optimized_cycle(ct):
# Good: Heavy work only when needed
if ct.flank100c:
expensive_calculation()
# Good: Keep cycle logic minimal
ct.io.output.value = ct.io.input.value
# Bad: Don't do this every cycle
# expensive_calculation() # 100ms processing!
**Guidelines:**
* Keep cycle time ≥20ms for stability
* Avoid blocking operations (network, file I/O)
* Use flank flags for expensive operations
* Profile your cycle function if experiencing timing issues
Choose Appropriate Cycle Time
------------------------------
Match cycle time to application requirements:
.. code-block:: python
# Fast control (motion, high-speed counting)
rpi.cycletime = 20 # 50 Hz
# Normal control (most applications)
rpi.cycletime = 50 # 20 Hz
# Slow monitoring (temperature, status)
rpi.cycletime = 100 # 10 Hz
**Trade-offs:**
* Faster = Higher CPU usage, better responsiveness
* Slower = Lower CPU usage, adequate for most tasks
Minimize Event Callbacks
-------------------------
Keep event callbacks lightweight:
.. code-block:: python
# Good: Fast callback
def good_callback(ioname, iovalue):
rpi.io.output.value = iovalue
# Poor: Slow callback blocks event loop
def poor_callback(ioname, iovalue):
time.sleep(1) # Blocks!
complex_calculation() # Slow!
rpi.io.output.value = iovalue
# Better: Use threaded events for slow work
def threaded_callback(eventcallback):
complex_calculation()
rpi.io.output.value = result
rpi.io.trigger.reg_event(threaded_callback, as_thread=True)
Error Handling
==============
Graceful Error Recovery
-----------------------
Always implement safe failure modes:
.. code-block:: python
def safe_cycle(ct):
try:
value = ct.io.sensor.value
result = process(value)
ct.io.output.value = result
except ValueError as e:
print(f"Sensor error: {e}")
ct.io.output.value = 0 # Safe default
except Exception as e:
print(f"Unexpected error: {e}")
ct.io.output.value = False # Safe state
Resource Cleanup
----------------
Always clean up resources:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
try:
# Your program logic
rpi.cycleloop(main_cycle)
except KeyboardInterrupt:
print("Interrupted by user")
except Exception as e:
print(f"Error: {e}")
finally:
# Always clean up
rpi.setdefaultvalues() # Reset outputs to defaults
rpi.exit()
Monitor I/O Errors
------------------
Track and handle I/O errors:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.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")
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}")
Best Practices
==============
Naming Conventions
------------------
Use descriptive IO names in piCtory:
.. code-block:: python
# Good - Clear intent
if rpi.io.emergency_stop.value:
rpi.io.motor.value = False
rpi.io.alarm.value = True
# Poor - Generic names
if rpi.io.I_15.value:
rpi.io.O_3.value = False
rpi.io.O_7.value = True
Code Organization
-----------------
Structure your code for maintainability:
.. code-block:: python
import revpimodio2
# Constants
TEMP_HIGH_THRESHOLD = 75
TEMP_LOW_THRESHOLD = 65
# Initialize
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def initialize(ct):
"""Initialize system state."""
ct.var.cooling_active = False
ct.var.alarm_active = False
ct.io.motor.value = False
def monitor_temperature(ct):
"""Temperature monitoring logic."""
temp = ct.io.temperature.value
if temp > TEMP_HIGH_THRESHOLD:
ct.io.cooling.value = True
ct.var.cooling_active = True
if temp < TEMP_LOW_THRESHOLD:
ct.io.cooling.value = False
ct.var.cooling_active = False
def main_cycle(ct):
"""Main control loop."""
if ct.first:
initialize(ct)
monitor_temperature(ct)
if ct.last:
ct.io.cooling.value = False
# Run
try:
rpi.cycleloop(main_cycle)
finally:
rpi.exit()
Documentation
-------------
Document complex logic:
.. code-block:: python
def control_cycle(ct):
"""Control cycle for temperature management.
State machine:
- IDLE: Waiting for start
- HEATING: Active heating to setpoint
- COOLING: Active cooling from overshoot
- ERROR: Fault condition
Hysteresis: ±5°C around setpoint
"""
if ct.first:
ct.var.state = "IDLE"
ct.var.setpoint = 70.0
# 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
-------
Implement proper logging:
.. code-block:: python
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def main_cycle(ct):
if ct.first:
logging.info("System started")
ct.var.error_count = 0
# Log errors
if ct.io.error_sensor.value:
ct.var.error_count += 1
logging.error(f"Error detected: {ct.var.error_count}")
# Log status periodically
if ct.flank100c:
logging.info(f"Temperature: {ct.io.temperature.value}°C")
if ct.last:
logging.info("System stopped")
Security Considerations
=======================
Validate External Input
-----------------------
Always validate external inputs:
.. code-block:: python
def on_setpoint_change(ioname, iovalue):
"""Validate setpoint range."""
if 0 <= iovalue <= 100:
rpi.io.setpoint.value = iovalue
else:
print(f"Invalid setpoint: {iovalue}")
rpi.io.error_led.value = True
Fail-Safe Defaults
------------------
Use safe defaults for critical outputs:
.. code-block:: python
def main_cycle(ct):
if ct.first:
# Safe defaults
ct.io.motor.value = False
ct.io.heater.value = False
ct.io.valve.value = False
try:
# Control logic
control_logic(ct)
except Exception as e:
# Revert to safe state on error
ct.io.motor.value = False
ct.io.heater.value = False
See Also
========
* :doc:`basics` - Core concepts and configuration
* :doc:`cyclic_programming` - Cyclic programming patterns
* :doc:`event_programming` - Event-driven programming patterns
* :doc:`api/index` - API reference

163
docs/api/device.rst Normal file
View File

@@ -0,0 +1,163 @@
==============
Device Classes
==============
Classes for managing Revolution Pi devices.
.. currentmodule:: revpimodio2.device
Device
======
.. autoclass:: Device
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Base class for all Revolution Pi devices.
DeviceList
==========
.. autoclass:: DeviceList
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__, __getitem__, __iter__
Container for accessing devices.
**Example:**
.. code-block:: python
# Access device by name
dio_module = rpi.device.DIO_Module_1
# Access device by position
first_device = rpi.device[0]
# Iterate all devices
for device in rpi.device:
print(f"Device: {device.name}")
Base
====
.. autoclass:: Base
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Base class for Revolution Pi base modules.
GatewayMixin
============
.. autoclass:: GatewayMixin
:members:
:undoc-members:
:show-inheritance:
Mixin class providing gateway functionality for piGate modules.
ModularBaseConnect_4_5
======================
.. autoclass:: ModularBaseConnect_4_5
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Base class for Connect 4 and Connect 5 modules.
Core
====
.. autoclass:: Core
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Revolution Pi Core module.
Connect
=======
.. autoclass:: Connect
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Revolution Pi Connect module.
Connect4
========
.. autoclass:: Connect4
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Revolution Pi Connect 4 module.
Connect5
========
.. autoclass:: Connect5
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Revolution Pi Connect 5 module.
DioModule
=========
.. autoclass:: DioModule
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Digital I/O module.
RoModule
========
.. autoclass:: RoModule
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Relay output module.
Gateway
=======
.. autoclass:: Gateway
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Gateway module (ModbusTCP, Profinet, etc.).
Virtual
=======
.. autoclass:: Virtual
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Virtual device for custom applications.

194
docs/api/helper.rst Normal file
View File

@@ -0,0 +1,194 @@
==============
Helper Classes
==============
Helper classes for cyclic and event-driven programming.
.. currentmodule:: revpimodio2.helper
Cycletools
==========
.. autoclass:: Cycletools
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Toolkit provided to cyclic functions via ``.cycleloop()``.
This class provides tools for cyclic functions including timing flags
and edge markers. Note that edge markers (flank flags) are all True
during the first cycle!
**Attributes:**
Reference to RevPiModIO core object
Reference to RevPiModIO device object
Reference to RevPiModIO io object
True only on first cycle
True when shutdown signal received
Current function execution time in seconds
Container for cycle-persistent variables
**Toggle Flags** - Alternate between True/False:
1 cycle True, 1 cycle False
2 cycles True, 2 cycles False
5 cycles True, 5 cycles False
10 cycles True, 10 cycles False
20 cycles True, 20 cycles False
**Flank Flags** - True every nth cycle:
True every 5 cycles
True every 10 cycles
True every 15 cycles
True every 20 cycles
**Example:**
.. code-block:: python
def main(ct: revpimodio2.Cycletools):
if ct.first:
# Initialize
ct.var.counter = 0
# Main logic
if ct.changed(ct.io.sensor):
ct.var.counter += 1
# Blink LED using timing flag
ct.io.led.value = ct.flag5c
if ct.last:
# Cleanup
print(f"Final: {ct.var.counter}")
Change Detection
----------------
Timer Functions
---------------
On-Delay Timers
~~~~~~~~~~~~~~~
Off-Delay Timers
~~~~~~~~~~~~~~~~
Pulse Timers
~~~~~~~~~~~~
EventCallback
=============
.. autoclass:: EventCallback
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Thread for internal event function calls.
This class is passed to threaded event handlers registered with
``as_thread=True``. The event function receives this thread object
as a parameter to access event information and control execution.
**Attributes:**
Name of IO that triggered the event
Value of IO when event was triggered
Threading event for abort conditions
**Example:**
.. code-block:: python
def threaded_handler(eventcallback: revpimodio2.EventCallback):
print(f"{eventcallback.ioname} = {eventcallback.iovalue}")
# Interruptible wait (3 seconds)
if eventcallback.exit.wait(3):
print("Wait interrupted!")
return
# Check if stop was called
if eventcallback.exit.is_set():
return
# Register as threaded event
rpi.io.button.reg_event(threaded_handler, as_thread=True)
Methods
-------
ProcimgWriter
=============
.. autoclass:: ProcimgWriter
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Internal thread for process image writing and event management.

148
docs/api/index.rst Normal file
View File

@@ -0,0 +1,148 @@
.. _api_reference:
=============
API Reference
=============
Complete API reference for RevPiModIO2 Python library.
.. contents:: Table of Contents
:local:
:depth: 2
Overview
========
RevPiModIO provides several main classes for programming Revolution Pi hardware:
* :class:`~revpimodio2.modio.RevPiModIO` - Main class for managing all devices and IOs
* :class:`~revpimodio2.modio.RevPiModIOSelected` - Manage specific devices only
* :class:`~revpimodio2.modio.RevPiModIODriver` - Write data to virtual device inputs
* :class:`~revpimodio2.io.IOList` - Container for accessing IOs
* :class:`~revpimodio2.io.IOBase` - Base class for all IO objects
* :class:`~revpimodio2.helper.Cycletools` - Toolkit for cyclic programming
* :class:`~revpimodio2.helper.EventCallback` - Event handler class
Quick Examples
==============
Basic Usage
-----------
.. code-block:: python
import revpimodio2
# Initialize
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Read input and control output
if rpi.io.button.value:
rpi.io.led.value = True
# Cleanup
rpi.exit()
Cyclic Programming
------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct: revpimodio2.Cycletools):
if ct.first:
ct.var.counter = 0
if ct.changed(ct.io.sensor):
ct.var.counter += 1
rpi.cycleloop(main_cycle)
Event-Driven Programming
------------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def on_change(ioname, iovalue):
print(f"{ioname} = {iovalue}")
rpi.io.button.reg_event(on_change)
rpi.handlesignalend()
rpi.mainloop()
Constants
=========
Edge Detection
--------------
.. py:data:: revpimodio2.RISING
:type: int
Detect low-to-high transitions
.. py:data:: revpimodio2.FALLING
:type: int
Detect high-to-low transitions
.. py:data:: revpimodio2.BOTH
:type: int
Detect any transition
LED Colors
----------
.. py:data:: revpimodio2.OFF
:type: int
LED off
.. py:data:: revpimodio2.GREEN
:type: int
Green LED
.. py:data:: revpimodio2.RED
:type: int
Red LED
IO Types
--------
.. py:data:: INP
:type: int
:value: 300
Input type
.. py:data:: OUT
:type: int
:value: 301
Output type
.. py:data:: MEM
:type: int
:value: 302
Memory type
See Also
========
* :doc:`../installation` - Installation guide
* :doc:`../quickstart` - Quick start guide
* :doc:`../basics` - Core concepts
* :doc:`../cyclic_programming` - Cyclic programming patterns
* :doc:`../event_programming` - Event-driven programming patterns
* :doc:`../advanced` - Advanced topics

212
docs/api/io.rst Normal file
View File

@@ -0,0 +1,212 @@
====================
IO Classes and Types
====================
Classes for managing Revolution Pi inputs and outputs.
.. currentmodule:: revpimodio2.io
IOList
======
.. autoclass:: IOList
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__, __getitem__, __contains__, __iter__
Container for accessing all IO objects.
The IOList provides multiple ways to access IOs:
* **Direct attribute access**: ``rpi.io.button.value``
* **String-based access**: ``rpi.io["button"].value``
* **Iteration**: ``for io in rpi.io: ...``
**Example:**
.. code-block:: python
# Direct access
value = rpi.io.I_1.value
rpi.io.O_1.value = True
# String-based access
value = rpi.io["I_1"].value
# Check if IO exists
if "sensor" in rpi.io:
print(rpi.io.sensor.value)
# Iterate all IOs
for io in rpi.io:
print(f"{io.name}: {io.value}")
IOBase
======
.. autoclass:: IOBase
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Base class for all IO objects.
**Properties:**
IO name from piCtory configuration
Current IO value (read/write)
Byte address in process image
Byte length (0 for single bits)
IO type: 300=INPUT, 301=OUTPUT, 302=MEMORY
Whether value is signed
"little" or "big" endian
Configured default value from piCtory
Comment/description from piCtory
Export flag status
Event Registration Methods
---------------------------
Value Manipulation Methods
---------------------------
IntIO
=====
.. autoclass:: IntIO
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
IO objects with integer value access.
**Example:**
.. code-block:: python
# Get integer value
temp = rpi.io.temperature.get_intvalue()
# Set integer value
rpi.io.setpoint.set_intvalue(1500)
Integer Value Methods
---------------------
IntIOCounter
============
.. autoclass:: IntIOCounter
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Counter input objects with reset capability.
**Example:**
.. code-block:: python
# Read counter
count = rpi.io.counter.value
# Reset counter
rpi.io.counter.reset()
StructIO
========
.. autoclass:: StructIO
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Structured IO with format strings for complex data types.
**Example:**
.. code-block:: python
# Get structured value
value = rpi.io.sensor_data.get_structvalue()
Structured Value Methods
------------------------
MemIO
=====
.. autoclass:: MemIO
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Memory IO with variant value access (string or integer).
RelaisOutput
============
.. autoclass:: RelaisOutput
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Relay output with switching cycle counter.
**Example:**
.. code-block:: python
# Get number of switching cycles
cycles = rpi.io.relay.get_switching_cycles()
IOEvent
=======
.. autoclass:: IOEvent
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Internal class for IO event management.

141
docs/api/revpimodio.rst Normal file
View File

@@ -0,0 +1,141 @@
==================
RevPiModIO Classes
==================
Main classes for managing Revolution Pi hardware.
.. currentmodule:: revpimodio2.modio
RevPiModIO
==========
.. autoclass:: RevPiModIO
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Main class for managing all devices and IOs from the piCtory configuration.
This class manages the complete piCtory configuration and loads all devices
and IOs. It handles exclusive management of the process image and ensures
data synchronization.
**Constructor Parameters:**
:param autorefresh: Automatically sync process image (recommended: True)
:type autorefresh: bool
:param monitoring: Read-only mode for supervision (no writes)
:type monitoring: bool
:param syncoutputs: Load current output values on initialization
:type syncoutputs: bool
:param debug: Enable detailed error messages and logging
:type debug: bool
**Key Attributes:**
Access to all configured inputs/outputs
Access to RevPi Core values (LEDs, status)
Access to specific devices by name
Update frequency in milliseconds
Threading event for clean shutdown
Count of read/write failures
Exception threshold (0 = disabled)
**Example:**
.. code-block:: python
import revpimodio2
# Initialize with auto-refresh
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Access IOs
if rpi.io.button.value:
rpi.io.led.value = True
# Clean shutdown
rpi.exit()
Loop Execution Methods
----------------------
Data Synchronization Methods
-----------------------------
Utility Methods
---------------
RevPiModIOSelected
==================
.. autoclass:: RevPiModIOSelected
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Manage only specific devices from the piCtory configuration.
Use this class when you only need to control specific devices instead of
loading the entire configuration.
**Example:**
.. code-block:: python
# Manage only specific devices
rpi = revpimodio2.RevPiModIOSelected("DIO_Module_1", "AIO_Module_1")
RevPiModIODriver
================
.. autoclass:: RevPiModIODriver
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Write data to virtual device inputs for driver development.
**Example:**
.. code-block:: python
# Create driver for virtual device
driver = revpimodio2.RevPiModIODriver("VirtualDevice")
DevSelect
=========
.. autoclass:: DevSelect
:members:
:undoc-members:
:show-inheritance:
:special-members: __init__
Customized search filter for RevPiModIOSelected.

331
docs/basics.rst Normal file
View File

@@ -0,0 +1,331 @@
======
Basics
======
Core concepts and fundamental usage of RevPiModIO.
.. contents:: Contents
:local:
:depth: 2
Programming Paradigms
=====================
RevPiModIO supports two complementary programming approaches:
**Cyclic Programming** - Execute a function at regular intervals, similar to PLC programming.
* Best for deterministic timing, state machines, and time-critical control
* Runs your function every cycle (typically 20-50ms)
* See :doc:`cyclic_programming` for details
**Event-Driven Programming** - Register callbacks triggered by hardware state changes.
* Best for user interactions, sporadic events, and system integration
* Consumes CPU only when events occur
* See :doc:`event_programming` for details
Both approaches can be combined in a single application. See :doc:`advanced` for examples.
Getting Started
===============
Basic Instantiation
-------------------
Create a RevPiModIO instance to access your hardware:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Your code here
rpi.exit()
Configuration Parameters
------------------------
.. code-block:: python
rpi = revpimodio2.RevPiModIO(
autorefresh=True, # Auto-sync process image (recommended)
monitoring=False, # Read-only mode
syncoutputs=True, # Load output values on init
debug=False # Enable debug messages
)
**autorefresh** - Automatically reads inputs and writes outputs. Set to ``True`` for most applications.
**monitoring** - Read-only mode. Use when monitoring without controlling hardware.
**syncoutputs** - Load current output values on startup. Prevents outputs from resetting.
**debug** - Enable debug logging for troubleshooting.
Cycle Timing
------------
Default update rates depend on your hardware:
* **Core 1**: 40ms (25Hz)
* **Core3/Connect**: 20ms (50Hz)
* **NetIO**: 50ms (20Hz)
Adjust cycle time to match your needs:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycletime = 100 # Set to 100ms
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements.
Error Handling
--------------
Configure I/O error threshold:
.. code-block:: python
rpi.maxioerrors = 10 # Raise exception after 10 errors
# Check error count
if rpi.ioerrors > 5:
print("Warning: I/O errors detected")
Core Objects
============
rpi.io - Input/Output Access
-----------------------------
Access all configured IOs from piCtory:
.. code-block:: python
# Direct attribute access
value = rpi.io.button.value
rpi.io.led.value = True
# String-based access
rpi.io["button"].value
# Check existence
if "sensor" in rpi.io:
print(rpi.io.sensor.value)
# Iterate all IOs
for io in rpi.io:
print(f"{io.name}: {io.value}")
IO Properties
~~~~~~~~~~~~~
Each IO object has these properties:
* ``.name`` - IO name from piCtory
* ``.value`` - Current value (read/write)
* ``.address`` - Byte address in process image
* ``.type`` - IO type (INPUT=300, OUTPUT=301, MEMORY=302)
* ``.defaultvalue`` - Default value from piCtory
rpi.core - System Control
--------------------------
Access Revolution Pi system features:
LED Control
~~~~~~~~~~~
.. code-block:: python
# Using constants
rpi.core.A1 = revpimodio2.GREEN
rpi.core.A2 = revpimodio2.RED
rpi.core.A3 = revpimodio2.OFF
# Individual colors
rpi.core.a1green.value = True
rpi.core.a1red.value = False
System Status
~~~~~~~~~~~~~
.. code-block:: python
# CPU information
temp = rpi.core.temperature.value
freq = rpi.core.frequency.value
# piBridge status
cycle_time = rpi.core.iocycle.value
errors = rpi.core.ioerrorcount.value
Watchdog
~~~~~~~~
.. code-block:: python
# Toggle watchdog
rpi.core.wd_toggle()
# Watchdog IO object
rpi.core.wd.value = True
See :doc:`advanced` for complete watchdog management examples.
rpi.device - Device Access
---------------------------
Access specific hardware devices:
.. code-block:: python
# By name
dio = rpi.device.DIO_Module_1
# By position
first = rpi.device[0]
# Iterate
for device in rpi.device:
print(device.name)
Signal Handling
===============
Graceful Shutdown
-----------------
Handle SIGINT and SIGTERM for clean program termination:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Enable signal handling
rpi.handlesignalend()
# Run main loop
rpi.mainloop()
Custom Signal Handler
---------------------
Implement custom cleanup logic:
.. code-block:: python
def cleanup(signum, frame):
print("Shutting down...")
rpi.setdefaultvalues()
rpi.exit()
rpi.handlesignalend(cleanup)
rpi.mainloop()
Simple Examples
===============
Read Input, Control Output
---------------------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Read input and control output
if rpi.io.button.value:
rpi.io.led.value = True
else:
rpi.io.led.value = False
rpi.exit()
LED Control
-----------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Control status LEDs
rpi.core.A1 = revpimodio2.GREEN
rpi.core.A2 = revpimodio2.RED
rpi.exit()
Iterate All IOs
---------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Print all IOs and their values
for io in rpi.io:
print(f"{io.name}: {io.value}")
rpi.exit()
Best Practices
==============
Use Descriptive IO Names
-------------------------
Configure descriptive names in piCtory:
.. code-block:: python
# Good - Clear intent
if rpi.io.emergency_stop.value:
rpi.io.motor.value = False
# Poor - Generic names
if rpi.io.I_15.value:
rpi.io.O_3.value = False
Always Clean Up
---------------
Always call ``rpi.exit()`` to clean up resources:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
try:
# Your code here
pass
finally:
rpi.exit()
Check IO Existence
------------------
Verify IOs exist before accessing:
.. code-block:: python
if "optional_sensor" in rpi.io:
value = rpi.io.optional_sensor.value
else:
print("Sensor not configured")
See Also
========
* :doc:`cyclic_programming` - Cyclic programming patterns
* :doc:`event_programming` - Event-driven programming patterns
* :doc:`advanced` - Advanced topics and best practices
* :doc:`api/index` - API reference

View File

@@ -15,19 +15,73 @@ project = 'revpimodio2'
copyright = '2023, Sven Sager'
author = 'Sven Sager'
version = __version__
release = __version__
# -- General configuration ---------------------------------------------------
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinx.ext.autosummary',
'sphinx.ext.todo',
'sphinx.ext.viewcode'
]
# Autodoc configuration
autodoc_default_options = {
'members': True,
'member-order': 'bysource',
'special-members': '__init__',
'undoc-members': True,
'exclude-members': '__weakref__'
}
# Napoleon settings (for NumPy and Google style docstrings)
napoleon_google_docstring = True
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = True
napoleon_include_private_with_doc = False
napoleon_include_special_with_doc = True
napoleon_use_admonition_for_examples = True
napoleon_use_admonition_for_notes = True
napoleon_use_admonition_for_references = True
napoleon_use_ivar = False
napoleon_use_param = True
napoleon_use_rtype = True
napoleon_preprocess_types = True
napoleon_type_aliases = None
napoleon_attr_annotations = True
# Autosummary settings
autosummary_generate = True
# Intersphinx configuration
intersphinx_mapping = {
'python': ('https://docs.python.org/3', None),
}
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
# Theme options
html_theme_options = {
'navigation_depth': 4,
'collapse_navigation': False,
'sticky_navigation': True,
'includehidden': True,
'titles_only': False
}
# -- Options for todo extension ----------------------------------------------
todo_include_todos = True

604
docs/cyclic_programming.rst Normal file
View File

@@ -0,0 +1,604 @@
==================
Cyclic Programming
==================
Cyclic programming executes a function at regular intervals, similar to PLC programming.
.. contents:: Contents
:local:
:depth: 2
When to Use Cyclic Programming
===============================
**Cyclic programming is ideal for:**
* Deterministic timing requirements
* Traditional PLC-style logic
* State machines
* Time-critical control
* Continuous monitoring and control
**Advantages:**
* Predictable timing
* Simple mental model
* Easy to reason about program flow
* Natural for control systems
**Considerations:**
* Consumes CPU even when idle
* Cycle time affects responsiveness
* Must keep cycle logic fast
Basic Structure
===============
Simple Cycle Loop
-----------------
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct: revpimodio2.Cycletools):
"""Execute each cycle."""
if ct.io.start_button.value:
ct.io.motor.value = True
if ct.io.stop_button.value:
ct.io.motor.value = False
rpi.cycleloop(main_cycle)
The ``main_cycle`` function is called repeatedly at the configured cycle time (typically 20-50ms).
Understanding Cycle Time
-------------------------
The cycle time determines execution frequency:
* **Core 1**: 40ms (25 Hz)
* **Core3/Connect**: 20ms (50 Hz)
* **NetIO**: 50ms (20 Hz)
Adjust cycle time to match your needs:
.. code-block:: python
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycletime = 100 # 100ms = 10 Hz
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements.
Cycletools Object
=================
The ``Cycletools`` object is passed to your cycle function, providing access to:
* ``ct.io`` - All IOs
* ``ct.core`` - System control
* ``ct.device`` - Device access
* ``ct.var`` - Persistent variables
* Lifecycle flags (``first``, ``last``)
* Timing flags (``flag5c``, ``flank10c``, etc.)
* Timer functions (``set_tonc``, ``get_tofc``, etc.)
* Change detection (``changed``)
Initialization and Cleanup
===========================
Use ``ct.first`` and ``ct.last`` for setup and teardown:
.. code-block:: python
def main_cycle(ct: revpimodio2.Cycletools):
if ct.first:
# Initialize on first cycle
ct.var.counter = 0
ct.var.state = "IDLE"
print("System started")
# Main logic runs every cycle
ct.var.counter += 1
if ct.last:
# Cleanup before exit
ct.io.motor.value = False
print(f"Total cycles: {ct.var.counter}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(main_cycle)
Persistent Variables
====================
Use ``ct.var`` to store variables that persist between cycles:
.. code-block:: python
def main_cycle(ct):
if ct.first:
ct.var.counter = 0
ct.var.state = "IDLE"
ct.var.accumulator = 0.0
# Variables persist between cycles
ct.var.counter += 1
ct.var.accumulator += ct.io.sensor.value
# Access variables later
average = ct.var.accumulator / ct.var.counter
Variables defined in ``ct.var`` maintain their values across all cycle executions.
Change Detection
================
Detect input changes efficiently without storing previous values:
.. code-block:: python
def main_cycle(ct: revpimodio2.Cycletools):
# Detect any change
if ct.changed(ct.io.sensor):
print(f"Sensor changed to: {ct.io.sensor.value}")
# Detect rising edge (button press)
if ct.changed(ct.io.button, edge=revpimodio2.RISING):
print("Button pressed!")
# Detect falling edge (button release)
if ct.changed(ct.io.button, edge=revpimodio2.FALLING):
print("Button released!")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(main_cycle)
Edge types:
* ``revpimodio2.RISING`` - False to True transition
* ``revpimodio2.FALLING`` - True to False transition
* ``revpimodio2.BOTH`` - Any change (default)
Timing Flags
============
Built-in timing flags provide periodic execution without manual counting.
Toggle Flags
------------
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
# Different blink rates
ct.io.fast_blink.value = ct.flag2c # Every 2 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
Flank Flags
-----------
Flank flags are True for exactly one cycle at regular intervals:
.. code-block:: python
def main_cycle(ct):
# Execute task every 10 cycles
if ct.flank10c:
print(f"Runtime: {ct.runtime:.3f}s")
# Execute task every 20 cycles
if ct.flank20c:
temp = ct.io.temperature.value
print(f"Temperature: {temp}°C")
**Available flank flags:**
* ``ct.flank5c`` - True every 5 cycles
* ``ct.flank10c`` - True every 10 cycles
* ``ct.flank15c`` - True every 15 cycles
* ``ct.flank20c`` - True every 20 cycles
Timers
======
RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts.
On-Delay Timer (TON/TONC)
--------------------------
Output becomes True only after input is continuously True for specified cycles:
.. code-block:: python
def main_cycle(ct):
# Input: sensor value
ct.set_tonc("delay", 10)
# Output goes high after input is high for 10 cycles
if ct.get_tonc("delay"):
ct.io.output.value = True
else:
ct.io.output.value = False
**How it works:**
1. Input goes True
2. Timer starts counting
3. If input stays True for 10 cycles, output goes True
4. If input goes False before 10 cycles, timer resets
**Use cases:**
* Button debouncing
* Startup delays
* Confirming sustained conditions
Off-Delay Timer (TOF/TOFC)
---------------------------
Output stays True for specified cycles after input goes False:
.. code-block:: python
def main_cycle(ct):
# Input: button value
ct.set_tofc("motor_coast", 20)
# Motor continues for 20 cycles after button release
ct.io.motor.value = ct.get_tofc("motor_coast")
**How it works:**
1. Input is True, output is True
2. Input goes False
3. Output stays True for 20 more cycles
4. After 20 cycles, output goes False
**Use cases:**
* Motor coast-down
* Relay hold-in
* Graceful shutdowns
Pulse Timer (TP/TPC)
--------------------
Generates a one-shot pulse of specified duration:
.. code-block:: python
def main_cycle(ct):
# Trigger pulse on button press
if ct.changed(ct.io.trigger, edge=revpimodio2.RISING):
ct.set_tpc("pulse", 5)
# Output is True for 5 cycles
ct.io.pulse_output.value = ct.get_tpc("pulse")
**How it works:**
1. Call ``set_tpc`` to trigger pulse
2. Output is True for 5 cycles
3. After 5 cycles, output goes False
4. Additional triggers during pulse are ignored
**Use cases:**
* One-shot operations
* 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
==============
State machines implement complex control logic with distinct operational modes.
Simple State Machine
---------------------
.. code-block:: python
def traffic_light(ct: revpimodio2.Cycletools):
"""Traffic light controller."""
if ct.first:
ct.var.state = "GREEN"
if ct.var.state == "GREEN":
ct.io.green_led.value = True
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"):
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.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.var.state = "GREEN"
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(traffic_light)
Complex State Machine
----------------------
.. code-block:: python
def machine_controller(ct: revpimodio2.Cycletools):
"""Multi-state machine controller."""
if ct.first:
ct.var.state = "IDLE"
ct.var.production_count = 0
# State: IDLE - Ready to start
if ct.var.state == "IDLE":
ct.io.motor.value = False
ct.io.green_led.value = True
ct.io.red_led.value = False
if ct.changed(ct.io.start_button, edge=revpimodio2.RISING):
ct.var.state = "STARTING"
print("Starting...")
# State: STARTING - Startup sequence
elif ct.var.state == "STARTING":
ct.io.yellow_led.value = True
# 2-second startup delay
ct.set_tonc("startup", 100)
if ct.get_tonc("startup"):
ct.var.state = "RUNNING"
print("Running")
# State: RUNNING - Normal operation
elif ct.var.state == "RUNNING":
ct.io.motor.value = True
ct.io.yellow_led.value = False
ct.io.green_led.value = ct.flag5c # Blink
# Count production
if ct.changed(ct.io.sensor, edge=revpimodio2.RISING):
ct.var.production_count += 1
# Check for stop
if ct.io.stop_button.value:
ct.var.state = "STOPPING"
# Check for error
if ct.io.error_sensor.value:
ct.var.state = "ERROR"
# 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")
if not ct.io.motor.value:
ct.var.state = "IDLE"
print("Stopped")
# State: ERROR - Fault condition
elif ct.var.state == "ERROR":
ct.io.motor.value = False
ct.io.red_led.value = ct.flag2c # Blink red
if ct.changed(ct.io.ack_button, edge=revpimodio2.RISING):
if not ct.io.error_sensor.value:
ct.var.state = "IDLE"
print("Error cleared")
if ct.last:
print(f"Total production: {ct.var.production_count}")
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(machine_controller)
Practical Examples
==================
Temperature Control
-------------------
Temperature monitoring with hysteresis control:
.. code-block:: python
def temperature_monitor(ct: revpimodio2.Cycletools):
"""Monitor temperature and control cooling."""
if ct.first:
ct.var.cooling_active = False
temp = ct.io.temperature.value
# Hysteresis: ON at 75°C, OFF at 65°C
if temp > 75 and not ct.var.cooling_active:
ct.io.cooling_fan.value = True
ct.var.cooling_active = True
print(f"Cooling ON: {temp}°C")
elif temp < 65 and ct.var.cooling_active:
ct.io.cooling_fan.value = False
ct.var.cooling_active = False
print(f"Cooling OFF: {temp}°C")
# Warning if too hot
if temp > 85:
ct.io.warning_led.value = ct.flag2c # Blink
# Emergency shutdown
if temp > 95:
ct.io.emergency_shutdown.value = True
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(temperature_monitor)
Production Counter
------------------
Count production items with start/stop control:
.. code-block:: python
def production_counter(ct: revpimodio2.Cycletools):
"""Track production count."""
if ct.first:
ct.var.total_count = 0
ct.var.running = False
# Start/stop control
if ct.changed(ct.io.start_button, edge=revpimodio2.RISING):
ct.var.running = True
if ct.changed(ct.io.stop_button, edge=revpimodio2.RISING):
ct.var.running = False
# Count items
if ct.var.running:
if ct.changed(ct.io.item_sensor, edge=revpimodio2.RISING):
ct.var.total_count += 1
ct.set_tpc("count_pulse", 5) # Pulse LED
print(f"Item #{ct.var.total_count}")
ct.io.count_led.value = ct.get_tpc("count_pulse")
# Reset counter
if ct.changed(ct.io.reset_button, edge=revpimodio2.RISING):
print(f"Final count: {ct.var.total_count}")
ct.var.total_count = 0
rpi = revpimodio2.RevPiModIO(autorefresh=True)
rpi.cycleloop(production_counter)
Best Practices
==============
Keep Cycle Logic Fast
----------------------
Minimize processing time in each cycle:
.. code-block:: python
def optimized_cycle(ct):
# Heavy work only when needed
if ct.flank100c:
heavy_calculation()
# Keep cycle logic minimal
ct.io.output.value = ct.io.input.value
**Guidelines:**
* Avoid blocking operations (network, file I/O)
* Use flank flags for expensive operations
* Keep cycle time ≥20ms for stability
Use Appropriate Cycle Time
---------------------------
Match cycle time to application requirements:
.. code-block:: python
# Fast control (motion, high-speed counting)
rpi.cycletime = 20 # 50 Hz
# Normal control (most applications)
rpi.cycletime = 50 # 20 Hz
# Slow monitoring (temperature, status)
rpi.cycletime = 100 # 10 Hz
Handle Errors Safely
--------------------
Always implement safe failure modes:
.. code-block:: python
def safe_cycle(ct):
try:
value = ct.io.sensor.value
ct.io.output.value = process(value)
except Exception as e:
print(f"Error: {e}")
ct.io.output.value = False # Safe state
Initialize Properly
-------------------
Use ``ct.first`` for all initialization:
.. code-block:: python
def main_cycle(ct):
if ct.first:
# Initialize all variables
ct.var.counter = 0
ct.var.state = "IDLE"
ct.var.last_value = 0
# Set initial outputs
ct.io.motor.value = False
See Also
========
* :doc:`basics` - Core concepts and configuration
* :doc:`event_programming` - Event-driven programming
* :doc:`advanced` - Advanced topics and examples
* :doc:`api/helper` - Cycletools API reference

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

View File

@@ -1,18 +1,87 @@
.. revpimodio2 documentation main file, created by
sphinx-quickstart on Sun Jan 22 17:49:41 2023.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
====================================
Welcome to RevPiModIO Documentation!
====================================
Welcome to revpimodio2's documentation!
=======================================
RevPiModIO is a Python3 module for programming Revolution Pi hardware from KUNBUS GmbH.
It provides easy access to all devices and IOs from the piCtory configuration, supporting
both cyclic (PLC-style) and event-driven programming paradigms.
.. note::
**New to RevPiModIO?** Start with :doc:`installation` and :doc:`quickstart`.
Key Features
============
* **Dual Programming Models**: Cyclic (PLC-style) and event-driven approaches
* **Direct Hardware Access**: Simple Python interface to all I/O devices
* **Automatic Configuration**: Loads piCtory device configuration
* **Event System**: Callbacks for value changes and timer events
* **Open Source**: LGPLv2 license, no licensing fees
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)
**Event-Driven Programming**::
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def on_change(ioname, iovalue):
print(f"{ioname} = {iovalue}")
rpi.io.button.reg_event(on_change)
rpi.handlesignalend()
rpi.mainloop()
Documentation
=============
.. toctree::
:maxdepth: 2
:caption: Contents:
:caption: Getting Started
installation
quickstart
.. toctree::
:maxdepth: 2
:caption: User Guide
Indices and tables
basics
cyclic_programming
event_programming
advanced
.. toctree::
:maxdepth: 3
:caption: API Reference
api/index
api/revpimodio
api/io
api/helper
api/device
External Resources
==================
* **Official Website**: `revpimodio.org <https://revpimodio.org>`_
* **GitHub Repository**: `github.com/naruxde/ <https://github.com/naruxde/>`_
* **Discussion Forum**: `revpimodio.org/diskussionsforum/ <https://revpimodio.org/diskussionsforum/>`_
* **Revolution Pi Hardware**: `revolution.kunbus.com <https://revolution.kunbus.com>`_
Indices and Tables
==================
* :ref:`genindex`

85
docs/installation.rst Normal file
View File

@@ -0,0 +1,85 @@
============
Installation
============
System Requirements
===================
* Python 3.7 or higher
* Revolution Pi hardware (Core, Core3, Connect, Compact, Flat)
* piCtory configuration tool
Prerequisites
=============
User Permissions
----------------
On Bookworm images, users must belong to the ``picontrol`` group::
sudo usermod -a -G picontrol username
Log out and log back in for the group change to take effect.
Installing RevPiModIO
=====================
Using pip
---------
Install from PyPI::
pip install revpimodio2
From Source
-----------
Clone the repository and install::
git clone https://github.com/naruxde/revpimodio2.git
cd revpimodio2
pip install .
Verify Installation
===================
Test the installation::
python3 -c "import revpimodio2; print(revpimodio2.__version__)"
Optional Components
===================
RevPiPyLoad
-----------
For advanced features like XML-RPC server and MQTT integration::
sudo apt-get update
sudo apt-get install revpipyload
Configure XML-RPC Server
~~~~~~~~~~~~~~~~~~~~~~~~~
Edit ``/etc/revpipyload/revpipyload.conf``::
[XMLRPC]
xmlrpc = 1
Configure access permissions in ``/etc/revpipyload/aclxmlrpc.conf``, then restart::
sudo service revpipyload restart
RevPi Commander
---------------
RevPi Commander provides a GUI for testing I/O without programming:
1. Download from `revpimodio.org <https://revpimodio.org/quellen/revpicommander/>`_
2. Configure connection via File → Connections with your RevPi's IP address (port: 55123)
3. Use "PLC watch mode" to monitor sensors and control outputs
Next Steps
==========
After installation, proceed to :doc:`quickstart` to write your first program.

View File

@@ -1,7 +0,0 @@
src
===
.. toctree::
:maxdepth: 4
revpimodio2

207
docs/quickstart.rst Normal file
View File

@@ -0,0 +1,207 @@
==========
Quick Start
==========
This guide will help you write your first RevPiModIO program.
Basic Concepts
==============
RevPiModIO provides two main programming paradigms:
* **Cyclic Programming** - Execute a function at regular intervals (PLC-style)
* **Event-Driven Programming** - Register callbacks triggered by hardware changes
Both approaches use the same core objects:
* ``rpi.io`` - Access inputs and outputs by name
* ``rpi.core`` - Control LEDs, watchdog, and system status
* ``rpi.device`` - Access specific hardware devices
Hardware Configuration
======================
Before programming, configure your hardware using piCtory:
1. Access piCtory web interface on your RevPi Core module
2. Add and configure your I/O modules
3. Assign symbolic names to inputs and outputs
* Example: ``button``, ``led``, ``temperature``
* Good names make your code readable
4. Save configuration and activate
Your First Program
==================
Simple Input to Output
----------------------
The simplest program reads an input and controls an output:
.. code-block:: python
import revpimodio2
# Initialize with auto-refresh
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Read input and control output
if rpi.io.button.value:
rpi.io.led.value = True
else:
rpi.io.led.value = False
# Clean up
rpi.exit()
Cyclic Program
--------------
For continuous operation, use a cyclic loop:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def main_cycle(ct: revpimodio2.Cycletools):
"""Called every cycle (default: 20-50ms)."""
if ct.first:
# Initialize on first cycle
ct.var.counter = 0
print("Program started")
# Main logic
if ct.io.button.value:
ct.io.led.value = True
else:
ct.io.led.value = False
# Count button presses
if ct.changed(ct.io.button, edge=revpimodio2.RISING):
ct.var.counter += 1
print(f"Button pressed {ct.var.counter} times")
if ct.last:
# Cleanup on exit
print("Program stopped")
# Run cyclic loop
rpi.cycleloop(main_cycle)
Event-Driven Program
--------------------
For event-based operation, use callbacks:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
def on_button_press(ioname, iovalue):
"""Called when button changes."""
print(f"Button is now: {iovalue}")
rpi.io.led.value = iovalue
# Register event callback
rpi.io.button.reg_event(on_button_press)
# Handle shutdown signals
rpi.handlesignalend()
# Start event loop
rpi.mainloop()
LED Control
===========
Control the RevPi status LEDs:
.. code-block:: python
import revpimodio2
rpi = revpimodio2.RevPiModIO(autorefresh=True)
# Set LED colors using constants
rpi.core.A1 = revpimodio2.GREEN # Success
rpi.core.A2 = revpimodio2.RED # Error
rpi.core.A3 = revpimodio2.OFF # Off
# Or control individual colors
rpi.core.a1green.value = True
rpi.core.a1red.value = False
rpi.exit()
Common Patterns
===============
Initialize and Cleanup
----------------------
Always initialize variables and clean up resources:
.. code-block:: python
def main_cycle(ct):
if ct.first:
# Initialize
ct.var.state = "IDLE"
ct.var.error_count = 0
# Main logic here...
if ct.last:
# Cleanup
ct.io.motor.value = False
print(f"Errors: {ct.var.error_count}")
Edge Detection
--------------
Detect rising or falling edges:
.. code-block:: python
def main_cycle(ct):
# Detect button press (rising edge)
if ct.changed(ct.io.button, edge=revpimodio2.RISING):
print("Button pressed!")
# Detect button release (falling edge)
if ct.changed(ct.io.button, edge=revpimodio2.FALLING):
print("Button released!")
Timers
------
Use built-in cycle-based timers:
.. code-block:: python
def main_cycle(ct):
# On-delay: Input must be True for 10 cycles
ct.set_tonc("startup", 10)
if ct.get_tonc("startup"):
ct.io.motor.value = True
# Pulse: Generate 5-cycle pulse
if ct.io.trigger.value:
ct.set_tpc("pulse", 5)
ct.io.pulse_output.value = ct.get_tpc("pulse")
Next Steps
==========
* :doc:`basics` - Core concepts and configuration
* :doc:`cyclic_programming` - Cyclic programming patterns
* :doc:`event_programming` - Event-driven programming patterns
* :doc:`advanced` - Advanced topics and best practices
* :doc:`api/index` - API reference

View File

@@ -1,85 +0,0 @@
revpimodio2 package
===================
Submodules
----------
revpimodio2.app module
----------------------
.. automodule:: revpimodio2.app
:members:
:undoc-members:
:show-inheritance:
revpimodio2.device module
-------------------------
.. automodule:: revpimodio2.device
:members:
:undoc-members:
:show-inheritance:
revpimodio2.errors module
-------------------------
.. automodule:: revpimodio2.errors
:members:
:undoc-members:
:show-inheritance:
revpimodio2.helper module
-------------------------
.. automodule:: revpimodio2.helper
:members:
:undoc-members:
:show-inheritance:
revpimodio2.io module
---------------------
.. automodule:: revpimodio2.io
:members:
:undoc-members:
:show-inheritance:
revpimodio2.modio module
------------------------
.. automodule:: revpimodio2.modio
:members:
:undoc-members:
:show-inheritance:
revpimodio2.netio module
------------------------
.. automodule:: revpimodio2.netio
:members:
:undoc-members:
:show-inheritance:
revpimodio2.pictory module
--------------------------
.. automodule:: revpimodio2.pictory
:members:
:undoc-members:
:show-inheritance:
revpimodio2.summary module
--------------------------
.. automodule:: revpimodio2.summary
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: revpimodio2
:members:
:undoc-members:
:show-inheritance: