From 3294c5e980efb0445c66d55627b918d3b94c043f Mon Sep 17 00:00:00 2001 From: Sven Sager Date: Tue, 17 Feb 2026 12:05:58 +0100 Subject: [PATCH] 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 --- docs/advanced.rst | 186 +++--------------------------------- docs/basics.rst | 9 +- docs/cyclic_programming.rst | 96 ++++++++----------- docs/event_programming.rst | 82 ++-------------- docs/index.rst | 3 +- docs/installation.rst | 5 +- docs/quickstart.rst | 4 +- 7 files changed, 71 insertions(+), 314 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 89695fe..f2b9b15 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -36,7 +36,6 @@ Gateway modules provide generic IOs (like ``Input_1``, ``Output_1``, etc.) that rpi.io.Input_1.replace_io( "temperature", # New IO name "h", # struct format: signed short - defaultvalue=0 # Default value ) # Use the custom IO by its new name @@ -70,6 +69,7 @@ Common format codes for ``replace_io`` (see `Python struct format characters 5: - ct.io.actuator.value = True - elif error < -5: - ct.io.actuator.value = False - - def on_setpoint_change(ioname, iovalue): - """Event handler for user setpoint changes.""" - print(f"New setpoint: {iovalue}") - # Access ct.var from event requires thread-safe approach - # In practice, use shared data structure or message queue - - def on_start(ioname, iovalue): - print("System started") - - def on_stop(ioname, iovalue): - print("System stopped") - - # Register user events - rpi.io.start_button.reg_event(on_start, edge=revpimodio2.RISING) - rpi.io.stop_button.reg_event(on_stop, edge=revpimodio2.RISING) - rpi.io.setpoint_input.reg_event(on_setpoint_change, delay=100) - - # Run cyclic loop in background - threading.Thread( - target=lambda: rpi.cycleloop(cyclic_control), - daemon=True - ).start() - - # Run event loop in main thread - rpi.handlesignalend() - rpi.mainloop() - -Event Triggers with Cyclic Processing --------------------------------------- - -Use events to trigger actions, cyclic for processing: - -.. code-block:: python - - import revpimodio2 - - rpi = revpimodio2.RevPiModIO(autorefresh=True) - - def cyclic_processor(ct): - """Process work queue.""" - if ct.first: - ct.var.work_queue = [] - - # Process queued work - if ct.var.work_queue: - item = ct.var.work_queue.pop(0) - process_item(item) - - def on_new_item(ioname, iovalue): - """Queue work from events.""" - # Note: Accessing ct.var from events requires synchronization - # This is a simplified example - print(f"New item queued from {ioname}") - - rpi.io.trigger1.reg_event(on_new_item, edge=revpimodio2.RISING) - rpi.io.trigger2.reg_event(on_new_item, edge=revpimodio2.RISING) - - rpi.cycleloop(cyclic_processor) - Performance Optimization ======================== @@ -487,23 +357,19 @@ Track and handle I/O errors: .. code-block:: python - rpi = revpimodio2.RevPiModIO(autorefresh=True) - rpi.maxioerrors = 10 # Exception after 10 errors + maxioerrors = 10 # Exception after 10 errors def main_cycle(ct): # Check error count periodically if ct.flank20c: - if rpi.ioerrors > 5: - print(f"Warning: {rpi.ioerrors} I/O errors detected") + if rpi.core.ioerrorcount > maxioerrors: + print(f"Warning: {rpi.core.ioerrorcount} I/O errors detected") ct.io.warning_led.value = True # Normal logic ct.io.output.value = ct.io.input.value - try: - rpi.cycleloop(main_cycle) - except RuntimeError as e: - print(f"I/O error threshold exceeded: {e}") + revpimodio2.run_plc(main_cycle) Best Practices ============== @@ -600,35 +466,6 @@ Document complex logic: # State machine implementation # ... -Testing -------- - -Test your code thoroughly: - -.. code-block:: python - - def test_temperature_control(ct): - """Test temperature control logic.""" - - if ct.first: - ct.var.cooling_active = False - ct.var.test_temp = 20.0 - - # Simulate temperature increase - if ct.var.test_temp < 80: - ct.var.test_temp += 0.5 - - # Test control logic - temp = ct.var.test_temp - - if temp > 75 and not ct.var.cooling_active: - assert ct.io.cooling.value == True - ct.var.cooling_active = True - - if temp < 65 and ct.var.cooling_active: - assert ct.io.cooling.value == False - ct.var.cooling_active = False - Logging ------- @@ -676,6 +513,7 @@ Always validate external inputs: """Validate setpoint range.""" if 0 <= iovalue <= 100: rpi.io.setpoint.value = iovalue + rpi.io.error_led.value = False else: print(f"Invalid setpoint: {iovalue}") rpi.io.error_led.value = True diff --git a/docs/basics.rst b/docs/basics.rst index 54a71ef..7181855 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -78,10 +78,11 @@ Adjust cycle time to match your needs: .. code-block:: python - rpi = revpimodio2.RevPiModIO(autorefresh=True) + rpi = revpimodio2.RevPiModIO() rpi.cycletime = 100 # Set to 100ms + rpi.autorefresh_all() -**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements. +**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements. Default values will fit most needs. Error Handling -------------- @@ -90,10 +91,10 @@ Configure I/O error threshold: .. code-block:: python - rpi.maxioerrors = 10 # Raise exception after 10 errors + maxioerrors = 10 # Raise exception after 10 errors # Check error count - if rpi.ioerrors > 5: + if rpi.core.ioerrorcount > maxioerrors: print("Warning: I/O errors detected") Core Objects diff --git a/docs/cyclic_programming.rst b/docs/cyclic_programming.rst index eeeeb6e..c37164e 100644 --- a/docs/cyclic_programming.rst +++ b/docs/cyclic_programming.rst @@ -42,8 +42,6 @@ Simple Cycle Loop import revpimodio2 - rpi = revpimodio2.RevPiModIO(autorefresh=True) - def main_cycle(ct: revpimodio2.Cycletools): """Execute each cycle.""" if ct.io.start_button.value: @@ -51,10 +49,17 @@ Simple Cycle Loop if ct.io.stop_button.value: ct.io.motor.value = False - rpi.cycleloop(main_cycle) + revpimodio2.run_plc(main_cycle) + + # .run_plc is a shortcut for: + # rpi = revpimodio2.RevPiModIO(autorefresh=True) + # rpi.handlesignalend() + # rpi.cycleloop(main_cycle) The ``main_cycle`` function is called repeatedly at the configured cycle time (typically 20-50ms). +**Info:** ``rpi.handlesignalend()`` + Understanding Cycle Time ------------------------- @@ -68,10 +73,9 @@ Adjust cycle time to match your needs: .. code-block:: python - rpi = revpimodio2.RevPiModIO(autorefresh=True) - rpi.cycletime = 100 # 100ms = 10 Hz + revpimodio2.run_plc(main_cycle, cycletime=100) # 100ms = 10 Hz -**Important:** Faster cycle times consume more CPU. Choose the slowest cycle time that meets your requirements. +**Important:** Faster cycle times consume more CPU but will detect fast changes of input values. Cycletools Object ================= @@ -109,8 +113,7 @@ Use ``ct.first`` and ``ct.last`` for setup and teardown: ct.io.motor.value = False print(f"Total cycles: {ct.var.counter}") - rpi = revpimodio2.RevPiModIO(autorefresh=True) - rpi.cycleloop(main_cycle) + revpimodio2.run_plc(main_cycle) Persistent Variables ==================== @@ -155,6 +158,7 @@ Detect input changes efficiently without storing previous values: print("Button released!") rpi = revpimodio2.RevPiModIO(autorefresh=True) + rpi.handlesignalend() rpi.cycleloop(main_cycle) Edge types: @@ -176,17 +180,16 @@ Toggle flags alternate between True/False at regular intervals: .. code-block:: python def main_cycle(ct): - # Blink LED - flag5c alternates every 5 cycles - ct.io.blink_led.value = ct.flag5c + # Blink LED - flag5c alternates every cycle + ct.io.blink_led.value = ct.flag1c # Different blink rates - ct.io.fast_blink.value = ct.flag2c # Every 2 cycles + ct.io.fast_blink.value = ct.flag5c # Every 5 cycles ct.io.slow_blink.value = ct.flag20c # Every 20 cycles **Available toggle flags:** * ``ct.flag1c`` - Every cycle -* ``ct.flag2c`` - Every 2 cycles * ``ct.flag5c`` - Every 5 cycles * ``ct.flag10c`` - Every 10 cycles * ``ct.flag20c`` - Every 20 cycles @@ -218,12 +221,12 @@ Flank flags are True for exactly one cycle at regular intervals: Timers ====== -RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts. +RevPiModIO provides three timer types based on PLC standards. All timers are specified in cycle counts or milliseconds. On-Delay Timer (TON/TONC) -------------------------- -Output becomes True only after input is continuously True for specified cycles: +Output becomes True only after input is continuously True for specified cycles (use ton with milliseconds value instead of cycles): .. code-block:: python @@ -253,7 +256,7 @@ Output becomes True only after input is continuously True for specified cycles: Off-Delay Timer (TOF/TOFC) --------------------------- -Output stays True for specified cycles after input goes False: +Output stays True for specified cycles or milliseconds after input goes False (use tof with milliseconds value instead of cycles): .. code-block:: python @@ -280,7 +283,7 @@ Output stays True for specified cycles after input goes False: Pulse Timer (TP/TPC) -------------------- -Generates a one-shot pulse of specified duration: +Generates a one-shot pulse of specified duration (use tp with milliseconds value instead of cycles): .. code-block:: python @@ -305,25 +308,6 @@ Generates a one-shot pulse of specified duration: * Acknowledgment pulses * Retriggerable delays -Converting Time to Cycles --------------------------- - -Calculate cycles from desired time: - -.. code-block:: python - - # At 20ms cycle time: - # 1 second = 50 cycles - # 100ms = 5 cycles - # 2 seconds = 100 cycles - - def main_cycle(ct): - cycle_time_ms = rpi.cycletime - desired_time_ms = 1500 # 1.5 seconds - - cycles_needed = int(desired_time_ms / cycle_time_ms) - ct.set_tonc("my_delay", cycles_needed) - State Machines ============== @@ -345,28 +329,29 @@ Simple State Machine ct.io.yellow_led.value = False ct.io.red_led.value = False - # After 100 cycles (2s @ 20ms), go to yellow - ct.set_tonc("green_time", 100) - if ct.get_tonc("green_time"): + # After 2 seconds, go to yellow + ct.set_ton("green_time", 2000) + if ct.get_ton("green_time"): ct.var.state = "YELLOW" elif ct.var.state == "YELLOW": ct.io.green_led.value = False ct.io.yellow_led.value = True - ct.set_tonc("yellow_time", 25) # 500ms - if ct.get_tonc("yellow_time"): + ct.set_ton("yellow_time", 500) + if ct.get_ton("yellow_time"): ct.var.state = "RED" elif ct.var.state == "RED": ct.io.yellow_led.value = False ct.io.red_led.value = True - ct.set_tonc("red_time", 150) # 3s - if ct.get_tonc("red_time"): + ct.set_ton("red_time", 3000) + if ct.get_ton("red_time"): ct.var.state = "GREEN" rpi = revpimodio2.RevPiModIO(autorefresh=True) + rpi.handlesignalend() rpi.cycleloop(traffic_light) Complex State Machine @@ -396,8 +381,8 @@ Complex State Machine ct.io.yellow_led.value = True # 2-second startup delay - ct.set_tonc("startup", 100) - if ct.get_tonc("startup"): + ct.set_ton("startup", 2000) + if ct.get_ton("startup"): ct.var.state = "RUNNING" print("Running") @@ -422,8 +407,8 @@ Complex State Machine # State: STOPPING - Controlled shutdown elif ct.var.state == "STOPPING": # Coast motor for 1 second - ct.set_tofc("coast", 50) - ct.io.motor.value = ct.get_tofc("coast") + ct.set_tof("coast", 1000) + ct.io.motor.value = ct.get_tof("coast") if not ct.io.motor.value: ct.var.state = "IDLE" @@ -432,7 +417,7 @@ Complex State Machine # State: ERROR - Fault condition elif ct.var.state == "ERROR": ct.io.motor.value = False - ct.io.red_led.value = ct.flag2c # Blink red + ct.io.red_led.value = ct.flag5c # Blink red if ct.changed(ct.io.ack_button, edge=revpimodio2.RISING): if not ct.io.error_sensor.value: @@ -442,8 +427,7 @@ Complex State Machine if ct.last: print(f"Total production: {ct.var.production_count}") - rpi = revpimodio2.RevPiModIO(autorefresh=True) - rpi.cycleloop(machine_controller) + revpimodio.run_plc(machine_controller) Practical Examples ================== @@ -476,14 +460,17 @@ Temperature monitoring with hysteresis control: # Warning if too hot if temp > 85: - ct.io.warning_led.value = ct.flag2c # Blink + ct.core.a1green.value = False + ct.core.a1red.value = ct.flag5c # Blink + else: + ct.core.a1green.value = ct.flag5c # Blink + ct.core.a1red.value = False # Emergency shutdown if temp > 95: ct.io.emergency_shutdown.value = True - rpi = revpimodio2.RevPiModIO(autorefresh=True) - rpi.cycleloop(temperature_monitor) + revpimodio2.run_plc(temperature_monitor) Production Counter ------------------ @@ -520,8 +507,7 @@ Count production items with start/stop control: print(f"Final count: {ct.var.total_count}") ct.var.total_count = 0 - rpi = revpimodio2.RevPiModIO(autorefresh=True) - rpi.cycleloop(production_counter) + revpimodio2.run_plc(production_counter) Best Practices ============== @@ -544,7 +530,7 @@ Minimize processing time in each cycle: **Guidelines:** * Avoid blocking operations (network, file I/O) -* Use flank flags for expensive operations +* Use flank flags for expensive operations or even Threads * Keep cycle time ≥20ms for stability Use Appropriate Cycle Time diff --git a/docs/event_programming.rst b/docs/event_programming.rst index 0b21dad..0042f78 100644 --- a/docs/event_programming.rst +++ b/docs/event_programming.rst @@ -94,29 +94,6 @@ Register callbacks for IO value changes: * ``revpimodio2.FALLING`` - True to False transition * ``revpimodio2.BOTH`` - Any change (default) -Lambda Functions ----------------- - -Use lambda for simple callbacks: - -.. code-block:: python - - rpi = revpimodio2.RevPiModIO(autorefresh=True) - - # Simple lambda callback - rpi.io.button.reg_event( - lambda name, val: print(f"Button: {val}") - ) - - # Lambda with edge filter - rpi.io.start_button.reg_event( - lambda name, val: print("Started!"), - edge=revpimodio2.RISING - ) - - rpi.handlesignalend() - rpi.mainloop() - Multiple Events --------------- @@ -212,12 +189,15 @@ Debouncing with Edge Detection Timer Events ============ -Execute callbacks at regular intervals independent of IO changes: +The timer is started when the IO value changes and executes the passed +function - even if the IO value has changed in the meantime. If the +timer has not expired and the condition is met again, the timer is NOT +reset to the delay value or started a second time. .. code-block:: python def periodic_task(ioname, iovalue): - """Called every 500ms.""" + """Called after 500ms.""" print(f"Periodic task: {iovalue}") rpi = revpimodio2.RevPiModIO(autorefresh=True) @@ -255,8 +235,8 @@ Timer Event Parameters **Parameters:** -* ``interval`` - Milliseconds between calls -* ``prefire`` - If True, trigger immediately on registration +* ``interval`` - Delay in milliseconds. +* ``prefire`` - If True, trigger immediately after starting the mainloop. Threaded Events =============== @@ -425,29 +405,6 @@ Sensor Logging rpi.handlesignalend() rpi.mainloop() -Periodic Status Report ----------------------- - -.. code-block:: python - - import revpimodio2 - - rpi = revpimodio2.RevPiModIO(autorefresh=True) - - def status_report(ioname, iovalue): - """Print system status every 10 seconds.""" - print("=== Status Report ===") - print(f"Temperature: {rpi.core.temperature.value}°C") - print(f"CPU Frequency: {rpi.core.frequency.value} MHz") - print(f"IO Errors: {rpi.core.ioerrorcount.value}") - print() - - # Status report every 10 seconds - rpi.io.dummy.reg_timerevent(status_report, 10000, prefire=True) - - rpi.handlesignalend() - rpi.mainloop() - Threaded Data Processing ------------------------- @@ -515,19 +472,6 @@ For slow operations, use threaded events: rpi.io.trigger.reg_event(slow_task, as_thread=True) -Use Debouncing --------------- - -Always debounce mechanical inputs: - -.. code-block:: python - - # Good - Debounced button - rpi.io.button.reg_event(callback, delay=30) - - # Poor - No debounce (may trigger multiple times) - rpi.io.button.reg_event(callback) - Handle Errors Gracefully ------------------------- @@ -543,18 +487,6 @@ Protect callbacks from exceptions: print(f"Error in callback: {e}") rpi.io.output.value = False # Safe state -Check IO Existence ------------------- - -Verify IOs exist before registering events: - -.. code-block:: python - - if "optional_button" in rpi.io: - rpi.io.optional_button.reg_event(callback) - else: - print("Optional button not configured") - Clean Up Threads ---------------- diff --git a/docs/index.rst b/docs/index.rst index ce031ef..cdb3dc6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,13 +24,12 @@ Quick Example **Cyclic Programming**:: import revpimodio2 - rpi = revpimodio2.RevPiModIO(autorefresh=True) def main(ct): if ct.io.button.value: ct.io.led.value = True - rpi.cycleloop(main) + revpimodio2.run_plc(main) **Event-Driven Programming**:: diff --git a/docs/installation.rst b/docs/installation.rst index 856d032..b46cab1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -5,7 +5,7 @@ Installation System Requirements =================== -* Python 3.7 or higher +* Python 3.2 or higher * Revolution Pi hardware (Core, Core3, Connect, Compact, Flat) * piCtory configuration tool @@ -24,6 +24,9 @@ Log out and log back in for the group change to take effect. Installing RevPiModIO ===================== +RevPiModIO is preinstalled on your Revolution Pi. It is distributed as debian package and will be +updated by `apt`. + Using pip --------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 6a8520d..a996169 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -65,8 +65,6 @@ For continuous operation, use a cyclic loop: import revpimodio2 - rpi = revpimodio2.RevPiModIO(autorefresh=True) - def main_cycle(ct: revpimodio2.Cycletools): """Called every cycle (default: 20-50ms).""" @@ -91,7 +89,7 @@ For continuous operation, use a cyclic loop: print("Program stopped") # Run cyclic loop - rpi.cycleloop(main_cycle) + revpimodio2.run_plc(main_cycle) Event-Driven Program --------------------