Raspberry Pi5 and Pi-Plates RELAYplate

I’m working on a new controller for my mushroom tent, and went with a raspberry pi5 and a Pi-Plates RELAYplate to control the fan, humidifier, lights, pumps, etc. Little did I know how much of a headache the changes to how the pi5 handles the GPIO would be. After struggling for a bit trying to figure out how to get a custom mutli-channel output working, I turned to claude.ai, and after trying several methods finally found one that worked. I’ve attached the documentation and code, in case someone else runs into something similar. The issue was the system-site-packages are not included in the stock Mycodo environment, so another one is created and used to control the RELAYplate.

Caveats

  • This was just for fun. Using the existing outputs for relays still works great, and I had been using that.
  • I’m not super fluent in python, and the code was generated. Use at your own risk
  • It’s been working for a whole 3 hours at the time of this post.

Also, the footprint of the RELAYplate matches the pi-plates CM4 carrier, not a standard pi. It fits, but does hang over the sides a bit. I recommend some kapton or electrical tape over the USB shell of the pi if you’re mounting it like a hat.

Generated Documentation:

Hardware Prerequisites

Before setting up the software integration, ensure:

  1. Hardware Installation:
  • Pi-Plates RELAYplate is properly connected to the Raspberry Pi GPIO header
  • Power is connected to the Pi-Plates board if required
  • Jumpers are set correctly for the desired board address (default is 0)
  1. System Configuration:

bash

# Enable SPI interface (required for Pi-Plates)
sudo raspi-config
# Navigate to: Interface Options > SPI > Enable > Yes > Finish

# Alternatively, enable SPI from command line
sudo raspi-config nonint do_spi 0

# Reboot to apply changes
sudo reboot

# Verify SPI is enabled
ls -la /dev/spidev*
# Should show devices like /dev/spidev0.0 and /dev/spidev0.1

# Install required system packages
sudo apt-get update
sudo apt-get install -y python3-venv python3-dev python3-pip
sudo apt-get install -y python3-gpiod gpiod
  1. User Permissions:

bash

# Add user to required groups
sudo usermod -a -G gpio,spi root

# Set permissions for GPIO and SPI devices
sudo chmod 660 /dev/gpiochip*
sudo chmod 660 /dev/spidev*

# Create udev rules for persistent permissions (optional)
echo 'SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="gpio", MODE="0660"' | sudo tee /etc/udev/rules.d/99-gpio.rules
echo 'SUBSYSTEM=="spidev", KERNEL=="spidev*", GROUP="spi", MODE="0660"' | sudo tee /etc/udev/rules.d/99-spi.rules

# Reload udev rules
sudo udevadm control --reload-rules
sudo udevadm trigger
```# Pi-Plates Integration with Mycodo

This document provides comprehensive documentation for the integration of Pi-Plates RELAYplate hardware with the Mycodo environmental monitoring and control system.

Overview

The integration allows Mycodo to control Pi-Plates RELAYplate boards through a bridge script that leverages an existing Pi-Plates virtual environment where the library is properly configured and working.

File Structure

/opt/
├── Mycodo/                         # Main Mycodo installation
│   ├── env/                        # Mycodo virtual environment
│   │   └── ...
│   └── mycodo/
│       ├── user_python_code/       # Contains Mycodo Python Code outputs
│       │   └── output_*.py         # Python code for output modules
│       └── ...
└── python_scripts_env/             # Virtual environment for Pi-Plates
    ├── bin/
    │   └── python                  # Python interpreter
    ├── lib/
    │   └── python3.11/
    │       └── site-packages/
    │           └── piplates/       # Pi-Plates library
    ├── relay_bridge.py             # Bridge script for Pi-Plates control
    └── relay_bridge.py.bak         # Backup of the bridge script (optional)

Virtual Environments

Two virtual environments are involved in this integration:

  1. Mycodo Virtual Environment
  • Path: /opt/Mycodo/env/
  • Python Version: 3.11.2
  • Purpose: Runs the Mycodo application but cannot directly access Pi-Plates hardware due to GPIO access issues
  1. Pi-Plates Virtual Environment
  • Path: /opt/python_scripts_env/
  • Python Version: 3.11.2
  • Configuration: Created with --system-site-packages flag
  • Purpose: Contains the working Pi-Plates library with proper hardware access and the bridge script
  • Note: This environment is critical for the integration as it has working GPIO and SPI access

Scripts

Pi-Plates Bridge Script

Location: /opt/python_scripts_env/relay_bridge.py

Purpose: Acts as a bridge between Mycodo and the Pi-Plates hardware by executing commands through the Pi-Plates virtual environment where the library works correctly.

Implementation Details:

  • Located within the Pi-Plates virtual environment directory
  • Uses the virtual environment’s Python interpreter to access the Pi-Plates library
  • Provides a consistent JSON-based API for Mycodo to interact with
  • Handles errors and returns standardized responses

Script Content:

python

#!/usr/bin/env python3
"""
Bridge script to control pi-plates RELAY boards.
This script runs directly in the Pi-Plates virtual environment.
"""

import sys
import json
import piplates.RELAYplate as RELAY

def handle_command(address, command, *args):
    """Handle relay commands"""
    try:
        # Force initialize the relaysPresent array
        RELAY.relaysPresent = [0] * 8
        RELAY.relaysPresent[address] = 1
        
        if command == "on" and len(args) > 0:
            relay_num = int(args[0])
            RELAY.relayON(address, relay_num)
            return {"status": "success", "message": f"Relay {relay_num} turned ON"}
            
        elif command == "off" and len(args) > 0:
            relay_num = int(args[0])
            RELAY.relayOFF(address, relay_num)
            return {"status": "success", "message": f"Relay {relay_num} turned OFF"}
            
        elif command == "state":
            state = RELAY.relaySTATE(address)
            return {"status": "success", "state": state}
            
        else:
            return {"status": "error", "message": f"Unknown command: {command}"}
            
    except Exception as e:
        return {"status": "error", "message": str(e)}

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(json.dumps({"status": "error", "message": "Usage: relay_bridge.py [address] [command] [args...]"}))
        sys.exit(1)
    
    try:
        address = int(sys.argv[1])
        command = sys.argv[2]
        args = sys.argv[3:]
        
        result = handle_command(address, command, *args)
        print(json.dumps(result))
    except Exception as e:
        print(json.dumps({"status": "error", "message": str(e)}))

Usage:

bash

# Turn on relay 3 at address 0
/opt/python_scripts_env/relay_bridge.py 0 on 3

# Turn off relay 3 at address 0
/opt/python_scripts_env/relay_bridge.py 0 off 3

# Get state of all relays at address 0
/opt/python_scripts_env/relay_bridge.py 0 state

Mycodo Python Code Output

Location: In Mycodo’s database, accessible via the web interface under Setup → Output → On/Off: Python Code

Purpose: Provides the interface between Mycodo’s output system and the bridge script

On Code:

python

# Import modules at the top level
import subprocess
import json

# Run the relay_bridge.py script to turn on relay 3 at address 0
try:
    result = subprocess.run(
        ['/opt/python_scripts_env/relay_bridge.py', '0', 'on', '3'],
        capture_output=True,
        text=True,
        check=False
    )
    
    if result.returncode != 0:
        self.logger.error(f"Command failed: {result.stderr}")
        return False
    
    response = json.loads(result.stdout)
    if response['status'] == 'success':
        return True
    
    self.logger.error(f"Error turning relay on: {response.get('message', 'Unknown error')}")
    return False
except Exception as e:
    self.logger.error(f"Exception turning relay on: {str(e)}")
    return False

Off Code:

python

# Import modules at the top level
import subprocess
import json

# Run the relay_bridge.py script to turn off relay 3 at address 0
try:
    result = subprocess.run(
        ['/opt/python_scripts_env/relay_bridge.py', '0', 'off', '3'],
        capture_output=True,
        text=True,
        check=False
    )
    
    if result.returncode != 0:
        self.logger.error(f"Command failed: {result.stderr}")
        return False
    
    response = json.loads(result.stdout)
    if response['status'] == 'success':
        return True
    
    self.logger.error(f"Error turning relay off: {response.get('message', 'Unknown error')}")
    return False
except Exception as e:
    self.logger.error(f"Exception turning relay off: {str(e)}")
    return False

Hardware Configuration

  • Hardware: Pi-Plates RELAYplate
  • Address: 0 (configurable via jumpers on the board)
  • Relays: 1-7 available, relay 3 used in example
  • SPI Interface: Pi-Plates uses SPI for communication
  • GPIO Access: Managed through the Pi-Plates virtual environment

Setup Process

  1. Create and Configure the Virtual Environment for Pi-Plates:

bash

# Create the directory for the virtual environment
sudo mkdir -p /opt/python_scripts_env

# Create the virtual environment with system packages
sudo python3 -m venv --system-site-packages /opt/python_scripts_env

# Set proper permissions
sudo chown -R root:root /opt/python_scripts_env
sudo chmod -R 755 /opt/python_scripts_env

# Activate the environment
source /opt/python_scripts_env/bin/activate

# Install pi-plates and all required dependencies in a single command
pip install pi-plates six spidev gpiod

# Verify installations
pip list | grep -E 'pi-plates|six|spidev|gpiod'

# Deactivate when done
deactivate
  1. Create the Bridge Script within the Virtual Environment:

bash

# Create the script file
sudo nano /opt/python_scripts_env/relay_bridge.py

# Paste the bridge script content
# (Copy and paste the script content from the documentation)

# Make the script executable
sudo chmod +x /opt/python_scripts_env/relay_bridge.py

# Test the script
/opt/python_scripts_env/relay_bridge.py 0 state
  1. Create Backup (optional):

bash

sudo cp /opt/python_scripts_env/relay_bridge.py /opt/python_scripts_env/relay_bridge.py.bak
  1. Configure Mycodo Outputs:
  • Access Mycodo web interface
  • Go to Setup → Output
  • Click Add Output
  • Select “On/Off: Python Code” from the dropdown
  • Configure with appropriate On/Off code as documented above
  • Save the output

Troubleshooting

Common Issues

  1. Permission Errors:
  • Ensure bridge script is executable: sudo chmod +x /opt/python_scripts_env/relay_bridge.py
  • Check permissions for virtual environment: ls -la /opt/python_scripts_env/bin/python
  1. Path Errors:
  • Ensure Mycodo can find the bridge script: Use absolute path /opt/python_scripts_env/relay_bridge.py in Python code
  1. SPI Issues:
  • Check if SPI is enabled: ls -la /dev/spidev*
  • Enable SPI if needed: sudo raspi-config → Interfacing Options → SPI → Enable
  1. Virtual Environment Issues:
  • Ensure pi-plates is installed in the environment: sudo /opt/python_scripts_env/bin/pip list | grep pi-plates
  • Try reinstalling if needed: sudo /opt/python_scripts_env/bin/pip install --force-reinstall pi-plates

Debugging

To debug issues, add print statements to the bridge script or use logging:

python

# Add to bridge script for debugging
import logging
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='/var/log/python_scripts.log',
    filemode='a'
)
logger = logging.getLogger('python_scripts')

Then check the log:

bash

sudo tail -f /var/log/python_scripts.log

Extending the Integration

Adding More Relays

To control additional relays, create new outputs in Mycodo with modified relay numbers:

python

# Example for relay 4 instead of relay 3
subprocess.run(['/opt/python_scripts_env/relay_bridge.py', '0', 'on', '4'], ...)

Adding ADC Functionality

To add analog input functionality, extend the bridge script:

  1. Add ADC handler to handle_command function:

python

elif command == "adc" and len(args) > 0:
    channel = int(args[0])
    value = RELAY.getADC(address, channel)
    return {"status": "success", "value": value}
  1. Create a Command Input in Mycodo to read the ADC value:
  • Go to Setup → Input
  • Add a new “Linux Command” input
  • Configure the command: /opt/python_scripts_env/relay_bridge.py 0 adc 0

Maintenance

Backup Strategy

Periodically back up the bridge script:

bash

sudo cp /opt/python_scripts_env/relay_bridge.py /opt/python_scripts_env/relay_bridge.py.bak

Updates

When updating Mycodo, verify the integration still works by:

  1. Testing the bridge script directly from command line
  2. Testing the output functionality in Mycodo web interface

Conclusion

This integration provides a robust solution for controlling Pi-Plates RELAYplate hardware from Mycodo by leveraging a dedicated virtual environment at /opt/python_scripts_env/. This environment is configured with system-site-packages access to ensure proper GPIO and SPI functionality required for communicating with the Pi-Plates hardware.

The bridge script approach ensures compatibility even if Mycodo’s virtual environment changes in the future, as it delegates all hardware communication to the dedicated Pi-Plates environment.

^Claude’s words, not mine.

1 Like

Hi Elliott,

Nice detailed writeup. I think I can help make a single output. Just a couple questions:

Is the product you’re referring to this? https://pi-plates.com/product/relayplate/

Is the library you’re referring to this? https://pypi.org/project/pi-plates/ Edit: Found it in your documentation

I think this could be simplified a lot so I quickly threw together an output to test. Import this output from the Config/Custom Outputs page and see if it works: on_off_piplates_relayplate.py (9.1 KB)

Enable debug logging in the Output Config and see if any errors appear in the daemon log while using it.

Wow, thanks for taking a look! I’d found a previous ADC one you had written back in 2019 for another pi-plate, and based my initial tests on it.

I’ve loaded the output, and with the initial file it just kept asking to install the dependencies over and over again even though they were already met. I removed the versioning for the Pi-Plates library in the script, and that seems to have fixed that issue. Once it was loaded, I get the same errors I was running into with my first few attempts:

2025-05-15 08:57:28,551 - INFO - mycodo - Mycodo daemon v8.16.1 starting
2025-05-15 08:57:28,556 - INFO - mycodo.pyro_daemon - Starting Pyro5 daemon
2025-05-15 08:57:28,801 - INFO - mycodo.outputs.on_off_python_c133592a - Initialized in 130.3 ms
2025-05-15 08:57:28,936 - INFO - mycodo.outputs.on_off_python_18589f2d - Initialized in 134.9 ms
2025-05-15 08:57:29,066 - INFO - mycodo.outputs.on_off_python_b6ce09c0 - Initialized in 128.8 ms
2025-05-15 08:57:29,199 - INFO - mycodo.outputs.on_off_python_f71a45f7 - Initialized in 132.3 ms
2025-05-15 08:57:29,328 - INFO - mycodo.outputs.on_off_python_a9457e11 - Initialized in 128.6 ms
2025-05-15 08:57:29,461 - INFO - mycodo.outputs.on_off_python_96de8219 - Initialized in 131.8 ms
2025-05-15 08:57:29,655 - INFO - mycodo.outputs.on_off_python_c9c4717a - Initialized in 193.7 ms
2025-05-15 08:57:29,719 - ERROR - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Error initializing, trying again in 5 seconds: Failed to initialize any GPIO chip. Check permissions and device availability.
Traceback (most recent call last):
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 36, in
raise FileNotFoundError(“Could not connect to any available GPIO chip”)
FileNotFoundError: Could not connect to any available GPIO chip

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 41, in
chip = gpiod.Chip(‘gpiochip0’)
^^^^^^^^^^^^^^^^^^^^^^^
File “/opt/Mycodo/env/lib/python3.11/site-packages/gpiod/chip.py”, line 64, in init
self._chip: Union[_ext.Chip, None] = _ext.Chip(path)
^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/opt/Mycodo/mycodo/abstract_base_controller.py”, line 57, in try_initialize
self.initialize()
File “/opt/Mycodo/mycodo/outputs/custom_outputs/output_on_off_piplates_relayplate_01.py”, line 196, in initialize
import piplates.RELAYplate as RELAY
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/RELAYplate.py”, line 20, in
import CMD5 as CMD
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 43, in
raise RuntimeError(“Failed to initialize any GPIO chip. Check permissions and device availability.”)
RuntimeError: Failed to initialize any GPIO chip. Check permissions and device availability.
2025-05-15 08:57:34,722 - ERROR - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Error initializing, trying again in 5 seconds: Failed to initialize any GPIO chip. Check permissions and device availability.
Traceback (most recent call last):
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 36, in
raise FileNotFoundError(“Could not connect to any available GPIO chip”)
FileNotFoundError: Could not connect to any available GPIO chip

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 41, in
chip = gpiod.Chip(‘gpiochip0’)
^^^^^^^^^^^^^^^^^^^^^^^
File “/opt/Mycodo/env/lib/python3.11/site-packages/gpiod/chip.py”, line 64, in init
self._chip: Union[_ext.Chip, None] = _ext.Chip(path)
^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/opt/Mycodo/mycodo/abstract_base_controller.py”, line 57, in try_initialize
self.initialize()
File “/opt/Mycodo/mycodo/outputs/custom_outputs/output_on_off_piplates_relayplate_01.py”, line 196, in initialize
import piplates.RELAYplate as RELAY
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/RELAYplate.py”, line 20, in
import CMD5 as CMD
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 43, in
raise RuntimeError(“Failed to initialize any GPIO chip. Check permissions and device availability.”)
RuntimeError: Failed to initialize any GPIO chip. Check permissions and device availability.
2025-05-15 08:57:39,724 - ERROR - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Initialization errored 3 times; giving up. Maybe the following traceback can help diagnose the issue.
Traceback (most recent call last):
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 36, in
raise FileNotFoundError(“Could not connect to any available GPIO chip”)
FileNotFoundError: Could not connect to any available GPIO chip

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 41, in
chip = gpiod.Chip(‘gpiochip0’)
^^^^^^^^^^^^^^^^^^^^^^^
File “/opt/Mycodo/env/lib/python3.11/site-packages/gpiod/chip.py”, line 64, in init
self._chip: Union[_ext.Chip, None] = _ext.Chip(path)
^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/opt/Mycodo/mycodo/abstract_base_controller.py”, line 57, in try_initialize
self.initialize()
File “/opt/Mycodo/mycodo/outputs/custom_outputs/output_on_off_piplates_relayplate_01.py”, line 196, in initialize
import piplates.RELAYplate as RELAY
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/RELAYplate.py”, line 20, in
import CMD5 as CMD
File “/opt/Mycodo/env/lib/python3.11/site-packages/piplates/CMD5.py”, line 43, in
raise RuntimeError(“Failed to initialize any GPIO chip. Check permissions and device availability.”)
RuntimeError: Failed to initialize any GPIO chip. Check permissions and device availability.
2025-05-15 08:57:39,725 - INFO - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Initialized in 10069.4 ms
2025-05-15 08:57:39,725 - INFO - mycodo.controllers.controller_output - Activated in 11134.6 ms
2025-05-15 08:57:40,226 - INFO - mycodo - All activated Conditional controllers started
2025-05-15 08:57:40,226 - INFO - mycodo - All activated Trigger controllers started
2025-05-15 08:57:40,315 - INFO - mycodo.controllers.controller_input_c01ce25e - Activated in 61.9 ms
2025-05-15 08:57:40,437 - INFO - mycodo.controllers.controller_input_d4768170 - Activated in 80.2 ms
2025-05-15 08:57:40,656 - INFO - mycodo.controllers.controller_input_385a2505 - Activated in 181.4 ms
2025-05-15 08:57:40,809 - INFO - mycodo.controllers.controller_input_e5959677 - Activated in 110.0 ms
2025-05-15 08:57:40,907 - INFO - mycodo.controllers.controller_input_de1d12f1 - Activated in 63.7 ms
2025-05-15 08:57:40,907 - INFO - mycodo - All activated Input controllers started
2025-05-15 08:57:40,907 - INFO - mycodo - All activated PID controllers started
2025-05-15 08:57:40,907 - INFO - mycodo - All activated Function controllers started
2025-05-15 08:57:40,977 - INFO - mycodo.controllers.controller_widget - Activated in 69.8 ms
2025-05-15 08:57:41,315 - DEBUG - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - output_on_off(on, 0, sec, 0.0, 0.0, True)
2025-05-15 08:57:41,315 - ERROR - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Cannot manipulate Output 68c7523a-0794-4530-96ba-0346b6362009: output channel doesn’t exist: 0
2025-05-15 08:57:41,978 - INFO - mycodo - Mycodo daemon started in 13.426 seconds
2025-05-15 08:57:42,875 - DEBUG - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - output_on_off(on, 1, sec, 0.0, 0.0, True)
2025-05-15 08:57:42,875 - ERROR - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Cannot manipulate Output 68c7523a-0794-4530-96ba-0346b6362009: output channel doesn’t exist: 1
2025-05-15 08:57:43,362 - DEBUG - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - output_on_off(on, 2, sec, 0.0, 0.0, True)
2025-05-15 08:57:43,362 - ERROR - mycodo.outputs.output_on_off_piplates_relayplate_01_68c7523a - Cannot manipulate Output 68c7523a-0794-4530-96ba-0346b6362009: output channel doesn’t exist: 2

Thanks for the feedback. I’ll fix the dependency issue when adding the module. The errors may be due to the module only installing pi-plates but not six, spidev, or gpiod library. I’ll take a closer look later and see if I can update the module for testing.

1 Like

Went into the Mycodo venv to check, and it looks like they are there:

Requirement already satisfied: six in /opt/Mycodo/env/lib/python3.11/site-packages (1.17.0)
Requirement already satisfied: spidev in /opt/Mycodo/env/lib/python3.11/site-packages (3.7)
Requirement already satisfied: gpiod in /opt/Mycodo/env/lib/python3.11/site-packages (2.3.0)

I also added them to your output script in the dependencies_module, but still get the same errors.