Coder Social home page Coder Social logo

python-roku's Introduction

python-roku

Screw remotes. Control your Roku via Python.

Supports Python 3.7 to 3.11.

Installation

pip install roku

Usage

The Basics

To start, import the Roku object and create it with the IP address or hostname of your Roku. :

>>> from roku import Roku
>>> roku = Roku('192.168.10.163')

The Roku object has a method for each of the buttons on the remote. :

>>> roku.home()
>>> roku.right()
>>> roku.select()

To support keyup and keydown events simply pass "keyup" or "keydown" when you call the command. :

>>> roku.right("keydown")
>>> roku.right("keyup")

To see a full list of available commands, use the commands property. :

>>> roku.commands
['back', 'backspace', 'down', 'enter', 'forward', 'home', 'info', 'left', 'literal', 'play', 'replay', 'reverse', 'right', 'search', 'select', 'up']

If you are following along on your home network and are connected to your Roku, you should see it doing stuff. Cool!

Apps

The apps property will return a list of the applications on your device. :

>>> roku.apps
[<Application: [2285] Hulu Plus v2.7.6>, <Application: [13] Amazon Instant Video v5.1.3>, <Application: [20445] VEVO v2.0.12092013>]

Apps have id, name, and version properties. :

>>> app = roku.apps[0]
>>> print(app.id, app.name, app.version)
2285 Hulu Plus 2.7.6

You can get an individual app from the Roku object by either its name or id. :

>>> roku['Hulu Plus']
<Application: [2285] Hulu Plus v2.7.6>
>>> roku[2285]
<Application: [2285] Hulu Plus v2.7.6>

Seeing the reference to this Hulu Plus app makes me really want to watch the latest episode of Nashville. Let's launch it! :

>>> hulu = roku['Hulu Plus']
>>> hulu.launch()

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? :

>>> hulu.store()

You can also get the app's icon. :

>>> with open('hulu.png', 'w') as f:
...     f.write(hulu.icon)

>>> print hulu.icon_url
http://0.0.0.0:8060/query/icon/2285

You can get the current running app. :

>>> roku.active_app
<Application: [12] Netflix v4.2.75015046>

Entering Text

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:

>>> roku.literal('stargate')

What if I now want to watch The Informant!? Again, with the search open and waiting for text entry:

>>> roku.literal('The Informant!')

This will iterate over each character, sending it individually to the Roku.

Advanced Stuff

Discovery

Roku devices can be discovered using SSDP. A class method is available on the Roku object that will return Roku object instances for each device found on the same network. :

>>> Roku.discover()
[<Roku: 192.168.10.163:8060>]

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. :

>>> Roku.discover(timeout=10)
[<Roku: 192.168.10.163:8060>, <Roku: 192.168.10.204:8060>]

Thanks to Dan Krause for his SSDP code.

Sensors

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. :

>>> roku.orientation(1, 1, 1)

The parameters to all of the sensor methods are x, y, and z values. Available methods include:

  • acceleration - in each dimension relative to free fall measured in meters/sec^2
  • magnetic - magnetic field strength in microtesla
  • orientation - angular displacement from flat/level and north in radians
  • rotation - angular rotation rate about each axis using the right hand rule in radians/sec

Touch

Some Roku input devices support touch. The parameters to the touch method are the x and y coordinates of the touch. :

>>> roku.touch(10, 40)

You can change the event triggered by passing an optional op parameter. :

>>> roku.touch(10, 40, op='up')

Supported events are:

  • down
  • up
  • press (down and up)
  • move
  • cancel

Multitouch is not yet supported in this package.

Integrations

Generic Input

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. :

>>> params = {'touch.0.x': 10, 'touch.0.y': 20, 'touch.0.op': 'press'}
>>> roku.input(params)

More information about input, touch, and sensors is available in the Roku External Control docs.

TODO

  • Tests, of course.
  • Multitouch support.
  • A Flask proxy server that can listen to requests and forward them to devices on the local network. Control multiple devices at once, eh?
  • A server that mimics the Roku interface so you can make your own Roku-like stuff.
  • A task runner that will take a set of commands and run them with delays that are appropriate for most devices.

python-roku's People

Contributors

alberthdev avatar brentahughes avatar bschlenk avatar bwarden avatar ctalkington avatar dannixon avatar davidcdean avatar dbehnke avatar dependabot[bot] avatar frdfsnlght avatar iantrich avatar jcarbaugh avatar keatontaylor avatar kk7ds avatar kyesh avatar mjg59 avatar pvizeli avatar ronnie-llamado avatar soberstadt avatar springstan avatar zombielinux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-roku's Issues

Remove lxml requirement

It's really not that much XML we're dealing with here. Kind of a waste to install lxml just for a small bit of it.

Invoke options?

Is there a way now (or, if not, could a way be added?) to invoke the options menu? I can do this now by going back to home and using up, down, and select but if there were a way to invoke the equivalent of pressing the option key on the TCL/Roku remote that would be more efficient.

Missing __main__ error

Hi there! I get the following error when I try to run this package:

No module named roku. main; 'roku' is a package and cannot be directly executed

Any idea what I'm doing wrong?

Add timeout option to python-roku

Sometimes, the Roku might freeze up for whatever reason, causing any python-roku calls to never return. By default, the requests library does not have a timeout set, which means that any requests sent will be blocked forever until the request is closed forcefully on either side.

That said, it looks like this wouldn't be too bad to add to on the requests side of things - however, I'm not totally sure how this would look from python-roku's perspective.

Two proposed API changes:

  • Add timeout option to all method calls:

    This would allow devs to do something like r.launch(app, timeout=30) to try and launch an app within 30 seconds, and letting requests throw an exception if this timeout is exceeded. There might be issues when dealing with properties, though (such as r.apps).

  • Add timeout object property:

    Devs could change the timeout property within their Roku object to adjust how the Roku object should behave for all operations that involve the network. This could easily be changed within the object, as well as passed into the object constructor to allow customization.

    Example:

    r = Roku('192.168.1.2', timeout=30)
    r.home()
    r.timeout = 10
    r.home()

For both API changes, I think passing the timeout option transparently to requests would be interesting, since it would allow some fine-tuning of timeout options. requests allows you to optionally specify a timeout tuple containing both the connect and read timeouts, which might offer users some flexibility should they need to customize this behavior.

Finally, if this API is added, what would be a good default for the timeout? My guess would be ~30 seconds, but not sure what others have experienced with Roku's delays...

What do you think?

Idea as an addition to your library

if possible, it would be amazing to be able to remove text I know literal adds text but removing text is annoying to do with the library i know the extreme limitations to things like this but just an idea

Running roku commands from Python IDE

I am trying to accept commands through a loop like this example below, but it seems the specified IP is not 'saved' in the console. This question may be more suited for stackoverflow as I'm sure it is user error. There is a lot I do not know.

from roku import Roku
roku = Roku('ipaddresshere')
while True:
    user_input = input('--> ')
    if user_input.lower() == 'home':
        roku.home()

This however does not work. Is there a way for this to be done?

Not discovering

Roku.discover(timeout=10)

is not always finding the Roku. In fact most times it does not. IP of device didn't change but it just doesn't find it, returns empty array.

View Currently Running Application.

Is there a way to view the current application that is running, or if there is not applications running? I am using this so that i can keep an application alive. But i do not know of a way to view if the application has closed, or is still up, or crashed. Thank you!

New release pushed to pypi?

It has been a while since pypi was updated. I have looked into updating pypi but the project is still versioned at 2.0.

I would like to get a 2.1 pushed and pypi updated

Power control?

Any plans to push power control to python-roku? I see you were experimenting with this in another thread. Happy to assist! I'm able to power on the unit with WOL, not sure how to shut it off, yet.

Get volume?

Is there a way to get the current volume?

Add 'poweron' keypress support

Power and PowerOff are supported here:

python-roku/roku/core.py

Lines 58 to 60 in 54cb21e

# For devices that support being turned on/off
'power': 'Power',
'poweroff': 'PowerOff',

Power is functionally a toggle operation. While the ECP doesn't mention PowerOn, it's accepted by at least some models, so it would be nice to add an explicit poweron method to ensure the Roku device is on, regardless of prior state.

Testing, demonstrating that Power, PowerOn, and PowerOff are all accepted with HTTP status 202, while phony PowerBad is rejected with 400:

$ curl -v -d '' http://192.168.101.59:8060/keypress/Power
*   Trying 192.168.101.59...
* Connected to 192.168.101.59 (192.168.101.59) port 8060 (#0)
> POST /keypress/Power HTTP/1.1
> Host: 192.168.101.59:8060
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 0
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 202 Accepted
< Server: Roku UPnP/1.0 MiniUPnPd/1.4
< Content-Length: 0
<
* Connection #0 to host 192.168.101.59 left intact
$ curl -v -d '' http://192.168.101.59:8060/keypress/PowerOn
*   Trying 192.168.101.59...
* Connected to 192.168.101.59 (192.168.101.59) port 8060 (#0)
> POST /keypress/PowerOn HTTP/1.1
> Host: 192.168.101.59:8060
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 0
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 202 Accepted
< Server: Roku UPnP/1.0 MiniUPnPd/1.4
< Content-Length: 0
<
* Connection #0 to host 192.168.101.59 left intact
$ curl -v -d '' http://192.168.101.59:8060/keypress/PowerOff
*   Trying 192.168.101.59...
* Connected to 192.168.101.59 (192.168.101.59) port 8060 (#0)
> POST /keypress/PowerOff HTTP/1.1
> Host: 192.168.101.59:8060
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 0
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 202 Accepted
< Server: Roku UPnP/1.0 MiniUPnPd/1.4
< Content-Length: 0
<
* Connection #0 to host 192.168.101.59 left intact
$ curl -v -d '' http://192.168.101.59:8060/keypress/PowerBad
*   Trying 192.168.101.59...
* Connected to 192.168.101.59 (192.168.101.59) port 8060 (#0)
> POST /keypress/PowerBad HTTP/1.1
> Host: 192.168.101.59:8060
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 0
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 400 Bad Request
< Server: Roku UPnP/1.0 MiniUPnPd/1.4
< Content-Length: 0
* HTTP error before end of send, stop sending
<
* Closing connection 0
$


AttributeError: 'NoneType' object has no attribute 'launch'

Traceback (most recent call last):
File "d:\Google Drive\Coding\Python\Scripts\Roku-Control\Roku.py", line 21, in
Change_to_ABC()
File "d:\Google Drive\Coding\Python\Scripts\Roku-Control\Roku.py", line 11, in Change_to_ABC
YoutubeTV.launch()

AttributeError: 'NoneType' object has no attribute 'launch'

I can get many buttons to work but launching does not work on my Roku Ultra at this time.

Missing shared object file on import

I'm trying to use this package on a Raspberry Pi 3b+ running latest Raspbian (headless, so no X, if that matters). When I import I get:

  File "/usr/local/lib/python3.7/dist-packages/roku/__init__.py", line 1, in <module>
    from roku.core import Roku, Application, Channel, RokuException, __version__
  File "/usr/local/lib/python3.7/dist-packages/roku/core.py", line 4, in <module>
    from lxml import etree as ET
ImportError: libxslt.so.1: cannot open shared object file: No such file or directory

Any idea what's going on?

libxml2 (which provides libxslt, I believe) seems to be installed on my system:

libxml2/stable,now 2.9.4+dfsg1-7+b1 armhf [installed,automatic]

Any ideas?

Not working on python 3.7

Roku==3.0.0 is not building on python 3.7. It is failing on the lxml install. I have a higher level version of lxml running and it wants to downgrade but is not building.

pip install roku==3.0.0
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting roku==3.0.0
Using cached https://www.piwheels.org/simple/roku/roku-3.0-py3-none-any.whl
Requirement already satisfied: six in ./lib/python3.7/site-packages (from roku==3.0.0) (1.11.0)
Collecting requests<2.11,>=2.10 (from roku==3.0.0)
Using cached https://files.pythonhosted.org/packages/99/b4/63d99ba8e189c47d906b43bae18af4396e336f2b1bfec86af31efe2d2cb8/requests-2.10.0-py2.py3-none-any.whl
Collecting lxml<3.7,>=3.6 (from roku==3.0.0)
Using cached https://files.pythonhosted.org/packages/4f/3f/cf6daac551fc36cddafa1a71ed48ea5fd642e5feabd3a0d83b8c3dfd0cb4/lxml-3.6.4.tar.gz
Building wheels for collected packages: lxml
Building wheel for lxml (setup.py) ... error
ERROR: Command errored out with exit status 1:
command: /home/pi/homeassistant3.7/bin/python3.7 -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-axaz03dc/lxml/setup.py'"'"'; file='"'"'/tmp/pip-install-axaz03dc/lxml/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(file);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, file, '"'"'exec'"'"'))' bdist_wheel -d /tmp/pip-wheel-wn6cmto6 --python-tag cp37
cwd: /tmp/pip-install-axaz03dc/lxml/
Complete output (149 lines):
Building lxml version 3.6.4.
Building without Cython.
Using build configuration of libxslt 1.1.29
running bdist_wheel
running build
running build_py
creating build
creating build/lib.linux-armv7l-3.7
creating build/lib.linux-armv7l-3.7/lxml
copying src/lxml/usedoctest.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/doctestcompare.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/sax.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/pyclasslookup.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/builder.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/ElementInclude.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/cssselect.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/_elementpath.py -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/init.py -> build/lib.linux-armv7l-3.7/lxml
creating build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/init.py -> build/lib.linux-armv7l-3.7/lxml/includes
creating build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/diff.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/usedoctest.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/defs.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/soupparser.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/ElementSoup.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/clean.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/html5parser.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/_html5builder.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/_setmixin.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/builder.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/init.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/formfill.py -> build/lib.linux-armv7l-3.7/lxml/html
copying src/lxml/html/_diffcommand.py -> build/lib.linux-armv7l-3.7/lxml/html
creating build/lib.linux-armv7l-3.7/lxml/isoschematron
copying src/lxml/isoschematron/init.py -> build/lib.linux-armv7l-3.7/lxml/isoschematron
copying src/lxml/lxml.etree.h -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/lxml.etree_api.h -> build/lib.linux-armv7l-3.7/lxml
copying src/lxml/includes/xinclude.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/xmlschema.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/c14n.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/config.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/xmlerror.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/xpath.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/xslt.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/etreepublic.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/dtdvalid.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/xmlparser.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/htmlparser.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/relaxng.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/uri.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/tree.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/schematron.pxd -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/lxml-version.h -> build/lib.linux-armv7l-3.7/lxml/includes
copying src/lxml/includes/etree_defs.h -> build/lib.linux-armv7l-3.7/lxml/includes
creating build/lib.linux-armv7l-3.7/lxml/isoschematron/resources
creating build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/rng
copying src/lxml/isoschematron/resources/rng/iso-schematron.rng -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/rng
creating build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl
copying src/lxml/isoschematron/resources/xsl/XSD2Schtrn.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl
copying src/lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl
creating build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
copying src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
copying src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_abstract_expand.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
copying src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
copying src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_dsdl_include.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
copying src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_message.xsl -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
copying src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/readme.txt -> build/lib.linux-armv7l-3.7/lxml/isoschematron/resources/xsl/iso-schematron-xslt1
running build_ext
building 'lxml.etree' extension
creating build/temp.linux-armv7l-3.7
creating build/temp.linux-armv7l-3.7/src
creating build/temp.linux-armv7l-3.7/src/lxml
gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/usr/include/libxml2 -Isrc/lxml/includes -I/home/pi/homeassistant3.7/include -I/usr/local/include/python3.7m -c src/lxml/lxml.etree.c -o build/temp.linux-armv7l-3.7/src/lxml/lxml.etree.o -w
src/lxml/lxml.etree.c: In function ‘__Pyx_ExceptionSave’:
src/lxml/lxml.etree.c:221065:19: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
*type = tstate->exc_type;
^~
src/lxml/lxml.etree.c:221066:20: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
*value = tstate->exc_value;
^~
src/lxml/lxml.etree.c:221067:17: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
*tb = tstate->exc_traceback;
^~
src/lxml/lxml.etree.c: In function ‘__Pyx_ExceptionReset’:
src/lxml/lxml.etree.c:221079:22: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
tmp_type = tstate->exc_type;
^~
src/lxml/lxml.etree.c:221080:23: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
tmp_value = tstate->exc_value;
^~
src/lxml/lxml.etree.c:221081:20: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
tmp_tb = tstate->exc_traceback;
^~
src/lxml/lxml.etree.c:221082:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
tstate->exc_type = type;
^~
src/lxml/lxml.etree.c:221083:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
tstate->exc_value = value;
^~
src/lxml/lxml.etree.c:221084:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
tstate->exc_traceback = tb;
^~
src/lxml/lxml.etree.c: In function ‘__Pyx_GetException’:
src/lxml/lxml.etree.c:221127:22: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
tmp_type = tstate->exc_type;
^~
src/lxml/lxml.etree.c:221128:23: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
tmp_value = tstate->exc_value;
^~
src/lxml/lxml.etree.c:221129:20: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
tmp_tb = tstate->exc_traceback;
^~
src/lxml/lxml.etree.c:221130:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
tstate->exc_type = local_type;
^~
src/lxml/lxml.etree.c:221131:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
tstate->exc_value = local_value;
^~
src/lxml/lxml.etree.c:221132:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
tstate->exc_traceback = local_tb;
^~
src/lxml/lxml.etree.c: In function ‘__Pyx_ExceptionSwap’:
src/lxml/lxml.etree.c:221441:22: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
tmp_type = tstate->exc_type;
^~
src/lxml/lxml.etree.c:221442:23: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
tmp_value = tstate->exc_value;
^~
src/lxml/lxml.etree.c:221443:20: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
tmp_tb = tstate->exc_traceback;
^~
src/lxml/lxml.etree.c:221444:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_type’; did you mean ‘curexc_type’?
tstate->exc_type = *type;
^~
src/lxml/lxml.etree.c:221445:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_value’; did you mean ‘curexc_value’?
tstate->exc_value = *value;
^~
src/lxml/lxml.etree.c:221446:11: error: ‘PyThreadState {aka struct _ts}’ has no member named ‘exc_traceback’; did you mean ‘curexc_traceback’?
tstate->exc_traceback = *tb;
^~
Compile failed: command 'gcc' failed with exit status 1
creating tmp
cc -I/usr/include/libxml2 -I/usr/include/libxml2 -c /tmp/xmlXPathInitkcwz2cs1.c -o tmp/xmlXPathInitkcwz2cs1.o
/tmp/xmlXPathInitkcwz2cs1.c:2:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
main (int argc, char **argv) {
^~~~
cc tmp/xmlXPathInitkcwz2cs1.o -lxml2 -o a.out
error: command 'gcc' failed with exit status 1

ERROR: Failed building wheel for lxml
Running setup.py clean for lxml
Failed to build lxml

discovery.py doesn't discover Roku 4

But it does provoke a response from it:

tcpdump output during a Roku.discover() call:

tcpdump -ne -i any port 1900
21:32:43.638146 Out <ma:ca:dd:re:ss> ethertype IPv4 (0x0800), length 138: 192.168.1.2.34202 > 239.255.255.250.ssdp: UDP, length 94
21:32:46.063044  In <ma:ca:dd:re:ss> ethertype IPv4 (0x0800), length 259: 192.168.1.8.ssdp > 192.168.5.2.34202: UDP, length 215

192.168.1.8 is indeed a Roku, so it's not the cast, it's the response not being picked up by the socket.

For a workaround I just query arp tables for the IP using something like:

ip neigh | awk '/ro:ku:ma:ca:dr/ { print $1 }' | tr -d '()'

and pass it to the application. This issue seems like a recurring issue with Python and multicast requests, so this can (and probably should) be closed, unless someone knows what's going on. I just figured this might be a good place to put it for those who ran into the same problem.

Add option to poweroff for non-Roku TVs

Roku devices that aren’t Roku TVs will continue to play content when they are powered off (this is a Roku specific issue and not merely of this Python package). So, it would be nice when using the package to add an option to the poweroff function that will, depending on provided argument(s), pause the content or to activate the home option.

Quick Correction to Docs

The part in the readme where it has the user print out the app.id, app.name, app.version needs parentheses.

Correction:
print(app.id, app.name, app.version)

App launches than goes back to home screen

I have a RCA RokuTv

In my script I have
Tv5=Roku('192.168.1.225’)
Pan= Tv5[‘Pandora’]
Pan.launch()

Pandora will load, will say logging in than return to the home screen.

Implement Search for the External Control API

This is a placeholder for a pull request I intend to work on that will allow search queries via the python library following the external control api. The end goal is to provide home assistant the means for launching searches so that it can be implemented with the new video skills api provided by alexa.

Essentially this:

Alexa play Game of Thrones Season 1 Episode 5.

Video Skills Api will parse show name, season # and episode # then pass along to haaska for processing which will pass the parameters along to home assistant which is running locally to call and ultimately call the python-roku library to execute a search.

For shows that can be found exactly, auto launching can occur. (Works for netflix, but not for HBO GO)

Turning the TV on

Is there a function that can turn the TV on? I read the documentation and couldn't find anything like this, maybe it's an upcoming feature? I just thought that you wouldn't want to have to get your remote to turn on your TV and then just use what ever program you made.

launch() returning exception

Looks like the Roku is returning a 204 when being requested to launch an app it is already running.

      if resp.status_code != 200:
            raise RokuException(resp.content)

should read:

        if resp.status_code not in [200, 204]:
            raise RokuException(resp.content)

Add power on/off support for Roku TV (Enhancement)

I have a Roku TV (TCL brand) and I'm trying to find a way to turn the power on/off remotely from Home Assistant.

I didn't see a way to do this in the Roku External Control Guide, but I noticed that the Roku App for iPhone can do this.

I decided to do a packet capture while pressing the key on the Roku App, and here's what I was able to capture:

......{"request":"key-press","request-id":"12","param-key":"Power"}.M{"response":"key-press","response-id":"12","status":"200","status-msg":"OK"}
.W{"notify":"power-mode-changed","param-power-mode":"power-on","timestamp":"843087.742"}
......{"request":"key-press","request-id":"13","param-key":"Power"}.M{"response":"key-press","response-id":"13","status":"200","status-msg":"OK"}
.Z{"notify":"power-mode-changed","param-power-mode":"display-off","timestamp":"843094.468"}
..%CO.......%CO.......{"request":"key-press","request-id":"14","param-key":"Power"}.M{"response":"key-press","response-id":"14","status":"200","status-msg":"OK"}
.W{"notify":"power-mode-changed","param-power-mode":"power-on","timestamp":"843296.953"}
......{"request":"key-press","request-id":"15","param-key":"Power"}.M{"response":"key-press","response-id":"15","status":"200","status-msg":"OK"}
.Z{"notify":"power-mode-changed","param-power-mode":"display-off","timestamp":"843300.429"}
......{"request":"key-press","request-id":"16","param-key":"Power"}.M{"response":"key-press","response-id":"16","status":"200","status-msg":"OK"}
.W{"notify":"power-mode-changed","param-power-mode":"power-on","timestamp":"843305.781"}
......{"request":"key-press","request-id":"17","param-key":"Power"}.M{"response":"key-press","response-id":"17","status":"200","status-msg":"OK"}
.Z{"notify":"power-mode-changed","param-power-mode":"display-off","timestamp":"843309.832"}
......{"request":"key-press","request-id":"18","param-key":"Power"}.S{"response":"key-press","response-id":"18","status":"202","status-msg":"Accepted"}
......{"request":"key-press","request-id":"19","param-key":"Power"}.M{"response":"key-press","response-id":"19","status":"200","status-msg":"OK"}
.W{"notify":"power-mode-changed","param-power-mode":"power-on","timestamp":"843316.327"}

Is this a function that could be added to the library? Would it help if I did another kind of capture?

Thanks!
Roddie

No SSDP auto discovery?

Did you implement the UPNP SSDP discovery portion of the API?. so there would be no need to enter the ip address

Timed Out

I am attempting to re-create my application of 2019 that is titled RokuDOS. But I keep getting this error when i installed python-roku on my Alexa. But this error keeps me from finishing the project.

ERROR

Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/urllib3/util/connection.py", line 84, in create_connection
raise err
File "/usr/local/lib/python3.7/site-packages/urllib3/util/connection.py", line 74, in create_connection
sock.connect(sa)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 677, in urlopen
chunked=chunked,
File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 392, in _make_request
conn.request(method, url, **httplib_request_kw)
File "/usr/local/lib/python3.7/http/client.py", line 1252, in request
self._send_request(method, url, body, headers, encode_chunked)
File "/usr/local/lib/python3.7/http/client.py", line 1298, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "/usr/local/lib/python3.7/http/client.py", line 1247, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "/usr/local/lib/python3.7/http/client.py", line 1026, in _send_output
self.send(msg)
File "/usr/local/lib/python3.7/http/client.py", line 966, in send
self.connect()
File "/usr/local/lib/python3.7/site-packages/urllib3/connection.py", line 187, in connect
conn = self._new_conn()
File "/usr/local/lib/python3.7/site-packages/urllib3/connection.py", line 167, in _new_conn
% (self.host, self.timeout),
urllib3.exceptions.ConnectTimeoutError: (<urllib3.connection.HTTPConnection object at 0x7f7d55081690>, 'Connection to 192.168.1.101 timed out. (connect timeout=10)')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/requests/adapters.py", line 449, in send
timeout=timeout
File "/usr/local/lib/python3.7/site-packages/urllib3/connectionpool.py", line 725, in urlopen
method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]
File "/usr/local/lib/python3.7/site-packages/urllib3/util/retry.py", line 439, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='192.168.1.101', port=8060): Max retries exceeded with url: /keypress/Home (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7f7d55081690>, 'Connection to 192.168.1.101 timed out. (connect timeout=10)'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/usr/local/lib/python3.7/runpy.py", line 193, in _run_module_as_main
"main", mod_spec)
File "/usr/local/lib/python3.7/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/main.py", line 28, in
roku.home()
File "/lib/python3.7/site-packages/roku/core.py", line 185, in command
self._post(path)
File "/lib/python3.7/site-packages/roku/core.py", line 214, in _post
return self._call("POST", path, *args, **kwargs)
File "/lib/python3.7/site-packages/roku/core.py", line 228, in _call
resp = func(url, timeout=self.timeout, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/requests/sessions.py", line 578, in post
return self.request('POST', url, data=data, json=json, **kwargs)
File "/usr/local/lib/python3.7/site-packages/requests/sessions.py", line 530, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/lib/python3.7/site-packages/requests/sessions.py", line 643, in send
r = adapter.send(request, **kwargs)
File "/usr/local/lib/python3.7/site-packages/requests/adapters.py", line 504, in send
raise ConnectTimeout(e, request=request)
requests.exceptions.ConnectTimeout: HTTPConnectionPool(host='192.168.1.101', port=8060): Max retries exceeded with url: /keypress/Home (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7f7d55081690>, 'Connection to 192.168.1.101 timed out. (connect timeout=10)'))

Expose 'Model Type' device info property (especially useful for Roku TVs)

I think it would be useful to provide the Model Type property of a Roku device (in addition to model name, model number etc.). This currently has value 'STB' for standard set top Rokus and 'TV' for Roku TVs, according to this:

https://sdkdocs.roku.com/display/sdkdoc/ifDeviceInfo#ifDeviceInfo-GetModelType()asString

This would allow consumers of this project (such as Home Assistant) to configure behaviour based on whether the device is a TV or not. Specifically, I'm thinking of something like this: "when polling the device and there's no response, if it's a STB consider that a warning, but if it's a TV, just assume it's off and set its status accordingly".

Using search function

I have a script which does:

roku.search()
roku.literal('something')

What I get is the search box on the TV filled in with 'omething' (1st letter lost). Is there a better way to do a search?

Required Requests version to old

Home assistant requires a newer version of requests. Can we support a newer version of requests library?

warrant 0.6.1 has requirement requests>=2.13.0, but you'll have requests 2.10.0 which is incompatible.
homeassistant 0.97.2 has requirement requests==2.22.0, but you'll have requests 2.10.0 which is incompatible.
Installing collected packages: requests, roku
  Found existing installation: requests 2.22.0
    Uninstalling requests-2.22.0:
      Successfully uninstalled requests-2.22.0

Using python-roku generates a circular reference error when running initial code

My apologies if I am being a numpty, but I am new to this, so thought I would start with something simple.

I used pip install python-roku successfully.

I then thought I would try to generate some code that I could expand upon:-

#!/usr/bin/python3

from roku import Roku

myroku = Roku('192.168.10.163')

I changed the example from e.g. "roku = Roku..." to "myroku = Roku..." to ensure it wasn't this generating the error.

The error I get is:-

./roku.py Traceback (most recent call last): File "./roku.py", line 3, in <module> from roku import Roku File "<dirpath>/roku.py", line 3, in <module> from roku import roku ImportError: cannot import name 'roku' from partially initialized module 'roku' (most likely due to a circular import) (<dirpath>/roku.py)
I have tried all I can think of (which isn't much!) and cannot find an answer on the internet, so any help would be most appreciated.

I have tried this on two computers (both Ubuntu 20.04 or 22.04) in case it was an install issue...

Many thanks,

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.