denon_avr 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []