Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f9bca18
Added test code for cable_box (orange pi zero 3 version)
rekkitcwts Jul 23, 2026
0455975
Downgraded to gpiod 1.x
rekkitcwts Jul 23, 2026
7d0d6af
Replaced functions
rekkitcwts Jul 23, 2026
a45b227
Downgrading the gpiod 2.x to 1.x
rekkitcwts Jul 23, 2026
da0b08f
Replacing condition
rekkitcwts Jul 24, 2026
b64060a
Fix syntax error
rekkitcwts Jul 24, 2026
e1595cd
Fix wrong attribute for event
rekkitcwts Jul 24, 2026
47d7015
Fix syntax error again
rekkitcwts Jul 24, 2026
f8eb713
Fixed another syntax error
rekkitcwts Jul 24, 2026
bd0537f
Added debug code to start the XORG
rekkitcwts Jul 24, 2026
4e2a389
Xorg failed. Replaced with another test
rekkitcwts Jul 24, 2026
524a649
Tested startx
rekkitcwts Jul 24, 2026
dd1300f
Changed the dummy test debug button into a refresh/restart button in …
rekkitcwts Jul 24, 2026
2c2077c
Renamed opizero3 cable box file due to gpiod version diffs
rekkitcwts Jul 25, 2026
5684cfa
Renamed variables to separate one GPIO-based button from another
rekkitcwts Jul 25, 2026
7adc5b4
Fixed some tab vs space errors
rekkitcwts Jul 25, 2026
46a5c3f
Fixed another taberror
rekkitcwts Jul 25, 2026
70133b6
Fixed error in renamed variable
rekkitcwts Jul 25, 2026
c4037a9
Fixed another renamed variable
rekkitcwts Jul 25, 2026
967c407
Merge branch 'shane-mason:main' into main
rekkitcwts Jul 26, 2026
b6bbb52
Merge branch 'shane-mason:main' into main
rekkitcwts Jul 29, 2026
9f49db2
Ported more code from the Raspberry Pi version
rekkitcwts Jul 29, 2026
0797a8c
Merge branch 'main' of https://github.com/rekkitcwts/FieldStation42
rekkitcwts Jul 29, 2026
7143835
Added channel down button
rekkitcwts Jul 30, 2026
3bd3b4c
[Orange Pi Zero 3] Added README for cable box script
rekkitcwts Jul 30, 2026
58b125d
Merge branch 'shane-mason:main' into main
rekkitcwts Jul 31, 2026
0411765
Merge branch 'shane-mason:main' into main
rekkitcwts Aug 6, 2026
eab6b2a
Added v2 of cablebox code, preparing for TM1637 support
rekkitcwts Aug 8, 2026
5229bd4
Merge branch 'main' of https://github.com/rekkitcwts/FieldStation42
rekkitcwts Aug 8, 2026
41b66e1
Added test code for remote support, requires orange pi zero 3 expansi…
rekkitcwts Aug 8, 2026
c8ae5f6
Made IR detection flexible
rekkitcwts Aug 8, 2026
bb26ade
Added guide channel button
rekkitcwts Aug 9, 2026
e5a78e0
Fixed missing debounce time variable
rekkitcwts Aug 9, 2026
a41809e
Borrowed some code from the original remote controller script and imp…
rekkitcwts Aug 9, 2026
d318ee2
Added OSD restart feature
rekkitcwts Aug 9, 2026
fd710d0
Added tm1637 module to cable box v2 code
rekkitcwts Aug 9, 2026
1739592
Merge branch 'shane-mason:main' into main
rekkitcwts Aug 10, 2026
2e54b40
Added more tm1637 to cablebox v2 code
rekkitcwts Aug 10, 2026
3cb36db
Added missing parts of tm1637 code
rekkitcwts Aug 10, 2026
d996e7e
Added missing variable
rekkitcwts Aug 10, 2026
9d68d57
Added a few more lines that check remote digits
rekkitcwts Aug 10, 2026
a6cc4c7
Ported more functions from original remote controller code
rekkitcwts Aug 10, 2026
ecd10f6
Mapped remaining buttons
rekkitcwts Aug 10, 2026
e329a1d
Hardcoded station_player resolutions to try to fix autobump issues
rekkitcwts Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions docs/ORANGE_PI_ZERO3_CABLE_BOX_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# FieldStation42 Cable Box Script for Orange Pi Zero 3

## Overview

This is an alternative implementation of the FieldStation42 cable box script for the **Orange Pi Zero 3** running **Armbian**.

Unlike the Raspberry Pi implementation (`cable_box.py`), this version uses **libgpiod v1** to access GPIO pins through the Linux GPIO character device interface.

This script was tested using:

* Orange Pi Zero 3
* Armbian (Linux 6.6.x)
* Python 3
* libgpiod v1 Python bindings (`gpiod < 2`)

---

# Hardware

The current implementation assumes three physical push buttons:

| Function | GPIO Pin | GPIO Line |
| ---------------------- | -------- | --------: |
| Restart FieldStation42 | PH2 | 226 |
| Channel Up | PC11 | 75 |
| Channel Down | PC15 | 79 |

The buttons are configured as active-low.

Current prototype hardware uses a breadboard.

---

# Prerequisites

Install the required system packages:

```bash
sudo apt update
sudo apt install gpiod python3-pip
```

Install the Python bindings for **libgpiod v1**:

```bash
pip3 install "gpiod<2"
```

> **Note**
>
> This script was written against the libgpiod v1 API.
>
> libgpiod v2 introduced API changes and is **not compatible** with this script.

---

# GPIO Access

Your user must have permission to access `/dev/gpiochip*`.

On Armbian this is typically done by adding the user to the `gpio` group.

Example:

```bash
sudo usermod -aG gpio $USER
```

Log out and log back in (or reboot) afterwards.

To verify GPIO access:

```bash
gpiodetect
```

Example output:

```text
gpiochip0 [platform] (27 lines)
```

To inspect GPIO line assignments:

```bash
gpioinfo
```

The script uses:

```
PH2 -> line 226
PC11 -> line 75
PC15 -> line 79
```

The `gpioinfo` utility is part of **libgpiod** and can identify GPIO chips by number, name, or device path (for example `/dev/gpiochip0`).

---

# Installing the Script

Copy

```
cable_box_opizero3-gpiodv1.py
```

into the same directory as the original cable box script.

---

# Updating the Service

The default service is configured for Raspberry Pi:

```
cable_box.py
```

Edit the user service:

```bash
nano ~/.config/systemd/user/fs42-cable-box.service
```

Replace the Raspberry Pi script:

```text
ExecStart=/usr/bin/python3 .../cable_box.py
```

with

```text
ExecStart=/usr/bin/python3 .../cable_box_opizero3-gpiodv1.py
```

Reload systemd:

```bash
systemctl --user daemon-reload
systemctl --user restart fs42-cable-box
```

---

# Current Button Mapping

| Button | Action |
| ------------ | ---------------------------------------- |
| Channel Up | Moves to the next channel |
| Channel Down | Moves to the previous channel |
| Restart | Restarts the FieldStation42 user service |

The restart button is intended as a recovery button if the frontend becomes unresponsive.

---

# Notes

* This implementation intentionally mirrors the behaviour of the Raspberry Pi cable box script while replacing the GPIO backend with libgpiod.
* GPIO numbering follows the Orange Pi Zero 3 pin assignments reported by `gpioinfo`.
* This implementation currently supports physical buttons only.
* IR receiver support is not yet implemented.

---

# Acknowledgements

Based on the original Raspberry Pi cable box implementation included with FieldStation42.

Orange Pi Zero 3 support implemented using the Linux GPIO character device (`libgpiod`).
108 changes: 108 additions & 0 deletions fs42/pi/cable_box_opizero3-gpiodv1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import json
import time
import os
import sys
import subprocess

# Orange Pi gpiod
import gpiod
from datetime import timedelta

# Adjust chip and line offset based on your configuration
# (e.g., /dev/gpiochip0 and line offset for your specific pin)
# PH2 - restart button, in case FS42 freezes
# PC11 - channel up
# PC15 - channel down
CHIP_PATH = "/dev/gpiochip0"
RESET_LINE_OFFSET = 226 # Pin 8, labeled PH2
CHANNEL_UP_LINE_OFFSET = 75 # Pin 12, labeled PC11
CHANNEL_DOWN_LINE_OFFSET = 79 # Pin 16, labeled PC15

class CableBox:
def __init__(self, channel_socket="runtime/channel.socket", status_socket="runtime/play_status.socket", press_socket="runtime/press.socket"):
self.channel_socket = channel_socket
self.status_socket = status_socket
self.press_socket = press_socket

self.chip = gpiod.chip(CHIP_PATH)
self.reset_line = self.chip.get_line(RESET_LINE_OFFSET)
self.channel_up_line = self.chip.get_line(CHANNEL_UP_LINE_OFFSET)
self.channel_down_line = self.chip.get_line(CHANNEL_DOWN_LINE_OFFSET)

config = gpiod.line_request()
config.consumer = "fieldstation42"
config.request_type = gpiod.line_request.EVENT_BOTH_EDGES

self.reset_line.request(config)
self.channel_up_line.request(config)
self.channel_down_line.request(config)

self.temp_mode = False
self.last_button_time = time.monotonic()
self.show_time = True
self.time_format = "%H:%M"

def send_command(self, command, channel=-1):
as_obj = {"command": command, "channel": channel}
as_str = json.dumps(as_obj)
print(f"Sending command: {as_str}")
with open(self.channel_socket, "w") as fp:
fp.write(as_str)

# This is where the GPIO buttons are pressed
def read_keys(self):
if self.reset_line.event_wait(timedelta(milliseconds=50)):
reset_event = self.reset_line.event_read()

if reset_event.event_type == gpiod.line_event.FALLING_EDGE:
print("Reset button pressed!")
return "RESET_BUTTON"

if self.channel_up_line.event_wait(timedelta(milliseconds=50)):
channel_up_event = self.channel_up_line.event_read()

if channel_up_event.event_type == gpiod.line_event.FALLING_EDGE:
print("Channel Up Button pressed!")
return "CHANNEL_UP_BUTTON"
if self.channel_down_line.event_wait(timedelta(milliseconds=50)):
channel_down_event = self.channel_down_line.event_read()

if channel_down_event.event_type == gpiod.line_event.FALLING_EDGE:
print("Channel Down Button pressed!")
return "CHANNEL_DOWN_BUTTON"

return None

def event_loop(self):
last_pressed = ""
in_selection = False
last_selection_tick = -1
channel_num = 0
tick_count = 0
while True:
key_pressed = self.read_keys()

# Print something here for now
if key_pressed:
print("Key pressed:", key_pressed)

if key_pressed == "RESET_BUTTON": # Restarts FS42
print("Button pressed from service!", flush=True)
os.system("systemctl --user restart fs42")
time.sleep(0.3)
elif key_pressed == "CHANNEL_UP_BUTTON": # Moves channel UP
self.send_command("up")
channel_num += 1
# self.tm.show("----")
in_selection = False
elif key_pressed == "CHANNEL_DOWN_BUTTON": # Moves channel DOWN
if channel_num > 0:
self.send_command("down")
channel_num -= 1
# self.tm.show("----")
in_selection = False


if __name__ == "__main__":
cable_box = CableBox()
cable_box.event_loop()
Loading