onkyo_eiscp_ruby 2.1.1 → 2.1.6

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 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 "