mirror of
https://github.com/naruxde/revpimodio2.git
synced 2026-03-31 23:18:04 +02:00
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:
711
docs/advanced.rst
Normal file
711
docs/advanced.rst
Normal 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
163
docs/api/device.rst
Normal 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
194
docs/api/helper.rst
Normal 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
148
docs/api/index.rst
Normal 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
212
docs/api/io.rst
Normal 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
141
docs/api/revpimodio.rst
Normal 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
331
docs/basics.rst
Normal 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
|
||||||
58
docs/conf.py
58
docs/conf.py
@@ -15,19 +15,73 @@ project = 'revpimodio2'
|
|||||||
copyright = '2023, Sven Sager'
|
copyright = '2023, Sven Sager'
|
||||||
author = 'Sven Sager'
|
author = 'Sven Sager'
|
||||||
version = __version__
|
version = __version__
|
||||||
|
release = __version__
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.autodoc',
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.napoleon',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.autosummary',
|
||||||
'sphinx.ext.todo',
|
'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']
|
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']
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
html_theme = 'alabaster'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
html_static_path = ['_static']
|
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
604
docs/cyclic_programming.rst
Normal 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
584
docs/event_programming.rst
Normal 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
|
||||||
@@ -1,18 +1,87 @@
|
|||||||
.. revpimodio2 documentation main file, created by
|
====================================
|
||||||
sphinx-quickstart on Sun Jan 22 17:49:41 2023.
|
Welcome to RevPiModIO Documentation!
|
||||||
You can adapt this file completely to your liking, but it should at least
|
====================================
|
||||||
contain the root `toctree` directive.
|
|
||||||
|
|
||||||
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::
|
.. toctree::
|
||||||
:maxdepth: 2
|
: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`
|
* :ref:`genindex`
|
||||||
|
|||||||
85
docs/installation.rst
Normal file
85
docs/installation.rst
Normal 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.
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
src
|
|
||||||
===
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 4
|
|
||||||
|
|
||||||
revpimodio2
|
|
||||||
207
docs/quickstart.rst
Normal file
207
docs/quickstart.rst
Normal 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
|
||||||
@@ -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:
|
|
||||||
Reference in New Issue
Block a user