Skip to content

Commit acb93d7

Browse files
committed
switch to uv
1 parent b85bd34 commit acb93d7

11 files changed

Lines changed: 1113 additions & 1000 deletions

File tree

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
exclude = .venv

.github/workflows/pythonpackage.yml

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,18 @@ jobs:
1515

1616
steps:
1717
- uses: actions/checkout@v6
18+
- name: Install uv
19+
uses: astral-sh/setup-uv@v6
1820
- name: Set up Python ${{ matrix.python-version }}
19-
uses: actions/setup-python@v6
20-
with:
21-
python-version: ${{ matrix.python-version }}
21+
run: uv python install ${{ matrix.python-version }}
2222
- name: Install dependencies
23-
run: |
24-
python -m pip install --upgrade pip
25-
pip install pipenv
26-
pipenv install --dev --python `which python3`
23+
run: uv sync --python ${{ matrix.python-version }}
2724
- name: Lint with flake8
2825
run: |
2926
# stop the build if there are Python syntax errors or undefined names
30-
pipenv run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
27+
uv run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
3128
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
32-
pipenv run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
29+
uv run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3330
- name: Test with pytest
3431
run: |
35-
pipenv run pytest
32+
uv run pytest

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
*.pyc
2+
*.egg-info/
3+
24
.DS_Store
35
.env
6+
47
.cache/
8+
.mypy_cache/
59
.tox/
10+
.venv/
611
dist/

MANIFEST.in

Lines changed: 0 additions & 3 deletions
This file was deleted.

Pipfile

Lines changed: 0 additions & 17 deletions
This file was deleted.

Pipfile.lock

Lines changed: 0 additions & 734 deletions
This file was deleted.

README.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# python-roku
2+
3+
Screw remotes. Control your [Roku](http://www.roku.com) via Python.
4+
5+
Supports Python 3.10 to 3.14.
6+
7+
## Installation
8+
9+
```
10+
uv add roku
11+
```
12+
13+
or
14+
15+
```
16+
pip install roku
17+
```
18+
19+
## Usage
20+
21+
### The Basics
22+
23+
To start, import the Roku object and create it with the IP address or hostname of your Roku.
24+
25+
```python
26+
>>> from roku import Roku
27+
>>> roku = Roku('192.168.10.163')
28+
```
29+
30+
The Roku object has a method for each of the buttons on the remote.
31+
32+
```python
33+
>>> roku.home()
34+
>>> roku.right()
35+
>>> roku.select()
36+
```
37+
38+
To support keyup and keydown events simply pass "keyup" or "keydown" when you call the command.
39+
40+
```python
41+
>>> roku.right("keydown")
42+
>>> roku.right("keyup")
43+
```
44+
45+
To see a full list of available commands, use the *commands* property.
46+
47+
```python
48+
>>> roku.commands
49+
['back', 'backspace', 'down', 'enter', 'forward', 'home', 'info', 'left', 'literal', 'play', 'replay', 'reverse', 'right', 'search', 'select', 'up']
50+
```
51+
52+
If you are following along on your home network and are connected to your Roku, you should see it doing stuff. *Cool!*
53+
54+
### Apps
55+
56+
The *apps* property will return a list of the applications on your device.
57+
58+
```python
59+
>>> roku.apps
60+
[<Application: [2285] Hulu Plus v2.7.6>, <Application: [13] Amazon Instant Video v5.1.3>, <Application: [20445] VEVO v2.0.12092013>]
61+
```
62+
63+
Apps have *id*, *name*, and *version* properties.
64+
65+
```python
66+
>>> app = roku.apps[0]
67+
>>> print(app.id, app.name, app.version)
68+
2285 Hulu Plus 2.7.6
69+
```
70+
71+
You can get an individual app from the Roku object by either its *name* or *id*.
72+
73+
```python
74+
>>> roku['Hulu Plus']
75+
<Application: [2285] Hulu Plus v2.7.6>
76+
>>> roku[2285]
77+
<Application: [2285] Hulu Plus v2.7.6>
78+
```
79+
80+
Seeing the reference to this Hulu Plus app makes me really want to watch the latest episode of [Nashville](http://abc.go.com/shows/nashville). Let's launch it!
81+
82+
```python
83+
>>> hulu = roku['Hulu Plus']
84+
>>> hulu.launch()
85+
```
86+
87+
Again, if you are following along at home, you should see that your Roku has launched the Hulu Plus app. Want to see the app's entry in the Channel Store?
88+
89+
```python
90+
>>> hulu.store()
91+
```
92+
93+
You can also get the app's icon.
94+
95+
```python
96+
>>> with open('hulu.png', 'w') as f:
97+
... f.write(hulu.icon)
98+
99+
>>> print hulu.icon_url
100+
http://0.0.0.0:8060/query/icon/2285
101+
```
102+
103+
You can get the current running app.
104+
105+
```python
106+
>>> roku.active_app
107+
<Application: [12] Netflix v4.2.75015046>
108+
```
109+
110+
### Entering Text
111+
112+
Okay, I've already seen all of the available episodes of Nashville, so I'm going to search for *Stargate*. With the search open and waiting for text entry:
113+
114+
```python
115+
>>> roku.literal('stargate')
116+
```
117+
118+
What if I now want to watch *The Informant!*? Again, with the search open and waiting for text entry:
119+
120+
```python
121+
>>> roku.literal('The Informant!')
122+
```
123+
124+
This will iterate over each character, sending it individually to the Roku.
125+
126+
## Advanced Stuff
127+
128+
### Discovery
129+
130+
Roku devices can be discovered using [SSDP](http://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol). A class method is available on the Roku object that will return Roku object instances for each device found on the same network.
131+
132+
```python
133+
>>> Roku.discover()
134+
[<Roku: 192.168.10.163:8060>]
135+
```
136+
137+
It may take a few seconds for a device to be found. You can call discover again or change the *timeout* or *retries* parameters on the discover method. This will take longer, but will find more devices.
138+
139+
```python
140+
>>> Roku.discover(timeout=10)
141+
[<Roku: 192.168.10.163:8060>, <Roku: 192.168.10.204:8060>]
142+
```
143+
144+
Thanks to [Dan Krause](https://github.com/dankrause) for his [SSDP code](https://gist.github.com/dankrause/6000248).
145+
146+
### Sensors
147+
148+
Newer Roku remotes have extra sensors built into them that measure acceleration, orientation, and other things. You can mimic these sensors using the provided helper methods.
149+
150+
```python
151+
>>> roku.orientation(1, 1, 1)
152+
```
153+
154+
The parameters to all of the sensor methods are x, y, and z values. Available methods include:
155+
156+
- acceleration - in each dimension relative to free fall measured in meters/sec^2
157+
- magnetic - magnetic field strength in microtesla
158+
- orientation - angular displacement from flat/level and north in radians
159+
- rotation - angular rotation rate about each axis using the right hand rule in radians/sec
160+
161+
### Touch
162+
163+
Some Roku input devices support touch. The parameters to the *touch* method are the *x* and *y* coordinates of the touch.
164+
165+
```python
166+
>>> roku.touch(10, 40)
167+
```
168+
169+
You can change the event triggered by passing an optional *op* parameter.
170+
171+
```python
172+
>>> roku.touch(10, 40, op='up')
173+
```
174+
175+
Supported events are:
176+
177+
- down
178+
- up
179+
- press (down and up)
180+
- move
181+
- cancel
182+
183+
Multitouch is not yet supported in this package.
184+
185+
### Generic Input
186+
187+
Both the sensor and touch methods rely on the generic *input* method for sending data to a running application. If you refuse to use covenience methods because they make people lazy and weak, you can call the sensor and touch methods directly.
188+
189+
```python
190+
>>> params = {'touch.0.x': 10, 'touch.0.y': 20, 'touch.0.op': 'press'}
191+
>>> roku.input(params)
192+
```
193+
194+
More information about input, touch, and sensors is available in the [Roku External Control docs](http://sdkdocs.roku.com/display/sdkdoc/External+Control+Guide#ExternalControlGuide-31ExternalControlInputCommandConventions).
195+
196+
## TODO
197+
198+
- Multitouch support.
199+
- A Flask proxy server that can listen to requests and forward them to devices on the local network. Control multiple devices at once, eh?
200+
- A server that mimics the Roku interface so you can make your own Roku-like stuff.
201+
- A task runner that will take a set of commands and run them with delays that are appropriate for most devices.

0 commit comments

Comments
 (0)