roku-ecp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +15 -0
- data/bin/console +5 -0
- data/bin/roku +5 -0
- data/lib/roku/app.rb +13 -0
- data/lib/roku/client.rb +101 -0
- data/lib/roku/discover.rb +56 -0
- data/lib/roku/input.rb +61 -0
- data/lib/roku.rb +9 -0
- data/readme.md +43 -0
- data/roku-ecp.gemspec +23 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4596a3e8de44cf9e2828527b7c1db97b868226df2c142a939a673f6954bbeb5a
|
4
|
+
data.tar.gz: 445dd74fd2fea0890730b4daec31ce60ce0ec6eff4fa84a785b4a25ab0835a19
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e3870be57187c600a341aac7d53598c4981f31156e7b7a1230cbac3534f7daa9ffe8c388f0e0053df0295e74d35e27a42dd194a54aa95808af6b816e9a3a0b4b
|
7
|
+
data.tar.gz: f88c5ff06c91c9e0ec8cc093b86b874b453d042507999c0c2cd033e7cc50da59c2af1064c881a30e94c6827e4b9dcf975fd827bc4dbd6280b7742d47e9e06a95
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/bin/console
ADDED
data/bin/roku
ADDED
data/lib/roku/app.rb
ADDED
data/lib/roku/client.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Roku
|
4
|
+
module Client
|
5
|
+
include HTTParty
|
6
|
+
format :xml
|
7
|
+
|
8
|
+
KEYS = %i[
|
9
|
+
Home
|
10
|
+
Rev
|
11
|
+
Fwd
|
12
|
+
Play
|
13
|
+
Select
|
14
|
+
Left
|
15
|
+
Right
|
16
|
+
Down
|
17
|
+
Up
|
18
|
+
Back
|
19
|
+
InstantReplay
|
20
|
+
Info
|
21
|
+
Backspace
|
22
|
+
Search
|
23
|
+
Enter
|
24
|
+
FindRemote
|
25
|
+
VolumeDown
|
26
|
+
VolumeMute
|
27
|
+
VolumeUp
|
28
|
+
PowerOff
|
29
|
+
InputTuner
|
30
|
+
InputHDMI1
|
31
|
+
InputHDMI2
|
32
|
+
InputHDMI3
|
33
|
+
InputHDMI4
|
34
|
+
InputAV1
|
35
|
+
].freeze
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def find_device!
|
39
|
+
address = Roku::Discover.search
|
40
|
+
base_uri(address)
|
41
|
+
end
|
42
|
+
|
43
|
+
def apps
|
44
|
+
get('/query/apps').parsed_response['apps']['app'].map do |app|
|
45
|
+
App.parse(app)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def active_app
|
50
|
+
app = get('/query/active-app').parsed_response['active_app']['app']
|
51
|
+
App.parse(app)
|
52
|
+
end
|
53
|
+
|
54
|
+
def device_info
|
55
|
+
get('/query/device-info').parsed_response['device_info']
|
56
|
+
end
|
57
|
+
|
58
|
+
def tv_channels
|
59
|
+
get('/query/tv-channels').parsed_response['tv_channels']
|
60
|
+
end
|
61
|
+
|
62
|
+
def tv_active_channel
|
63
|
+
get('/query/tv-active-channel').parsed_response['tv_channel']
|
64
|
+
end
|
65
|
+
|
66
|
+
def launch(app_id)
|
67
|
+
post("/launch/#{app_id}").success?
|
68
|
+
end
|
69
|
+
|
70
|
+
def install(app_id)
|
71
|
+
post("/install/#{app_id}").success?
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_text(string)
|
75
|
+
string.split('').map do |c|
|
76
|
+
next if c == ' '
|
77
|
+
post("/keypress/#{c}").success?
|
78
|
+
end.all?
|
79
|
+
end
|
80
|
+
|
81
|
+
def keypress(key)
|
82
|
+
return unless KEYS.include?(key.to_sym)
|
83
|
+
post("/keypress/#{key}").success?
|
84
|
+
end
|
85
|
+
|
86
|
+
def keydown(key)
|
87
|
+
return unless KEYS.include?(key.to_sym)
|
88
|
+
post("/keydown/#{key}").success?
|
89
|
+
end
|
90
|
+
|
91
|
+
def keyup(key)
|
92
|
+
return unless KEYS.include?(key.to_sym)
|
93
|
+
post("/keyup/#{key}").success?
|
94
|
+
end
|
95
|
+
|
96
|
+
def input(options = {})
|
97
|
+
post('/input', options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Roku
|
4
|
+
module Discover
|
5
|
+
MULTICAST_ADDR = '239.255.255.250'.freeze
|
6
|
+
BIND_ADDR = '0.0.0.0'.freeze
|
7
|
+
PORT = 1900
|
8
|
+
REQUEST = "M-SEARCH * HTTP/1.1\n" \
|
9
|
+
"Host: 239.255.255.250:1900\n" \
|
10
|
+
"Man: \"ssdp:discover\"\n" \
|
11
|
+
"ST: roku:ecp\n\n".freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def search
|
15
|
+
bind
|
16
|
+
socket.send(REQUEST, 0, MULTICAST_ADDR, PORT)
|
17
|
+
parse_address(await_response)
|
18
|
+
end
|
19
|
+
|
20
|
+
def await_response
|
21
|
+
Timeout.timeout(5) do
|
22
|
+
loop do
|
23
|
+
response, = socket.recvfrom(1024)
|
24
|
+
if response.include?('LOCATION') && response.include?('200 OK')
|
25
|
+
return response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def bind
|
34
|
+
return if @bound
|
35
|
+
socket.bind(BIND_ADDR, PORT)
|
36
|
+
@bound = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_address(response)
|
40
|
+
response.scan(/LOCATION: (.*)\r/).first.first
|
41
|
+
end
|
42
|
+
|
43
|
+
def socket
|
44
|
+
@socket ||= UDPSocket.open.tap do |socket|
|
45
|
+
socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, bind_address)
|
46
|
+
socket.setsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL, 1)
|
47
|
+
socket.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def bind_address
|
52
|
+
IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(BIND_ADDR).hton
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/roku/input.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
|
3
|
+
module Roku
|
4
|
+
class Input
|
5
|
+
def run
|
6
|
+
while (input = read_char)
|
7
|
+
case input
|
8
|
+
when ' '
|
9
|
+
Roku::Client.keypress(:Play)
|
10
|
+
when "\r"
|
11
|
+
Roku::Client.keypress(:Select)
|
12
|
+
when "\e[A"
|
13
|
+
Roku::Client.keypress(:Up)
|
14
|
+
when "\e[B"
|
15
|
+
Roku::Client.keypress(:Down)
|
16
|
+
when "\e[C"
|
17
|
+
Roku::Client.keypress(:Right)
|
18
|
+
when "\e[D"
|
19
|
+
Roku::Client.keypress(:Left)
|
20
|
+
when "\u007F"
|
21
|
+
Roku::Client.keypress(:Back)
|
22
|
+
when "\e[1;5A"
|
23
|
+
Roku::Client.keypress(:VolumeUp)
|
24
|
+
when "\e[1;5B"
|
25
|
+
Roku::Client.keypress(:VolumeDown)
|
26
|
+
when 'a'
|
27
|
+
query = prompt('Launch: ')
|
28
|
+
app = Roku::Client.apps.find { |a| a.name.casecmp(query) }
|
29
|
+
if app.nil?
|
30
|
+
print "\rNo app found."
|
31
|
+
else
|
32
|
+
print "\rLaunching #{app.name}."
|
33
|
+
app.launch!
|
34
|
+
end
|
35
|
+
when 'q', "\u0003"
|
36
|
+
break
|
37
|
+
else
|
38
|
+
puts input.inspect
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def read_char
|
46
|
+
$stdin.raw do |stdin|
|
47
|
+
input = stdin.getc.chr
|
48
|
+
if input == "\e"
|
49
|
+
input << stdin.read_nonblock(3) rescue nil
|
50
|
+
input << stdin.read_nonblock(2) rescue nil
|
51
|
+
end
|
52
|
+
return input
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def prompt(label)
|
57
|
+
print label
|
58
|
+
$stdin.gets.chomp
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/roku.rb
ADDED
data/readme.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
Roku External Control Protocol
|
2
|
+
==============================
|
3
|
+
|
4
|
+
This library is a client for the Roku ECP, which allows you to control Roku
|
5
|
+
devices on your local network via http.
|
6
|
+
|
7
|
+
`Roku::Discover` allows you to find a Roku device on your network.
|
8
|
+
|
9
|
+
`Roku::Client` allows you to interact with Roku devices.
|
10
|
+
|
11
|
+
`Roku::Input` is a small terminal application that provides keyboard shortcuts
|
12
|
+
for interacting.
|
13
|
+
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Find your Roku device and automatically configure the client to use it.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
Roku::Client.find_device!
|
21
|
+
# => "http://192.168.0.106:8060/"
|
22
|
+
```
|
23
|
+
|
24
|
+
Begin interacting
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
Roku::Client.active_app
|
28
|
+
# => #<Struct:Roku::App:0x5611ec31c2a8
|
29
|
+
# id = "13535"
|
30
|
+
# name = "Plex"
|
31
|
+
# type = "appl"
|
32
|
+
# version = "5.3.4"
|
33
|
+
|
34
|
+
Roku::Client.keypress(:Play)
|
35
|
+
# => true
|
36
|
+
```
|
37
|
+
|
38
|
+
View lib/roku/client.rb for complete list of buttons presses.
|
39
|
+
|
40
|
+
## Limitations / TODO
|
41
|
+
|
42
|
+
This approach only supports one device at a time. I only have one device, so I
|
43
|
+
can't test a scenario where multiple devices can be found on the network.
|
data/roku-ecp.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'roku-ecp'
|
6
|
+
spec.version = '0.1.0'
|
7
|
+
spec.authors = ['Jacob Evan Shreve']
|
8
|
+
spec.email = ['github@shreve.io']
|
9
|
+
|
10
|
+
spec.summary = 'A library for controlling Roku devices on your local network'
|
11
|
+
spec.homepage = 'https://github.com/shreve/roku-ecp'
|
12
|
+
spec.license = 'MIT'
|
13
|
+
|
14
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
15
|
+
f.match(%r{^(test|spec|features)/})
|
16
|
+
end
|
17
|
+
|
18
|
+
spec.bindir = 'bin'
|
19
|
+
spec.executables = ['roku']
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'httparty', '~> 0.16.2'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: roku-ecp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacob Evan Shreve
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httparty
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.16.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.16.2
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- github@shreve.io
|
30
|
+
executables:
|
31
|
+
- roku
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- Gemfile
|
36
|
+
- Gemfile.lock
|
37
|
+
- bin/console
|
38
|
+
- bin/roku
|
39
|
+
- lib/roku.rb
|
40
|
+
- lib/roku/app.rb
|
41
|
+
- lib/roku/client.rb
|
42
|
+
- lib/roku/discover.rb
|
43
|
+
- lib/roku/input.rb
|
44
|
+
- readme.md
|
45
|
+
- roku-ecp.gemspec
|
46
|
+
homepage: https://github.com/shreve/roku-ecp
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 2.7.6
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: A library for controlling Roku devices on your local network
|
70
|
+
test_files: []
|