mirror of
https://github.com/naruxde/revpimodio2.git
synced 2026-03-31 15:08:09 +02:00
docs: Update documentation for improved clarity and consistency
Revised various sections across multiple documentation files to reflect updated methods (`run_plc` replacing manual setup with `cycleloop`) and adjust for new default parameters (e.g., `autorefresh`). Enhanced descriptions for timers, Cycletools usage, and new method explanations. Removed outdated or redundant examples and updated system requirements. Signed-off-by: Sven Sager <akira@narux.de>
This commit is contained in:
@@ -36,7 +36,6 @@ Gateway modules provide generic IOs (like ``Input_1``, ``Output_1``, etc.) that
|
|||||||
rpi.io.Input_1.replace_io(
|
rpi.io.Input_1.replace_io(
|
||||||
"temperature", # New IO name
|
"temperature", # New IO name
|
||||||
"h", # struct format: signed short
|
"h", # struct format: signed short
|
||||||
defaultvalue=0 # Default value
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use the custom IO by its new name
|
# Use the custom IO by its new name
|
||||||
@@ -70,6 +69,7 @@ Common format codes for ``replace_io`` (see `Python struct format characters <ht
|
|||||||
* ``'i'`` - signed int (-2147483648 to 2147483647)
|
* ``'i'`` - signed int (-2147483648 to 2147483647)
|
||||||
* ``'I'`` - unsigned int (0 to 4294967295)
|
* ``'I'`` - unsigned int (0 to 4294967295)
|
||||||
* ``'f'`` - float (32-bit)
|
* ``'f'`` - float (32-bit)
|
||||||
|
* ``'d'`` - float (64-bit)
|
||||||
|
|
||||||
Multiple Custom IOs
|
Multiple Custom IOs
|
||||||
-------------------
|
-------------------
|
||||||
@@ -82,8 +82,8 @@ Define multiple custom IOs programmatically by replacing generic gateway IOs:
|
|||||||
|
|
||||||
# Replace multiple gateway IOs with custom definitions
|
# Replace multiple gateway IOs with custom definitions
|
||||||
# Assuming a gateway module with Input_1, Input_2, Output_1, Output_2
|
# 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_1.replace_io("temperature", "h")
|
||||||
rpi.io.Input_2.replace_io("humidity", "h", defaultvalue=0)
|
rpi.io.Input_2.replace_io("humidity", "h")
|
||||||
rpi.io.Output_1.replace_io("setpoint", "h", defaultvalue=700)
|
rpi.io.Output_1.replace_io("setpoint", "h", defaultvalue=700)
|
||||||
rpi.io.Output_2.replace_io("control_word", "H", defaultvalue=0)
|
rpi.io.Output_2.replace_io("control_word", "H", defaultvalue=0)
|
||||||
|
|
||||||
@@ -126,12 +126,10 @@ Create an INI-style configuration file (``replace_ios.conf``):
|
|||||||
[temperature]
|
[temperature]
|
||||||
replace = Input_1
|
replace = Input_1
|
||||||
frm = h
|
frm = h
|
||||||
defaultvalue = 0
|
|
||||||
|
|
||||||
[humidity]
|
[humidity]
|
||||||
replace = Input_2
|
replace = Input_2
|
||||||
frm = h
|
frm = h
|
||||||
defaultvalue = 0
|
|
||||||
|
|
||||||
[setpoint]
|
[setpoint]
|
||||||
replace = Output_1
|
replace = Output_1
|
||||||
@@ -181,7 +179,9 @@ This is useful for:
|
|||||||
Watchdog Management
|
Watchdog Management
|
||||||
===================
|
===================
|
||||||
|
|
||||||
The hardware watchdog monitors your program and resets the system if it stops responding.
|
The hardware watchdog monitors your program and resets the system if it stops responding. If you
|
||||||
|
use the software watchdog will will only works if you use RevPiPyControl as runtime for your python
|
||||||
|
program.
|
||||||
|
|
||||||
How the Watchdog Works
|
How the Watchdog Works
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -197,8 +197,6 @@ Cyclic Watchdog Toggle
|
|||||||
|
|
||||||
import revpimodio2
|
import revpimodio2
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
|
||||||
|
|
||||||
def main_cycle(ct):
|
def main_cycle(ct):
|
||||||
# Toggle every 10 cycles (200ms @ 20ms)
|
# Toggle every 10 cycles (200ms @ 20ms)
|
||||||
if ct.flank10c:
|
if ct.flank10c:
|
||||||
@@ -207,7 +205,7 @@ Cyclic Watchdog Toggle
|
|||||||
# Your control logic
|
# Your control logic
|
||||||
ct.io.output.value = ct.io.input.value
|
ct.io.output.value = ct.io.input.value
|
||||||
|
|
||||||
rpi.cycleloop(main_cycle)
|
revpimodio2.run_plc(main_cycle)
|
||||||
|
|
||||||
Event-Driven Watchdog Toggle
|
Event-Driven Watchdog Toggle
|
||||||
-----------------------------
|
-----------------------------
|
||||||
@@ -234,134 +232,6 @@ Event-Driven Watchdog Toggle
|
|||||||
rpi.handlesignalend()
|
rpi.handlesignalend()
|
||||||
rpi.mainloop()
|
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
|
Performance Optimization
|
||||||
========================
|
========================
|
||||||
|
|
||||||
@@ -487,23 +357,19 @@ Track and handle I/O errors:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
maxioerrors = 10 # Exception after 10 errors
|
||||||
rpi.maxioerrors = 10 # Exception after 10 errors
|
|
||||||
|
|
||||||
def main_cycle(ct):
|
def main_cycle(ct):
|
||||||
# Check error count periodically
|
# Check error count periodically
|
||||||
if ct.flank20c:
|
if ct.flank20c:
|
||||||
if rpi.ioerrors > 5:
|
if rpi.core.ioerrorcount > maxioerrors:
|
||||||
print(f"Warning: {rpi.ioerrors} I/O errors detected")
|
print(f"Warning: {rpi.core.ioerrorcount} I/O errors detected")
|
||||||
ct.io.warning_led.value = True
|
ct.io.warning_led.value = True
|
||||||
|
|
||||||
# Normal logic
|
# Normal logic
|
||||||
ct.io.output.value = ct.io.input.value
|
ct.io.output.value = ct.io.input.value
|
||||||
|
|
||||||
try:
|
revpimodio2.run_plc(main_cycle)
|
||||||
rpi.cycleloop(main_cycle)
|
|
||||||
except RuntimeError as e:
|
|
||||||
print(f"I/O error threshold exceeded: {e}")
|
|
||||||
|
|
||||||
Best Practices
|
Best Practices
|
||||||
==============
|
==============
|
||||||
@@ -600,35 +466,6 @@ Document complex logic:
|
|||||||
# State machine implementation
|
# 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
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@@ -676,6 +513,7 @@ Always validate external inputs:
|
|||||||
"""Validate setpoint range."""
|
"""Validate setpoint range."""
|
||||||
if 0 <= iovalue <= 100:
|
if 0 <= iovalue <= 100:
|
||||||
rpi.io.setpoint.value = iovalue
|
rpi.io.setpoint.value = iovalue
|
||||||
|
rpi.io.error_led.value = False
|
||||||
else:
|
else:
|
||||||
print(f"Invalid setpoint: {iovalue}")
|
print(f"Invalid setpoint: {iovalue}")
|
||||||
rpi.io.error_led.value = True
|
rpi.io.error_led.value = True
|
||||||
|
|||||||
@@ -78,10 +78,11 @@ Adjust cycle time to match your needs:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
rpi = revpimodio2.RevPiModIO()
|
||||||
rpi.cycletime = 100 # Set to 100ms
|
rpi.cycletime = 100 # Set to 100ms
|
||||||
|
rpi.autorefresh_all()
|
||||||
|
|
||||||
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements.
|
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements. Default values will fit most needs.
|
||||||
|
|
||||||
Error Handling
|
Error Handling
|
||||||
--------------
|
--------------
|
||||||
@@ -90,10 +91,10 @@ Configure I/O error threshold:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
rpi.maxioerrors = 10 # Raise exception after 10 errors
|
maxioerrors = 10 # Raise exception after 10 errors
|
||||||
|
|
||||||
# Check error count
|
# Check error count
|
||||||
if rpi.ioerrors > 5:
|
if rpi.core.ioerrorcount > maxioerrors:
|
||||||
print("Warning: I/O errors detected")
|
print("Warning: I/O errors detected")
|
||||||
|
|
||||||
Core Objects
|
Core Objects
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ Simple Cycle Loop
|
|||||||
|
|
||||||
import revpimodio2
|
import revpimodio2
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
|
||||||
|
|
||||||
def main_cycle(ct: revpimodio2.Cycletools):
|
def main_cycle(ct: revpimodio2.Cycletools):
|
||||||
"""Execute each cycle."""
|
"""Execute each cycle."""
|
||||||
if ct.io.start_button.value:
|
if ct.io.start_button.value:
|
||||||
@@ -51,10 +49,17 @@ Simple Cycle Loop
|
|||||||
if ct.io.stop_button.value:
|
if ct.io.stop_button.value:
|
||||||
ct.io.motor.value = False
|
ct.io.motor.value = False
|
||||||
|
|
||||||
rpi.cycleloop(main_cycle)
|
revpimodio2.run_plc(main_cycle)
|
||||||
|
|
||||||
|
# .run_plc is a shortcut for:
|
||||||
|
# rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
||||||
|
# rpi.handlesignalend()
|
||||||
|
# rpi.cycleloop(main_cycle)
|
||||||
|
|
||||||
The ``main_cycle`` function is called repeatedly at the configured cycle time (typically 20-50ms).
|
The ``main_cycle`` function is called repeatedly at the configured cycle time (typically 20-50ms).
|
||||||
|
|
||||||
|
**Info:** ``rpi.handlesignalend()``
|
||||||
|
|
||||||
Understanding Cycle Time
|
Understanding Cycle Time
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
@@ -68,10 +73,9 @@ Adjust cycle time to match your needs:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
revpimodio2.run_plc(main_cycle, cycletime=100) # 100ms = 10 Hz
|
||||||
rpi.cycletime = 100 # 100ms = 10 Hz
|
|
||||||
|
|
||||||
**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements.
|
**Important:** Faster cycle times consume more CPU but will detect fast changes of input values.
|
||||||
|
|
||||||
Cycletools Object
|
Cycletools Object
|
||||||
=================
|
=================
|
||||||
@@ -109,8 +113,7 @@ Use ``ct.first`` and ``ct.last`` for setup and teardown:
|
|||||||
ct.io.motor.value = False
|
ct.io.motor.value = False
|
||||||
print(f"Total cycles: {ct.var.counter}")
|
print(f"Total cycles: {ct.var.counter}")
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
revpimodio2.run_plc(main_cycle)
|
||||||
rpi.cycleloop(main_cycle)
|
|
||||||
|
|
||||||
Persistent Variables
|
Persistent Variables
|
||||||
====================
|
====================
|
||||||
@@ -155,6 +158,7 @@ Detect input changes efficiently without storing previous values:
|
|||||||
print("Button released!")
|
print("Button released!")
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
||||||
|
rpi.handlesignalend()
|
||||||
rpi.cycleloop(main_cycle)
|
rpi.cycleloop(main_cycle)
|
||||||
|
|
||||||
Edge types:
|
Edge types:
|
||||||
@@ -176,17 +180,16 @@ Toggle flags alternate between True/False at regular intervals:
|
|||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def main_cycle(ct):
|
def main_cycle(ct):
|
||||||
# Blink LED - flag5c alternates every 5 cycles
|
# Blink LED - flag5c alternates every cycle
|
||||||
ct.io.blink_led.value = ct.flag5c
|
ct.io.blink_led.value = ct.flag1c
|
||||||
|
|
||||||
# Different blink rates
|
# Different blink rates
|
||||||
ct.io.fast_blink.value = ct.flag2c # Every 2 cycles
|
ct.io.fast_blink.value = ct.flag5c # Every 5 cycles
|
||||||
ct.io.slow_blink.value = ct.flag20c # Every 20 cycles
|
ct.io.slow_blink.value = ct.flag20c # Every 20 cycles
|
||||||
|
|
||||||
**Available toggle flags:**
|
**Available toggle flags:**
|
||||||
|
|
||||||
* ``ct.flag1c`` - Every cycle
|
* ``ct.flag1c`` - Every cycle
|
||||||
* ``ct.flag2c`` - Every 2 cycles
|
|
||||||
* ``ct.flag5c`` - Every 5 cycles
|
* ``ct.flag5c`` - Every 5 cycles
|
||||||
* ``ct.flag10c`` - Every 10 cycles
|
* ``ct.flag10c`` - Every 10 cycles
|
||||||
* ``ct.flag20c`` - Every 20 cycles
|
* ``ct.flag20c`` - Every 20 cycles
|
||||||
@@ -218,12 +221,12 @@ Flank flags are True for exactly one cycle at regular intervals:
|
|||||||
Timers
|
Timers
|
||||||
======
|
======
|
||||||
|
|
||||||
RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts.
|
RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts or milliseconds.
|
||||||
|
|
||||||
On-Delay Timer (TON/TONC)
|
On-Delay Timer (TON/TONC)
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Output becomes True only after input is continuously True for specified cycles:
|
Output becomes True only after input is continuously True for specified cycles (use ton with milliseconds value instead of cycles):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -253,7 +256,7 @@ Output becomes True only after input is continuously True for specified cycles:
|
|||||||
Off-Delay Timer (TOF/TOFC)
|
Off-Delay Timer (TOF/TOFC)
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Output stays True for specified cycles after input goes False:
|
Output stays True for specified cycles or milliseconds after input goes False (use tof with milliseconds value instead of cycles):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -280,7 +283,7 @@ Output stays True for specified cycles after input goes False:
|
|||||||
Pulse Timer (TP/TPC)
|
Pulse Timer (TP/TPC)
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
Generates a one-shot pulse of specified duration:
|
Generates a one-shot pulse of specified duration (use tp with milliseconds value instead of cycles):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -305,25 +308,6 @@ Generates a one-shot pulse of specified duration:
|
|||||||
* Acknowledgment pulses
|
* Acknowledgment pulses
|
||||||
* Retriggerable delays
|
* 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
|
||||||
==============
|
==============
|
||||||
|
|
||||||
@@ -345,28 +329,29 @@ Simple State Machine
|
|||||||
ct.io.yellow_led.value = False
|
ct.io.yellow_led.value = False
|
||||||
ct.io.red_led.value = False
|
ct.io.red_led.value = False
|
||||||
|
|
||||||
# After 100 cycles (2s @ 20ms), go to yellow
|
# After 2 seconds, go to yellow
|
||||||
ct.set_tonc("green_time", 100)
|
ct.set_ton("green_time", 2000)
|
||||||
if ct.get_tonc("green_time"):
|
if ct.get_ton("green_time"):
|
||||||
ct.var.state = "YELLOW"
|
ct.var.state = "YELLOW"
|
||||||
|
|
||||||
elif ct.var.state == "YELLOW":
|
elif ct.var.state == "YELLOW":
|
||||||
ct.io.green_led.value = False
|
ct.io.green_led.value = False
|
||||||
ct.io.yellow_led.value = True
|
ct.io.yellow_led.value = True
|
||||||
|
|
||||||
ct.set_tonc("yellow_time", 25) # 500ms
|
ct.set_ton("yellow_time", 500)
|
||||||
if ct.get_tonc("yellow_time"):
|
if ct.get_ton("yellow_time"):
|
||||||
ct.var.state = "RED"
|
ct.var.state = "RED"
|
||||||
|
|
||||||
elif ct.var.state == "RED":
|
elif ct.var.state == "RED":
|
||||||
ct.io.yellow_led.value = False
|
ct.io.yellow_led.value = False
|
||||||
ct.io.red_led.value = True
|
ct.io.red_led.value = True
|
||||||
|
|
||||||
ct.set_tonc("red_time", 150) # 3s
|
ct.set_ton("red_time", 3000)
|
||||||
if ct.get_tonc("red_time"):
|
if ct.get_ton("red_time"):
|
||||||
ct.var.state = "GREEN"
|
ct.var.state = "GREEN"
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
||||||
|
rpi.handlesignalend()
|
||||||
rpi.cycleloop(traffic_light)
|
rpi.cycleloop(traffic_light)
|
||||||
|
|
||||||
Complex State Machine
|
Complex State Machine
|
||||||
@@ -396,8 +381,8 @@ Complex State Machine
|
|||||||
ct.io.yellow_led.value = True
|
ct.io.yellow_led.value = True
|
||||||
|
|
||||||
# 2-second startup delay
|
# 2-second startup delay
|
||||||
ct.set_tonc("startup", 100)
|
ct.set_ton("startup", 2000)
|
||||||
if ct.get_tonc("startup"):
|
if ct.get_ton("startup"):
|
||||||
ct.var.state = "RUNNING"
|
ct.var.state = "RUNNING"
|
||||||
print("Running")
|
print("Running")
|
||||||
|
|
||||||
@@ -422,8 +407,8 @@ Complex State Machine
|
|||||||
# State: STOPPING - Controlled shutdown
|
# State: STOPPING - Controlled shutdown
|
||||||
elif ct.var.state == "STOPPING":
|
elif ct.var.state == "STOPPING":
|
||||||
# Coast motor for 1 second
|
# Coast motor for 1 second
|
||||||
ct.set_tofc("coast", 50)
|
ct.set_tof("coast", 1000)
|
||||||
ct.io.motor.value = ct.get_tofc("coast")
|
ct.io.motor.value = ct.get_tof("coast")
|
||||||
|
|
||||||
if not ct.io.motor.value:
|
if not ct.io.motor.value:
|
||||||
ct.var.state = "IDLE"
|
ct.var.state = "IDLE"
|
||||||
@@ -432,7 +417,7 @@ Complex State Machine
|
|||||||
# State: ERROR - Fault condition
|
# State: ERROR - Fault condition
|
||||||
elif ct.var.state == "ERROR":
|
elif ct.var.state == "ERROR":
|
||||||
ct.io.motor.value = False
|
ct.io.motor.value = False
|
||||||
ct.io.red_led.value = ct.flag2c # Blink red
|
ct.io.red_led.value = ct.flag5c # Blink red
|
||||||
|
|
||||||
if ct.changed(ct.io.ack_button, edge=revpimodio2.RISING):
|
if ct.changed(ct.io.ack_button, edge=revpimodio2.RISING):
|
||||||
if not ct.io.error_sensor.value:
|
if not ct.io.error_sensor.value:
|
||||||
@@ -442,8 +427,7 @@ Complex State Machine
|
|||||||
if ct.last:
|
if ct.last:
|
||||||
print(f"Total production: {ct.var.production_count}")
|
print(f"Total production: {ct.var.production_count}")
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
revpimodio.run_plc(machine_controller)
|
||||||
rpi.cycleloop(machine_controller)
|
|
||||||
|
|
||||||
Practical Examples
|
Practical Examples
|
||||||
==================
|
==================
|
||||||
@@ -476,14 +460,17 @@ Temperature monitoring with hysteresis control:
|
|||||||
|
|
||||||
# Warning if too hot
|
# Warning if too hot
|
||||||
if temp > 85:
|
if temp > 85:
|
||||||
ct.io.warning_led.value = ct.flag2c # Blink
|
ct.core.a1green.value = False
|
||||||
|
ct.core.a1red.value = ct.flag5c # Blink
|
||||||
|
else:
|
||||||
|
ct.core.a1green.value = ct.flag5c # Blink
|
||||||
|
ct.core.a1red.value = False
|
||||||
|
|
||||||
# Emergency shutdown
|
# Emergency shutdown
|
||||||
if temp > 95:
|
if temp > 95:
|
||||||
ct.io.emergency_shutdown.value = True
|
ct.io.emergency_shutdown.value = True
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
revpimodio2.run_plc(temperature_monitor)
|
||||||
rpi.cycleloop(temperature_monitor)
|
|
||||||
|
|
||||||
Production Counter
|
Production Counter
|
||||||
------------------
|
------------------
|
||||||
@@ -520,8 +507,7 @@ Count production items with start/stop control:
|
|||||||
print(f"Final count: {ct.var.total_count}")
|
print(f"Final count: {ct.var.total_count}")
|
||||||
ct.var.total_count = 0
|
ct.var.total_count = 0
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
revpimodio2.run_plc(production_counter)
|
||||||
rpi.cycleloop(production_counter)
|
|
||||||
|
|
||||||
Best Practices
|
Best Practices
|
||||||
==============
|
==============
|
||||||
@@ -544,7 +530,7 @@ Minimize processing time in each cycle:
|
|||||||
**Guidelines:**
|
**Guidelines:**
|
||||||
|
|
||||||
* Avoid blocking operations (network, file I/O)
|
* Avoid blocking operations (network, file I/O)
|
||||||
* Use flank flags for expensive operations
|
* Use flank flags for expensive operations or even Threads
|
||||||
* Keep cycle time ≥20ms for stability
|
* Keep cycle time ≥20ms for stability
|
||||||
|
|
||||||
Use Appropriate Cycle Time
|
Use Appropriate Cycle Time
|
||||||
|
|||||||
@@ -94,29 +94,6 @@ Register callbacks for IO value changes:
|
|||||||
* ``revpimodio2.FALLING`` - True to False transition
|
* ``revpimodio2.FALLING`` - True to False transition
|
||||||
* ``revpimodio2.BOTH`` - Any change (default)
|
* ``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
|
Multiple Events
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@@ -212,12 +189,15 @@ Debouncing with Edge Detection
|
|||||||
Timer Events
|
Timer Events
|
||||||
============
|
============
|
||||||
|
|
||||||
Execute callbacks at regular intervals independent of IO changes:
|
The timer is started when the IO value changes and executes the passed
|
||||||
|
function - even if the IO value has changed in the meantime. If the
|
||||||
|
timer has not expired and the condition is met again, the timer is NOT
|
||||||
|
reset to the delay value or started a second time.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def periodic_task(ioname, iovalue):
|
def periodic_task(ioname, iovalue):
|
||||||
"""Called every 500ms."""
|
"""Called after 500ms."""
|
||||||
print(f"Periodic task: {iovalue}")
|
print(f"Periodic task: {iovalue}")
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
||||||
@@ -255,8 +235,8 @@ Timer Event Parameters
|
|||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
|
|
||||||
* ``interval`` - Milliseconds between calls
|
* ``interval`` - Delay in milliseconds.
|
||||||
* ``prefire`` - If True, trigger immediately on registration
|
* ``prefire`` - If True, trigger immediately after starting the mainloop.
|
||||||
|
|
||||||
Threaded Events
|
Threaded Events
|
||||||
===============
|
===============
|
||||||
@@ -425,29 +405,6 @@ Sensor Logging
|
|||||||
rpi.handlesignalend()
|
rpi.handlesignalend()
|
||||||
rpi.mainloop()
|
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
|
Threaded Data Processing
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
@@ -515,19 +472,6 @@ For slow operations, use threaded events:
|
|||||||
|
|
||||||
rpi.io.trigger.reg_event(slow_task, as_thread=True)
|
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
|
Handle Errors Gracefully
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
@@ -543,18 +487,6 @@ Protect callbacks from exceptions:
|
|||||||
print(f"Error in callback: {e}")
|
print(f"Error in callback: {e}")
|
||||||
rpi.io.output.value = False # Safe state
|
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
|
Clean Up Threads
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ Quick Example
|
|||||||
**Cyclic Programming**::
|
**Cyclic Programming**::
|
||||||
|
|
||||||
import revpimodio2
|
import revpimodio2
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
|
||||||
|
|
||||||
def main(ct):
|
def main(ct):
|
||||||
if ct.io.button.value:
|
if ct.io.button.value:
|
||||||
ct.io.led.value = True
|
ct.io.led.value = True
|
||||||
|
|
||||||
rpi.cycleloop(main)
|
revpimodio2.run_plc(main)
|
||||||
|
|
||||||
**Event-Driven Programming**::
|
**Event-Driven Programming**::
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Installation
|
|||||||
System Requirements
|
System Requirements
|
||||||
===================
|
===================
|
||||||
|
|
||||||
* Python 3.7 or higher
|
* Python 3.2 or higher
|
||||||
* Revolution Pi hardware (Core, Core3, Connect, Compact, Flat)
|
* Revolution Pi hardware (Core, Core3, Connect, Compact, Flat)
|
||||||
* piCtory configuration tool
|
* piCtory configuration tool
|
||||||
|
|
||||||
@@ -24,6 +24,9 @@ Log out and log back in for the group change to take effect.
|
|||||||
Installing RevPiModIO
|
Installing RevPiModIO
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
RevPiModIO is preinstalled on your Revolution Pi. It is distributed as debian package and will be
|
||||||
|
updated by `apt`.
|
||||||
|
|
||||||
Using pip
|
Using pip
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ For continuous operation, use a cyclic loop:
|
|||||||
|
|
||||||
import revpimodio2
|
import revpimodio2
|
||||||
|
|
||||||
rpi = revpimodio2.RevPiModIO(autorefresh=True)
|
|
||||||
|
|
||||||
def main_cycle(ct: revpimodio2.Cycletools):
|
def main_cycle(ct: revpimodio2.Cycletools):
|
||||||
"""Called every cycle (default: 20-50ms)."""
|
"""Called every cycle (default: 20-50ms)."""
|
||||||
|
|
||||||
@@ -91,7 +89,7 @@ For continuous operation, use a cyclic loop:
|
|||||||
print("Program stopped")
|
print("Program stopped")
|
||||||
|
|
||||||
# Run cyclic loop
|
# Run cyclic loop
|
||||||
rpi.cycleloop(main_cycle)
|
revpimodio2.run_plc(main_cycle)
|
||||||
|
|
||||||
Event-Driven Program
|
Event-Driven Program
|
||||||
--------------------
|
--------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user