├── raspberryPiOnly ├── version ├── gitHubInfo ├── packageDependencies ├── .gitattributes ├── FileSets ├── fileListPatched ├── VersionIndependent │ ├── VenusGpioOverlayLegacy.dtbo │ ├── VenusGpioOverlayForCanHats.dtbo │ └── VenusGpioOverlayForCanPlusHat.dtbo ├── fileListVersionIndependent ├── PatchSource │ ├── relaystate.py-v3.62.patch │ ├── relaystate.py-v3.70~61.patch │ ├── relaystate.py-v3.70~9.patch │ ├── relaystate.py-v3.62.orig │ ├── relaystate.py-v3.62 │ ├── relaystate.py-v3.70~9.orig │ ├── relaystate.py-v3.70~9 │ ├── relaystate.py-v3.70~61.orig │ └── relaystate.py-v3.70~61 ├── gpio_listForCanPlusHat ├── gpio_list └── gpio_listForCanHats ├── PI GPIO pins.pdf ├── PI GPIO pins.numbers ├── .github └── workflows │ └── latest-tag.yml ├── OverlayInstructions ├── VenusGpioOverlayLegacy.dts ├── VenusGpioOverlayForCanHats.dts ├── VenusGpioOverlayForCanPlusHat.dts ├── changes ├── ReadMe └── setup /raspberryPiOnly: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | v4.13 2 | -------------------------------------------------------------------------------- /gitHubInfo: -------------------------------------------------------------------------------- 1 | kwindrem:latest 2 | -------------------------------------------------------------------------------- /packageDependencies: -------------------------------------------------------------------------------- 1 | RemoteGPIO uninstalled 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /FileSets/fileListPatched: -------------------------------------------------------------------------------- 1 | /opt/victronenergy/dbus-systemcalc-py/delegates/relaystate.py 2 | -------------------------------------------------------------------------------- /PI GPIO pins.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/RpiGpioSetup/HEAD/PI GPIO pins.pdf -------------------------------------------------------------------------------- /PI GPIO pins.numbers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/RpiGpioSetup/HEAD/PI GPIO pins.numbers -------------------------------------------------------------------------------- /FileSets/VersionIndependent/VenusGpioOverlayLegacy.dtbo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/RpiGpioSetup/HEAD/FileSets/VersionIndependent/VenusGpioOverlayLegacy.dtbo -------------------------------------------------------------------------------- /FileSets/VersionIndependent/VenusGpioOverlayForCanHats.dtbo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/RpiGpioSetup/HEAD/FileSets/VersionIndependent/VenusGpioOverlayForCanHats.dtbo -------------------------------------------------------------------------------- /FileSets/VersionIndependent/VenusGpioOverlayForCanPlusHat.dtbo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwindrem/RpiGpioSetup/HEAD/FileSets/VersionIndependent/VenusGpioOverlayForCanPlusHat.dtbo -------------------------------------------------------------------------------- /FileSets/fileListVersionIndependent: -------------------------------------------------------------------------------- 1 | /u-boot/overlays/VenusGpioOverlayLegacy.dtbo 2 | /u-boot/overlays/VenusGpioOverlayForCanHats.dtbo 3 | /u-boot/overlays/VenusGpioOverlayForCanPlusHat.dtbo 4 | -------------------------------------------------------------------------------- /.github/workflows/latest-tag.yml: -------------------------------------------------------------------------------- 1 | name: Add latest tag to new release 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | 7 | jobs: 8 | run: 9 | name: Run local action 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@master 14 | 15 | - name: Run latest-tag 16 | uses: EndBug/latest-tag@v1 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /OverlayInstructions: -------------------------------------------------------------------------------- 1 | 2 | compile: 3 | dtc -@ -I dts -O dtb -o VenusGpioOverlayLegacy.dtbo VenusGpioOverlayLegacy.dts 4 | dtc -@ -I dts -O dtb -o VenusGpioOverlayForCanHats.dtbo VenusGpioOverlayForCanHats.dts 5 | dtc -@ -I dts -O dtb -o VenusGpioOverlayForCanPlusHat.dtbo VenusGpioOverlayForCanPlusHat.dts 6 | 7 | the setup script moves the appropirate overlay to /u-boot/overlays: 8 | 9 | and adds a line to /u-boot/config.txt: 10 | dtoverlay=VenusGpioOverlay... 11 | 12 | Venus OS also does NOT have dtc installed. 13 | You will need to compile host that has dtc installed such as Raspberry PI OS. 14 | MacOS can install with brew install dtc 15 | 16 | Note /u-boot typically appears as /boot when the SD card is mounted on a computer 17 | -------------------------------------------------------------------------------- /VenusGpioOverlayLegacy.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "brcm,bcm2735,bcm2736,bcm2737,bcm2737A0,bcm2737B0,bcm2711"; 6 | 7 | // make all Venus GX digital inputs have pull-ups 8 | // the chip default is GPIO 1 - 8 have pull-ups, the rest are pull-downs 9 | 10 | // compile with 11 | // dtc -@ -I dts -O dtb -o VenusGpioOverlay.dtb VenusGpioOverlay.dts 12 | 13 | fragment@0 { 14 | target = <&gpio>; 15 | __overlay__ { 16 | pinctrl-names = "default"; 17 | pinctrl-0 = <&my_pins>; 18 | 19 | my_pins: my_pins { 20 | brcm,pins = <5 6 13 16 19 26>; /* gpio no. */ 21 | brcm,function = <0>; /* 0:in, 1:out */ 22 | brcm,pull = <2>; /* 2:up 1:down 0:none */ 23 | }; 24 | }; 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /VenusGpioOverlayForCanHats.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "brcm,bcm2735,bcm2736,bcm2737,bcm2737A0,bcm2737B0,bcm2711"; 6 | 7 | // make all Venus GX digital inputs have pull-ups 8 | // This is an alternate overlay to use with the alternate gpio_list 9 | // the chip default is GPIO 1 - 8 have pull-ups, the rest are pull-downs 10 | 11 | // compile with 12 | // dtc -@ -I dts -O dtb -o VenusGpioOverlay.dtb VenusGpioOverlay.dts 13 | 14 | fragment@0 { 15 | target = <&gpio>; 16 | __overlay__ { 17 | pinctrl-names = "default"; 18 | pinctrl-0 = <&my_pins>; 19 | 20 | my_pins: my_pins { 21 | brcm,pins = <3 5 6 13 16 26>; /* gpio no. */ 22 | brcm,function = <0>; /* 0:in, 1:out */ 23 | brcm,pull = <2>; /* 2:up 1:down 0:none */ 24 | }; 25 | }; 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /VenusGpioOverlayForCanPlusHat.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "brcm,bcm2735,bcm2736,bcm2737,bcm2737A0,bcm2737B0,bcm2711"; 6 | 7 | // make all Venus GX digital inputs have pull-ups 8 | // This is an alternate overlay to use with the alternate gpio_list 9 | // the chip default is GPIO 1 - 8 have pull-ups, the rest are pull-downs 10 | 11 | // compile with 12 | // dtc -@ -I dts -O dtb -o VenusGpioOverlay.dtb VenusGpioOverlay.dts 13 | 14 | fragment@0 { 15 | target = <&gpio>; 16 | __overlay__ { 17 | pinctrl-names = "default"; 18 | pinctrl-0 = <&my_pins>; 19 | 20 | my_pins: my_pins { 21 | brcm,pins = <3 5 6 12 25 26>; /* gpio no. */ 22 | brcm,function = <0>; /* 0:in, 1:out */ 23 | brcm,pull = <2>; /* 2:up 1:down 0:none */ 24 | }; 25 | }; 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.62.patch: -------------------------------------------------------------------------------- 1 | --- /Users/Kevin/GitHub/RpiGpioSetup.copy/FileSets/PatchSource/relaystate.py-v3.62.orig 2024-02-23 09:55:27 2 | +++ /Users/Kevin/GitHub/RpiGpioSetup.copy/FileSets/PatchSource/relaystate.py-v3.62 2024-02-23 22:32:14 3 | @@ -21,10 +21,16 @@ 4 | ('com.victronenergy.settings', [ 5 | '/Settings/Relay/Function'])] # Managed by the gui 6 | 7 | +#### add relays 3 - 6 for RpiGpioSetup 8 | def get_settings(self): 9 | return [ 10 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 11 | - ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1) 12 | + ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1), 13 | + ('/Relay/2/State', '/Settings/Relay/2/InitialState', 0, 0, 1), 14 | + ('/Relay/3/State', '/Settings/Relay/3/InitialState', 0, 0, 1), 15 | + ('/Relay/4/State', '/Settings/Relay/4/InitialState', 0, 0, 1), 16 | + ('/Relay/5/State', '/Settings/Relay/5/InitialState', 0, 0, 1), 17 | + ('/Relay/6/State', '/Settings/Relay/6/InitialState', 0, 0, 1) 18 | ] 19 | 20 | @property 21 | -------------------------------------------------------------------------------- /changes: -------------------------------------------------------------------------------- 1 | v4.13: 2 | add support for v3.70~61 3 | 4 | v4.12: 5 | add support for /Switchable (v3.70~9) 6 | 7 | v4.11: 8 | add support for Waveshare CAN Plus (3rd GPIO assignment set) 9 | 10 | v4.10: 11 | remove local copy of HelperResources 12 | 13 | v4.9: 14 | reverted to v7.17 HelperResources 15 | 16 | v4.8: 17 | switched to using patch for some files 18 | fixed typo in ReadMe 19 | 20 | v4.7: 21 | removed spi1-1cs.dtbo - unused, and confilcts with VeCanSetup 22 | 23 | v4.6: 24 | fixed: v4.5 had corrupted file sets (v2.73 and maybe others) 25 | cleanup overlay install 26 | move to version-independent and patched replacements 27 | 28 | v4.5: 29 | install spi1-1cs overlay for use in VeCanSetup for CANbus hats that use spi1 30 | spi1-3cd conflicts with Shutdown pin 31 | 32 | v4.4: 33 | added alternate GPIO overlay to remove conflicts with FD can hat 34 | 35 | v4.3: 36 | fixed: no file sets error created in v4.2 37 | 38 | v4.2: 39 | fixed: selecting alternate GPIO set fails (setup script crashes) 40 | 41 | v4.1: 42 | added support for v3.11 43 | 44 | v4.0: 45 | add option to move relay 5 & 6 do different GPIOs to avoid conflict with CAN hats 46 | renamed overlay .dtbo to support v2.90 47 | 48 | v3.8: 49 | dropped support for Venus OS v2.4x and 2.5x 50 | 51 | v3.7: 52 | added v2.80 - no functional changes 53 | 54 | v3.6: 55 | added custom gpio_list (/data/setupOptions/RpiGpioSetup/gpio_list) 56 | if present, this file is installed in place of the one in the package directory 57 | 58 | v3.5: 59 | added comment in the ReadMe about the GPIO pion for shutdown 60 | no functional changes 61 | 62 | v3.4: 63 | added raspberryPiOnly flag file 64 | add graceful shutdown GPIO pin - used by ShutdownMonitor v2.0 or later 65 | add exit codes 66 | this supports SetupHelper v4 67 | removed manual download/install instructions from the ReadMe 68 | -------------------------------------------------------------------------------- /FileSets/gpio_listForCanPlusHat: -------------------------------------------------------------------------------- 1 | #### ALTERNATE assignments for Relay 1, 5 and 6 and Dig In 4 2 | #### for Waveshare Can Plus hat 3 | #### avoids conflict with second CANbus hat interface 4 | #### NO i2c bus is possible since relay 1 and dig in 4 use these pins !!!! 5 | #### must use spi1-1cs.dtbo overlay, NOT spi1-3cs.dtbo because of conflicts 6 | # 7 | #### for use by the RpiGpio package on Raspberry PI platforms ONLY 8 | 9 | # This is a list of GPIO pins, set by the script /etc/rcS.d/S90gpio_pins.sh 10 | # They are documented at: https://github.com/victronenergy/venus/wiki/bbb-gpio 11 | # Format: 12 | # 13 | # The part is used to create an symlink in /dev/gpio. 14 | 15 | # relays are active HIGH 16 | # Relay 1 GPIO 2 / header pin 3 ALTERNATE assignment 17 | 2 out relay_1 18 | # Relay 2 GPIO 18 / header pin 12 ALTERNATE assignment 19 | 18 out relay_2 20 | 21 | # Relay 3 GPIO 27 / header pin 13 22 | 27 out relay_3 23 | # Relay 4 GPIO 4 / header pin 7 ALTERNATE assignment 24 | 4 out relay_4 25 | # Relay 5 GPIO 23 / header pin 16 26 | 23 out relay_5 27 | # Relay 6 GPIO 24 / header pin 18 28 | 24 out relay_6 29 | 30 | # these have pull UPs 31 | # Digital input 1 GPIO 5 / header pin 29 32 | 5 in digital_input_1 33 | # Digital input 2 GPIO 6 / header pin 31 34 | 6 in digital_input_2 35 | 36 | # in stock code these have pull DOWNs 37 | #### modified to pull UPs by the GPIO overlay that is installed as part of this package 38 | 39 | # Digital input 3 GPIO 12 / header pin 32 ALTERNATE assignment 40 | 12 in digital_input_3 41 | # Digital input 4 GPIO 3 / header pin 5 ALTERNATE assignment 42 | 3 in digital_input_4 43 | # Digital input 5 GPIO 26 / header pin 37 44 | 26 in digital_input_5 45 | 46 | #### Gracefull shutdown input 47 | #### Note this input is NOT added to the available I/O used by Venus OS !!!! 48 | # GPIO 25 / header pin 22 ALTERNATE assignment 49 | 25 in digital_input_6 50 | 51 | -------------------------------------------------------------------------------- /FileSets/gpio_list: -------------------------------------------------------------------------------- 1 | #### LEGACY assignments for Relay 1, 5 and 6 and Dig In 4 2 | #### WILL CONFLICT with second channel on WaveShare dual channel CANbus hats, and possiblly others 3 | #### interrupts, chip selects and second SPI bus 4 | #### single channel CANbus hats should be OK with these assignments 5 | #### this includes using only the first channel of the WaveShare dual channel hats 6 | #### note that WaveShare sells two different dual channel CANbus hats: the non FD and FD (flexible data rate) 7 | 8 | ### for use by the RpiGpio package on Raspberry PI platforms ONLY 9 | 10 | # This is a list of GPIO pins, set by the script /etc/rcS.d/S90gpio_pins.sh 11 | # They are documented at: https://github.com/victronenergy/venus/wiki/bbb-gpio 12 | # Format: 13 | # 14 | # The part is used to create an symlink in /dev/gpio. 15 | 16 | # relays are active HIGH 17 | # Relay 1 GPIO 21 / header pin 40 18 | 21 out relay_1 19 | # Relay 2 GPIO 17 / header pin 11 20 | 17 out relay_2 21 | 22 | # Relay 3 GPIO 27 / header pin 13 23 | 27 out relay_3 24 | # Relay 4 GPIO 22 / header pin 15 25 | 22 out relay_4 26 | # Relay 5 GPIO 23 / header pin 16 27 | 23 out relay_5 28 | # Relay 6 GPIO 24 / header pin 18 29 | 24 out relay_6 30 | 31 | # these have pull UPs 32 | # Digital input 1 GPIO 5 / header pin 29 33 | 5 in digital_input_1 34 | # Digital input 2 GPIO 6 / header pin 31 35 | 6 in digital_input_2 36 | 37 | # in stock code these have pull DOWNs 38 | #### modified to pull UPs by the GPIO overlay that is installed as part of this package 39 | 40 | # Digital input 3 GPIO 13 / header pin 33 41 | 13 in digital_input_3 42 | # Digital input 4 GPIO 19 / header pin 35 43 | 19 in digital_input_4 44 | # Digital input 5 GPIO 26 / header pin 37 45 | 26 in digital_input_5 46 | 47 | #### Gracefull shutdown input 48 | #### Note this input is NOT added to the available I/O used by Venus OS !!!! 49 | # GPIO 16 / header pin 36 50 | 16 in digital_input_6 51 | 52 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.70~61.patch: -------------------------------------------------------------------------------- 1 | --- /Users/Kevin/GitHub/RpiGpioSetup.copy/FileSets/PatchSource/relaystate.py-v3.70~61.orig 2025-11-26 02:04:45 2 | +++ /Users/Kevin/GitHub/RpiGpioSetup.copy/FileSets/PatchSource/relaystate.py-v3.70~61 2025-12-14 09:06:03 3 | @@ -37,6 +37,26 @@ 4 | ('/SwitchableOutput/1/Settings/Group', '/Settings/Relay/1/Group', "", 0, 0), 5 | ('/SwitchableOutput/1/Settings/CustomName', '/Settings/Relay/1/CustomName', "", 0, 0), 6 | ('/SwitchableOutput/1/Settings/ShowUIControl', '/Settings/Relay/1/ShowUIControl', 1, 0, 0), 7 | + 8 | + ('/Relay/2/State', '/Settings/Relay/2/InitialState', 0, 0, 1), 9 | + ('/SwitchableOutput/2/Settings/Group', '/Settings/Relay/2/Group', "", 0, 0), 10 | + ('/SwitchableOutput/2/Settings/CustomName', '/Settings/Relay/2/CustomName', "", 0, 0), 11 | + ('/SwitchableOutput/2/Settings/ShowUIControl', '/Settings/Relay/2/ShowUIControl', 1, 0, 1), 12 | + 13 | + ('/Relay/3/State', '/Settings/Relay/3/InitialState', 0, 0, 1), 14 | + ('/SwitchableOutput/3/Settings/Group', '/Settings/Relay/3/Group', "", 0, 0), 15 | + ('/SwitchableOutput/3/Settings/CustomName', '/Settings/Relay/3/CustomName', "", 0, 0), 16 | + ('/SwitchableOutput/3/Settings/ShowUIControl', '/Settings/Relay/3/ShowUIControl', 1, 0, 1), 17 | + 18 | + ('/Relay/4/State', '/Settings/Relay/4/InitialState', 0, 0, 1), 19 | + ('/SwitchableOutput/4/Settings/Group', '/Settings/Relay/4/Group', "", 0, 0), 20 | + ('/SwitchableOutput/4/Settings/CustomName', '/Settings/Relay/4/CustomName', "", 0, 0), 21 | + ('/SwitchableOutput/4/Settings/ShowUIControl', '/Settings/Relay/4/ShowUIControl', 1, 0, 1), 22 | + 23 | + ('/Relay/5/State', '/Settings/Relay/5/InitialState', 0, 0, 1), 24 | + ('/SwitchableOutput/5/Settings/Group', '/Settings/Relay/5/Group', "", 0, 0), 25 | + ('/SwitchableOutput/5/Settings/CustomName', '/Settings/Relay/5/CustomName', "", 0, 0), 26 | + ('/SwitchableOutput/5/Settings/ShowUIControl', '/Settings/Relay/5/ShowUIControl', 1, 0, 1), 27 | ] 28 | # Add settings for additional relays, for the tinkerers. These are not 29 | # managed by venus-platform. 30 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.70~9.patch: -------------------------------------------------------------------------------- 1 | --- /Users/Kevin/GitHub/RpiGpioSetup.copy/FileSets/PatchSource/relaystate.py-v3.70~9.orig 2025-07-08 04:49:41 2 | +++ /Users/Kevin/GitHub/RpiGpioSetup.copy/FileSets/PatchSource/relaystate.py-v3.70~9 2025-07-12 11:52:40 3 | @@ -23,6 +23,7 @@ 4 | '/Settings/Relay/Function', # Managed by the gui 5 | '/Settings/Relay/1/Function'])] 6 | 7 | +#### add relays 3 - 6 for RpiGpioSetup 8 | def get_settings(self): 9 | return [ 10 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 11 | @@ -34,6 +35,26 @@ 12 | ('/SwitchableOutput/1/Settings/Group', '/Settings/Relay/1/Group', "", 0, 0), 13 | ('/SwitchableOutput/1/Settings/CustomName', '/Settings/Relay/1/CustomName', "", 0, 0), 14 | ('/SwitchableOutput/1/Settings/ShowUIControl', '/Settings/Relay/1/ShowUIControl', 1, 0, 1), 15 | + 16 | + ('/Relay/2/State', '/Settings/Relay/2/InitialState', 0, 0, 1), 17 | + ('/SwitchableOutput/2/Settings/Group', '/Settings/Relay/2/Group', "", 0, 0), 18 | + ('/SwitchableOutput/2/Settings/CustomName', '/Settings/Relay/2/CustomName', "", 0, 0), 19 | + ('/SwitchableOutput/2/Settings/ShowUIControl', '/Settings/Relay/2/ShowUIControl', 1, 0, 1), 20 | + 21 | + ('/Relay/3/State', '/Settings/Relay/3/InitialState', 0, 0, 1), 22 | + ('/SwitchableOutput/3/Settings/Group', '/Settings/Relay/3/Group', "", 0, 0), 23 | + ('/SwitchableOutput/3/Settings/CustomName', '/Settings/Relay/3/CustomName', "", 0, 0), 24 | + ('/SwitchableOutput/3/Settings/ShowUIControl', '/Settings/Relay/3/ShowUIControl', 1, 0, 1), 25 | + 26 | + ('/Relay/4/State', '/Settings/Relay/4/InitialState', 0, 0, 1), 27 | + ('/SwitchableOutput/4/Settings/Group', '/Settings/Relay/4/Group', "", 0, 0), 28 | + ('/SwitchableOutput/4/Settings/CustomName', '/Settings/Relay/4/CustomName', "", 0, 0), 29 | + ('/SwitchableOutput/4/Settings/ShowUIControl', '/Settings/Relay/4/ShowUIControl', 1, 0, 1), 30 | + 31 | + ('/Relay/5/State', '/Settings/Relay/5/InitialState', 0, 0, 1), 32 | + ('/SwitchableOutput/5/Settings/Group', '/Settings/Relay/5/Group', "", 0, 0), 33 | + ('/SwitchableOutput/5/Settings/CustomName', '/Settings/Relay/5/CustomName', "", 0, 0), 34 | + ('/SwitchableOutput/5/Settings/ShowUIControl', '/Settings/Relay/5/ShowUIControl', 1, 0, 1), 35 | ] 36 | 37 | def _relay_function(self, idx): 38 | -------------------------------------------------------------------------------- /FileSets/gpio_listForCanHats: -------------------------------------------------------------------------------- 1 | #### ALTERNATE assignments for Relay 1, 5 and 6 and Dig In 4 2 | #### avoids conflict with second CANbus hat interface 3 | #### supports Waveshare dual CAN and dual CAN FD 4 | #### should work with CAN FD mode A or mode B 5 | #### but not more than 1 CAN FD hat board 6 | #### NO i2c bus is possible since relay 1 and dig in 4 use these pins !!!! 7 | #### must use spi1-1cs.dtbo overlay, NOT spi1-3cs.dtbo because of conflicts 8 | #### digital input 3 conflicts with spi0-1 interrupt on CAN FD hat 9 | #### Shutdown monitor shutdown GPIO spi1-2 chip select on CAN FD hat 10 | #### relay 2 conflicts wih spi1-1 chip select on CAN FD hat 11 | #### relay 4 conflicts wih spi1-2 interrupt on CAN FD hat 12 | # 13 | #### other conflicts must be resolved manually by carefully selecting hat boards 14 | #### and creating a custom gpio_list file 15 | 16 | #### for use by the RpiGpio package on Raspberry PI platforms ONLY 17 | 18 | # This is a list of GPIO pins, set by the script /etc/rcS.d/S90gpio_pins.sh 19 | # They are documented at: https://github.com/victronenergy/venus/wiki/bbb-gpio 20 | # Format: 21 | # 22 | # The part is used to create an symlink in /dev/gpio. 23 | 24 | # relays are active HIGH 25 | # Relay 1 GPIO 2 / header pin 3 ALTERNATE assignment 26 | 2 out relay_1 27 | # Relay 2 GPIO 17 / header pin 11 28 | 17 out relay_2 29 | 30 | # Relay 3 GPIO 27 / header pin 13 31 | 27 out relay_3 32 | # Relay 4 GPIO 22 / header pin 15 33 | 22 out relay_4 34 | # Relay 5 GPIO 12 / header pin 32 ALTERNATE assignment 35 | 12 out relay_5 36 | # Relay 6 GPIO 4 / header pin 7 ALTERNATE assignment 37 | 4 out relay_6 38 | 39 | # these have pull UPs 40 | # Digital input 1 GPIO 5 / header pin 29 41 | 5 in digital_input_1 42 | # Digital input 2 GPIO 6 / header pin 31 43 | 6 in digital_input_2 44 | 45 | # in stock code these have pull DOWNs 46 | #### modified to pull UPs by the GPIO overlay that is installed as part of this package 47 | 48 | # Digital input 3 GPIO 13 / header pin 33 49 | 13 in digital_input_3 50 | # Digital input 4 GPIO 3 / header pin 5 ALTERNATE assignment 51 | 3 in digital_input_4 52 | # Digital input 5 GPIO 26 / header pin 37 53 | 26 in digital_input_5 54 | 55 | #### Gracefull shutdown input 56 | #### Note this input is NOT added to the available I/O used by Venus OS !!!! 57 | # GPIO 16 / header pin 36 58 | 16 in digital_input_6 59 | 60 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.62.orig: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib 2 | import logging 3 | import os 4 | import traceback 5 | from glob import glob 6 | 7 | # Victron packages 8 | from ve_utils import exit_on_error 9 | 10 | from delegates.base import SystemCalcDelegate 11 | 12 | class RelayState(SystemCalcDelegate): 13 | RELAY_GLOB = '/dev/gpio/relay_*' 14 | 15 | def __init__(self): 16 | SystemCalcDelegate.__init__(self) 17 | self._relays = {} 18 | 19 | def get_input(self): 20 | return [ 21 | ('com.victronenergy.settings', [ 22 | '/Settings/Relay/Function'])] # Managed by the gui 23 | 24 | def get_settings(self): 25 | return [ 26 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 27 | ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1) 28 | ] 29 | 30 | @property 31 | def relay_function(self): 32 | return self._dbusmonitor.get_value('com.victronenergy.settings', 33 | '/Settings/Relay/Function') 34 | 35 | def set_sources(self, dbusmonitor, settings, dbusservice): 36 | SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) 37 | relays = sorted(glob(self.RELAY_GLOB)) 38 | 39 | if len(relays) == 0: 40 | logging.info('No relays found') 41 | return 42 | 43 | self._relays.update({'/Relay/{}/State'.format(i): os.path.join(r, 'value') \ 44 | for i, r in enumerate(relays) }) 45 | 46 | GLib.idle_add(exit_on_error, self._init_relay_state) 47 | for dbus_path in self._relays.keys(): 48 | self._dbusservice.add_path(dbus_path, value=None, writeable=True, 49 | onchangecallback=self._on_relay_state_changed) 50 | 51 | logging.info('Relays found: {}'.format(', '.join(self._relays.values()))) 52 | 53 | def _init_relay_state(self): 54 | if self.relay_function is None: 55 | return True # Try again on the next idle event 56 | 57 | for dbus_path, path in self._relays.items(): 58 | if self.relay_function != 2 and dbus_path == '/Relay/0/State': 59 | continue # Skip primary relay if function is not manual 60 | try: 61 | state = self._settings[dbus_path] 62 | except KeyError: 63 | pass 64 | else: 65 | self._dbusservice[dbus_path] = state 66 | self.__on_relay_state_changed(dbus_path, state) 67 | 68 | # Sync state back to dbus 69 | self._update_relay_state() 70 | 71 | # Watch changes and update dbus. Do we still need this? 72 | GLib.timeout_add(5000, exit_on_error, self._update_relay_state) 73 | return False 74 | 75 | def _update_relay_state(self): 76 | # @todo EV Do we still need this? Maybe only at startup? 77 | for dbus_path, file_path in self._relays.items(): 78 | try: 79 | with open(file_path, 'rt') as r: 80 | state = int(r.read().strip()) 81 | self._dbusservice[dbus_path] = state 82 | except (IOError, ValueError): 83 | traceback.print_exc() 84 | return True 85 | 86 | def __on_relay_state_changed(self, dbus_path, state): 87 | try: 88 | path = self._relays[dbus_path] 89 | with open(path, 'wt') as w: 90 | w.write(str(state)) 91 | except IOError: 92 | traceback.print_exc() 93 | return False 94 | return True 95 | 96 | def _on_relay_state_changed(self, dbus_path, value): 97 | try: 98 | state = int(bool(value)) 99 | except ValueError: 100 | traceback.print_exc() 101 | return False 102 | try: 103 | return self.__on_relay_state_changed(dbus_path, state) 104 | finally: 105 | # Remember the state to restore after a restart 106 | self._settings[dbus_path] = state 107 | -------------------------------------------------------------------------------- /ReadMe: -------------------------------------------------------------------------------- 1 | This package adds additional digital inputs and relay outputs 2 | for Venus OS running on Raspberry PI platforms. 3 | It is not suitable for other GX devices such as CCGX or Cerbo GX. 4 | 5 | The stock RPI Venus distribution there are NO relays or digital inputs defined. 6 | 7 | This script adds: 8 | 5 digital inputs 9 | makes additional digital inputs available 10 | makes all digital inputs behave the same: pull up so they are active low 11 | 6 relay outputs 12 | activates GPIO 16 (header pin 36) as a shutdown pin 13 | This requires ShutdownMonitor v2.0 or later 14 | 15 | The Raspberry PI default is for the first 8 GPIO pins to have pull UPs 16 | and the remaining have pull DOWNs. This makes some digital input pins behave differently 17 | This script installs a DT overlay that switches to pull UPs for all digital input pins 18 | 19 | 20 | Only the relay 1 can be assigned to a function: alarm, generator start/stop or pump. 21 | In addition, relay 2 can be assigned to temperature alarm 22 | The remaining 4 relays are only provided manual on/off control from the menu. 23 | 24 | Note that the Raspberry PI has no relays or isolated/filtered digital inputs on board. 25 | 26 | Relay outputs are 0 volts when inactive and 3.3 volts when active. 27 | Current sink and source is limited. Check Raspberry PI specifications for details. 28 | You must attach the GPIO pins to external relays. 29 | Relays should active on a high (3.3 volt) input. 30 | Relays that activate on a low signal are NOT COMPATIBLE since 31 | the relays would still close during Raspberry PI boot regardless of the logic provided by Venus OS. 32 | 33 | Digital inputs expect 0 volts for an inactive input and 3.3 volts for an active input. 34 | You must provide isolation for the digital inputs to avoid damage to the CPU chip!!! 35 | 36 | Venus OS does not provide native support for i2c relay boards and this package does not add any. 37 | 38 | This script will accept default options when run from PackagManager. 39 | 40 | 41 | This script will select default options when run from PackageManager. 42 | This may not be appropirate. See section below on GPIO conflicts: 43 | The script MUST be run from the command line to set options. 44 | After that, those options will be used when installing via PackageManager, 45 | including automatic reinstall following a firmware update. 46 | 47 | 48 | GPIO conflicts: 49 | 50 | GPIO assignments for relays 5 and 6 may cause conflicts with hat board interrupts. 51 | This conflict may cause the system to not boot or be extremely sluggish. 52 | This script allows for two assignments for relays 5 and 6. 53 | gpio_list is the legacy one: GPIO 23 and 24, respectively 54 | gpio_listForCanHats provides compatibility with 55 | Waveshare CANbus dual channel hats (and possibly others) 56 | GPIOs 2, 12 and 4 are used for relays 1. 5 and 6, respectively 57 | GPIO 3 is used for digital input 4 58 | However, i2c is disabled since it uses GPIO 2 and 3 ! 59 | 60 | An option to select the legacy or compatible set is offered 61 | when running setup from the command line. 62 | 63 | Custom GPIO list: 64 | 65 | If /data/setupOptions/RpiGpioSetup/gpio_list exists it is used in place 66 | of the one in the package directory. 67 | This accommodates Relay HATs, etc. that use different GPIO pins. 68 | 69 | Installation: 70 | 71 | RpiGpioSetup requires that SetupHelper is installed first. 72 | 73 | The easiest way to install RpiGpioSetup is to do a "blind install" of SetupHelper 74 | and then add the RpiGpioSetup package via the PackageManager menus. 75 | 76 | Refer to the SetupHelper ReadMe for instructions and for other install options: 77 | https://github.com/kwindrem/SetupHelper 78 | 79 | 80 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.62: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib 2 | import logging 3 | import os 4 | import traceback 5 | from glob import glob 6 | 7 | # Victron packages 8 | from ve_utils import exit_on_error 9 | 10 | from delegates.base import SystemCalcDelegate 11 | 12 | class RelayState(SystemCalcDelegate): 13 | RELAY_GLOB = '/dev/gpio/relay_*' 14 | 15 | def __init__(self): 16 | SystemCalcDelegate.__init__(self) 17 | self._relays = {} 18 | 19 | def get_input(self): 20 | return [ 21 | ('com.victronenergy.settings', [ 22 | '/Settings/Relay/Function'])] # Managed by the gui 23 | 24 | #### add relays 3 - 6 for RpiGpioSetup 25 | def get_settings(self): 26 | return [ 27 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 28 | ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1), 29 | ('/Relay/2/State', '/Settings/Relay/2/InitialState', 0, 0, 1), 30 | ('/Relay/3/State', '/Settings/Relay/3/InitialState', 0, 0, 1), 31 | ('/Relay/4/State', '/Settings/Relay/4/InitialState', 0, 0, 1), 32 | ('/Relay/5/State', '/Settings/Relay/5/InitialState', 0, 0, 1), 33 | ('/Relay/6/State', '/Settings/Relay/6/InitialState', 0, 0, 1) 34 | ] 35 | 36 | @property 37 | def relay_function(self): 38 | return self._dbusmonitor.get_value('com.victronenergy.settings', 39 | '/Settings/Relay/Function') 40 | 41 | def set_sources(self, dbusmonitor, settings, dbusservice): 42 | SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) 43 | relays = sorted(glob(self.RELAY_GLOB)) 44 | 45 | if len(relays) == 0: 46 | logging.info('No relays found') 47 | return 48 | 49 | self._relays.update({'/Relay/{}/State'.format(i): os.path.join(r, 'value') \ 50 | for i, r in enumerate(relays) }) 51 | 52 | GLib.idle_add(exit_on_error, self._init_relay_state) 53 | for dbus_path in self._relays.keys(): 54 | self._dbusservice.add_path(dbus_path, value=None, writeable=True, 55 | onchangecallback=self._on_relay_state_changed) 56 | 57 | logging.info('Relays found: {}'.format(', '.join(self._relays.values()))) 58 | 59 | def _init_relay_state(self): 60 | if self.relay_function is None: 61 | return True # Try again on the next idle event 62 | 63 | for dbus_path, path in self._relays.items(): 64 | if self.relay_function != 2 and dbus_path == '/Relay/0/State': 65 | continue # Skip primary relay if function is not manual 66 | try: 67 | state = self._settings[dbus_path] 68 | except KeyError: 69 | pass 70 | else: 71 | self._dbusservice[dbus_path] = state 72 | self.__on_relay_state_changed(dbus_path, state) 73 | 74 | # Sync state back to dbus 75 | self._update_relay_state() 76 | 77 | # Watch changes and update dbus. Do we still need this? 78 | GLib.timeout_add(5000, exit_on_error, self._update_relay_state) 79 | return False 80 | 81 | def _update_relay_state(self): 82 | # @todo EV Do we still need this? Maybe only at startup? 83 | for dbus_path, file_path in self._relays.items(): 84 | try: 85 | with open(file_path, 'rt') as r: 86 | state = int(r.read().strip()) 87 | self._dbusservice[dbus_path] = state 88 | except (IOError, ValueError): 89 | traceback.print_exc() 90 | return True 91 | 92 | def __on_relay_state_changed(self, dbus_path, state): 93 | try: 94 | path = self._relays[dbus_path] 95 | with open(path, 'wt') as w: 96 | w.write(str(state)) 97 | except IOError: 98 | traceback.print_exc() 99 | return False 100 | return True 101 | 102 | def _on_relay_state_changed(self, dbus_path, value): 103 | try: 104 | state = int(bool(value)) 105 | except ValueError: 106 | traceback.print_exc() 107 | return False 108 | try: 109 | return self.__on_relay_state_changed(dbus_path, state) 110 | finally: 111 | # Remember the state to restore after a restart 112 | self._settings[dbus_path] = state 113 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.70~9.orig: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib 2 | import logging 3 | import os 4 | import traceback 5 | from glob import glob 6 | from functools import partial 7 | 8 | # Victron packages 9 | from ve_utils import exit_on_error 10 | 11 | from delegates.base import SystemCalcDelegate 12 | 13 | class RelayState(SystemCalcDelegate): 14 | RELAY_GLOB = '/dev/gpio/relay_*' 15 | 16 | def __init__(self): 17 | SystemCalcDelegate.__init__(self) 18 | self._relays = {} 19 | 20 | def get_input(self): 21 | return [ 22 | ('com.victronenergy.settings', [ 23 | '/Settings/Relay/Function', # Managed by the gui 24 | '/Settings/Relay/1/Function'])] 25 | 26 | def get_settings(self): 27 | return [ 28 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 29 | ('/SwitchableOutput/0/Settings/Group', '/Settings/Relay/0/Group', "", 0, 0), 30 | ('/SwitchableOutput/0/Settings/CustomName', '/Settings/Relay/0/CustomName', "", 0, 0), 31 | ('/SwitchableOutput/0/Settings/ShowUIControl', '/Settings/Relay/0/ShowUIControl', 1, 0, 1), 32 | 33 | ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1), 34 | ('/SwitchableOutput/1/Settings/Group', '/Settings/Relay/1/Group', "", 0, 0), 35 | ('/SwitchableOutput/1/Settings/CustomName', '/Settings/Relay/1/CustomName', "", 0, 0), 36 | ('/SwitchableOutput/1/Settings/ShowUIControl', '/Settings/Relay/1/ShowUIControl', 1, 0, 1), 37 | ] 38 | 39 | def _relay_function(self, idx): 40 | return self._dbusmonitor.get_value('com.victronenergy.settings', 41 | ('/Settings/Relay/Function' if idx == 0 else 42 | f'/Settings/Relay/{idx}/Function')) 43 | 44 | def set_relay_function(self, valid, idx, v): 45 | # check that function is allowed. The relevant bit must be in the 46 | # valid mask 47 | if 0 <= v <= 5 and bool(2**v & valid): 48 | self._dbusmonitor.set_value('com.victronenergy.settings', 49 | ('/Settings/Relay/Function' if idx == 0 else 50 | f'/Settings/Relay/{idx}/Function'), v) 51 | return True 52 | return False 53 | 54 | @property 55 | def relay_function(self): 56 | return self._relay_function(0) 57 | 58 | def set_sources(self, dbusmonitor, settings, dbusservice): 59 | SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) 60 | relays = sorted(glob(self.RELAY_GLOB)) 61 | 62 | if len(relays) == 0: 63 | logging.info('No relays found') 64 | return 65 | 66 | self._relays.update({i: os.path.join(r, 'value') \ 67 | for i, r in enumerate(relays) }) 68 | 69 | GLib.idle_add(exit_on_error, self._init_relay_state) 70 | for idx in self._relays.keys(): 71 | self._dbusservice.add_path(f'/Relay/{idx}/State', value=None, writeable=True, 72 | onchangecallback=partial(self._on_relay_state_changed, idx)) 73 | 74 | # Switchable output paths 75 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/State', value=None, 76 | writeable=True, onchangecallback=partial(self._on_relay_state_changed, idx)) 77 | 78 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Name', f'GX internal relay {idx+1}') 79 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Status', value=None) 80 | 81 | # Switchable output settings 82 | for setting, typ in (('Group', str), ('CustomName', str), ('ShowUIControl', bool)): 83 | self._dbusservice.add_path(p := f'/SwitchableOutput/{idx}/Settings/{setting}', 84 | value=self._settings[p], writeable=True, 85 | onchangecallback=partial(self._on_relay_setting_changed, idx, typ)) 86 | 87 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/Type', 88 | value=1, writeable=True, onchangecallback=(lambda p, v: v == 1)) # R/W, but only accepts toggle 89 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/ValidTypes', 90 | value=2) # Toggle 91 | 92 | # All functions for first relay, Manual and temperature for the rest 93 | functions = 0b111111 if idx == 0 else 0b10100 94 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/Function', 95 | value=self._relay_function(idx), writeable=True, 96 | onchangecallback=lambda p, v, f=functions, idx=idx: self.set_relay_function(f, idx, int(v))) 97 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/ValidFunctions', 98 | value=functions) 99 | 100 | logging.info('Relays found: {}'.format(', '.join(self._relays.values()))) 101 | 102 | def _init_relay_state(self): 103 | if self.relay_function is None: 104 | return True # Try again on the next idle event 105 | 106 | for idx, path in self._relays.items(): 107 | if self.relay_function != 2 and idx == 0: 108 | continue # Skip primary relay if function is not manual 109 | try: 110 | state = self._settings[f'/Relay/{idx}/State'] 111 | except KeyError: 112 | pass 113 | else: 114 | self._set_relay_dbus_state(idx, state) 115 | self.__on_relay_state_changed(idx, state) 116 | 117 | # Sync state back to dbus 118 | self._update_relay_state() 119 | 120 | # Watch changes and update dbus. Do we still need this? 121 | GLib.timeout_add(5000, exit_on_error, self._update_relay_state) 122 | return False 123 | 124 | def _update_relay_state(self): 125 | # @todo EV Do we still need this? Maybe only at startup? 126 | for idx, file_path in self._relays.items(): 127 | try: 128 | with open(file_path, 'rt') as r: 129 | state = int(r.read().strip()) 130 | self._set_relay_dbus_state(idx, state) 131 | except (IOError, ValueError): 132 | traceback.print_exc() 133 | 134 | # Make sure updates to relay function in settings is reflected here 135 | self._dbusservice[f'/SwitchableOutput/{idx}/Settings/Function'] = self._relay_function(idx) 136 | 137 | return True 138 | 139 | def _set_relay_dbus_state(self, idx, state): 140 | self._dbusservice[f'/Relay/{idx}/State'] = state 141 | self._dbusservice[f'/SwitchableOutput/{idx}/State'] = state 142 | self._dbusservice[f'/SwitchableOutput/{idx}/Status'] = 0x09 if state else 0x00 143 | 144 | def __on_relay_state_changed(self, idx, state): 145 | try: 146 | path = self._relays[idx] 147 | with open(path, 'wt') as w: 148 | w.write(str(state)) 149 | except IOError: 150 | traceback.print_exc() 151 | return False 152 | return True 153 | 154 | def _on_relay_state_changed(self, idx, dbus_path, value): 155 | try: 156 | state = int(bool(value)) 157 | except ValueError: 158 | traceback.print_exc() 159 | return False 160 | 161 | self._set_relay_dbus_state(idx, state) 162 | try: 163 | return self.__on_relay_state_changed(idx, state) 164 | finally: 165 | # Remember the state to restore after a restart 166 | self._settings[f'/Relay/{idx}/State'] = state 167 | 168 | def _on_relay_setting_changed(self, idx, _type, dbus_path, value): 169 | try: 170 | self._settings[dbus_path] = _type(value) 171 | except (KeyError, ValueError): 172 | return False 173 | return True 174 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.70~9: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib 2 | import logging 3 | import os 4 | import traceback 5 | from glob import glob 6 | from functools import partial 7 | 8 | # Victron packages 9 | from ve_utils import exit_on_error 10 | 11 | from delegates.base import SystemCalcDelegate 12 | 13 | class RelayState(SystemCalcDelegate): 14 | RELAY_GLOB = '/dev/gpio/relay_*' 15 | 16 | def __init__(self): 17 | SystemCalcDelegate.__init__(self) 18 | self._relays = {} 19 | 20 | def get_input(self): 21 | return [ 22 | ('com.victronenergy.settings', [ 23 | '/Settings/Relay/Function', # Managed by the gui 24 | '/Settings/Relay/1/Function'])] 25 | 26 | #### add relays 3 - 6 for RpiGpioSetup 27 | def get_settings(self): 28 | return [ 29 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 30 | ('/SwitchableOutput/0/Settings/Group', '/Settings/Relay/0/Group', "", 0, 0), 31 | ('/SwitchableOutput/0/Settings/CustomName', '/Settings/Relay/0/CustomName', "", 0, 0), 32 | ('/SwitchableOutput/0/Settings/ShowUIControl', '/Settings/Relay/0/ShowUIControl', 1, 0, 1), 33 | 34 | ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1), 35 | ('/SwitchableOutput/1/Settings/Group', '/Settings/Relay/1/Group', "", 0, 0), 36 | ('/SwitchableOutput/1/Settings/CustomName', '/Settings/Relay/1/CustomName', "", 0, 0), 37 | ('/SwitchableOutput/1/Settings/ShowUIControl', '/Settings/Relay/1/ShowUIControl', 1, 0, 1), 38 | 39 | ('/Relay/2/State', '/Settings/Relay/2/InitialState', 0, 0, 1), 40 | ('/SwitchableOutput/2/Settings/Group', '/Settings/Relay/2/Group', "", 0, 0), 41 | ('/SwitchableOutput/2/Settings/CustomName', '/Settings/Relay/2/CustomName', "", 0, 0), 42 | ('/SwitchableOutput/2/Settings/ShowUIControl', '/Settings/Relay/2/ShowUIControl', 1, 0, 1), 43 | 44 | ('/Relay/3/State', '/Settings/Relay/3/InitialState', 0, 0, 1), 45 | ('/SwitchableOutput/3/Settings/Group', '/Settings/Relay/3/Group', "", 0, 0), 46 | ('/SwitchableOutput/3/Settings/CustomName', '/Settings/Relay/3/CustomName', "", 0, 0), 47 | ('/SwitchableOutput/3/Settings/ShowUIControl', '/Settings/Relay/3/ShowUIControl', 1, 0, 1), 48 | 49 | ('/Relay/4/State', '/Settings/Relay/4/InitialState', 0, 0, 1), 50 | ('/SwitchableOutput/4/Settings/Group', '/Settings/Relay/4/Group', "", 0, 0), 51 | ('/SwitchableOutput/4/Settings/CustomName', '/Settings/Relay/4/CustomName', "", 0, 0), 52 | ('/SwitchableOutput/4/Settings/ShowUIControl', '/Settings/Relay/4/ShowUIControl', 1, 0, 1), 53 | 54 | ('/Relay/5/State', '/Settings/Relay/5/InitialState', 0, 0, 1), 55 | ('/SwitchableOutput/5/Settings/Group', '/Settings/Relay/5/Group', "", 0, 0), 56 | ('/SwitchableOutput/5/Settings/CustomName', '/Settings/Relay/5/CustomName', "", 0, 0), 57 | ('/SwitchableOutput/5/Settings/ShowUIControl', '/Settings/Relay/5/ShowUIControl', 1, 0, 1), 58 | ] 59 | 60 | def _relay_function(self, idx): 61 | return self._dbusmonitor.get_value('com.victronenergy.settings', 62 | ('/Settings/Relay/Function' if idx == 0 else 63 | f'/Settings/Relay/{idx}/Function')) 64 | 65 | def set_relay_function(self, valid, idx, v): 66 | # check that function is allowed. The relevant bit must be in the 67 | # valid mask 68 | if 0 <= v <= 5 and bool(2**v & valid): 69 | self._dbusmonitor.set_value('com.victronenergy.settings', 70 | ('/Settings/Relay/Function' if idx == 0 else 71 | f'/Settings/Relay/{idx}/Function'), v) 72 | return True 73 | return False 74 | 75 | @property 76 | def relay_function(self): 77 | return self._relay_function(0) 78 | 79 | def set_sources(self, dbusmonitor, settings, dbusservice): 80 | SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) 81 | relays = sorted(glob(self.RELAY_GLOB)) 82 | 83 | if len(relays) == 0: 84 | logging.info('No relays found') 85 | return 86 | 87 | self._relays.update({i: os.path.join(r, 'value') \ 88 | for i, r in enumerate(relays) }) 89 | 90 | GLib.idle_add(exit_on_error, self._init_relay_state) 91 | for idx in self._relays.keys(): 92 | self._dbusservice.add_path(f'/Relay/{idx}/State', value=None, writeable=True, 93 | onchangecallback=partial(self._on_relay_state_changed, idx)) 94 | 95 | # Switchable output paths 96 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/State', value=None, 97 | writeable=True, onchangecallback=partial(self._on_relay_state_changed, idx)) 98 | 99 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Name', f'GX internal relay {idx+1}') 100 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Status', value=None) 101 | 102 | # Switchable output settings 103 | for setting, typ in (('Group', str), ('CustomName', str), ('ShowUIControl', bool)): 104 | self._dbusservice.add_path(p := f'/SwitchableOutput/{idx}/Settings/{setting}', 105 | value=self._settings[p], writeable=True, 106 | onchangecallback=partial(self._on_relay_setting_changed, idx, typ)) 107 | 108 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/Type', 109 | value=1, writeable=True, onchangecallback=(lambda p, v: v == 1)) # R/W, but only accepts toggle 110 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/ValidTypes', 111 | value=2) # Toggle 112 | 113 | # All functions for first relay, Manual and temperature for the rest 114 | functions = 0b111111 if idx == 0 else 0b10100 115 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/Function', 116 | value=self._relay_function(idx), writeable=True, 117 | onchangecallback=lambda p, v, f=functions, idx=idx: self.set_relay_function(f, idx, int(v))) 118 | self._dbusservice.add_path(f'/SwitchableOutput/{idx}/Settings/ValidFunctions', 119 | value=functions) 120 | 121 | logging.info('Relays found: {}'.format(', '.join(self._relays.values()))) 122 | 123 | def _init_relay_state(self): 124 | if self.relay_function is None: 125 | return True # Try again on the next idle event 126 | 127 | for idx, path in self._relays.items(): 128 | if self.relay_function != 2 and idx == 0: 129 | continue # Skip primary relay if function is not manual 130 | try: 131 | state = self._settings[f'/Relay/{idx}/State'] 132 | except KeyError: 133 | pass 134 | else: 135 | self._set_relay_dbus_state(idx, state) 136 | self.__on_relay_state_changed(idx, state) 137 | 138 | # Sync state back to dbus 139 | self._update_relay_state() 140 | 141 | # Watch changes and update dbus. Do we still need this? 142 | GLib.timeout_add(5000, exit_on_error, self._update_relay_state) 143 | return False 144 | 145 | def _update_relay_state(self): 146 | # @todo EV Do we still need this? Maybe only at startup? 147 | for idx, file_path in self._relays.items(): 148 | try: 149 | with open(file_path, 'rt') as r: 150 | state = int(r.read().strip()) 151 | self._set_relay_dbus_state(idx, state) 152 | except (IOError, ValueError): 153 | traceback.print_exc() 154 | 155 | # Make sure updates to relay function in settings is reflected here 156 | self._dbusservice[f'/SwitchableOutput/{idx}/Settings/Function'] = self._relay_function(idx) 157 | 158 | return True 159 | 160 | def _set_relay_dbus_state(self, idx, state): 161 | self._dbusservice[f'/Relay/{idx}/State'] = state 162 | self._dbusservice[f'/SwitchableOutput/{idx}/State'] = state 163 | self._dbusservice[f'/SwitchableOutput/{idx}/Status'] = 0x09 if state else 0x00 164 | 165 | def __on_relay_state_changed(self, idx, state): 166 | try: 167 | path = self._relays[idx] 168 | with open(path, 'wt') as w: 169 | w.write(str(state)) 170 | except IOError: 171 | traceback.print_exc() 172 | return False 173 | return True 174 | 175 | def _on_relay_state_changed(self, idx, dbus_path, value): 176 | try: 177 | state = int(bool(value)) 178 | except ValueError: 179 | traceback.print_exc() 180 | return False 181 | 182 | self._set_relay_dbus_state(idx, state) 183 | try: 184 | return self.__on_relay_state_changed(idx, state) 185 | finally: 186 | # Remember the state to restore after a restart 187 | self._settings[f'/Relay/{idx}/State'] = state 188 | 189 | def _on_relay_setting_changed(self, idx, _type, dbus_path, value): 190 | try: 191 | self._settings[dbus_path] = _type(value) 192 | except (KeyError, ValueError): 193 | return False 194 | return True 195 | -------------------------------------------------------------------------------- /setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # adds additional digital inputs and relay outputs to the Raspberry Pi platforms 4 | 5 | # the stock RPI Venus distribution includes two relays and two digital inputs 6 | # adding the additional GPIO pins to /etc/venus/gpio_list 7 | # makes additional digital inputs available 8 | # all 5 defined digital inputs are activated by this script 9 | # 10 | # the Raspberry PI default is for the first 8 GPIO pins to have pull UPs 11 | # and the remaining have pull DOWNs. This makes some digital input pins behave differently 12 | # this script also installs a DT overlay that switches to pull UPs for all digital input pins 13 | # 14 | # Only the first relay can be assigned to a function: alarm, genertor start/stop or pump. 15 | # Others are only provided manual on/off control from the menu. 16 | # Six relays are supported. Only the first relay can be assigned to functions 17 | # The remaining relays are manual on/off only. 18 | 19 | # run manually initally, then called from reinstallMods 20 | # to uninstall/deactivate, call this script with "uninstall" as first parameter 21 | 22 | # original assignments for relays 5 and 6 create conflicts with interrypt pins 23 | # on hat boards such as the Waveshare dual CANbus hats (regular and FD versions) 24 | # An option in this setup script allows selection of different GPIO assignments 25 | # for relays 5 and 6 to avoid these conflicts 26 | # 27 | # Because this conflict will be rare, the option is considered optional. 28 | # That is, an automatic install WILL be permitted even if a manual install has not 29 | # been perfomred. 30 | # 31 | # VeCanSetup will alert the user of conflicts and prevent installing the second 32 | # CANbus interface until this script is rerun to move the relay assignments. 33 | 34 | # Note, this script DOES require a system reboot. 35 | 36 | 37 | gpioListFile="/etc/venus/gpio_list" 38 | configFile="/u-boot/config.txt" 39 | 40 | # no log file for this package 41 | packageLogFile="" 42 | 43 | #### following line incorporates helper resources into this script 44 | source "/data/SetupHelper/HelperResources/IncludeHelpers" 45 | #### end of lines to include helper resources 46 | 47 | # convert old options to new one 48 | # leave old options in place in case RpiGpioSetup is reverted to a previous verison 49 | if ! [ -e "$setupOptionsDir/selectedGpioList" ]; then 50 | if [ -f "$setupOptionsDir/gpio_list" ]; then 51 | selectedGpioList=4 52 | elif [ -e "$setupOptionsDir/alternateGpio" ]; then 53 | selectedGpioList=2 54 | else 55 | selectedGpioList=1 56 | fi 57 | echo $selectedGpioList > "$setupOptionsDir/selectedGpioList" 58 | fi 59 | 60 | 61 | # overlay remove and install GPIO overlay 62 | # Note: strings in these two functions MUST MATCH !!! 63 | 64 | overlayBeginText="#### change all digital inputs to pull ups" 65 | overlayEndText="#### end change all digital inputs to pull ups" 66 | 67 | function removeGpioOverlay () 68 | { 69 | if [ -f "$configFile" ] && [ $(grep -c "$overlayBeginText" "$configFile") > 0 ]; then 70 | logMessage "removing GPIO overlay from config.txt" 71 | sed -i -e "/$overlayBeginText/,/$overlayEndText/d" "$configFile" 72 | filesUpdated=true 73 | fi 74 | } 75 | 76 | function installGpioOverlay () 77 | { 78 | if [ -f "$configFile" ]; then 79 | # remove an existing GPIO overlay since it's now different 80 | if [ $(grep -c "$1" "$configFile") == 0 ]; then 81 | removeGpioOverlay 82 | fi 83 | 84 | logMessage "adding overlay $1 to config.txt" 85 | echo "$overlayBeginText" >> "$configFile" 86 | echo "dtoverlay=$1" >> "$configFile" 87 | echo "$overlayEndText" >> "$configFile" 88 | filesUpdated=true 89 | fi 90 | } 91 | 92 | 93 | #### running manually and OK to proceed - prompt for input 94 | if [ $scriptAction == 'NONE' ] ; then 95 | # display innitial message 96 | echo 97 | echo "This package makes the following modifications:" 98 | echo " Activates 6 relay outputs" 99 | echo " Activates 5 digital inputs" 100 | echo " Changes all digital inputs to have pull-ups in stead of a mix of pull up/down" 101 | 102 | standardActionPrompt 'MORE_PROMPTS' 103 | 104 | if [ $scriptAction == 'NONE' ]; then 105 | if [ -e "$setupOptionsDir/selectedGpioList" ]; then 106 | selectedGpioList=$( cat "$setupOptionsDir/selectedGpioList" ) 107 | else 108 | selectedGpioList=1 109 | fi 110 | 111 | if [ $selectedGpioList == 4 ] && ! [ -e "$setupOptionsDir/gpio_list" ]; then 112 | echo "WARNING: no custom gpio_list found" 113 | echo "create $setupOptionsDir/gpio_list then run this script again" 114 | echo "selecting legacy set for now" 115 | selectedGpioList=1 116 | fi 117 | 118 | gpioListText=( "unknown"\ 119 | "(1) legacy GPIO list"\ 120 | "(2) GPIO list for the Waveshare CAN Plus hat"\ 121 | "(3) GPIO list for most other 2-channel CANbus hats"\ 122 | "(4) custom GPIO list" ) 123 | echo 124 | echo "GPIO assignments for relays and digital inputs may conflict" 125 | echo " with some CANbus interface hats or other GPIO uses" 126 | echo "Conflicts WILL render the Raspberry PI at least partially unresponsive" 127 | echo "To help avoid conflicts, select the most appropirate GPIO list:" 128 | echo " ${gpioListText[1]}" 129 | echo " ${gpioListText[2]}" 130 | echo " ${gpioListText[3]}" 131 | echo " ${gpioListText[4]}" 132 | echo " manually create $setupOptionsDir/gpio_list" 133 | echo 134 | echo "current GPIO list: ${gpioListText[$selectedGpioList]}" 135 | while true ; do 136 | read -p "select new list by number (cr for no change): " response 137 | if [ -z $response ]; then 138 | echo "GPIO list unchanged: ${gpioListText[$selectedGpioList]}" 139 | break 140 | fi 141 | case $response in 142 | 1) 143 | selectedGpioList=1 144 | ;; 145 | 2) 146 | selectedGpioList=2 147 | ;; 148 | 3) 149 | selectedGpioList=3 150 | ;; 151 | 4) 152 | if [ -e "$setupOptionsDir/gpio_list" ]; then 153 | selectedGpioList=4 154 | else 155 | echo "WARNING: no custom GPIO list found" 156 | echo "create $setupOptionsDir/gpio_list then run this script again" 157 | echo "make another selection for now" 158 | continue 159 | fi 160 | ;; 161 | *) 162 | echo "$response is invalid" 163 | continue 164 | ;; 165 | esac 166 | echo "new GPIO list: ${gpioListText[$selectedGpioList]}" 167 | break 168 | done 169 | 170 | echo $selectedGpioList > "$setupOptionsDir/selectedGpioList" 171 | 172 | scriptAction='INSTALL' 173 | fi 174 | fi 175 | #### installing 176 | if [ $scriptAction == 'INSTALL' ] ; then 177 | 178 | 179 | if [ -e "$setupOptionsDir/selectedGpioList" ]; then 180 | selectedGpioList=$( cat "$setupOptionsDir/selectedGpioList" ) 181 | else 182 | selectedGpioList=1 183 | fi 184 | 185 | gpioOverlay="" 186 | case $selectedGpioList in 187 | 1) 188 | logMessage "installing ${gpioListText[$selectedGpioList]}" 189 | updateActiveFile "$gpioListFile" 190 | gpioOverlay="VenusGpioOverlayLegacy" 191 | 192 | ;; 193 | 2) 194 | logMessage "installing ${gpioListText[$selectedGpioList]}" 195 | updateActiveFile "$pkgFileSets/gpio_listForCanPlusHat" "$gpioListFile" 196 | gpioOverlay="VenusGpioOverlayForCanPlusHat" 197 | ;; 198 | 3) 199 | logMessage "installing ${gpioListText[$selectedGpioList]}" 200 | updateActiveFile "$pkgFileSets/gpio_listForCanHats" "$gpioListFile" 201 | gpioOverlay="VenusGpioOverlayForCanHats" 202 | ;; 203 | 4) 204 | if [ -e "$setupOptionsDir/gpio_list" ]; then 205 | logMessage "installing ${gpioListText[$selectedGpioList]}" 206 | updateActiveFile "$setupOptionsDir/gpio_list" "$gpioListFile" 207 | else 208 | setInstallFailed &EXIT_OPTIONS_NOT_SET "no custom GPIO list found" 209 | fi 210 | ;; 211 | *) 212 | setInstallFailed &EXIT_OPTIONS_NOT_SET "$selectedGpioList not a valid GPIO list number" 213 | ;; 214 | esac 215 | 216 | if ! $uninstallFailed ; then 217 | if ! [ -z $gpioOverlay ]; then 218 | installGpioOverlay $gpioOverlay 219 | else 220 | removeGpioOverlay 221 | fi 222 | fi 223 | fi 224 | 225 | # uninstalling - check scriptAction again 226 | # if an install step failed package needs to be removed 227 | if [ $scriptAction == 'UNINSTALL' ] ; then 228 | 229 | # remove mods from configFile - do not use restore in case other mods were made manually 230 | if [ -f "$configFile" ]; then 231 | removeGpioOverlay 232 | fi 233 | fi 234 | 235 | if $filesUpdated ; then 236 | rebootNeeded=true 237 | fi 238 | 239 | # thats all folks - SCRIPT EXITS INSIDE THE FUNCTION 240 | endScript 'INSTALL_FILES' 241 | 242 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.70~61.orig: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib 2 | import logging 3 | import os 4 | import traceback 5 | from glob import glob 6 | from functools import partial 7 | 8 | # Victron packages 9 | from ve_utils import exit_on_error 10 | 11 | from delegates.base import SystemCalcDelegate 12 | 13 | class RelayState(SystemCalcDelegate): 14 | RELAY_GLOB = '/dev/gpio/relay_*' 15 | 16 | def __init__(self): 17 | SystemCalcDelegate.__init__(self) 18 | self._relays = {} 19 | self._relay_dirs = sorted(glob(self.RELAY_GLOB)) 20 | 21 | def get_input(self): 22 | return [ 23 | ('com.victronenergy.settings', [ 24 | '/Settings/Relay/Function', 25 | '/Settings/Relay/1/Function', 26 | '/Settings/Relay/Polarity', 27 | '/Settings/Relay/1/Polarity'])] # Managed by venus-platform 28 | 29 | def get_settings(self): 30 | s = [ 31 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 32 | ('/SwitchableOutput/0/Settings/Group', '/Settings/Relay/0/Group', "", 0, 0), 33 | ('/SwitchableOutput/0/Settings/CustomName', '/Settings/Relay/0/CustomName', "", 0, 0), 34 | ('/SwitchableOutput/0/Settings/ShowUIControl', '/Settings/Relay/0/ShowUIControl', 1, 0, 0), 35 | 36 | ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1), 37 | ('/SwitchableOutput/1/Settings/Group', '/Settings/Relay/1/Group', "", 0, 0), 38 | ('/SwitchableOutput/1/Settings/CustomName', '/Settings/Relay/1/CustomName', "", 0, 0), 39 | ('/SwitchableOutput/1/Settings/ShowUIControl', '/Settings/Relay/1/ShowUIControl', 1, 0, 0), 40 | ] 41 | # Add settings for additional relays, for the tinkerers. These are not 42 | # managed by venus-platform. 43 | for i, r in enumerate(self._relay_dirs[2:], 2): 44 | s.extend(( 45 | (f'/Relay/{i}/State', f'/Settings/Relay/{i}/InitialState', 0, 0, 1), 46 | (f'/SwitchableOutput/{i}/Settings/Group', f'/Settings/Relay/{i}/Group', "", 0, 0), 47 | (f'/SwitchableOutput/{i}/Settings/CustomName', f'/Settings/Relay/{i}/CustomName', "", 0, 0), 48 | (f'/SwitchableOutput/{i}/Settings/ShowUIControl', f'/Settings/Relay/{i}/ShowUIControl', 1, 0, 0), 49 | )) 50 | return s 51 | 52 | def _relay_function(self, idx): 53 | return self._dbusmonitor.get_value('com.victronenergy.settings', 54 | ('/Settings/Relay/Function' if idx == 0 else 55 | f'/Settings/Relay/{idx}/Function'), 2) 56 | 57 | def set_relay_function(self, valid, idx, v): 58 | # check that function is allowed. The relevant bit must be in the 59 | # valid mask 60 | if 0 <= v <= 5 and bool(2**v & valid): 61 | self._dbusmonitor.set_value('com.victronenergy.settings', 62 | ('/Settings/Relay/Function' if idx == 0 else 63 | f'/Settings/Relay/{idx}/Function'), v) 64 | return True 65 | return False 66 | 67 | @property 68 | def relay_function(self): 69 | return self._relay_function(0) 70 | 71 | def _relay_polarity(self, idx): 72 | # Only manual polarity is flipped here. Alarm polarity is flipped 73 | # in venus-platform 74 | if self._relay_function(idx) == 2: 75 | # This ensures it can only ever return 0 or 1 76 | return int(self._dbusmonitor.get_value('com.victronenergy.settings', 77 | ('/Settings/Relay/Polarity' if idx == 0 else 78 | f'/Settings/Relay/{idx}/Polarity')) == 1) 79 | return 0 80 | 81 | def set_sources(self, dbusmonitor, settings, dbusservice): 82 | SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) 83 | 84 | if len(self._relay_dirs) == 0: 85 | logging.info('No relays found') 86 | return 87 | 88 | self._relays.update({i: os.path.join(r, 'value') \ 89 | for i, r in enumerate(self._relay_dirs) }) 90 | 91 | GLib.idle_add(exit_on_error, self._init_relay_state) 92 | logging.info('Relays found: {}'.format(', '.join(self._relays.values()))) 93 | 94 | def _init_relay_state(self): 95 | if self.relay_function is None: 96 | return True # Try again on the next idle event 97 | 98 | for idx, path in self._relays.items(): 99 | with self._dbusservice as s: 100 | s.add_path(f'/Relay/{idx}/State', value=None, writeable=True, 101 | onchangecallback=partial(self._on_relay_state_changed, idx)) 102 | 103 | # Switchable output paths 104 | s.add_path(f'/SwitchableOutput/{idx}/State', value=None, 105 | writeable=True, onchangecallback=partial(self._on_relay_state_changed, idx)) 106 | 107 | s.add_path(f'/SwitchableOutput/{idx}/Name', f'GX internal relay {idx+1}') 108 | s.add_path(f'/SwitchableOutput/{idx}/Status', value=None) 109 | 110 | # Switchable output settings 111 | for setting, typ in (('Group', str), ('CustomName', str), ('ShowUIControl', int)): 112 | s.add_path(p := f'/SwitchableOutput/{idx}/Settings/{setting}', 113 | value=self._settings[p], writeable=True, 114 | onchangecallback=partial(self._on_relay_setting_changed, idx, typ)) 115 | 116 | s.add_path(f'/SwitchableOutput/{idx}/Settings/Type', 117 | value=1, writeable=True, onchangecallback=(lambda p, v: v == 1)) # R/W, but only accepts toggle 118 | s.add_path(f'/SwitchableOutput/{idx}/Settings/ValidTypes', 119 | value=2) # Toggle 120 | 121 | # All functions for first relay, Manual and temperature for the rest 122 | functions = 0b111111 if idx == 0 else 0b10100 123 | s.add_path(f'/SwitchableOutput/{idx}/Settings/Function', 124 | value=self._relay_function(idx), writeable=True, 125 | onchangecallback=lambda p, v, f=functions, idx=idx: self.set_relay_function(f, idx, int(v))) 126 | s.add_path(f'/SwitchableOutput/{idx}/Settings/ValidFunctions', 127 | value=functions) 128 | 129 | # If relay is manual, restore previous state. Otherwise the 130 | # controlling service will set it correctly once it comes up. 131 | if (f := self._relay_function(idx)) == 2: 132 | try: 133 | state = self._settings[f'/Relay/{idx}/State'] 134 | except KeyError: 135 | pass 136 | else: 137 | self._set_relay_dbus_state(idx, state) # set dbus 138 | self.__on_relay_state_changed(idx, state) # set hardware 139 | elif f < 0: 140 | # relay is disabled, switch it off 141 | self._disable_relay(idx) 142 | self.__on_relay_state_changed(idx, 0) 143 | else: 144 | self.__update_relay_state(idx, path) 145 | 146 | # Watch changes and update dbus. Do we still need this? 147 | GLib.timeout_add(5000, exit_on_error, self._update_relay_state) 148 | return False 149 | 150 | def _update_relay_state(self): 151 | """ Maintenance tasked called periodically to make sure everything 152 | remains in sync. """ 153 | for idx, file_path in self._relays.items(): 154 | if self._relay_function(idx) < 0: # disabled 155 | self._disable_relay(idx) 156 | else: 157 | self.__update_relay_state(idx, file_path) 158 | 159 | # Make sure updates to relay function in settings is reflected here 160 | self._dbusservice[f'/SwitchableOutput/{idx}/Settings/Function'] = self._relay_function(idx) 161 | 162 | return True 163 | 164 | def __update_relay_state(self, idx, file_path): 165 | """ Sync back the actual state of the relay to dbus. """ 166 | try: 167 | with open(file_path, 'rt') as r: 168 | state = int(r.read().strip()) 169 | except (IOError, ValueError): 170 | traceback.print_exc() 171 | else: 172 | # Flip state if polarity is NC and function is manual 173 | state = state ^ self._relay_polarity(idx) 174 | self._set_relay_dbus_state(idx, state) 175 | 176 | def _set_relay_dbus_state(self, idx, state): 177 | self._dbusservice[f'/Relay/{idx}/State'] = state 178 | self._dbusservice[f'/SwitchableOutput/{idx}/State'] = state 179 | self._dbusservice[f'/SwitchableOutput/{idx}/Status'] = 0x09 if state else 0x00 180 | 181 | def _disable_relay(self, idx): 182 | self._dbusservice[f'/Relay/{idx}/State'] = None 183 | self._dbusservice[f'/SwitchableOutput/{idx}/State'] = None 184 | self._dbusservice[f'/SwitchableOutput/{idx}/Status'] = 0x20 185 | 186 | def __on_relay_state_changed(self, idx, state): 187 | try: 188 | # Flip state if polarity is NC and function is manual 189 | state = state ^ self._relay_polarity(idx) 190 | path = self._relays[idx] 191 | with open(path, 'wt') as w: 192 | w.write(str(state)) 193 | except IOError: 194 | traceback.print_exc() 195 | return False 196 | return True 197 | 198 | def _on_relay_state_changed(self, idx, dbus_path, value): 199 | """ This is called when a write is done from dbus. """ 200 | if self._relay_function(idx) < 0: 201 | return False # No writes to disabled relays 202 | 203 | try: 204 | state = int(bool(value)) 205 | except ValueError: 206 | traceback.print_exc() 207 | return False 208 | 209 | self._set_relay_dbus_state(idx, state) 210 | try: 211 | return self.__on_relay_state_changed(idx, state) 212 | finally: 213 | # Remember the state to restore after a restart 214 | self._settings[f'/Relay/{idx}/State'] = state 215 | 216 | def _on_relay_setting_changed(self, idx, _type, dbus_path, value): 217 | try: 218 | self._settings[dbus_path] = _type(value) 219 | except (KeyError, ValueError): 220 | return False 221 | return True 222 | -------------------------------------------------------------------------------- /FileSets/PatchSource/relaystate.py-v3.70~61: -------------------------------------------------------------------------------- 1 | from gi.repository import GLib 2 | import logging 3 | import os 4 | import traceback 5 | from glob import glob 6 | from functools import partial 7 | 8 | # Victron packages 9 | from ve_utils import exit_on_error 10 | 11 | from delegates.base import SystemCalcDelegate 12 | 13 | class RelayState(SystemCalcDelegate): 14 | RELAY_GLOB = '/dev/gpio/relay_*' 15 | 16 | def __init__(self): 17 | SystemCalcDelegate.__init__(self) 18 | self._relays = {} 19 | self._relay_dirs = sorted(glob(self.RELAY_GLOB)) 20 | 21 | def get_input(self): 22 | return [ 23 | ('com.victronenergy.settings', [ 24 | '/Settings/Relay/Function', 25 | '/Settings/Relay/1/Function', 26 | '/Settings/Relay/Polarity', 27 | '/Settings/Relay/1/Polarity'])] # Managed by venus-platform 28 | 29 | def get_settings(self): 30 | s = [ 31 | ('/Relay/0/State', '/Settings/Relay/0/InitialState', 0, 0, 1), 32 | ('/SwitchableOutput/0/Settings/Group', '/Settings/Relay/0/Group', "", 0, 0), 33 | ('/SwitchableOutput/0/Settings/CustomName', '/Settings/Relay/0/CustomName', "", 0, 0), 34 | ('/SwitchableOutput/0/Settings/ShowUIControl', '/Settings/Relay/0/ShowUIControl', 1, 0, 0), 35 | 36 | ('/Relay/1/State', '/Settings/Relay/1/InitialState', 0, 0, 1), 37 | ('/SwitchableOutput/1/Settings/Group', '/Settings/Relay/1/Group', "", 0, 0), 38 | ('/SwitchableOutput/1/Settings/CustomName', '/Settings/Relay/1/CustomName', "", 0, 0), 39 | ('/SwitchableOutput/1/Settings/ShowUIControl', '/Settings/Relay/1/ShowUIControl', 1, 0, 0), 40 | 41 | ('/Relay/2/State', '/Settings/Relay/2/InitialState', 0, 0, 1), 42 | ('/SwitchableOutput/2/Settings/Group', '/Settings/Relay/2/Group', "", 0, 0), 43 | ('/SwitchableOutput/2/Settings/CustomName', '/Settings/Relay/2/CustomName', "", 0, 0), 44 | ('/SwitchableOutput/2/Settings/ShowUIControl', '/Settings/Relay/2/ShowUIControl', 1, 0, 1), 45 | 46 | ('/Relay/3/State', '/Settings/Relay/3/InitialState', 0, 0, 1), 47 | ('/SwitchableOutput/3/Settings/Group', '/Settings/Relay/3/Group', "", 0, 0), 48 | ('/SwitchableOutput/3/Settings/CustomName', '/Settings/Relay/3/CustomName', "", 0, 0), 49 | ('/SwitchableOutput/3/Settings/ShowUIControl', '/Settings/Relay/3/ShowUIControl', 1, 0, 1), 50 | 51 | ('/Relay/4/State', '/Settings/Relay/4/InitialState', 0, 0, 1), 52 | ('/SwitchableOutput/4/Settings/Group', '/Settings/Relay/4/Group', "", 0, 0), 53 | ('/SwitchableOutput/4/Settings/CustomName', '/Settings/Relay/4/CustomName', "", 0, 0), 54 | ('/SwitchableOutput/4/Settings/ShowUIControl', '/Settings/Relay/4/ShowUIControl', 1, 0, 1), 55 | 56 | ('/Relay/5/State', '/Settings/Relay/5/InitialState', 0, 0, 1), 57 | ('/SwitchableOutput/5/Settings/Group', '/Settings/Relay/5/Group', "", 0, 0), 58 | ('/SwitchableOutput/5/Settings/CustomName', '/Settings/Relay/5/CustomName', "", 0, 0), 59 | ('/SwitchableOutput/5/Settings/ShowUIControl', '/Settings/Relay/5/ShowUIControl', 1, 0, 1), 60 | ] 61 | # Add settings for additional relays, for the tinkerers. These are not 62 | # managed by venus-platform. 63 | for i, r in enumerate(self._relay_dirs[2:], 2): 64 | s.extend(( 65 | (f'/Relay/{i}/State', f'/Settings/Relay/{i}/InitialState', 0, 0, 1), 66 | (f'/SwitchableOutput/{i}/Settings/Group', f'/Settings/Relay/{i}/Group', "", 0, 0), 67 | (f'/SwitchableOutput/{i}/Settings/CustomName', f'/Settings/Relay/{i}/CustomName', "", 0, 0), 68 | (f'/SwitchableOutput/{i}/Settings/ShowUIControl', f'/Settings/Relay/{i}/ShowUIControl', 1, 0, 0), 69 | )) 70 | return s 71 | 72 | def _relay_function(self, idx): 73 | return self._dbusmonitor.get_value('com.victronenergy.settings', 74 | ('/Settings/Relay/Function' if idx == 0 else 75 | f'/Settings/Relay/{idx}/Function'), 2) 76 | 77 | def set_relay_function(self, valid, idx, v): 78 | # check that function is allowed. The relevant bit must be in the 79 | # valid mask 80 | if 0 <= v <= 5 and bool(2**v & valid): 81 | self._dbusmonitor.set_value('com.victronenergy.settings', 82 | ('/Settings/Relay/Function' if idx == 0 else 83 | f'/Settings/Relay/{idx}/Function'), v) 84 | return True 85 | return False 86 | 87 | @property 88 | def relay_function(self): 89 | return self._relay_function(0) 90 | 91 | def _relay_polarity(self, idx): 92 | # Only manual polarity is flipped here. Alarm polarity is flipped 93 | # in venus-platform 94 | if self._relay_function(idx) == 2: 95 | # This ensures it can only ever return 0 or 1 96 | return int(self._dbusmonitor.get_value('com.victronenergy.settings', 97 | ('/Settings/Relay/Polarity' if idx == 0 else 98 | f'/Settings/Relay/{idx}/Polarity')) == 1) 99 | return 0 100 | 101 | def set_sources(self, dbusmonitor, settings, dbusservice): 102 | SystemCalcDelegate.set_sources(self, dbusmonitor, settings, dbusservice) 103 | 104 | if len(self._relay_dirs) == 0: 105 | logging.info('No relays found') 106 | return 107 | 108 | self._relays.update({i: os.path.join(r, 'value') \ 109 | for i, r in enumerate(self._relay_dirs) }) 110 | 111 | GLib.idle_add(exit_on_error, self._init_relay_state) 112 | logging.info('Relays found: {}'.format(', '.join(self._relays.values()))) 113 | 114 | def _init_relay_state(self): 115 | if self.relay_function is None: 116 | return True # Try again on the next idle event 117 | 118 | for idx, path in self._relays.items(): 119 | with self._dbusservice as s: 120 | s.add_path(f'/Relay/{idx}/State', value=None, writeable=True, 121 | onchangecallback=partial(self._on_relay_state_changed, idx)) 122 | 123 | # Switchable output paths 124 | s.add_path(f'/SwitchableOutput/{idx}/State', value=None, 125 | writeable=True, onchangecallback=partial(self._on_relay_state_changed, idx)) 126 | 127 | s.add_path(f'/SwitchableOutput/{idx}/Name', f'GX internal relay {idx+1}') 128 | s.add_path(f'/SwitchableOutput/{idx}/Status', value=None) 129 | 130 | # Switchable output settings 131 | for setting, typ in (('Group', str), ('CustomName', str), ('ShowUIControl', int)): 132 | s.add_path(p := f'/SwitchableOutput/{idx}/Settings/{setting}', 133 | value=self._settings[p], writeable=True, 134 | onchangecallback=partial(self._on_relay_setting_changed, idx, typ)) 135 | 136 | s.add_path(f'/SwitchableOutput/{idx}/Settings/Type', 137 | value=1, writeable=True, onchangecallback=(lambda p, v: v == 1)) # R/W, but only accepts toggle 138 | s.add_path(f'/SwitchableOutput/{idx}/Settings/ValidTypes', 139 | value=2) # Toggle 140 | 141 | # All functions for first relay, Manual and temperature for the rest 142 | functions = 0b111111 if idx == 0 else 0b10100 143 | s.add_path(f'/SwitchableOutput/{idx}/Settings/Function', 144 | value=self._relay_function(idx), writeable=True, 145 | onchangecallback=lambda p, v, f=functions, idx=idx: self.set_relay_function(f, idx, int(v))) 146 | s.add_path(f'/SwitchableOutput/{idx}/Settings/ValidFunctions', 147 | value=functions) 148 | 149 | # If relay is manual, restore previous state. Otherwise the 150 | # controlling service will set it correctly once it comes up. 151 | if (f := self._relay_function(idx)) == 2: 152 | try: 153 | state = self._settings[f'/Relay/{idx}/State'] 154 | except KeyError: 155 | pass 156 | else: 157 | self._set_relay_dbus_state(idx, state) # set dbus 158 | self.__on_relay_state_changed(idx, state) # set hardware 159 | elif f < 0: 160 | # relay is disabled, switch it off 161 | self._disable_relay(idx) 162 | self.__on_relay_state_changed(idx, 0) 163 | else: 164 | self.__update_relay_state(idx, path) 165 | 166 | # Watch changes and update dbus. Do we still need this? 167 | GLib.timeout_add(5000, exit_on_error, self._update_relay_state) 168 | return False 169 | 170 | def _update_relay_state(self): 171 | """ Maintenance tasked called periodically to make sure everything 172 | remains in sync. """ 173 | for idx, file_path in self._relays.items(): 174 | if self._relay_function(idx) < 0: # disabled 175 | self._disable_relay(idx) 176 | else: 177 | self.__update_relay_state(idx, file_path) 178 | 179 | # Make sure updates to relay function in settings is reflected here 180 | self._dbusservice[f'/SwitchableOutput/{idx}/Settings/Function'] = self._relay_function(idx) 181 | 182 | return True 183 | 184 | def __update_relay_state(self, idx, file_path): 185 | """ Sync back the actual state of the relay to dbus. """ 186 | try: 187 | with open(file_path, 'rt') as r: 188 | state = int(r.read().strip()) 189 | except (IOError, ValueError): 190 | traceback.print_exc() 191 | else: 192 | # Flip state if polarity is NC and function is manual 193 | state = state ^ self._relay_polarity(idx) 194 | self._set_relay_dbus_state(idx, state) 195 | 196 | def _set_relay_dbus_state(self, idx, state): 197 | self._dbusservice[f'/Relay/{idx}/State'] = state 198 | self._dbusservice[f'/SwitchableOutput/{idx}/State'] = state 199 | self._dbusservice[f'/SwitchableOutput/{idx}/Status'] = 0x09 if state else 0x00 200 | 201 | def _disable_relay(self, idx): 202 | self._dbusservice[f'/Relay/{idx}/State'] = None 203 | self._dbusservice[f'/SwitchableOutput/{idx}/State'] = None 204 | self._dbusservice[f'/SwitchableOutput/{idx}/Status'] = 0x20 205 | 206 | def __on_relay_state_changed(self, idx, state): 207 | try: 208 | # Flip state if polarity is NC and function is manual 209 | state = state ^ self._relay_polarity(idx) 210 | path = self._relays[idx] 211 | with open(path, 'wt') as w: 212 | w.write(str(state)) 213 | except IOError: 214 | traceback.print_exc() 215 | return False 216 | return True 217 | 218 | def _on_relay_state_changed(self, idx, dbus_path, value): 219 | """ This is called when a write is done from dbus. """ 220 | if self._relay_function(idx) < 0: 221 | return False # No writes to disabled relays 222 | 223 | try: 224 | state = int(bool(value)) 225 | except ValueError: 226 | traceback.print_exc() 227 | return False 228 | 229 | self._set_relay_dbus_state(idx, state) 230 | try: 231 | return self.__on_relay_state_changed(idx, state) 232 | finally: 233 | # Remember the state to restore after a restart 234 | self._settings[f'/Relay/{idx}/State'] = state 235 | 236 | def _on_relay_setting_changed(self, idx, _type, dbus_path, value): 237 | try: 238 | self._settings[dbus_path] = _type(value) 239 | except (KeyError, ValueError): 240 | return False 241 | return True 242 | --------------------------------------------------------------------------------