onkyo_eiscp_ruby 2.1.1 → 2.1.6

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
2
  SHA256:
3
- metadata.gz: a4b06cd590c97b2221cbb2aea133003a16d59c0ad917c48e3cba7ee0758e9c88
4
- data.tar.gz: a0eae4abe4ec00ea74db045b4218b6e7adc86f4aefa839e9faa53eee0d135598
3
+ metadata.gz: d3dd4eba4824a8b6f5698a427829515653a327f7e7a9a3439b0c66be8ed71ce1
4
+ data.tar.gz: 5e747f4b17191a38c76067df0461f9a59b7e0e116a78f34c6d8507e1ff06b2f2
5
5
  SHA512:
6
- metadata.gz: 7b2274ea6e68eb2405c32aa4db290621a49bddfdb4b8550d76da52013981691c783b9b03088e6fde4f845ab18a5d858a3bf145cf4a7d60e2ae4531bb4fcecfa4
7
- data.tar.gz: bd9fa98a349f0eee2dfb80f7c46c8f8ae5ba0a624f6563bd43983cc2d9cf531f0a57095f2db0eb5d7b68232b97bdbda25f1585930b858c4177af0c5a3d2a96f0
6
+ metadata.gz: c0053cc9c49574d835e967190edeea8519dd99ef25f3c397046274fb3da3d319292719cc5c002f0a839d69d8da271ad274cf4f449cfbab695c8c00acdeb0df42
7
+ data.tar.gz: 60476141723946c9741f957fea30e3b0709c13907155619e559cd403f6d2abf5c88bd693153cf33a35d47f9f3b07d3444ef14880f641b91d8628e83d9232f812
data/README.md CHANGED
@@ -3,21 +3,24 @@ onkyo_eiscp_ruby
3
3
  [![Gem Version](https://badge.fury.io/rb/onkyo_eiscp_ruby.png)](http://badge.fury.io/rb/onkyo_eiscp_ruby)
4
4
  [![GitHub version](https://badge.fury.io/gh/mikerodrigues%2Fonkyo_eiscp_ruby.png)](http://badge.fury.io/gh/mikerodrigues%2Fonkyo_eiscp_ruby)
5
5
 
6
- *A Ruby implementation of eISCP for controlling Onkyo receivers.*
6
+ *A Ruby implementation of eISCP (ethernet Integra Serial Control Protocol) for controlling Onkyo receivers.*
7
7
 
8
- **This code is not really still under development and using it might still make you sick.**
8
+ **I'm still sort of updating this code. Please feel free to reach out if there's something you need that it doesn't do, I may be willing to help.**
9
9
 
10
10
  **The python version linked below sees much more activity.**
11
11
 
12
- Automatically discover receivers in the broadcast domain
13
12
 
14
- Send commands to receivers and parse returned messages
13
+ Features
14
+ ---------------
15
+ * Automatically discover receivers in the broadcast domain
16
+
17
+ * Send commands to receivers and parse returned messages
15
18
 
16
- Open a TCP socket to receive solicited and non-solicited status updates.
19
+ * Open a TCP socket to receive solicited and non-solicited status updates.
17
20
 
18
- Mock reciever (currently only responds to discovery)
21
+ * Mock reciever (currently only responds to discovery)
19
22
 
20
- Human-readable commands
23
+ * Human-readable commands
21
24
 
22
25
  **Inspired by https://github.com/miracle2k/onkyo-eiscp
23
26
 
@@ -40,7 +43,11 @@ What's missing?
40
43
 
41
44
  Using the Library
42
45
  -----------------
43
- * require the library
46
+ * Install the library
47
+
48
+ gem install onkyo_eiscp_ruby
49
+
50
+ * Require the library
44
51
 
45
52
  require 'eiscp'
46
53
 
@@ -49,7 +56,7 @@ Using the Library
49
56
  `VERSION`)
50
57
 
51
58
  * 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
59
+ want to accept user input you will probably want to use the `Parser` module. Be
53
60
  sure to check out the RDocs or dig through the source code. I try to keep it
54
61
  well commented/documented, and there's more functionality to the library than
55
62
  is shown here:
@@ -75,7 +82,7 @@ Using the Library
75
82
  EISCP::Receiver.discover
76
83
  ```
77
84
 
78
- * Create `Receiver` object from first discovered Receiver on the LAN
85
+ * Create `Receiver` object from first discovered receiver on the LAN
79
86
 
80
87
  ```ruby
81
88
  receiver = EISCP::Receiver.new
@@ -97,8 +104,9 @@ Using the Library
97
104
  end
98
105
  ```
99
106
 
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.
107
+ * `Receivers` created without a block will not connect automatically. If you try
108
+ to send a command it will connect transparently, otherwise you can use the
109
+ `connect` method to explicitly open a socket to the receiver.
102
110
 
103
111
  ```ruby
104
112
  receiver.connect
@@ -162,6 +170,11 @@ Using the Library
162
170
 
163
171
  # Set the master volume to 45
164
172
  receiver.master_volume "45"
173
+
174
+ # Change the input to TV/CD
175
+ # Note: when a command value has more than one name (an array in the YAML file)
176
+ # we default to using the first entry. So for `['cd', 'tv', 'cd']` you get:
177
+ receiver.input_selector "cd"
165
178
  ```
166
179
 
167
180
  * Parse ISCP and human readable strings:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.1
1
+ 2.1.6
data/bin/mock_receiver.rb CHANGED
@@ -11,7 +11,8 @@ module EISCP
11
11
  #
12
12
  class MockReceiver
13
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")
14
+ ONKYO_DISCOVERY_RESPONSE = Message.new('ECN',
15
+ "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
 
16
17
  # Create/start the server object.
17
18
 
data/bin/onkyo.rb CHANGED
@@ -95,7 +95,7 @@ class Options
95
95
  puts "\n"
96
96
  command_hash[:values].each do |_value, attr_hash|
97
97
  if modelsets.include? attr_hash[:models]
98
- puts " '#{attr_hash[:name]}' - "\
98
+ puts " '#{EISCP::Dictionary.command_value_to_value_name(command, _value)}' - "\
99
99
  " #{attr_hash[:description]}"
100
100
  end
101
101
  end
@@ -115,8 +115,8 @@ class Options
115
115
  puts ' Value - Description>'
116
116
  puts "\n"
117
117
  command_hash[:values].each do |_value, attr_hash|
118
- puts " '#{attr_hash[:name]}' - "\
119
- " #{attr_hash[:description]}"
118
+ puts " '#{EISCP::Dictionary.command_value_to_value_name(command, _value)}' - "\
119
+ " #{attr_hash[:description]}"
120
120
  end
121
121
  puts "\n"
122
122
  end
@@ -37,8 +37,8 @@ module EISCP
37
37
  tmp.merge!((value[0] + v.to_s) =>
38
38
  {
39
39
  name: value[0].downcase + v,
40
- description: @commands[zone][command][:values][value[0] + '{xx}'][:description].gsub(/\(.*[\]|\)]$/, v),
41
- models: @commands[zone][command][:values][value[0] + '{xx}'][:models]
40
+ description: @commands[zone][command][:values]["#{value[0]}{xx}"][:description].gsub(/\(.*[\]|)]$/, v),
41
+ models: @commands[zone][command][:values]["#{value[0]}{xx}"][:models]
42
42
  })
43
43
  end
44
44
  tmp
@@ -52,7 +52,7 @@ module EISCP
52
52
  tmp.merge!(v.to_s =>
53
53
  {
54
54
  name: v.downcase,
55
- description: @commands[zone][command][:values]['{xx}'][:description].gsub(/\(.*[\]|\)]$/, v),
55
+ description: @commands[zone][command][:values]['{xx}'][:description].gsub(/\(.*[\]|)]$/, v),
56
56
  models: @commands[zone][command][:values]['{xx}'][:models]
57
57
  })
58
58
  end
@@ -51,7 +51,12 @@ module EISCP
51
51
  # Return a value name from a command and a value
52
52
  def command_value_to_value_name(command, value)
53
53
  zone = zone_from_command(command)
54
- @commands[zone][command][:values][value][:name]
54
+ command_value = @commands[zone][command][:values][value][:name]
55
+ if command_value.instance_of?(String)
56
+ command_value
57
+ elsif command_value.instance_of?(Array)
58
+ command_value.first
59
+ end
55
60
  rescue StandardError
56
61
  nil
57
62
  end
@@ -60,13 +65,13 @@ module EISCP
60
65
  def command_value_name_to_value(command, value_name)
61
66
  zone = zone_from_command(command)
62
67
  @commands[zone][command][:values].each_pair do |k, v|
63
- if v[:name].class == String
68
+ if v[:name].instance_of?(String)
64
69
  return k if v[:name] == value_name.to_s
65
- elsif v[:name].class == Array
70
+ elsif v[:name].instance_of?(Array)
66
71
  return k if v[:name].first == value_name.to_s
67
72
  end
68
73
  end
69
- return nil
74
+ nil
70
75
  rescue StandardError
71
76
  nil
72
77
  end
@@ -14,14 +14,12 @@ module EISCP
14
14
  extend DictionaryHelpers
15
15
 
16
16
  class << self
17
- attr_reader :zones
18
- attr_reader :modelsets
19
- attr_reader :commands
17
+ attr_reader :zones, :modelsets, :commands
20
18
  end
21
19
 
22
20
  DEFAULT_ZONE = 'main'
23
21
  @yaml_file_path = File.join(__dir__, '../../eiscp-commands.yaml')
24
- @commands = YAML.load(File.read(@yaml_file_path))
22
+ @commands = YAML.safe_load(File.read(@yaml_file_path), permitted_classes: [Symbol])
25
23
  @modelsets = @commands[:modelsets]
26
24
  @commands.delete(:modelsets)
27
25
  @zones = @commands.map { |k, _| k }
@@ -32,11 +30,12 @@ module EISCP
32
30
  command = command[0]
33
31
  @commands[zone][command][:values].each do |value|
34
32
  value = value[0]
35
- if value.is_a? Array
33
+ case value
34
+ when Array
36
35
  @additions << [zone, command, value, create_range_commands(zone, command, value)]
37
- elsif value.match(/^(B|T){xx}$/)
36
+ when /^(B|T){xx}$/
38
37
  @additions << [zone, command, value, create_treble_bass_commands(zone, command, value)]
39
- elsif value.match(/^{xx}$/)
38
+ when /^{xx}$/
40
39
  @additions << [zone, command, value, create_balance_commands(zone, command, value)]
41
40
  else
42
41
  next
data/lib/eiscp/message.rb CHANGED
@@ -15,6 +15,7 @@ module EISCP
15
15
  class Message
16
16
  # EISCP header
17
17
  attr_accessor :header
18
+
18
19
  # ISCP "magic" indicates the start of an eISCP message.
19
20
  MAGIC = 'ISCP'
20
21
  # eISCP header size, fixed length.
@@ -53,13 +54,11 @@ module EISCP
53
54
  # @param [String] value variable length ISCP command value
54
55
  # @param [String] unit_type_character override default unit type character, optional
55
56
  # @param [String] start_character override default start character, optional
56
- def initialize(command: nil, value: nil, terminator: "\r\n", unit_type: '1', start: '!')
57
+ def initialize(command: nil, value: '', terminator: "\r\n", unit_type: '1', start: '!')
57
58
  unless Dictionary.known_command?(command)
58
59
  # STDERR.puts "Unknown command #{command}"
59
60
  end
60
61
 
61
- raise 'No value specified.' if value.nil?
62
-
63
62
  @command = command
64
63
  @value = value
65
64
  @terminator = terminator
@@ -17,14 +17,18 @@ module EISCP
17
17
  unit_type: msg.unit_type,
18
18
  start: msg.start
19
19
  )
20
- packet.header = {
20
+ packet.header = create_header(array)
21
+ packet
22
+ end
23
+
24
+ def self.create_header(array)
25
+ {
21
26
  magic: array[0],
22
27
  header_size: array[1],
23
28
  data_size: array[2],
24
29
  version: array[3],
25
30
  reserved: array[4]
26
31
  }
27
- packet
28
32
  end
29
33
 
30
34
  def self.validate(packet)
@@ -6,7 +6,7 @@ module EISCP
6
6
  #
7
7
  module ISCPParser
8
8
  # Regexp for parsing ISCP messages
9
- REGEX = /(?<start>!)?(?<unit_type>(\d|x))?(?<command>[A-Z]{3})\s?(?<value>.*?)(?<terminator>[[:cntrl:]]*$)/.freeze
9
+ REGEX = /(?<start>!)?(?<unit_type>(?:\d|x))?(?<command>[A-Z]{3})\s?(?<value>.*?)(?<terminator>[[:cntrl:]]*$)/
10
10
  def self.parse(string)
11
11
  match = string.match(REGEX)
12
12
 
@@ -17,9 +17,7 @@ module EISCP
17
17
  hash.delete_if { |_, v| v.nil? || v == '' }
18
18
 
19
19
  # Convert keys to symbols
20
- hash = hash.each_with_object({}) do |(k, v), memo|
21
- memo[k.to_sym] = v
22
- end
20
+ hash = hash.transform_keys(&:to_sym)
23
21
 
24
22
  Message.new(**hash)
25
23
  end
@@ -16,7 +16,7 @@ module EISCP
16
16
  Dictionary.commands[zone].each do |command, _values|
17
17
  command_name = Dictionary.command_to_name(command).to_s.gsub(/-/, '_')
18
18
  define_method(command_name) do |v|
19
- instance_exec Parser.parse(command_name.gsub(/_/, '-') + ' ' + v), &block
19
+ instance_exec Parser.parse("#{command_name.gsub(/_/, '-')} #{v}"), &block
20
20
  end
21
21
  rescue StandardError => e
22
22
  puts e
@@ -28,21 +28,23 @@ module EISCP
28
28
  # Returns an array of discovered Receiver objects.
29
29
  #
30
30
  def discover(discovery_port = Receiver::ONKYO_PORT)
31
- sock = UDPSocket.new
32
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
33
- sock.send(ONKYO_MAGIC, 0, '<broadcast>', discovery_port)
34
31
  data = []
35
- loop do
36
- msg, addr = sock.recvfrom_nonblock(1024)
37
- data << Receiver.new(addr[2], ecn_string_to_ecn_array(msg))
38
- rescue IO::WaitReadable
39
- io = IO.select([sock], nil, nil, 0.5)
40
- if io.nil?
41
- return data
42
- else
43
- retry
32
+ Socket.ip_address_list.each do | addr |
33
+ if addr.ipv4?
34
+ sock = UDPSocket.new
35
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
36
+ sock.bind(addr.ip_address, discovery_port)
37
+ sock.send(ONKYO_MAGIC, 0, '<broadcast>', discovery_port)
38
+ loop do
39
+ msg, addr = sock.recvfrom_nonblock(1024)
40
+ data << Receiver.new(addr[2], ecn_string_to_ecn_array(msg))
41
+ rescue IO::WaitReadable
42
+ io = IO.select([sock], nil, nil, 0.5)
43
+ break if io.nil?
44
+ end
44
45
  end
45
46
  end
47
+ return data
46
48
  end
47
49
  end
48
50
  end
@@ -133,9 +133,11 @@ module EISCP
133
133
  # Sends an EISCP::Message object or string on the network
134
134
  #
135
135
  def send(eiscp)
136
- if eiscp.is_a? EISCP::Message
136
+ connect if @socket.nil? || @socket.closed?
137
+ case eiscp
138
+ when EISCP::Message
137
139
  @socket.puts(eiscp.to_eiscp)
138
- elsif eiscp.is_a? String
140
+ when String
139
141
  @socket.puts eiscp
140
142
  end
141
143
  end
@@ -143,10 +145,9 @@ module EISCP
143
145
  # Reads the socket and returns and EISCP::Message
144
146
  #
145
147
  def recv
146
- data = ''
148
+ data = String.new
147
149
  data << @socket.gets until data.match(/\r\n$/)
148
- message = Parser.parse(data)
149
- message
150
+ Parser.parse(data)
150
151
  end
151
152
 
152
153
  # Sends an EISCP::Message object or string on the network and returns recieved data string.
@@ -171,7 +172,7 @@ module EISCP
171
172
  #
172
173
  def human_readable_state
173
174
  hash = {}
174
- @state.each do |c, v|
175
+ @state.dup.each do |c, v|
175
176
  hash[Dictionary.command_to_name(c).to_s] = (Dictionary.command_value_to_value_name(c, v) || v.to_s).to_s
176
177
  end
177
178
  hash
@@ -187,13 +188,7 @@ module EISCP
187
188
  info[:values].each do |value, _|
188
189
  next unless value == 'QSTN'
189
190
 
190
- send(Parser.parse(command + 'QSTN'))
191
- # If we send any faster we risk making the stereo drop replies.
192
- # A dropped reply is not necessarily indicative of the
193
- # receiver's failure to receive the command and change state
194
- # accordingly. In this case, we're only making queries, so we do
195
- # want to capture every reply.
196
- sleep DEFAULT_TIMEOUT
191
+ send_recv(Parser.parse("#{command}QSTN"))
197
192
  end
198
193
  end
199
194
  end
data/lib/eiscp.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # Library for controlling Onkyo receivers over TCP/IP.
4
4
  #
5
5
  module EISCP
6
- VERSION = '2.1.1'
6
+ VERSION = '2.1.6'
7
7
  end
8
8
 
9
9
  require_relative './eiscp/receiver'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lib/eiscp/dictionary.rb'
3
+ require_relative '../lib/eiscp/dictionary'
4
4
  require 'minitest/autorun'
5
5
 
6
6
  class TestDictionary < MiniTest::Test
data/test/tc_message.rb CHANGED
@@ -8,7 +8,10 @@ class TestMessage < MiniTest::Test
8
8
  DISCOVERY_STRING = DISCOVERY_PACKET.to_eiscp
9
9
 
10
10
  def test_create_discovery_iscp_message
11
- assert_equal(EISCP::Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x', start: '!').to_iscp, '!xECNQSTN')
11
+ assert_equal(
12
+ EISCP::Message.new(command: 'ECN', value: 'QSTN', terminator: "\r\n", unit_type: 'x',
13
+ start: '!').to_iscp, '!xECNQSTN'
14
+ )
12
15
  end
13
16
 
14
17
  def test_create_messages
data/test/tc_parser.rb CHANGED
@@ -29,6 +29,6 @@ class TestParser < MiniTest::Test
29
29
  end
30
30
 
31
31
  def test_return_nil_for_fake_human_readable
32
- assert_equal(EISCP::Parser.parse('fake-command value'), nil)
32
+ assert_nil(EISCP::Parser.parse('fake-command value'))
33
33
  end
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onkyo_eiscp_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Rodrigues
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-21 00:00:00.000000000 Z
11
+ date: 2021-11-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: "\n Control Onkyo receivers over the network.Use the provided binary
14
14
  or\n require the library for use in your scripts.\n "