onkyo_eiscp_ruby 0.0.3 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: be075bcd7a1830e7614b8384d610ea59d0b7160f
4
- data.tar.gz: 40cea299c77c937a786f387cfbf4c11e8696632c
2
+ SHA256:
3
+ metadata.gz: 8e9c2fd0e9e79716c2b0a084a6dd7859597732f86fb71c1f4bcd4462c8e9297b
4
+ data.tar.gz: df71853616b26d2f62f6490b99f263d2a5bb2590c4de4bc7b1930fab14604ea5
5
5
  SHA512:
6
- metadata.gz: 5066d43a66350cb39e84ddbae87038d4ba04057f2dd884eee1852c3dd108ed4a44842878157dfb854e7802d3e771cbe5789a70af936af0e338a36264e92332d6
7
- data.tar.gz: 6c2c8bb4ba7d3b7e9fee332d448a054cf97480194192252965b363b7efaa8d8b095ae76d2f68ab6f064fbf68ec2bac75c63b2ba53a26e891b055c0751f776cc4
6
+ metadata.gz: 5377a5163b1219647c708b0d125b1359108d3ebbd91bd4f2c8916a002aa4b83363d2531e0340deddc77a2251e2701cd26cb4fe9ab3b4a4c551a496ed2c20d6b0
7
+ data.tar.gz: fabc67566fb94c98b743c1db76173f86e3de03333ccad16dda9e5dbeb09747a2bad4ec62867593de404633065819644f0574e22c2c492e209d6f0bf17a609482
data/README.md CHANGED
@@ -1,21 +1,42 @@
1
1
  onkyo_eiscp_ruby
2
2
  ================
3
3
  [![Gem Version](https://badge.fury.io/rb/onkyo_eiscp_ruby.png)](http://badge.fury.io/rb/onkyo_eiscp_ruby)
4
+ [![GitHub version](https://badge.fury.io/gh/mikerodrigues%2Fonkyo_eiscp_ruby.png)](http://badge.fury.io/gh/mikerodrigues%2Fonkyo_eiscp_ruby)
4
5
 
5
- A Ruby implementation of eISCP for controlling Onkyo receivers.
6
+ *A Ruby implementation of eISCP for controlling Onkyo receivers.*
7
+
8
+ **This code is not really still under development and using it might still make you sick.**
9
+
10
+ **The python version linked below sees much more activity.**
11
+
12
+ Automatically discover receivers in the broadcast domain
13
+
14
+ Send commands to receivers and parse returned messages
15
+
16
+ Open a TCP socket to receive solicited and non-solicited status updates.
6
17
 
7
- **This code is still under heavy development and using it might make you sick.**
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
18
  Mock reciever (currently only responds to discovery)
13
19
 
20
+ Human-readable commands
21
+
14
22
  **Inspired by https://github.com/miracle2k/onkyo-eiscp
15
23
 
16
24
  **Protocol information from http://michael.elsdoerfer.name/onkyo/ISCP-V1.21_2011.xls
17
25
 
26
+ What's missing?
27
+ ---------------
28
+ * Command validation
29
+
30
+ * Parsing of all human readable commands (run the tests to see some commands that aren't parsable in human readable form yet.
18
31
 
32
+ * Reasonable variants for human-readable commands (ex. `main-volume` or`volume
33
+ ` as opposed to `master-volume`)
34
+
35
+ * Model compatability checking
36
+
37
+ * Logging
38
+
39
+ * Exhaustive testing and documentation
19
40
 
20
41
  Using the Library
21
42
  -----------------
@@ -23,53 +44,182 @@ Using the Library
23
44
 
24
45
  require 'eiscp'
25
46
 
26
- * Discover local receivers
27
-
47
+ * You might want to `include EISCP` if you know you won't pollute your namespace
48
+ with Constants under `EISCP` (`Dictionary`, `Message`, `Parser`, `Receiver`,
49
+ `VERSION`)
50
+
51
+ * You can do most everything through the `Receiver` and `Message` objects. If you
52
+ want to accept user input you will probably want to use the Parser module. Be
53
+ sure to check out the RDocs or dig through the source code. I try to keep it
54
+ well commented/documented, and there's more functionality to the library than
55
+ is shown here:
56
+
57
+ * The `Message` object is pretty self explanatory. `Message.new` is mostly used
58
+ internally, but you're better off using `Parser.parse` to create them. You
59
+ probably will want to interact with `Message` objects to get information:
60
+
61
+ ```ruby
62
+ msg = EISCP::Message.new(command: 'PWR', value: '01')
63
+ msg.zone => 'main'
64
+ msg.command => "PWR"
65
+ msg.value => "01"
66
+ msg.command_name => "system-power"
67
+ msg.command_description => "System Power Command"
68
+ msg.value_name => "on"
69
+ msg.value_description => "sets System On"
70
+ ```
71
+
72
+ * Discover local receivers (returns an `Array` of `Receiver` objects)
73
+
74
+ ```ruby
28
75
  EISCP::Receiver.discover
76
+ ```
29
77
 
30
- * Create Receiver object from first discovered
78
+ * Create `Receiver` object from first discovered Receiver on the LAN
31
79
 
32
- Receiver.new
80
+ ```ruby
81
+ receiver = EISCP::Receiver.new
82
+ ```
33
83
 
34
- * Open a TCP connection to monitor solicited updates
84
+ * Or create one manually by IP address or hostname
35
85
 
36
- receiver = Receiver.new('10.0.0.1')
37
- receiver.connect
86
+ ```ruby
87
+ receiver = EISCP::Receiver.new('10.0.0.132')
88
+ ```
38
89
 
39
- * You can also pass a block and operate on received packet strings:
90
+ * When you create a `Receiver` object with a callback block it will
91
+ connect and call your block on each message received.:
40
92
 
41
- receiver.connect do |data|
42
- puts EISCP::Receiver.parse(data).iscp_message
93
+ ```ruby
94
+ receiver = EISCP::Receiver.new do |msg|
95
+ puts msg.command
96
+ puts msg.value
43
97
  end
98
+ ```
44
99
 
45
- * Turn on the receiver
100
+ * Receivers created without a block will not connect automatically. You can use
101
+ the `connect` method to create a socket and connect to the receiver.
46
102
 
47
- message = EISCP::Message.parse("PWR", "01")
48
- message.send(message.to_eiscp)
103
+ ```ruby
104
+ receiver.connect
105
+ ```
49
106
 
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"
107
+ * You can also set or change the callback block later. This will kill the
108
+ existing callback thread, recreate the socket if necessary and start
109
+ a new callback thread using the provided block:
57
110
 
58
- * Parsing raw socket data
111
+ ```ruby
112
+ receiver.connect do |msg|
113
+ puts "Received: #{msg.command_name}:#{msg.value_name}"
114
+ end
115
+ ```
116
+
117
+ * You can also disconnect, which will close the socket and kill the connection
118
+ thread:
119
+
120
+ ```ruby
121
+ receiver.disconnect
122
+ ```
123
+
124
+ * Get information about the Receiver:
125
+
126
+ ```ruby
127
+ receiver.model => "TX-NR609"
128
+ receiver.host => "10.0.0.111"
129
+ receiver.port => 60128
130
+ receiver.mac_address => "001122334455"
131
+ receiver.area => "DX"
132
+ ```
133
+
134
+ * Receivers now have a `@state` hash that contains a mapping of commands and
135
+ values received. You can use this see the Receiver's last known state without
136
+ querying. Use the `#update_state` method to run every 'QSTN' command in the
137
+ Dictionary and update the state hash, but do note that it will take a few
138
+ seconds to finish:
139
+
140
+ ```ruby
141
+ receiver.update_state
142
+ receiver.state["MVL"] => "22"
143
+ receiver.human_readable_state["master-volume"] => 34
144
+ ```
145
+
146
+
147
+ * You can use `CommandMethods` to easily send a message and return the reply as
148
+ a Message object. Once `Receiver#connect`is called, a method is defined for each command listed in the
149
+ `Dictionary` using the `@command_name` attribute which is 'human readable'.
150
+ You can check the included yaml file or look at the output of
151
+ `EISCP::Dictionary.commands`. Here a few examples:
152
+
153
+ ```ruby
154
+ # Turn on receiver
155
+ receiver.system_power "on"
156
+
157
+ # Query current input source
158
+ receiver.input_selector "query"
159
+
160
+ # Turn the master volume up one level
161
+ receiver.master_volume "level-up"
162
+
163
+ # Set the master volume to 45
164
+ receiver.master_volume "45"
165
+ ```
166
+
167
+ * Parse ISCP and human readable strings:
168
+
169
+ ```ruby
170
+ # Parse various ISCP strings
171
+ iscp_message = EISCP::Parser.parse "PWR01"
172
+ iscp_message = EISCP::Parser.parse "PWR 01"
173
+ iscp_message = EISCP::Parser.parse "!1PWR01"
174
+ iscp_message = EISCP::Parser.parse "!1PWR 01"
175
+
176
+ # Parse human readable,
177
+ EISCP::Parser.parse("main-volume 34")
178
+ ```
179
+
180
+ * `Parser.parse` is also used internally by `Receiver` to parse raw eISCP socket
181
+ data.
59
182
 
60
- iscp_message_from_raw_eiscp = EISCP::Message.parse iscp_message.to_eiscp
61
183
 
62
184
  Using the Binaries
63
185
  ------------------
64
186
 
65
187
  * Discover local receivers
66
188
 
67
- $ onkyo.rb -d
189
+ `$ onkyo.rb -d`
190
+
191
+ * Send a human-readable command
192
+
193
+ `$ onkyo.rb system-power on # uses Parser.parse`
194
+
195
+ * Or send a raw command
68
196
 
69
- * Connect to the first discovered receiver to see status updates
197
+ `$ onkyo.rb PWRQSTN # Also tries to use Parser.parse`
70
198
 
71
- $ onkyo.rb -c
199
+ * Monitor the first discovered receiver to see status updates
200
+
201
+ `$ onkyo.rb -m`
72
202
 
73
203
  * Start the mock server (only responds to 'ECNQSTN')
74
204
 
75
- $ onkyo-server.rb
205
+ `$ onkyo-server.rb`
206
+
207
+ * Turn off the first receiver discovered:
208
+
209
+ `$ onkyo.rb system-power off`
210
+
211
+ * List all known commands and values:
212
+
213
+ `$ onkyo.rb -L`
214
+
215
+ * List all known commands known to work with discovered models:
216
+
217
+ `$ onkyo.rb -l`
218
+
219
+ Contributing
220
+ ------------
221
+
222
+ * Open an issue describing bug or feature
223
+ * Fork repo
224
+ * Create a branch
225
+ * Send pull request
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 2.1.2
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require_relative './receiver'
5
+ require_relative './message'
6
+
7
+ # Mock server that only responds to ECNQSTN.
8
+
9
+ module EISCP
10
+ # This class acts as an Onkyo Stereo. It can be used for testing
11
+ #
12
+ class MockReceiver
13
+ DISCOVERY_IP = '255.255.255.255'
14
+ ONKYO_DISCOVERY_RESPONSE = Message.new('ECN', "TX-NR609/60128/DX/14DAE9E967C8\x19\r\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
15
+
16
+ # Create/start the server object.
17
+
18
+ def initialize
19
+ Socket.udp_server_loop(DISCOVERY_IP, Receiver::ONKYO_PORT) do |msg, src|
20
+ src.reply "ISCP\x00\x00\x00\x10\x00\x00\x00&\x01\x00\x00\x00!1ECNTX-NR609/60128/DX/14DAE9E967C8\x19\r\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
21
+ puts msg
22
+ end
23
+ end
24
+ end
25
+ end
data/bin/onkyo.rb CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'eiscp'
4
5
  require 'optparse'
5
6
  require 'ostruct'
7
+
8
+ # This object parses ARGV and returns an @option hash
9
+ #
6
10
  class Options
7
- DEFAULT_OPTIONS = { verbose: true, all: false }
11
+ DEFAULT_OPTIONS = { verbose: true, all: false }.freeze
8
12
  USAGE = ' Usage: onkyo_rb [options]'
9
13
 
10
14
  def self.parse(args)
11
15
  @options = OpenStruct.new
12
16
 
13
17
  options = OptionParser.new do |opts|
14
-
15
18
  opts.banner = USAGE
16
19
 
17
20
  opts.on '-d', '--discover', 'Find Onkyo Receivers on the local broadcast domain' do |d|
@@ -34,53 +37,110 @@ class Options
34
37
  @options.list_all = l
35
38
  end
36
39
 
37
- opts.on '-c', '--connect', 'Connect to the first discovered reciever and show updates' do |c|
38
- @options.connect = c
40
+ opts.on '-m', '--monitor', 'Connect to the first discovered reciever and monitor updates' do |m|
41
+ @options.monitor = m
39
42
  end
40
-
41
43
  end
42
44
 
43
45
  options.parse!(args)
44
46
 
45
- if @options == nil && ARGV == []
46
- puts options
47
- end
47
+ puts options if @options.nil? && ARGV == []
48
48
 
49
49
  if @options.discover
50
- EISCP::Receiver.discover.each do |receiver|
51
- puts EISCP::Message.parse(receiver[0]).to_iscp
52
- end
50
+ EISCP::Receiver.discover.each { |rec| puts "#{rec.host}:#{rec.port} - #{rec.model} - #{rec.mac_address}" }
53
51
  exit 0
54
52
  end
55
53
 
56
54
  if @options.help
57
55
  puts options
58
- exit 0
56
+ exit 0
57
+ end
58
+
59
+ if @options.monitor
60
+ begin
61
+ rec = EISCP::Receiver.new do |reply|
62
+ puts "#{Time.now} #{rec.host} "\
63
+ "#{reply.zone}: "\
64
+ "#{reply.command_description || reply.command} "\
65
+ "-> #{reply.value_description || reply.value}"
66
+ end
67
+ rec.thread.join
68
+ rescue Interrupt
69
+ raise 'Exiting...'
70
+ rescue Exception => e
71
+ puts 'bummer...'
72
+ puts e
73
+ end
59
74
  end
60
75
 
61
- if @options.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
76
+ if @options.list
77
+ models = []
78
+ modelsets = []
79
+ EISCP::Receiver.discover.each do |rec|
80
+ models << rec.model
81
+ end
82
+ models.each do |model|
83
+ EISCP::Dictionary.modelsets.each do |modelset, list|
84
+ modelsets << modelset unless list.select { |x| x.match model }.empty?
85
+ end
86
+ end
87
+ EISCP::Dictionary.zones.each do |zone|
88
+ EISCP::Dictionary.commands[zone].each do |command, command_hash|
89
+ puts 'Command - Description'
90
+ puts "\n"
91
+ puts " '#{Dictionary.command_to_name(command)}' - "\
92
+ "#{Dictionary.description_from_command(command)}"
93
+ puts "\n"
94
+ puts ' Value - Description>'
95
+ puts "\n"
96
+ command_hash[:values].each do |_value, attr_hash|
97
+ if modelsets.include? attr_hash[:models]
98
+ puts " '#{EISCP::Dictionary.command_value_to_value_name(command, _value)}' - "\
99
+ " #{attr_hash[:description]}"
100
+ end
101
+ end
102
+ puts "\n"
103
+ end
65
104
  end
66
105
  end
67
106
 
107
+ if @options.list_all
108
+ EISCP::Dictionary.zones.each do |zone|
109
+ EISCP::Dictionary.commands[zone].each do |command, command_hash|
110
+ puts 'Command - Description'
111
+ puts "\n"
112
+ puts " '#{Dictionary.command_to_name(command)}' - "\
113
+ "#{Dictionary.description_from_command(command)}"
114
+ puts "\n"
115
+ puts ' Value - Description>'
116
+ puts "\n"
117
+ command_hash[:values].each do |_value, attr_hash|
118
+ puts " '#{EISCP::Dictionary.command_value_to_value_name(command, _value)}' - "\
119
+ " #{attr_hash[:description]}"
120
+ end
121
+ puts "\n"
122
+ end
123
+ end
124
+ exit 0
125
+ end
126
+
68
127
  if ARGV == []
69
128
  puts options
70
129
  exit 0
71
130
  end
72
131
  end
73
-
74
132
  end
75
133
 
134
+ include EISCP
76
135
 
77
136
  @options = Options.parse(ARGV)
78
137
 
79
-
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
83
-
84
-
85
-
86
-
138
+ receiver = EISCP::Receiver.discover[0]
139
+ receiver.connect
140
+ begin
141
+ command = EISCP::Parser.parse(ARGV.join(' '))
142
+ rescue StandardError
143
+ raise "Couldn't parse command"
144
+ end
145
+ reply = receiver.send_recv(command)
146
+ puts "#{Time.now}: Response from #{receiver.host}: #{reply.zone.capitalize} #{reply.command_description || reply.command} -> #{reply.value_description || reply.value}"