denon_avr 0.9.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0ff914eb593a6c9e07e5018c4b30dbb461ade89c09b26d67829292cfb31a8272
4
+ data.tar.gz: d384577aac520ec95c13b259266abf879cd59a865bbcb7168ad60c8d9ff3d8d5
5
+ SHA512:
6
+ metadata.gz: 9344bb3c74bf057637cc9208061a4332f4c942b4aac7f5be8c3720b275ab2b87888d4359db085021ec5de8496458db5b3dc822cf5389693417f099f79eefc1e9
7
+ data.tar.gz: 0fd5b879461e66ffe4f93872b9a234a2c0a5effc48300e0030fca397b6fea4642d14f586eb27c82c15bc48c6a582374226134e969b90e579796340475fdcdee6
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.byebug_history
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in denon_avr.gemspec
6
+ gemspec
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ denon_avr (0.1.0)
5
+ httparty
6
+ nokogiri
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ byebug (11.1.3)
12
+ httparty (0.18.1)
13
+ mime-types (~> 3.0)
14
+ multi_xml (>= 0.5.2)
15
+ mime-types (3.3.1)
16
+ mime-types-data (~> 3.2015)
17
+ mime-types-data (3.2020.0512)
18
+ mini_portile2 (2.4.0)
19
+ multi_xml (0.6.0)
20
+ nokogiri (1.10.10)
21
+ mini_portile2 (~> 2.4.0)
22
+ rake (10.5.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (~> 1.17)
29
+ byebug
30
+ denon_avr!
31
+ rake (~> 10.0)
32
+
33
+ BUNDLED WITH
34
+ 1.17.3
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Xavier Bick
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,136 @@
1
+ # DenonAvr
2
+
3
+ This gem allows you to control your Denon AVR home theater receiver programatically.
4
+
5
+ Current features:
6
+
7
+ * Power on and off
8
+ * Source input select
9
+ * Set volume
10
+ * Mute and unmute
11
+ * Multi-zone support for all functions
12
+
13
+ ## Usage
14
+
15
+ ```ruby
16
+ require 'denon_avr'
17
+
18
+ # For :host, use your receiver's IP address
19
+ receiver = DenonAvr::Receiver.new(host: '10.0.1.20')
20
+ receiver.power_on
21
+ receiver.input = 'Phono'
22
+ receiver.volume = 40
23
+
24
+ # For multi-zone support, instantiate with an optional :add_zones param:
25
+ receiver = DenonAvr::Receiver.new(host: '10.0.1.20', add_zones: ['Zone2', 'Zone3'])
26
+ receiver.zone2.power_on
27
+ receiver.zone2.input = 'CD'
28
+ receiver.zone2.volume = 32.0
29
+ ```
30
+
31
+ #### Architectural Note
32
+
33
+ Most functions are implemented on an instance of `Denon::Receiver::Zone` for optimal multi-zone support. Methods such as `receiver.power_on` are actually delegated to the Main Zone; they are identical to calling, for example, `receiver.main_zone.power_on`.
34
+
35
+ #### Receiver Status
36
+
37
+ ```ruby
38
+ # Example Receiver instantiation
39
+ receiver = DenonAvr::Receiver.new(host: '10.0.1.239', add_zones: ['Zone2'])
40
+ # => #<DenonAvr::Receiver:3fd47f0be51c @host="10.0.1.239", @zones=[#<DenonAvr::Receiver::Zone:3fd47f0d64b4 @name="MainZone", @volume=20.0, @input="Sonos", @power="ON", @muted=false>, #<DenonAvr::Receiver::Zone:3fd47f0de5b0 @name="Zone2", @volume=42.0, @input="Sonos", @power="ON", @muted=false>]>
41
+
42
+ receiver.host == '10.0.1.239' # No setter on this attribute
43
+
44
+ receiver.zones == [receiver.main_zone, receiver.zone2]
45
+
46
+ receiver.volume == 20.0
47
+ receiver.volume == receiver.main_zone.volume
48
+
49
+ receiver.input == 'Sonos' # Respects renamed input
50
+
51
+ receiver.power == 'ON'
52
+ receiver.power == DenonAvr::Receiver::POWER_ON
53
+
54
+ # Update status, refreshes all of the above
55
+ receiver.get_status
56
+ ```
57
+
58
+ #### Power
59
+
60
+ ```ruby
61
+ # Identical
62
+ receiver.power = :on
63
+ receiver.power = true
64
+ receiver.power = 'On'
65
+ receiver.power = Denon::Receiver::POWER_ON
66
+ receiver.power_on
67
+ receiver.main_zone.power_on
68
+
69
+ # Zones
70
+ receiver.zone2.power = true
71
+ receiver.zone3.power_on
72
+
73
+ # Powering off examples
74
+ receiver.power = false
75
+ receiver.power = :off
76
+ receiver.power_off
77
+
78
+ # Turn off all zones
79
+ receiver.all_off
80
+ ```
81
+
82
+ #### Mute / Unmute
83
+
84
+ ```ruby
85
+ receiver.mute
86
+ # These methods are identical
87
+ receiver.muted == receiver.muted? == true
88
+
89
+ receiver.unmute
90
+ receiver.muted? == false
91
+
92
+ receiver.zone2.mute = true
93
+ receiver.zone2.muted? == true
94
+ ```
95
+
96
+ #### Volume
97
+
98
+ ```ruby
99
+ receiver.volume = 24
100
+
101
+ receiver.zone2.volume = 45.5
102
+ ```
103
+
104
+ #### Input Select
105
+
106
+ ```ruby
107
+ receiver.input = 'Blu-ray'
108
+ receiver.zone2.input = 'CD'
109
+
110
+ # If you have renamed a source, the new name will be used
111
+ receiver.input = 'Apple TV'
112
+
113
+ # To get a list of source names available, use this method.
114
+ # Note that this will return a hash, the value is the internal mapping
115
+ # used by the receiver, and the key is the
116
+ # "human" name that should be used.
117
+ receiver.source_list
118
+ ```
119
+
120
+ ## Development
121
+
122
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
123
+
124
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
125
+
126
+ ## Attribution
127
+
128
+ Although this gem does not seek to keep an identical API or design paradigm, it is essentially a ruby port of scarface-471's excellent [denonavr](https://github.com/scarface-4711/denonavr) python package. Significant thanks must be given for his excellent and extensive work there, of which this gem only implements a portion. Kudos!
129
+
130
+ ## Contributing
131
+
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zeiv/denon_avr.
133
+
134
+ ## License
135
+
136
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "denon_avr"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,43 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "denon_avr/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "denon_avr"
8
+ spec.version = DenonAvr::VERSION
9
+ spec.authors = ["Xavier Bick"]
10
+ spec.email = ["fxb9500@gmail.com"]
11
+
12
+ spec.summary = %q{Command interface for Denon AVR receivers.}
13
+ spec.homepage = "https://github.com/zeiv/denon_avr"
14
+ spec.license = "MIT"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ # if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+ #
21
+ # spec.metadata["homepage_uri"] = spec.homepage
22
+ # spec.metadata["source_code_uri"] = "https://github.com/zeiv/denon_avr"
23
+ # else
24
+ # raise "RubyGems 2.0 or newer is required to protect against " \
25
+ # "public gem pushes."
26
+ # end
27
+
28
+ # Specify which files should be added to the gem when it is released.
29
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
31
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
+ end
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_dependency "httparty"
38
+ spec.add_dependency "nokogiri"
39
+
40
+ spec.add_development_dependency "byebug"
41
+ spec.add_development_dependency "bundler", "~> 1.17"
42
+ spec.add_development_dependency "rake", "~> 10.0"
43
+ end
@@ -0,0 +1,12 @@
1
+ require 'forwardable'
2
+ require 'httparty'
3
+ require 'nokogiri'
4
+
5
+ require 'denon_avr/version'
6
+ require 'denon_avr/exceptions'
7
+ require 'denon_avr/receiver'
8
+
9
+ module DenonAvr
10
+ class Error < StandardError; end
11
+ # Your code goes here...
12
+ end
@@ -0,0 +1,3 @@
1
+ module DenonAvr
2
+ class UnknownSourceError < StandardError; end
3
+ end
@@ -0,0 +1,93 @@
1
+ require 'denon_avr/receiver/constants'
2
+ require 'denon_avr/receiver/zone'
3
+
4
+ module DenonAvr
5
+ class Receiver
6
+ extend Forwardable
7
+
8
+ attr_reader :host, :zones, :source_list, :main_zone, :zone2, :zone3
9
+
10
+ def_delegators :@main_zone, :volume, :input, :power, :muted, :set_volume,
11
+ :mute, :unmute, :set_input, :status, :get_status, :power_on, :power_off,
12
+ :mute=, :volume=, :power=, :input=
13
+
14
+ def self.send_command(host:, path:, body: nil, method: :get)
15
+ return false unless [:get, :post].include?(method)
16
+
17
+ uri = URI("http://#{host}:8080#{path}")
18
+
19
+ res = HTTParty.send(method, uri, {
20
+ body: body,
21
+ headers: {
22
+ 'Content-Type': 'application/xml'
23
+ }
24
+ })
25
+ end
26
+
27
+ def self.app_command(host:, commands: [])
28
+ payload = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
29
+ xml.tx do
30
+ commands.each_with_index do |command, index|
31
+ xml.cmd(command, id: index+1)
32
+ end
33
+ end
34
+ end
35
+
36
+ send_command(host: host, path: APPCOMMAND_URL, body: payload.to_xml, method: :post)
37
+ end
38
+
39
+ def initialize(host:, name: nil, show_all_inputs: false, timeout: 2.0, add_zones: NO_ZONES)
40
+ @host = host
41
+ get_source_mapping
42
+ setup_zones(add_zones: add_zones)
43
+ end
44
+
45
+ def all_off
46
+ @zones.each do |zone|
47
+ zone.power_off
48
+ end
49
+ end
50
+
51
+ def send_command(path:, body: nil, method: :get)
52
+ self.class.send_command(host: @host, path: path, body: body, method: method)
53
+ end
54
+
55
+ def app_command(commands = [])
56
+ commands = [commands] if commands.is_a? String
57
+ self.class.app_command(host: @host, commands: commands)
58
+ end
59
+
60
+ def inspect
61
+ instance_variables_to_list = [:@host, :@zones]
62
+ instance_vars_string = instance_variables_to_list.map { |v|
63
+ "#{v.to_s}=#{instance_variable_get(v).inspect}"
64
+ }.join(', ')
65
+ "\#<#{self.class}:#{self.object_id.to_s(16).rjust(2, '0')} #{instance_vars_string}>"
66
+ end
67
+
68
+ private
69
+
70
+ def get_source_mapping
71
+ res = app_command('GetRenameSource')
72
+ raw_source_list = res.parsed_response&.fetch('rx', nil)&.fetch('cmd', nil)
73
+ &.fetch('functionrename', nil)&.fetch('list', nil)
74
+ # This consolidates the results into a hash that can actually be used
75
+ # for source lookups, using the internal source name where applicable.
76
+ @source_list = Hash[raw_source_list.map { |source|
77
+ key = SOURCE_MAPPING[source['name']] || source['name']
78
+ [key, source['rename'].strip]
79
+ }].invert
80
+ end
81
+
82
+ def setup_zones(add_zones: NO_ZONES)
83
+ @main_zone = Zone.new(receiver: self)
84
+ @zones = [@main_zone]
85
+ add_zones.each do |zone_name|
86
+ zone_added = Zone.new(receiver: self, name: zone_name)
87
+ @zone2 = zone_added if zone_name == 'Zone2'
88
+ @zone3 = zone_added if zone_name == 'Zone3'
89
+ @zones << zone_added
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,160 @@
1
+ module DenonAvr
2
+ class Receiver
3
+ # Image URLs
4
+ STATIC_ALBUM_URL = 'http://{host}:{port}/img/album%20art_S.png'
5
+ ALBUM_COVERS_URL = 'http://{host}:{port}/NetAudio/art.asp-jpg?{time}'
6
+
7
+ # General URLs
8
+ APPCOMMAND_URL = '/goform/AppCommand.xml'
9
+ DEVICEINFO_URL = '/goform/Deviceinfo.xml'
10
+ NETAUDIOSTATUS_URL = '/goform/formNetAudio_StatusXml.xml'
11
+ TUNERSTATUS_URL = '/goform/formTuner_TunerXml.xml'
12
+ HDTUNERSTATUS_URL = '/goform/formTuner_HdXml.xml'
13
+ COMMAND_NETAUDIO_POST_URL = '/NetAudio/index.put.asp'
14
+ COMMAND_PAUSE = '/goform/formiPhoneAppDirect.xml?NS9B'
15
+ COMMAND_PLAY = '/goform/formiPhoneAppDirect.xml?NS9A'
16
+
17
+ # Main Zone URLs
18
+ STATUS_URL = '/goform/formMainZone_MainZoneXmlStatus.xml'
19
+ STATUS_LITE_URL = '/goform/formMainZone_MainZoneXmlStatusLite.xml'
20
+ MAINZONE_URL = '/goform/formMainZone_MainZoneXml.xml'
21
+ COMMAND_SEL_SRC_URL = '/goform/formiPhoneAppDirect.xml?SI'
22
+ COMMAND_FAV_SRC_URL = '/goform/formiPhoneAppDirect.xml?ZM'
23
+ COMMAND_POWER_ON_URL = '/goform/formiPhoneAppPower.xml?1+PowerOn'
24
+ COMMAND_POWER_STANDBY_URL = '/goform/formiPhoneAppPower.xml?1+PowerStandby'
25
+ COMMAND_VOLUME_UP_URL = '/goform/formiPhoneAppDirect.xml?MVUP'
26
+ COMMAND_VOLUME_DOWN_URL = '/goform/formiPhoneAppDirect.xml?MVDOWN'
27
+ COMMAND_SET_VOLUME_URL = '/goform/formiPhoneAppVolume.xml?1+%.1f'
28
+ COMMAND_MUTE_ON_URL = '/goform/formiPhoneAppMute.xml?1+MuteOn'
29
+ COMMAND_MUTE_OFF_URL = '/goform/formiPhoneAppMute.xml?1+MuteOff'
30
+ COMMAND_SEL_SM_URL = '/goform/formiPhoneAppDirect.xml?MS'
31
+ COMMAND_SET_ZST_URL = '/goform/formiPhoneAppDirect.xml?MN'
32
+
33
+ # Zone 2 URLs
34
+ STATUS_Z2_URL = '/goform/formZone2_Zone2XmlStatus.xml'
35
+ STATUS_LITE_Z2_URL = '/goform/formZone2_Zone2XmlStatusLite.xml'
36
+ MAINZONE_Z2_URL = nil
37
+ COMMAND_SEL_SRC_Z2_URL = '/goform/formiPhoneAppDirect.xml?Z2'
38
+ COMMAND_FAV_SRC_Z2_URL = '/goform/formiPhoneAppDirect.xml?Z2'
39
+ COMMAND_POWER_ON_Z2_URL = '/goform/formiPhoneAppPower.xml?2+PowerOn'
40
+ COMMAND_POWER_STANDBY_Z2_URL = '/goform/formiPhoneAppPower.xml?2+PowerStandby'
41
+ COMMAND_VOLUME_UP_Z2_URL = '/goform/formiPhoneAppDirect.xml?Z2UP'
42
+ COMMAND_VOLUME_DOWN_Z2_URL = '/goform/formiPhoneAppDirect.xml?Z2DOWN'
43
+ COMMAND_SET_VOLUME_Z2_URL = '/goform/formiPhoneAppVolume.xml?2+%.1f'
44
+ COMMAND_MUTE_ON_Z2_URL = '/goform/formiPhoneAppMute.xml?2+MuteOn'
45
+ COMMAND_MUTE_OFF_Z2_URL = '/goform/formiPhoneAppMute.xml?2+MuteOff'
46
+
47
+ # Zone 3 URLs
48
+ STATUS_Z3_URL = '/goform/formZone3_Zone3XmlStatus.xml'
49
+ STATUS_LITE_Z3_URL = '/goform/formZone3_Zone3XmlStatusLite.xml'
50
+ MAINZONE_Z3_URL = nil
51
+ COMMAND_SEL_SRC_Z3_URL = '/goform/formiPhoneAppDirect.xml?Z3'
52
+ COMMAND_FAV_SRC_Z3_URL = '/goform/formiPhoneAppDirect.xml?Z3'
53
+ COMMAND_POWER_ON_Z3_URL = '/goform/formiPhoneAppPower.xml?3+PowerOn'
54
+ COMMAND_POWER_STANDBY_Z3_URL = '/goform/formiPhoneAppPower.xml?3+PowerStandby'
55
+ COMMAND_VOLUME_UP_Z3_URL = '/goform/formiPhoneAppDirect.xml?Z3UP'
56
+ COMMAND_VOLUME_DOWN_Z3_URL = '/goform/formiPhoneAppDirect.xml?Z3DOWN'
57
+ COMMAND_SET_VOLUME_Z3_URL = '/goform/formiPhoneAppVolume.xml?3+%.1f'
58
+ COMMAND_MUTE_ON_Z3_URL = '/goform/formiPhoneAppMute.xml?3+MuteOn'
59
+ COMMAND_MUTE_OFF_Z3_URL = '/goform/formiPhoneAppMute.xml?3+MuteOff'
60
+
61
+ ReceiverURLs = Struct.new(:appcommand, :status, :status_lite, :mainzone, :deviceinfo, :netaudiostatus,
62
+ :tunerstatus, :hdtunerstatus, :command_sel_src, :command_fav_src, :command_power_on,
63
+ :command_power_standby, :command_volume_up, :command_volume_down, :command_set_volume,
64
+ :command_mute_on, :command_mute_off, :command_sel_sound_mode, :command_netaudio_post,
65
+ :command_set_all_zone_stereo, :command_pause, :command_play, keyword_init: true)
66
+
67
+ MAIN_URLS = ReceiverURLs.new(
68
+ appcommand: APPCOMMAND_URL,
69
+ status: STATUS_URL,
70
+ status_lite: STATUS_LITE_URL,
71
+ mainzone: MAINZONE_URL,
72
+ deviceinfo: DEVICEINFO_URL,
73
+ netaudiostatus: NETAUDIOSTATUS_URL,
74
+ tunerstatus: TUNERSTATUS_URL,
75
+ hdtunerstatus: HDTUNERSTATUS_URL,
76
+ command_sel_src: COMMAND_SEL_SRC_URL,
77
+ command_fav_src: COMMAND_FAV_SRC_URL,
78
+ command_power_on: COMMAND_POWER_ON_URL,
79
+ command_power_standby: COMMAND_POWER_STANDBY_URL,
80
+ command_volume_up: COMMAND_VOLUME_UP_URL,
81
+ command_volume_down: COMMAND_VOLUME_DOWN_URL,
82
+ command_set_volume: COMMAND_SET_VOLUME_URL,
83
+ command_mute_on: COMMAND_MUTE_ON_URL,
84
+ command_mute_off: COMMAND_MUTE_OFF_URL,
85
+ command_sel_sound_mode: COMMAND_SEL_SM_URL,
86
+ command_netaudio_post: COMMAND_NETAUDIO_POST_URL,
87
+ command_set_all_zone_stereo: COMMAND_SET_ZST_URL,
88
+ command_pause: COMMAND_PAUSE,
89
+ command_play: COMMAND_PLAY
90
+ )
91
+
92
+ ZONE2_URLS = ReceiverURLs.new(
93
+ appcommand: APPCOMMAND_URL,
94
+ status: STATUS_Z2_URL,
95
+ status_lite: STATUS_LITE_Z2_URL,
96
+ mainzone: MAINZONE_Z2_URL,
97
+ deviceinfo: DEVICEINFO_URL,
98
+ netaudiostatus: NETAUDIOSTATUS_URL,
99
+ tunerstatus: TUNERSTATUS_URL,
100
+ hdtunerstatus: HDTUNERSTATUS_URL,
101
+ command_sel_src: COMMAND_SEL_SRC_Z2_URL,
102
+ command_fav_src: COMMAND_FAV_SRC_Z2_URL,
103
+ command_power_on: COMMAND_POWER_ON_Z2_URL,
104
+ command_power_standby: COMMAND_POWER_STANDBY_Z2_URL,
105
+ command_volume_up: COMMAND_VOLUME_UP_Z2_URL,
106
+ command_volume_down: COMMAND_VOLUME_DOWN_Z2_URL,
107
+ command_set_volume: COMMAND_SET_VOLUME_Z2_URL,
108
+ command_mute_on: COMMAND_MUTE_ON_Z2_URL,
109
+ command_mute_off: COMMAND_MUTE_OFF_Z2_URL,
110
+ command_sel_sound_mode: COMMAND_SEL_SM_URL,
111
+ command_netaudio_post: COMMAND_NETAUDIO_POST_URL,
112
+ command_set_all_zone_stereo: COMMAND_SET_ZST_URL,
113
+ command_pause: COMMAND_PAUSE,
114
+ command_play: COMMAND_PLAY
115
+ )
116
+
117
+ ZONE3_URLS = ReceiverURLs.new(
118
+ appcommand: APPCOMMAND_URL,
119
+ status: STATUS_Z3_URL,
120
+ status_lite: STATUS_LITE_Z3_URL,
121
+ mainzone: MAINZONE_Z3_URL,
122
+ deviceinfo: DEVICEINFO_URL,
123
+ netaudiostatus: NETAUDIOSTATUS_URL,
124
+ tunerstatus: TUNERSTATUS_URL,
125
+ hdtunerstatus: HDTUNERSTATUS_URL,
126
+ command_sel_src: COMMAND_SEL_SRC_Z3_URL,
127
+ command_fav_src: COMMAND_FAV_SRC_Z3_URL,
128
+ command_power_on: COMMAND_POWER_ON_Z3_URL,
129
+ command_power_standby: COMMAND_POWER_STANDBY_Z3_URL,
130
+ command_volume_up: COMMAND_VOLUME_UP_Z3_URL,
131
+ command_volume_down: COMMAND_VOLUME_DOWN_Z3_URL,
132
+ command_set_volume: COMMAND_SET_VOLUME_Z3_URL,
133
+ command_mute_on: COMMAND_MUTE_ON_Z3_URL,
134
+ command_mute_off: COMMAND_MUTE_OFF_Z3_URL,
135
+ command_sel_sound_mode: COMMAND_SEL_SM_URL,
136
+ command_netaudio_post: COMMAND_NETAUDIO_POST_URL,
137
+ command_set_all_zone_stereo: COMMAND_SET_ZST_URL,
138
+ command_pause: COMMAND_PAUSE,
139
+ command_play: COMMAND_PLAY
140
+ )
141
+
142
+ POWER_ON = 'ON'
143
+ POWER_OFF = 'OFF'
144
+ POWER_STANDBY = 'STANDBY'
145
+ STATE_ON = 'on'
146
+ STATE_OFF = 'off'
147
+ STATE_PLAYING = 'playing'
148
+ STATE_PAUSED = 'paused'
149
+
150
+ SOURCE_MAPPING = {"TV AUDIO" => "TV", "iPod/USB" => "USB/IPOD", "Bluetooth" => "BT",
151
+ "Blu-ray" => "BD", "CBL/SAT" => "SAT/CBL", "NETWORK" => "NET",
152
+ "Media Player" => "MPLAY", "AUX" => "AUX1", "Tuner" => "TUNER",
153
+ "FM" => "TUNER", "SpotifyConnect" => "Spotify Connect"}
154
+
155
+ NO_ZONES = []
156
+ ZONE2 = ['Zone2'] # {"Zone2": None}
157
+ ZONE3 = ['Zone3'] # {"Zone3": None}
158
+ ZONE2_ZONE3 = ['Zone2', 'Zone3'] # {"Zone2": None, "Zone3": None}
159
+ end
160
+ end
@@ -0,0 +1,152 @@
1
+ module DenonAvr
2
+ class Receiver
3
+ class Zone
4
+ attr_reader :name, :status, :volume, :input, :power, :muted
5
+
6
+ def initialize(receiver:, name: 'MainZone')
7
+ @receiver = receiver
8
+ @name = name
9
+ get_status
10
+ end
11
+
12
+ def power=(power)
13
+ power = :on if power === true
14
+ power = :off if power === false
15
+ power = power.downcase.to_sym
16
+ return false unless [:on, :off].include?(power)
17
+ case power
18
+ when :on
19
+ if power_on.success?
20
+ @power = POWER_ON
21
+ else
22
+ false
23
+ end
24
+ when :off
25
+ if power_off.success?
26
+ @power = POWER_OFF
27
+ else
28
+ false
29
+ end
30
+ end
31
+ end
32
+
33
+ def power_on
34
+ send_command(path: urls.command_power_on)
35
+ end
36
+
37
+ def power_off
38
+ send_command(path: urls.command_power_standby)
39
+ end
40
+
41
+ def mute=(new_mute_state)
42
+ if new_mute_state === true
43
+ if mute.success?
44
+ @muted = true
45
+ else
46
+ nil
47
+ end
48
+ elsif new_mute_state === false
49
+ if unmute.success?
50
+ @muted = false
51
+ else
52
+ nil
53
+ end
54
+ end
55
+ end
56
+
57
+ def mute
58
+ send_command(path: urls.command_mute_on)
59
+ end
60
+
61
+ def unmute
62
+ send_command(path: urls.command_mute_off)
63
+ end
64
+
65
+ def muted?
66
+ muted
67
+ end
68
+
69
+ def volume=(new_volume)
70
+ res = set_volume(new_volume)
71
+ if res.success?
72
+ @volume = parse_volume(res.parsed_response)
73
+ else
74
+ false
75
+ end
76
+ end
77
+
78
+ def set_volume(volume)
79
+ send_command(path: sprintf(urls.command_set_volume, (volume.to_f - 80.0)))
80
+ end
81
+
82
+ def input=(input)
83
+ begin
84
+ if set_input(input).success?
85
+ @muted = true
86
+ else
87
+ false
88
+ end
89
+ rescue UnknownSourceError
90
+ false
91
+ end
92
+ end
93
+
94
+ def set_input(input)
95
+ if @receiver.source_list[input].nil?
96
+ raise UnknownSourceError.new("#{input} is not in the receiver's source mapping")
97
+ end
98
+ send_command(path: "#{urls.command_sel_src}#{@receiver.source_list[input]}")
99
+ end
100
+
101
+ def get_status
102
+ res = send_command(path: urls.status_lite)
103
+ status_items = res.parsed_response&.fetch('item', {})
104
+ @status = status_items.each{|k,v| status_items[k] = v['value']}
105
+ @input = @receiver.source_list.invert[@status['InputFuncSelect']] || @status['InputFuncSelect']
106
+ @power = @status['Power']
107
+ @muted = @status['Mute'] == 'on' ? true : false
108
+ @volume = parse_volume(res.parsed_response)
109
+ end
110
+
111
+ def inspect
112
+ instance_variables_to_list = [:@name, :@volume, :@input, :@power, :@muted]
113
+ instance_vars_string = instance_variables_to_list.map { |v|
114
+ "#{v.to_s}=#{instance_variable_get(v).inspect}"
115
+ }.join(', ')
116
+ "\#<#{self.class}:#{self.object_id.to_s(16).rjust(2, '0')} #{instance_vars_string}>"
117
+ end
118
+
119
+ private
120
+
121
+ def urls
122
+ case name
123
+ when 'MainZone'
124
+ MAIN_URLS
125
+ when 'Zone2'
126
+ ZONE2_URLS
127
+ when 'Zone3'
128
+ ZONE3_URLS
129
+ end
130
+ end
131
+
132
+ def send_command(path:, body: nil, method: :get)
133
+ @receiver.send_command(path: path, body: body, method: method)
134
+ end
135
+
136
+ def app_command(host: @host, commands: [])
137
+ @receiver.app_command(commands)
138
+ end
139
+
140
+ def parse_volume(xml_response)
141
+ volume_display = xml_response.fetch('item', nil)&.fetch('VolumeDisplay', nil)
142
+ volume_display = volume_display.fetch('value', nil) if volume_display.is_a? Hash
143
+ master_volume = xml_response.fetch('item', nil)&.fetch('MasterVolume', nil)
144
+ master_volume = master_volume.fetch('value', nil) if master_volume.is_a? Hash
145
+ master_volume = master_volume.to_f
146
+ if volume_display == 'Absolute'
147
+ master_volume = 80.0 + master_volume
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,3 @@
1
+ module DenonAvr
2
+ VERSION = "0.9.0"
3
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: denon_avr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Xavier Bick
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-07-13 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'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.17'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.17'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ description:
84
+ email:
85
+ - fxb9500@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - denon_avr.gemspec
99
+ - lib/denon_avr.rb
100
+ - lib/denon_avr/exceptions.rb
101
+ - lib/denon_avr/receiver.rb
102
+ - lib/denon_avr/receiver/constants.rb
103
+ - lib/denon_avr/receiver/zone.rb
104
+ - lib/denon_avr/version.rb
105
+ homepage: https://github.com/zeiv/denon_avr
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubygems_version: 3.0.8
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Command interface for Denon AVR receivers.
128
+ test_files: []