diff --git a/docs/advanced.rst b/docs/advanced.rst new file mode 100644 index 0000000..89695fe --- /dev/null +++ b/docs/advanced.rst @@ -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 `_ 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 `_ 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 diff --git a/docs/api/device.rst b/docs/api/device.rst new file mode 100644 index 0000000..8d6e846 --- /dev/null +++ b/docs/api/device.rst @@ -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. diff --git a/docs/api/helper.rst b/docs/api/helper.rst new file mode 100644 index 0000000..51bf332 --- /dev/null +++ b/docs/api/helper.rst @@ -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. diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..306999b --- /dev/null +++ b/docs/api/index.rst @@ -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 diff --git a/docs/api/io.rst b/docs/api/io.rst new file mode 100644 index 0000000..721bf77 --- /dev/null +++ b/docs/api/io.rst @@ -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. diff --git a/docs/api/revpimodio.rst b/docs/api/revpimodio.rst new file mode 100644 index 0000000..d1902e1 --- /dev/null +++ b/docs/api/revpimodio.rst @@ -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. diff --git a/docs/basics.rst b/docs/basics.rst new file mode 100644 index 0000000..54a71ef --- /dev/null +++ b/docs/basics.rst @@ -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 diff --git a/docs/conf.py b/docs/conf.py index 2a556bd..513a578 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,19 +15,73 @@ project = 'revpimodio2' copyright = '2023, Sven Sager' author = 'Sven Sager' version = __version__ +release = __version__ # -- General configuration --------------------------------------------------- extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx', + 'sphinx.ext.autosummary', 'sphinx.ext.todo', - 'sphinx.ext.viewcode' ] +# Autodoc configuration +autodoc_default_options = { + 'members': True, + 'member-order': 'bysource', + 'special-members': '__init__', + 'undoc-members': True, + 'exclude-members': '__weakref__' +} + +# Napoleon settings (for NumPy and Google style docstrings) +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = True +napoleon_use_admonition_for_notes = True +napoleon_use_admonition_for_references = True +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_preprocess_types = True +napoleon_type_aliases = None +napoleon_attr_annotations = True + +# Autosummary settings +autosummary_generate = True + +# Intersphinx configuration +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} + +# Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] + +# Theme options +html_theme_options = { + 'navigation_depth': 4, + 'collapse_navigation': False, + 'sticky_navigation': True, + 'includehidden': True, + 'titles_only': False +} + +# -- Options for todo extension ---------------------------------------------- + +todo_include_todos = True diff --git a/docs/cyclic_programming.rst b/docs/cyclic_programming.rst new file mode 100644 index 0000000..eeeeb6e --- /dev/null +++ b/docs/cyclic_programming.rst @@ -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 diff --git a/docs/event_programming.rst b/docs/event_programming.rst new file mode 100644 index 0000000..0b21dad --- /dev/null +++ b/docs/event_programming.rst @@ -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 diff --git a/docs/index.rst b/docs/index.rst index 8fc05d2..ce031ef 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,18 +1,87 @@ -.. revpimodio2 documentation main file, created by - sphinx-quickstart on Sun Jan 22 17:49:41 2023. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +==================================== +Welcome to RevPiModIO Documentation! +==================================== -Welcome to revpimodio2's documentation! -======================================= +RevPiModIO is a Python3 module for programming Revolution Pi hardware from KUNBUS GmbH. +It provides easy access to all devices and IOs from the piCtory configuration, supporting +both cyclic (PLC-style) and event-driven programming paradigms. + +.. note:: + **New to RevPiModIO?** Start with :doc:`installation` and :doc:`quickstart`. + +Key Features +============ + +* **Dual Programming Models**: Cyclic (PLC-style) and event-driven approaches +* **Direct Hardware Access**: Simple Python interface to all I/O devices +* **Automatic Configuration**: Loads piCtory device configuration +* **Event System**: Callbacks for value changes and timer events +* **Open Source**: LGPLv2 license, no licensing fees + +Quick Example +============= + +**Cyclic Programming**:: + + import revpimodio2 + rpi = revpimodio2.RevPiModIO(autorefresh=True) + + def main(ct): + if ct.io.button.value: + ct.io.led.value = True + + rpi.cycleloop(main) + +**Event-Driven Programming**:: + + import revpimodio2 + rpi = revpimodio2.RevPiModIO(autorefresh=True) + + def on_change(ioname, iovalue): + print(f"{ioname} = {iovalue}") + + rpi.io.button.reg_event(on_change) + rpi.handlesignalend() + rpi.mainloop() + +Documentation +============= .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: Getting Started + installation + quickstart +.. toctree:: + :maxdepth: 2 + :caption: User Guide -Indices and tables + basics + cyclic_programming + event_programming + advanced + +.. toctree:: + :maxdepth: 3 + :caption: API Reference + + api/index + api/revpimodio + api/io + api/helper + api/device + +External Resources +================== + +* **Official Website**: `revpimodio.org `_ +* **GitHub Repository**: `github.com/naruxde/ `_ +* **Discussion Forum**: `revpimodio.org/diskussionsforum/ `_ +* **Revolution Pi Hardware**: `revolution.kunbus.com `_ + +Indices and Tables ================== * :ref:`genindex` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..856d032 --- /dev/null +++ b/docs/installation.rst @@ -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 `_ +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. diff --git a/docs/modules.rst b/docs/modules.rst deleted file mode 100644 index 76962cf..0000000 --- a/docs/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -src -=== - -.. toctree:: - :maxdepth: 4 - - revpimodio2 diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..6a8520d --- /dev/null +++ b/docs/quickstart.rst @@ -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 diff --git a/docs/revpimodio2.rst b/docs/revpimodio2.rst deleted file mode 100644 index 6980e58..0000000 --- a/docs/revpimodio2.rst +++ /dev/null @@ -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: