pimatic-denon-avr
Version:
Pimatic Plugin to monitor and control a Denon or Marantz AV Receiver
112 lines (101 loc) • 4.2 kB
text/coffeescript
# HttpAppProtocol class
module.exports = (env) ->
Promise = env.require 'bluebird'
rest = require('restler-promise')(Promise)
http = require 'http'
parseXmlString = Promise.promisify require('xml2js').parseString
retry = require('promise-retryer')(Promise)
commons = require('pimatic-plugin-commons')(env)
# ###DenonAvrPlugin class
class HttpAppProtocol extends require('events').EventEmitter
constructor: (@config) ->
@scheduledUpdates = {}
@host = @config.host
@port = @config.port || 80
@debug = @config.debug || false
@opts =
agent: new http.Agent()
@base = commons.base @, 'HttpAppProtocol'
@on "newListener", =>
@base.debug "Status response event listeners: #{1 + @listenerCount 'response'}"
pause: (ms=50) ->
@base.debug "Pausing:", ms, "ms"
Promise.delay ms
_mapZoneToUrlPath: (command) ->
switch command[...2]
when 'Z2' then return 'formZone2_Zone2'
when 'Z3' then return 'formZone3_Zone3'
else return 'formMainZone_MainZone'
_mapZoneToCommandPrefix: (command) ->
switch command[...2]
when 'Z2' then return 'Z2'
when 'Z3' then return 'Z3'
else return ''
_mapZoneToObjectKey: (command) ->
switch command[...2]
when 'Z2' then return 'zone2'
when 'Z3' then return 'zone3'
else return 'main'
_triggerResponse: (command, param) ->
# emulate the regex matcher of telnet transport - should be refactored
@emit 'response',
matchedResults: [
"#{command}#{param}"
"#{command}",
"#{param}"
index: 0
input: "#{command}#{param}"
]
command: command
param: param
message: "#{command}#{param}"
_requestUpdate: (command, param="") =>
url = "http://#{@host}:#{@port}/goform/#{@_mapZoneToUrlPath command}XmlStatusLite.xml"
@base.debug url
return rest.get url, @opts
.then (response) =>
if response.data.length isnt 0
@base.debug response.data
parseXmlString response.data
.then (dom) =>
prefix = @_mapZoneToCommandPrefix command
@_triggerResponse "#{prefix}MU", dom.item.Mute[0].value[0].toUpperCase()
@_triggerResponse "#{prefix}PW", dom.item.Power[0].value[0].toUpperCase()
volume = parseInt(dom.item.MasterVolume[0].value[0], 10)
if not isNaN volume
if dom.item.VolumeDisplay[0].value[0].toUpperCase() is 'ABSOLUTE'
volume += 80
@_triggerResponse "#{prefix}MV", volume
@_triggerResponse "#{prefix}SI", dom.item.InputFuncSelect[0].value[0].toUpperCase()
else
throw new Error "Empty result received for status request"
.catch (err) =>
@base.error err
.finally =>
if @scheduledUpdates[@_mapZoneToObjectKey command]?
delete @scheduledUpdates[@_mapZoneToObjectKey command]
_scheduleUpdate: (command, param="", immediate) ->
timeout=1500
if not @scheduledUpdates[@_mapZoneToObjectKey command]?
@base.debug "Scheduling update for zone #{@_mapZoneToObjectKey command}"
@scheduledUpdates[@_mapZoneToObjectKey command] = true
timeout=0 if immediate
else
@base.debug "Re-scheduling update for zone #{@_mapZoneToObjectKey command}"
@base.cancelUpdate()
@base.scheduleUpdate @_requestUpdate, timeout, command, param
return Promise.resolve()
sendRequest: (command, param="", immediate=false) ->
return new Promise (resolve, reject) =>
if param isnt '?'
url = "http://#{@host}:#{@port}/goform/formiPhoneAppDirect.xml?#{command}#{param}"
@base.debug url
promise = rest.get url, @opts
.then =>
@_triggerResponse command, param
else
promise = @_scheduleUpdate command, param, immediate
promise.then =>
resolve()
.catch (errorResult) =>
reject errorResult.error