onkyo_eiscp_ruby 0.0.2 → 0.0.3
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/README.md +60 -26
- data/VERSION +1 -1
- data/bin/onkyo-server.rb +2 -2
- data/bin/onkyo.rb +9 -7
- data/lib/eiscp/command.rb +31 -12
- data/lib/eiscp/message.rb +99 -0
- data/lib/eiscp/mock_receiver.rb +22 -0
- data/lib/eiscp/receiver.rb +180 -0
- data/lib/eiscp.rb +5 -6
- data/onkyo_eiscp_ruby.gemspec +2 -1
- data/test/tc_command.rb +17 -4
- data/test/tc_message.rb +27 -0
- data/test/{tc_eiscp.rb → tc_receiver.rb} +1 -1
- metadata +21 -27
- data/lib/eiscp/eiscp.rb +0 -125
- data/lib/eiscp/eiscp_packet.rb +0 -51
- data/lib/eiscp/eiscp_server.rb +0 -21
- data/lib/eiscp/iscp_message.rb +0 -24
- data/test/tc_eiscp_packet.rb +0 -17
- data/test/tc_iscp_message.rb +0 -14
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: be075bcd7a1830e7614b8384d610ea59d0b7160f
|
4
|
+
data.tar.gz: 40cea299c77c937a786f387cfbf4c11e8696632c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5066d43a66350cb39e84ddbae87038d4ba04057f2dd884eee1852c3dd108ed4a44842878157dfb854e7802d3e771cbe5789a70af936af0e338a36264e92332d6
|
7
|
+
data.tar.gz: 6c2c8bb4ba7d3b7e9fee332d448a054cf97480194192252965b363b7efaa8d8b095ae76d2f68ab6f064fbf68ec2bac75c63b2ba53a26e891b055c0751f776cc4
|
data/README.md
CHANGED
@@ -1,41 +1,75 @@
|
|
1
1
|
onkyo_eiscp_ruby
|
2
2
|
================
|
3
|
+
[](http://badge.fury.io/rb/onkyo_eiscp_ruby)
|
3
4
|
|
4
5
|
A Ruby implementation of eISCP for controlling Onkyo receivers.
|
5
6
|
|
6
7
|
**This code is still under heavy development and using it might make you sick.**
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
Create ISCP messages and eISCP packets
|
9
|
+
Automatically discover receiver's in the broadcast domain
|
10
|
+
Send/Recieve eISCP messages
|
11
|
+
Open a TCP socket to send commands and receive solicited and non-solicited status updates.
|
12
|
+
Mock reciever (currently only responds to discovery)
|
12
13
|
|
13
14
|
**Inspired by https://github.com/miracle2k/onkyo-eiscp
|
14
15
|
|
15
|
-
|
16
|
-
________________
|
16
|
+
**Protocol information from http://michael.elsdoerfer.name/onkyo/ISCP-V1.21_2011.xls
|
17
17
|
|
18
|
-
# require the library
|
19
18
|
|
20
|
-
require 'eiscp'
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# Open a TCP connection to monitor solicited updates
|
28
|
-
eiscp = EISCP.new('10.0.0.1')
|
29
|
-
eiscp.connect
|
20
|
+
Using the Library
|
21
|
+
-----------------
|
22
|
+
* require the library
|
30
23
|
|
31
|
-
|
32
|
-
eiscp.connect do |data|
|
33
|
-
puts EISCPPacket.parse(data).iscp_message
|
34
|
-
end
|
24
|
+
require 'eiscp'
|
35
25
|
|
36
|
-
|
37
|
-
iscp_message = ISCPMessage.new("PWR", "01")
|
38
|
-
eiscp_packet = EISCPPacket.new(iscp_message.message)
|
39
|
-
eiscp.send(eiscp_packet.to_s)
|
40
|
-
|
26
|
+
* Discover local receivers
|
41
27
|
|
28
|
+
EISCP::Receiver.discover
|
29
|
+
|
30
|
+
* Create Receiver object from first discovered
|
31
|
+
|
32
|
+
Receiver.new
|
33
|
+
|
34
|
+
* Open a TCP connection to monitor solicited updates
|
35
|
+
|
36
|
+
receiver = Receiver.new('10.0.0.1')
|
37
|
+
receiver.connect
|
38
|
+
|
39
|
+
* You can also pass a block and operate on received packet strings:
|
40
|
+
|
41
|
+
receiver.connect do |data|
|
42
|
+
puts EISCP::Receiver.parse(data).iscp_message
|
43
|
+
end
|
44
|
+
|
45
|
+
* Turn on the receiver
|
46
|
+
|
47
|
+
message = EISCP::Message.parse("PWR", "01")
|
48
|
+
message.send(message.to_eiscp)
|
49
|
+
|
50
|
+
* New 'parse' method makes creating EISCP objects more flexible.
|
51
|
+
This parses messages from command line or raw eiscp data from the socket
|
52
|
+
|
53
|
+
iscp_message = EISCP::Message.parse "PWR01"
|
54
|
+
iscp_message = EISCP::Message.parse "PWR 01"
|
55
|
+
iscp_message = EISCP::Message.parse "!1PWR01"
|
56
|
+
iscp_message = EISCP::Message.parse "!1PWR 01"
|
57
|
+
|
58
|
+
* Parsing raw socket data
|
59
|
+
|
60
|
+
iscp_message_from_raw_eiscp = EISCP::Message.parse iscp_message.to_eiscp
|
61
|
+
|
62
|
+
Using the Binaries
|
63
|
+
------------------
|
64
|
+
|
65
|
+
* Discover local receivers
|
66
|
+
|
67
|
+
$ onkyo.rb -d
|
68
|
+
|
69
|
+
* Connect to the first discovered receiver to see status updates
|
70
|
+
|
71
|
+
$ onkyo.rb -c
|
72
|
+
|
73
|
+
* Start the mock server (only responds to 'ECNQSTN')
|
74
|
+
|
75
|
+
$ onkyo-server.rb
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/bin/onkyo-server.rb
CHANGED
data/bin/onkyo.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'eiscp'
|
4
4
|
require 'optparse'
|
5
5
|
require 'ostruct'
|
6
|
-
|
7
6
|
class Options
|
8
7
|
DEFAULT_OPTIONS = { verbose: true, all: false }
|
9
8
|
USAGE = ' Usage: onkyo_rb [options]'
|
@@ -48,8 +47,8 @@ class Options
|
|
48
47
|
end
|
49
48
|
|
50
49
|
if @options.discover
|
51
|
-
EISCP.discover.each do |receiver|
|
52
|
-
puts
|
50
|
+
EISCP::Receiver.discover.each do |receiver|
|
51
|
+
puts EISCP::Message.parse(receiver[0]).to_iscp
|
53
52
|
end
|
54
53
|
exit 0
|
55
54
|
end
|
@@ -60,8 +59,10 @@ class Options
|
|
60
59
|
end
|
61
60
|
|
62
61
|
if @options.connect
|
63
|
-
eiscp = EISCP.new(EISCP.discover[0][1])
|
64
|
-
eiscp.connect
|
62
|
+
eiscp = EISCP::Receiver.new(EISCP::Receiver.discover[0][1])
|
63
|
+
eiscp.connect do |data|
|
64
|
+
puts msg = EISCP::Receiver.parse(data).to_iscp
|
65
|
+
end
|
65
66
|
end
|
66
67
|
|
67
68
|
if ARGV == []
|
@@ -76,8 +77,9 @@ end
|
|
76
77
|
@options = Options.parse(ARGV)
|
77
78
|
|
78
79
|
|
79
|
-
|
80
|
-
|
80
|
+
receiver = EISCP::Receiver.new(EISCP::Receiver.discover[0][1])
|
81
|
+
message = (EISCP::Message.parse(ARGV.join(" ")).to_eiscp)
|
82
|
+
puts receiver.send_recv message
|
81
83
|
|
82
84
|
|
83
85
|
|
data/lib/eiscp/command.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require 'eiscp/
|
2
|
+
require 'eiscp/receiver'
|
3
3
|
require 'ostruct'
|
4
4
|
|
5
5
|
module Command
|
@@ -11,19 +11,12 @@ module Command
|
|
11
11
|
@@zones = @@yaml_object.map{|k, v| k}
|
12
12
|
@@zones.each {|zone| class_variable_set("@@#{zone}", nil) }
|
13
13
|
@@main = @@yaml_object['main']
|
14
|
-
|
15
|
-
|
16
|
-
@@zones.each do |zone|
|
17
|
-
Command.class_variable_set("@@#{zone}", "[]")
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
14
|
|
22
15
|
def self.command_to_name(command)
|
23
16
|
return @@main[command]['name']
|
24
17
|
end
|
25
18
|
|
26
|
-
def self.
|
19
|
+
def self.command_name_to_command(name)
|
27
20
|
@@main.each_pair do |command, attrs|
|
28
21
|
if attrs['name'] == name
|
29
22
|
return command
|
@@ -44,10 +37,10 @@ module Command
|
|
44
37
|
end
|
45
38
|
|
46
39
|
|
47
|
-
def self.
|
40
|
+
def self.description_from_command_name(name)
|
48
41
|
@@main.each_pair do |command, attrs|
|
49
42
|
if attrs['name'] == name
|
50
|
-
return command['description']
|
43
|
+
return @@main[command]['description']
|
51
44
|
end
|
52
45
|
end
|
53
46
|
end
|
@@ -73,8 +66,34 @@ module Command
|
|
73
66
|
end
|
74
67
|
end
|
75
68
|
|
76
|
-
def self.list_compatible_commands
|
69
|
+
def self.list_compatible_commands(modelstring)
|
70
|
+
sets = []
|
71
|
+
@@modelsets.each_pair do |set, array|
|
72
|
+
if array.include? modelstring
|
73
|
+
sets << set
|
74
|
+
end
|
75
|
+
end
|
76
|
+
return sets
|
77
|
+
end
|
77
78
|
|
79
|
+
def self.parse(string)
|
80
|
+
array = string.split(" ")
|
81
|
+
zone = 'main'
|
82
|
+
command_name = ''
|
83
|
+
parameter_name = ''
|
84
|
+
if array.count == 3
|
85
|
+
zone = array.shift
|
86
|
+
command_name = array.shift
|
87
|
+
parameter_name = array.shift
|
88
|
+
elsif array.count == 2
|
89
|
+
command_name = array.shift
|
90
|
+
parameter_name = array.shift
|
91
|
+
end
|
92
|
+
command = Command.command_name_to_command(command_name)
|
93
|
+
parameter = Command.command_value_name_to_value(command, parameter_name)
|
94
|
+
return EISCP::Message.new(command, parameter)
|
78
95
|
end
|
96
|
+
|
79
97
|
end
|
80
98
|
|
99
|
+
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module EISCP
|
2
|
+
class Message
|
3
|
+
|
4
|
+
# EISCP header
|
5
|
+
attr_accessor :header
|
6
|
+
MAGIC = "ISCP"
|
7
|
+
HEADER_SIZE = 16
|
8
|
+
ISCP_VERSION = "\x01"
|
9
|
+
RESERVED = "\x00\x00\x00"
|
10
|
+
|
11
|
+
|
12
|
+
# ISCP attrs
|
13
|
+
attr_accessor :start
|
14
|
+
attr_accessor :unit_type
|
15
|
+
attr_accessor :command
|
16
|
+
attr_accessor :parameter
|
17
|
+
attr_reader :iscp_message
|
18
|
+
|
19
|
+
|
20
|
+
# REGEX
|
21
|
+
REGEX = /(?<start>!)?(?<unit_type>(\d|x))?(?<command>[A-Z]{3})\s?(?<parameter>.*)(?<end>\x1A)?/
|
22
|
+
|
23
|
+
def initialize(command, parameter, unit_type = "1", start = "!")
|
24
|
+
if unit_type == nil
|
25
|
+
@unit_type = "1"
|
26
|
+
else
|
27
|
+
@unit_type = unit_type
|
28
|
+
end
|
29
|
+
if start == nil
|
30
|
+
@start = "!"
|
31
|
+
else
|
32
|
+
@start = start
|
33
|
+
end
|
34
|
+
@command = command
|
35
|
+
@parameter = parameter
|
36
|
+
@iscp_message = [ @start, @unit_type, @command, @parameter ].inject(:+)
|
37
|
+
@header = { :magic => MAGIC,
|
38
|
+
:header_size => HEADER_SIZE,
|
39
|
+
:data_size => @iscp_message.length,
|
40
|
+
:version => ISCP_VERSION,
|
41
|
+
:reserved => RESERVED
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Check if two messages send the same command
|
47
|
+
def ==(message_object)
|
48
|
+
self.iscp_message == message_object.iscp_message ? true : false
|
49
|
+
end
|
50
|
+
# Identifies message format, calls appropriate parse function
|
51
|
+
# returns Message object.
|
52
|
+
|
53
|
+
def self.parse(string)
|
54
|
+
case string
|
55
|
+
when /^ISCP/
|
56
|
+
parse_eiscp_string(string)
|
57
|
+
when REGEX
|
58
|
+
parse_iscp_message(string)
|
59
|
+
else
|
60
|
+
puts "Not a valid ISCP or EISCP message."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
# ISCP Message string parser
|
67
|
+
|
68
|
+
def self.parse_iscp_message(msg_string)
|
69
|
+
match = msg_string.match(REGEX)
|
70
|
+
Message.new(match[:command], match[:parameter], match[:unit_type], match[:start])
|
71
|
+
end
|
72
|
+
|
73
|
+
#parse eiscp_message string
|
74
|
+
def self.parse_eiscp_string(eiscp_message_string)
|
75
|
+
array = eiscp_message_string.unpack("A4NNAa3A*")
|
76
|
+
iscp_message = Message.parse_iscp_message(array[5])
|
77
|
+
packet = Message.new(iscp_message.command, iscp_message.parameter, iscp_message.unit_type, iscp_message.start)
|
78
|
+
packet.header = {
|
79
|
+
:magic => array[0],
|
80
|
+
:header_size => array[1],
|
81
|
+
:data_size => array[2],
|
82
|
+
:version => array[3],
|
83
|
+
:reserved => array[4]
|
84
|
+
}
|
85
|
+
return packet
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return ISCP Message string
|
89
|
+
def to_iscp
|
90
|
+
return "#{@start + @unit_type + @command + @parameter}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return EISCP Message string
|
94
|
+
def to_eiscp
|
95
|
+
return [ @header[:magic], @header[:header_size], @header[:data_size], @header[:version], @header[:reserved], @iscp_message.to_s ].pack("A4NNAa3A*")
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'eiscp/receiver'
|
3
|
+
require 'eiscp/message'
|
4
|
+
|
5
|
+
# Mock server that only responds to ECNQSTN.
|
6
|
+
|
7
|
+
module EISCP
|
8
|
+
class MockReceiver
|
9
|
+
|
10
|
+
ONKYO_DISCOVERY_RESPONSE = Message.new("ECN", "TX-NR609/60128/DX/001122334455")
|
11
|
+
|
12
|
+
# Create/start the server object.
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
Socket.udp_server_loop("255.255.255.255", EISCP::ONKYO_PORT) do |msg, msg_src|
|
16
|
+
msg_src.reply ONKYO_DISCOVERY_RESPONSE.to_eiscp
|
17
|
+
puts msg
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'eiscp/message'
|
3
|
+
require 'resolv'
|
4
|
+
|
5
|
+
module EISCP
|
6
|
+
class Receiver
|
7
|
+
|
8
|
+
attr_accessor :host
|
9
|
+
attr_accessor :model
|
10
|
+
attr_accessor :port
|
11
|
+
attr_accessor :area
|
12
|
+
attr_accessor :mac_address
|
13
|
+
|
14
|
+
ONKYO_MAGIC = Message.new("ECN", "QSTN", "x").to_eiscp
|
15
|
+
ONKYO_PORT = 60128
|
16
|
+
|
17
|
+
# Create a new EISCP object to communicate with a receiver.
|
18
|
+
# If no host is given, use auto discovery and create a
|
19
|
+
# receiver object using the first host to respond.
|
20
|
+
|
21
|
+
def initialize(host = nil, port = ONKYO_PORT)
|
22
|
+
if host == nil
|
23
|
+
if first_rec = self.class.discover[0]
|
24
|
+
host = first_rec[1]
|
25
|
+
set_info first_rec[0]
|
26
|
+
else
|
27
|
+
raise Exception
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@host = Resolv.getaddress host
|
31
|
+
@port = port
|
32
|
+
unless @model
|
33
|
+
set_info get_ecn
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_info(ecn_string)
|
38
|
+
array = self.class.parse_ecn(ecn_string)
|
39
|
+
@model = array.shift
|
40
|
+
@port = array.shift.to_i
|
41
|
+
@area = array.shift
|
42
|
+
@mac_address = array.shift.split("\x19")[0]
|
43
|
+
return self
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_ecn
|
47
|
+
self.class.discover.each do |entry|
|
48
|
+
if @host == entry[1]
|
49
|
+
return entry[0]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Gets the ECNQSTN response of self using @host
|
55
|
+
# then parses it with parse_ecn, returning an array
|
56
|
+
# with receiver info
|
57
|
+
|
58
|
+
def get_ecn_array
|
59
|
+
self.class.discover.each do |entry|
|
60
|
+
if @host == entry[1]
|
61
|
+
array = self.class.parse_ecn(entry[0])
|
62
|
+
end
|
63
|
+
return array
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns array containing @model, @port, @area, and @mac_address
|
68
|
+
# from ECNQSTN response
|
69
|
+
|
70
|
+
def self.parse_ecn(ecn_string)
|
71
|
+
message = EISCP::Message.parse(ecn_string)
|
72
|
+
message.parameter.split("/")
|
73
|
+
end
|
74
|
+
|
75
|
+
# Internal method for receiving data with a timeout
|
76
|
+
|
77
|
+
def self.recv(sock, timeout = 0.5)
|
78
|
+
data = []
|
79
|
+
while true
|
80
|
+
ready = IO.select([sock], nil, nil, timeout)
|
81
|
+
if ready != nil
|
82
|
+
then readable = ready[0]
|
83
|
+
else
|
84
|
+
return data
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
readable.each do |socket|
|
89
|
+
begin
|
90
|
+
if socket == sock
|
91
|
+
data << sock.recv_nonblock(1024).chomp
|
92
|
+
end
|
93
|
+
rescue IO::WaitReadable
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an array of arrays consisting of a discovery response packet string
|
102
|
+
# and the source ip address of the reciever.
|
103
|
+
|
104
|
+
def self.discover
|
105
|
+
sock = UDPSocket.new
|
106
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
107
|
+
sock.send(ONKYO_MAGIC, 0, '<broadcast>', ONKYO_PORT)
|
108
|
+
data = []
|
109
|
+
while true
|
110
|
+
ready = IO.select([sock], nil, nil, 0.5)
|
111
|
+
if ready != nil
|
112
|
+
then readable = ready[0]
|
113
|
+
else
|
114
|
+
return data
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
readable.each do |socket|
|
119
|
+
begin
|
120
|
+
if socket == sock
|
121
|
+
msg, addr = sock.recvfrom_nonblock(1024)
|
122
|
+
data << [msg, addr[2]]
|
123
|
+
end
|
124
|
+
rescue IO::WaitReadable
|
125
|
+
retry
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sends a packet string on the network
|
133
|
+
|
134
|
+
def send(eiscp_packet)
|
135
|
+
sock = TCPSocket.new @host, @port
|
136
|
+
sock.puts eiscp_packet
|
137
|
+
sock.close
|
138
|
+
end
|
139
|
+
|
140
|
+
# Send a packet string and return recieved data string.
|
141
|
+
|
142
|
+
def send_recv(eiscp_packet)
|
143
|
+
sock = TCPSocket.new @host, @port
|
144
|
+
sock.puts eiscp_packet
|
145
|
+
return Receiver.recv(sock, 0.5)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Open a TCP connection to the host and print all received messages until
|
149
|
+
# killed.
|
150
|
+
|
151
|
+
def connect(&block)
|
152
|
+
sock = TCPSocket.new @host, @port
|
153
|
+
while true
|
154
|
+
ready = IO.select([sock], nil, nil, nil)
|
155
|
+
if ready != nil
|
156
|
+
then readable = ready[0]
|
157
|
+
else
|
158
|
+
return
|
159
|
+
end
|
160
|
+
|
161
|
+
readable.each do |socket|
|
162
|
+
begin
|
163
|
+
if socket == sock
|
164
|
+
data = sock.recv_nonblock(1024).chomp
|
165
|
+
if block_given?
|
166
|
+
yield data
|
167
|
+
else
|
168
|
+
puts data
|
169
|
+
end
|
170
|
+
end
|
171
|
+
rescue IO::WaitReadable
|
172
|
+
retry
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
data/lib/eiscp.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# Library for controlling Onkyo receivers over TCP/IP.
|
2
2
|
|
3
|
-
|
4
|
-
VERSION = '0.0.
|
3
|
+
module EISCP
|
4
|
+
VERSION = '0.0.3'
|
5
5
|
end
|
6
6
|
|
7
|
-
require 'eiscp/
|
8
|
-
require 'eiscp/
|
9
|
-
require 'eiscp/iscp_message'
|
7
|
+
require 'eiscp/receiver'
|
8
|
+
require 'eiscp/message'
|
10
9
|
require 'eiscp/command'
|
data/onkyo_eiscp_ruby.gemspec
CHANGED
@@ -11,7 +11,8 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = "https://github.com/mikerodrigues/onkyo_eiscp_ruby"
|
12
12
|
|
13
13
|
s.description = %q(
|
14
|
-
Use the provided binary script or
|
14
|
+
Control Onkyo receivers over the network.Use the provided binary script or
|
15
|
+
require the library for use in your scripts.
|
15
16
|
)
|
16
17
|
|
17
18
|
s.author = "Michael Rodrigues"
|
data/test/tc_command.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative "../lib/eiscp/command.rb"
|
2
|
+
require_relative "../lib/eiscp/message.rb"
|
2
3
|
require "test/unit"
|
3
4
|
|
4
5
|
class TestCommand < Test::Unit::TestCase
|
@@ -7,8 +8,8 @@ class TestCommand < Test::Unit::TestCase
|
|
7
8
|
assert_equal(Command.command_to_name("PWR"), "system-power")
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
assert_equal(Command.
|
11
|
+
def test_command_name_to_command
|
12
|
+
assert_equal(Command.command_name_to_command("system-power"), "PWR")
|
12
13
|
end
|
13
14
|
|
14
15
|
def test_command_value_to_value_name
|
@@ -19,12 +20,24 @@ class TestCommand < Test::Unit::TestCase
|
|
19
20
|
assert_equal(Command.command_value_name_to_value("PWR", "on"), "01")
|
20
21
|
end
|
21
22
|
|
22
|
-
def
|
23
|
-
assert_equal(Command.
|
23
|
+
def test_description_from_command_name
|
24
|
+
assert_equal(Command.description_from_command_name("system-power"), "System Power Command")
|
24
25
|
end
|
25
26
|
|
26
27
|
def test_description_from_command
|
27
28
|
assert_equal(Command.description_from_command("PWR"), "System Power Command")
|
28
29
|
end
|
29
30
|
|
31
|
+
def test_parse_system_power
|
32
|
+
assert_equal(Command.parse('system-power on'), EISCP::Message.parse('PWR01'))
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_parse_zone2_system_power
|
36
|
+
assert_equal(Command.parse('zone2 power on'), EISCP::Message.parse('ZPW01'))
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_parse_volume_as_integer
|
40
|
+
assert_equal(Command.parse('main-volume 25'), EISCP::Message.parse('MVL19'))
|
41
|
+
end
|
42
|
+
|
30
43
|
end
|
data/test/tc_message.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "../lib/eiscp/message"
|
2
|
+
require "test/unit"
|
3
|
+
|
4
|
+
class TestMessage < Test::Unit::TestCase
|
5
|
+
|
6
|
+
|
7
|
+
DISCOVERY_PACKET = EISCP::Message.new('ECN', 'QSTN', 'x', '!')
|
8
|
+
DISCOVERY_STRING = DISCOVERY_PACKET.to_eiscp
|
9
|
+
|
10
|
+
|
11
|
+
def test_create_discovery_iscp_message
|
12
|
+
assert_equal(EISCP::Message.new("ECN", "QSTN", "x", "!").to_iscp, "!xECNQSTN")
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_parse_discovery_iscp_message
|
16
|
+
assert_equal(EISCP::Message.parse("!xECNQSTN").to_iscp, "!xECNQSTN")
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_create_discovery_packet_string
|
20
|
+
assert_equal(DISCOVERY_PACKET.to_eiscp, DISCOVERY_STRING)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_parse_discovery_packet_string
|
24
|
+
assert_equal(EISCP::Message.parse(DISCOVERY_STRING).to_eiscp, DISCOVERY_PACKET.to_eiscp)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
metadata
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: onkyo_eiscp_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Michael Rodrigues
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-20 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
|
-
description:
|
15
|
-
in your scripts.\n "
|
13
|
+
description: "\n Control Onkyo receivers over the network.Use the provided binary
|
14
|
+
script or\n require the library for use in your scripts.\n "
|
16
15
|
email: mikebrodrigues@gmail.com
|
17
16
|
executables:
|
18
17
|
- onkyo.rb
|
@@ -21,49 +20,44 @@ extensions: []
|
|
21
20
|
extra_rdoc_files:
|
22
21
|
- README.md
|
23
22
|
files:
|
23
|
+
- README.md
|
24
|
+
- VERSION
|
24
25
|
- bin/onkyo-server.rb
|
25
26
|
- bin/onkyo.rb
|
26
|
-
-
|
27
|
-
- lib/eiscp/eiscp.rb
|
28
|
-
- lib/eiscp/eiscp_packet.rb
|
29
|
-
- lib/eiscp/command.rb
|
30
|
-
- lib/eiscp/iscp_message.rb
|
27
|
+
- eiscp-commands.yaml
|
31
28
|
- lib/eiscp.rb
|
32
|
-
-
|
33
|
-
-
|
34
|
-
-
|
35
|
-
-
|
36
|
-
- VERSION
|
29
|
+
- lib/eiscp/command.rb
|
30
|
+
- lib/eiscp/message.rb
|
31
|
+
- lib/eiscp/mock_receiver.rb
|
32
|
+
- lib/eiscp/receiver.rb
|
37
33
|
- onkyo_eiscp_ruby.gemspec
|
38
|
-
-
|
39
|
-
-
|
34
|
+
- test/tc_command.rb
|
35
|
+
- test/tc_message.rb
|
36
|
+
- test/tc_receiver.rb
|
40
37
|
homepage: https://github.com/mikerodrigues/onkyo_eiscp_ruby
|
41
38
|
licenses: []
|
39
|
+
metadata: {}
|
42
40
|
post_install_message:
|
43
41
|
rdoc_options: []
|
44
42
|
require_paths:
|
45
43
|
- lib
|
46
44
|
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
-
none: false
|
48
45
|
requirements:
|
49
|
-
- -
|
46
|
+
- - ">="
|
50
47
|
- !ruby/object:Gem::Version
|
51
48
|
version: '0'
|
52
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
50
|
requirements:
|
55
|
-
- -
|
51
|
+
- - ">="
|
56
52
|
- !ruby/object:Gem::Version
|
57
53
|
version: '0'
|
58
54
|
requirements: []
|
59
55
|
rubyforge_project:
|
60
|
-
rubygems_version:
|
56
|
+
rubygems_version: 2.2.0
|
61
57
|
signing_key:
|
62
|
-
specification_version:
|
58
|
+
specification_version: 4
|
63
59
|
summary: Manipulate Onkyo stereos with the eISCP protocol
|
64
60
|
test_files:
|
61
|
+
- test/tc_message.rb
|
62
|
+
- test/tc_receiver.rb
|
65
63
|
- test/tc_command.rb
|
66
|
-
- test/tc_iscp_message.rb
|
67
|
-
- test/tc_eiscp.rb
|
68
|
-
- test/tc_eiscp_packet.rb
|
69
|
-
has_rdoc:
|
data/lib/eiscp/eiscp.rb
DELETED
@@ -1,125 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'eiscp/eiscp_packet.rb'
|
3
|
-
require 'eiscp/iscp_message.rb'
|
4
|
-
|
5
|
-
class EISCP
|
6
|
-
ONKYO_PORT = 60128
|
7
|
-
ONKYO_MAGIC = EISCPPacket.new("ECN", "QSTN", "x").to_s
|
8
|
-
|
9
|
-
# Create a new EISCP object to communicate with a receiver.
|
10
|
-
|
11
|
-
def initialize(host)
|
12
|
-
@host = host
|
13
|
-
end
|
14
|
-
|
15
|
-
# Internal method for receiving data with a timeout
|
16
|
-
|
17
|
-
def self.recv(sock, timeout = 0.5)
|
18
|
-
data = []
|
19
|
-
while true
|
20
|
-
ready = IO.select([sock], nil, nil, timeout)
|
21
|
-
if ready != nil
|
22
|
-
then readable = ready[0]
|
23
|
-
else
|
24
|
-
return data
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
readable.each do |socket|
|
29
|
-
begin
|
30
|
-
if socket == sock
|
31
|
-
data << sock.recv_nonblock(1024).chomp
|
32
|
-
end
|
33
|
-
rescue IO::WaitReadable
|
34
|
-
retry
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Returns an array of arrays consisting of a discovery response packet string
|
42
|
-
# and the source ip address of the reciever.
|
43
|
-
|
44
|
-
def self.discover
|
45
|
-
sock = UDPSocket.new
|
46
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
47
|
-
sock.send(ONKYO_MAGIC, 0, '<broadcast>', ONKYO_PORT)
|
48
|
-
data = []
|
49
|
-
while true
|
50
|
-
ready = IO.select([sock], nil, nil, 0.5)
|
51
|
-
if ready != nil
|
52
|
-
then readable = ready[0]
|
53
|
-
else
|
54
|
-
return data
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
readable.each do |socket|
|
59
|
-
begin
|
60
|
-
if socket == sock
|
61
|
-
msg, addr = sock.recvfrom_nonblock(1024)
|
62
|
-
data << [msg, addr[2]]
|
63
|
-
end
|
64
|
-
rescue IO::WaitReadable
|
65
|
-
retry
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Sends a packet string on the network
|
73
|
-
|
74
|
-
def send(eiscp_packet)
|
75
|
-
sock = TCPSocket.new @host, ONKYO_PORT
|
76
|
-
sock.puts eiscp_packet
|
77
|
-
sock.close
|
78
|
-
end
|
79
|
-
|
80
|
-
# Send a packet string and return recieved data string.
|
81
|
-
|
82
|
-
def send_recv(eiscp_packet)
|
83
|
-
sock = TCPSocket.new @host, ONKYO_PORT
|
84
|
-
sock.puts eiscp_packet
|
85
|
-
puts EISCP.recv(sock, 0.5)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Open a TCP connection to the host and print all received messages until
|
89
|
-
# killed.
|
90
|
-
|
91
|
-
def connect(&block)
|
92
|
-
sock = TCPSocket.new @host, ONKYO_PORT
|
93
|
-
while true
|
94
|
-
ready = IO.select([sock], nil, nil, nil)
|
95
|
-
if ready != nil
|
96
|
-
then readable = ready[0]
|
97
|
-
else
|
98
|
-
return
|
99
|
-
end
|
100
|
-
|
101
|
-
readable.each do |socket|
|
102
|
-
begin
|
103
|
-
if socket == sock
|
104
|
-
data = sock.recv_nonblock(1024).chomp
|
105
|
-
if block_given?
|
106
|
-
yield data
|
107
|
-
else
|
108
|
-
puts data
|
109
|
-
end
|
110
|
-
end
|
111
|
-
rescue IO::WaitReadable
|
112
|
-
retry
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
data/lib/eiscp/eiscp_packet.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'eiscp/iscp_message'
|
2
|
-
|
3
|
-
# Public: Encapsulates ISCP Messages in eISCP packets to send on the network.
|
4
|
-
# You can alsoe use the class method 'parse' to create objects from strings
|
5
|
-
# captured from the network using the EISCP class.
|
6
|
-
|
7
|
-
class EISCPPacket
|
8
|
-
|
9
|
-
MAGIC = "ISCP"
|
10
|
-
HEADER_SIZE = 16
|
11
|
-
VERSION = "\x01"
|
12
|
-
RESERVED = "\x00\x00\x00"
|
13
|
-
|
14
|
-
attr_accessor :header
|
15
|
-
attr_reader :iscp_message
|
16
|
-
|
17
|
-
# Create a new EISCPPacket object with a given command and parameter
|
18
|
-
# -+command+ - an ISCP command like "PWR" for "system-power"
|
19
|
-
# -+parameter+ - an ISCP command parameter like "01" for "on" for
|
20
|
-
# "system-power"
|
21
|
-
|
22
|
-
def initialize(command, parameter, unit_type = "1", start = "!")
|
23
|
-
@iscp_message = ISCPMessage.new(command, parameter, unit_type, start)
|
24
|
-
@header = { :magic => MAGIC, :header_size => HEADER_SIZE, :data_size => @iscp_message.to_s.length, :version => VERSION, :reserved => RESERVED }
|
25
|
-
end
|
26
|
-
|
27
|
-
# Returns a raw packet string for sending on the network using EISCP object.
|
28
|
-
|
29
|
-
def to_s
|
30
|
-
return [ @header[:magic], @header[:header_size], @header[:data_size], @header[:version], @header[:reserved], @iscp_message.to_s ].pack("A4NNAa3A*")
|
31
|
-
end
|
32
|
-
|
33
|
-
# Returns an EISCPPacket from a raw packet string
|
34
|
-
|
35
|
-
def self.parse(eiscp_message_string)
|
36
|
-
array = eiscp_message_string.unpack("A4NNAa3A*")
|
37
|
-
iscp_message = ISCPMessage.parse(array[5])
|
38
|
-
packet = EISCPPacket.new(iscp_message.command, iscp_message.parameter, iscp_message.unit_type, iscp_message.start)
|
39
|
-
packet.header = {
|
40
|
-
:magic => array[0],
|
41
|
-
:header_size => array[1],
|
42
|
-
:data_size => array[2],
|
43
|
-
:version => array[3],
|
44
|
-
:reserved => array[4]
|
45
|
-
}
|
46
|
-
return packet
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
|
data/lib/eiscp/eiscp_server.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'eiscp/eiscp_packet'
|
3
|
-
require 'eiscp/eiscp'
|
4
|
-
|
5
|
-
# Mock server that only responds to ECNQSTN.
|
6
|
-
|
7
|
-
class EISCPServer
|
8
|
-
|
9
|
-
ONKYO_DISCOVERY_RESPONSE = EISCPPacket.new("ECN", "TX-NR609/60128/DX/001122334455")
|
10
|
-
|
11
|
-
# Create/start the server object.
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
Socket.udp_server_loop("255.255.255.255", EISCP::ONKYO_PORT) do |msg, msg_src|
|
15
|
-
msg_src.reply ONKYO_DISCOVERY_RESPONSE.to_s
|
16
|
-
puts msg
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
|
data/lib/eiscp/iscp_message.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
class ISCPMessage
|
2
|
-
|
3
|
-
attr_accessor :start
|
4
|
-
attr_accessor :unit_type
|
5
|
-
attr_accessor :command
|
6
|
-
attr_accessor :parameter
|
7
|
-
|
8
|
-
def initialize(command, parameter, unit_type = "1", start = "!")
|
9
|
-
@unit_type = unit_type
|
10
|
-
@start = start
|
11
|
-
@command = command
|
12
|
-
@parameter = parameter
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.parse(msg_string)
|
16
|
-
match = msg_string.match(/(?<start>!)(?<unit_type>\w)(?<command>[A-Z]{3})(?<parameter>\S+)/)
|
17
|
-
ISCPMessage.new(match[:command], match[:parameter], match[:unit_type], match[:start])
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_s
|
21
|
-
return "#{@start + @unit_type + @command + @parameter}\r"
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
data/test/tc_eiscp_packet.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
require_relative "../lib/eiscp/eiscp_packet.rb"
|
2
|
-
require "test/unit"
|
3
|
-
|
4
|
-
class TestEISCPPacket < Test::Unit::TestCase
|
5
|
-
|
6
|
-
DISCOVERY_STRING = "ISCP\x00\x00\x00\x10\x00\x00\x00\n\x01\x00\x00\x00!xECNQSTN\r"
|
7
|
-
DISCOVERY_PACKET = EISCPPacket.new('ECN', 'QSTN', 'x', '!')
|
8
|
-
|
9
|
-
def test_create_discovery_packet_string
|
10
|
-
assert_equal(DISCOVERY_PACKET.to_s, DISCOVERY_STRING)
|
11
|
-
end
|
12
|
-
|
13
|
-
def test_parse_discovery_packet_string
|
14
|
-
assert_equal(EISCPPacket.parse(DISCOVERY_STRING).to_s, DISCOVERY_PACKET.to_s)
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
data/test/tc_iscp_message.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require_relative "../lib/eiscp/iscp_message.rb"
|
2
|
-
require "test/unit"
|
3
|
-
|
4
|
-
class TestISCPMessage < Test::Unit::TestCase
|
5
|
-
|
6
|
-
def test_create_discovery_iscp_message
|
7
|
-
assert_equal(ISCPMessage.new("ECN", "QSTN", "x", "!").to_s, "!xECNQSTN\r")
|
8
|
-
end
|
9
|
-
|
10
|
-
def test_parse_discovery_iscp_message
|
11
|
-
assert_equal(ISCPMessage.parse("!xECNQSTN\r").to_s, "!xECNQSTN\r")
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|