--
-- CONFIDENTIAL AND PROPRIETARY.
-- © 2007 NetStreams, Inc.
-- This software code contains proprietary trade secrets of NetStreams and is also
-- protected by U.S. and other copyright laws and applicable international
-- treaties. Any use, compilation, modification, distribution, reproduction,
-- performance, display, or disclosure (“Use”) of this software code is subject to
-- the terms and conditions of your written agreement with NetStreams. If you do
-- not have such an agreement, then any Use of this material is strictly
-- prohibited. Unauthorized Use of this software code, or any portion of it, will
-- result in civil liability and/or criminal penalties. By modifying this software
-- code, you agree to assign any and all intellectual property rights related in
-- any way to your modification to NetStreams pursuant to your written agreement
-- with NetStreams.
--
--------------------------------------------------------------------------------
--
-- $Archive: /scripts/MultiFunction/Denon/Denon_AVR-3311CI.lua $
-- $Revision: 2 $
--
--------------------------------------------------------------------------------
--
-- Driver for Denon AVR-3311CI AV Surround Receiver
-- Seema V. Nikam
--
--[[
--]]
---------------------------------------------------------------------------------------------
-- various debugging flags
---------------------------------------------------------------------------------------------
setDebug('error', 'on')
setDebug('warning', 'on')
--setDebug('all', 'on')
--setDebug('state', 'on')
--setDebug('stream', 'on')
--setDebug('i/o', 'on')
setDebug('verbose', 'on')
setDebug('status', 'on')
setDebug('commands', 'on')
--setDebug('save', 'on')
--setDebug('scan', 'on')
local NSStatus = require('NSStatus')
-- for debugging set this to greatly shorten the channel scan
-- time at the expense of not having all channels in the menus
--xmMaxScanChannels = 20
-- this table must be kept in sync with the information above
-- and maps #INPUT parameters to @SRC parameters. Although the
-- AVR considers the AM and FM tuners to be separate sources
-- we want them to appear as a single source.
SourceMap = {
'TV',
'DVD',
'DVR',
'SAT/CBL',
'BD',
'CD',
'PHONO',
-- Instead of {'TUNER','XM'}: in Denon_AVR 3311 we have:
{'HDRADIO','SIRIUS'},
-- Following sources can only be controlled and selected through the
-- AVR itself, not through the netstreams system.
-- The '?' as the first key means that #INPUT 9, will not change the current
-- selection
--
{ '?', 'V.AUX', 'IPOD', 'NET/USB','GAME','DOCK', 'RHAPSODY','NAPSTER','PANDORA','FLICKR'},
Index2Key = function(self, index)
entry = self[tonumber(index)]
if not entry then
debug('error', 'Index2Key: no mapping for '..prettyprint(index))
return nil
elseif type(entry) == 'table' then
return entry[1]
elseif type(entry) ~= 'function' then
return entry
else
debug('error', 'Index2Key: function mapping for '..prettyprint(index))
return nil
end
end,
Key2Index = function(self, key)
for index, entry in ipairs(self) do
if type(entry) == 'table' then
for _, e in ipairs(entry) do
if e == key then
return index
end
end
elseif type(entry) ~= 'function' then
if entry == key then
return index
end
end
end
debug('error', 'Key2Index: no mapping for '..prettyprint(key))
return nil
end,
}
--
-- GUI: as it is taken from Denon_AVR-3808CI.lua
-- added on 12/4 (Dan McGauley)
--
-- Seema V. Nikam
-- Need to ask question about this: Answer: Refer to Line Number: 2469
-- XM and Tuner is not supported for this devide, so we probably don't need XM artwork
-- but we may need SIRIUS artworkNameLookup
--
--[[
artworkNameLookup = {
[4] = "XM/40s_on_4.swf",
[5] = "XM/50s_on_5.swf",
[6] = "XM/60s_on_6.swf",
[7] = "XM/70s_on_7.swf",
[8] = "XM/80s_on_8.swf",
[9] = "XM/90s_on_9.swf",
[10] = "XM/The_Roadhouse.swf",
[11] = "XM/Nashville!_cm.swf",
[12] = "XM/Outlaw_Country.swf",
[13] = "XM/Willie's_Place.swf",
[14] = "XM/Bluegrass_Juncti.swf",
[15] = "XM/Folk_Village.swf",
[16] = "XM/The_Highway.swf",
[17] = "XM/Prime_Country.swf",
[18] = "XM/Elvis_Radio.swf",
[20] = "XM/XM_20_on_20.swf",
[21] = "XM/KISS-XM_cm.swf",
[22] = "XM/MIX_22_cm.swf",
[23] = "XM/Love.swf",
[24] = "XM/Pink_cm.swf",
[25] = "XM/The_Blend.swf",
[26] = "XM/The_Pulse.swf",
[28] = "XM/Escape.swf",
[29] = "XM/BBC_Radio_1.swf",
[30] = "XM/Pop2K.swf",
[32] = "XM/The_Message.swf",
[33] = "XM/Praise.swf",
[34] = "XM/enLighten.swf",
[35] = "XM/Holly.swf",
[36] = "XM/HolidayTradition.swf",
[37] = "XM/Holiday_Pops.swf",
[39] = "XM/Led_Zeppelin.swf",
[40] = "XM/Deep_Tracks.swf",
[41] = "XM/Hair_Nation.swf",
[42] = "XM/Liquid_Metal.swf",
[43] = "XM/SIRIXM_U.swf",
[44] = "XM/1st_Wave.swf",
[45] = "XM/The_Spectrum.swf",
[46] = "XM/Classic_Vinyl.swf",
[47] = "XM/Alt_Nation.swf",
[48] = "XM/Octane.swf",
[49] = "XM/Classic_Rewind.swf",
[50] = "XM/The_Loft.swf",
[51] = "XM/The_Coffee_House.swf",
[52] = "XM/Faction.swf",
[53] = "XM/AC_DC_Radio.swf",
[54] = "XM/Lithium.swf",
[55] = "XM/Margaritaville.swf",
[56] = "XM/Jam_ON.swf",
[57] = "XM/Grateful_Dead.swf",
[58] = "XM/E_Street_Radio.swf",
[59] = "XM/Undergrnd_Garage.swf",
[60] = "XM/Soul_Town.swf",
[62] = "XM/Heart_&_Soul.swf",
[64] = "XM/The_Groove.swf",
[66] = "XM/Shade_45.swf",
[67] = "XM/Hip-HopNation.swf",
[68] = "XM/The_Heat.swf",
[70] = "XM/Real_Jazz.swf",
[71] = "XM/Watercolors.swf",
[72] = "XM/Spa.swf",
[73] = "XM/SIRIUSLY_Sinatra.swf",
[74] = "XM/Bluesville.swf",
[76] = "XM/Cinemagic.swf",
[75] = "XM/On_Broadway.swf",
[77] = "XM/Holiday_Pops.swf",
[78] = "XM/Symphony_Hall.swf",
[79] = "XM/Met_Opera_Radio.swf",
[80] = "XM/Area.swf",
[81] = "XM/BPM.swf",
[84] = "XM/Chill.swf",
[85] = "XM/Caliente_sp.swf",
[86] = "XM/The_Joint.swf",
[87] = "XM/The_Verge.swf",
[88] = "XM/Air_Musique_fr.swf",
[89] = "XM/Sur_La_Route_fr.swf",
[95] = "XM/XM_Scoreboard.swf",
[96] = "XM/Canada_360.swf",
[97] = "XM/Cal_Sportif_fr.swf",
[115] = "XM/Radio_Disney.swf",
[116] = "XM/Kids_Place_Live.swf",
[117] = "XM/Catholic_Channel.swf",
[119] = "XM/Doctor_Radio.swf",
[120] = "XM/Specials.swf",
[121] = "XM/Fox_News_Channel.swf",
[122] = "XM/CNN.swf",
[123] = "XM/CNN_HDLN.swf",
[125] = "XM/Quoi_De_Neuf_fr.swf",
[126] = "XM/CNN_espanol_sp.swf",
[127] = "XM/CNBC.swf",
[129] = "XM/Bloomberg_Radio.swf",
[130] = "XM/POTUS_Politics.swf",
[131] = "XM/BBC_World_Svc.swf",
[132] = "XM/C-SPAN_Radio.swf",
[133] = "XM/XM_Public_Radio.swf",
[134] = "XM/NPR_Now.swf",
[135] = "XM/World_Radio_Net.swf",
[136] = "XM/Fox_Business.swf",
[140] = "XM/ESPN_Radio.swf",
[141] = "XM/ESPN_Xtra.swf",
[142] = "XM/Fox_Sports_Radio.swf",
[144] = "XM/Mad_Dog_Radio.swf",
[145] = "XM/IndyCar_Racing.swf",
[146] = "XM/PGA_TOUR_Network.swf",
[147] = "XM/XM_Deportivo_sp.swf",
[148] = "XM/BlueCollarRad.swf",
[149] = "XM/The_Foxxhole.swf",
[150] = "XM/RawDog_Comedy.swf",
[151] = "XM/Laugh_USA.swf",
[152] = "XM/Extreme_Talk.swf",
[153] = "XM/Laugh_Attack.swf",
[154] = "XM/National_Lampoon.swf",
[155] = "XM/SIRIUSXMStars.swf",
[156] = "XM/Oprah_&_Friends.swf",
[158] = "XM/America's_Talk.swf",
[159] = "XM/ATN_Radio.swf",
[160] = "XM/ReachMD.swf",
[161] = "XM/Rock@Random_cm.swf",
[162] = "XM/Cosmo_Radio.swf",
[163] = "XM/Book_Radio.swf",
[164] = "XM/RadioClassics.swf",
[165] = "XM/Talk_Radio.swf",
[166] = "XM/America_Right.swf",
[167] = "XM/America_Left.swf",
[168] = "XM/Fox_News_Talk.swf",
[169] = "XM/The_Power.swf",
[170] = "XM/FAMILYTALK.swf",
[171] = "XM/Open_Road.swf",
[172] = "XM/RadioParallelefr.swf",
[173] = "XM/WLW_700.swf",
[174] = "XM/MLB_Espanol_sp.swf",
[175] = "XM/MLB_Home_Plate.swf",
[176] = "XM/MLB_Play-by-Play.swf",
[177] = "XM/MLB_Play-by-Play.swf",
[178] = "XM/MLB_Play-by-Play.swf",
[179] = "XM/MLB_Play-by-Play.swf",
[180] = "XM/MLB_Play-by-Play.swf",
[181] = "XM/MLB_Play-by-Play.swf",
[182] = "XM/MLB_Play-by-Play.swf",
[183] = "XM/MLB_Play-by-Play.swf",
[184] = "XM/MLB_Play-by-Play.swf",
[185] = "XM/MLB_Play-by-Play.swf",
[186] = "XM/MLB_Play-by-Play.swf",
[187] = "XM/MLB_Play-by-Play.swf",
[188] = "XM/MLB_Play-by-Play.swf",
[189] = "XM/MLB_Play-by-Play.swf",
[190] = "XM/ACC.swf",
[191] = "XM/ACC.swf",
[192] = "XM/ACC.swf",
[193] = "XM/Pac-10.swf",
[194] = "XM/Pac-10.swf",
[195] = "XM/Pac-10.swf",
[196] = "XM/Big_Ten.swf",
[197] = "XM/Big_Ten.swf",
[198] = "XM/Big_Ten.swf",
[199] = "XM/SEC.swf",
[200] = "XM/SEC.swf",
[201] = "XM/SEC.swf",
[202] = "XM/The_Virus.swf",
[203] = "XM/Big_East.swf",
[204] = "XM/NHL_Home_Ice.swf",
[205] = "XM/NHL_Play-by-P.swf",
[206] = "XM/NHL_Play-by-P.swf",
[207] = "XM/NHL_Play-by-P.swf",
[208] = "XM/NHL_Play-by-P.swf",
[209] = "XM/NHL_Play-by-P.swf",
[210] = "XM/Boston.swf",
[211] = "XM/New_York_City.swf",
[212] = "XM/Philadelphia.swf",
[213] = "XM/Baltimore.swf",
[214] = "XM/Washington_DC.swf",
[215] = "XM/Pittsburgh.swf",
[216] = "XM/Detroit.swf",
[217] = "XM/Chicago.swf",
[218] = "XM/St_Louis.swf",
[219] = "XM/Minneapolis.swf",
[220] = "XM/Seattle.swf",
[221] = "XM/San_Francisco.swf",
[222] = "XM/Los_Angeles.swf",
[223] = "XM/San_Diego.swf",
[224] = "XM/Phoenix.swf",
[225] = "XM/Dallas_Ft_Worth.swf",
[226] = "XM/Houston.swf",
[227] = "XM/Atlanta.swf",
[228] = "XM/Tampa.swf",
[229] = "XM/Orlando.swf",
[230] = "XM/Miami.swf",
[231] = "XM/Big_12.swf",
[232] = "XM/NBA_Play-By-Play.swf",
[233] = "XM/NBA_Play-By-Play.swf",
[234] = "XM/NBA_Play-By-Play.swf",
[235] = "XM/NBA_Play-By-Play.swf",
[236] = "XM/NBA_Play-By-Play.swf",
[237] = "XM/Sports_Play-by-P.swf",
[238] = "XM/Sports_Play-by-P.swf",
[239] = "XM/Sports_Play-by-P.swf",
[241] = "XM/Sports_Play-by-P.swf",
[242] = "XM/Sports_Play-by-P.swf",
[243] = "XM/Sports_Play-by-P.swf",
[244] = "XM/Sports_Play-by-P.swf",
[245] = "XM/Sports_Play-by-P.swf",
[246] = "XM/Sports_Play-by-P.swf",
[247] = "XM/Emergency_Alert.swf"
}
]]--
SurroundMode = {
{ key='DIRECT', label='Direct' },
{ key='PURE DIRECT', label='Pure Direct' },
{ key='STEREO', label='Stereo' },
{ key='STANDARD', label='Standard' },
{ key='DOLBY DIGITAL', label='Dolby Digital' },
{ key='DTS SURROUND', label='DTS Surround' },
{ key='7CH STEREO', label='7 Ch. Stereo' },
{ key='WIDE SCREEN', label='Wide Screen' },
{ key='NEURAL', label='Neural' },
{ key='SUPER STADIUM', label='Super Stadium' },
{ key='ROCK ARENA', label='Rock Arena' },
{ key='JAZZ CLUB', label='Jazz Club' },
{ key='CLASSIC CONCERT', label='Classic Concert' },
{ key='MONO MOVIE', label='Mono. Movie' },
{ key='MATRIX', label='Matrix' },
{ key='VIDEO GAME', label='Video Game' },
{ key='VIRTUAL', label='Virtual' },
-- { key='MPEG AAC', label='MPEG AAC' }, -- removed since it's Japan only
{ key='QUICK1', label='Quick 1' },
{ key='QUICK2', label='Quick 2' },
{ key='QUICK3', label='Quick 3' },
Index2Key = function(self, index)
local entry = self[tonumber(index)]
if entry == nil then
return nil
end
return entry.key
end,
Index2Label = function(self, index)
local entry = self[tonumber(index)]
if entry == nil then
return nil
end
return entry.label
end,
Key2Index = function(self, key)
for index, entry in ipairs(self) do
if entry.key == key then
return index
end
end
return nil
end,
Key2Label = function(self, key)
local index = self:Key2Index(key)
if index == nil then
return nil
end
local entry = self[tonumber(index)]
if entry == nil then
return nil
end
return entry.label
end,
}
--
-- set up default configuration parameters
--
if not config then
config = {}
end
if not config.port then
config.port = 'comm://default'
end
if not config.func then
debug('error', 'No Module Function Specified')
end
--
-- basic_set(self, param)
-- Seema V. Nikam
-- 06/01/2011
--
-- basic "Set" routine for most (all?) of the commands in the command
-- table
--
function basic_set(self, param)
if self.Encode then
param = self:Encode(param)
end
return stream:QueueCommand(self.Command, param, self.SetTimeout)
end
--
-- basic_query(self, param)
-- Seema V. Nikam
-- 06/01/2011
--
-- basic "Query" routine for most (all?) of the commands in the command
-- table. This sends a request to the device for the current state
-- of a command value
--
function basic_query(self)
return stream:QueueCommand(self.Command, Commands.Query, self.QueryTimeout)
end
--
-- encode_toggle(self, param)
-- Seema V. Nikam
-- 06/01/2011
--
-- an Encode function to be used by parameters that can toggle between
-- the On and Off states
--
function encode_toggle(self, param)
if param == nil or param == "" then
param = "TOGGLE"
else
param = param:upper()
end
if param == "TOGGLE" then
if self.Value == self.Param.On then
param = self.Param.Off
else
param = self.Param.On
end
elseif param == "ON" then
param = self.Param.On
elseif param == "OFF" then
param = self.Param.Off
end
return param
end
------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------
-- Commands Table, common to all device types --
------------------------------------------------------------------------------------------------------------------
-- Each entry in the Commands table must contain the following entries: --
-- Command The command prefix to be used when send or requesting the --
-- command value --
-- Set The method used to set the value in the device (usually --
-- basic_set --
-- Query The method used to request the value from the device --
-- usually basic_query) --
------------------------------------------------------------------------------------------------------------------
-- In addition, each entry in the Commands table may contain the following --
-- entries: --
-- Param A table of useful values to be used in the Set routine --
-- or that will be set in the Value entry --
-- Decode A method used to translate the response from the device --
-- into the entries in Param. If Decode is not specified --
-- the Value saved will be the same as returned from the --
-- device. --
-- Encode A method used to translate the entries in Param into a --
-- value to be sent to the device. If Encode is not specified --
-- the value passed to Query will be sent to the device --
-- unmolested. --
------------------------------------------------------------------------------------------------------------------
--
-- Commands Table:
--
Commands = {
Query = '?',
-- Select Input Source
Source = {
Command = 'SI',
SetTimeout = 1500,
Query = basic_query,
Param = {
TV = SourceMap:Key2Index('TV'),
DVD = SourceMap:Key2Index('DVD'),
DVR = SourceMap:Key2Index('DVR'),
Sat = SourceMap:Key2Index('SAT/CBL'),
BD = SourceMap:Key2Index('BD'),
CD = SourceMap:Key2Index('CD'),
Tuner = SourceMap:Key2Index('HDRADIO'),
ST = 'SIRIUS',
AMFM = 'HDRADIO',
},
Set = function(self, param)
basic_set(self, param)
-- If we're changing the source between AM/FM and SIRIUS (XM) then we won't get a channel update
-- (the channel hasn't actually changed, so it doesn't get sent)
-- so explicitly query for one
--
debug("verbose","Line:548: Command SI: Set ",param)
if param == self.Param.ST then
stream:QueueCommand('TFST', Commands.Query)
elseif param == self.Param.AMFM then
stream:QueueCommand('TFHD', Commands.Query)
end
end,
--
-- Decode(self, param)
-- Seema V. Nikam
-- 06/07/2011
--
-- find the source label returned in the command and turn it into
-- an index which is more useful for the DC
--
Decode = function(self, param)
self.RawValue = param
-- We also save the raw source value
-- so we know which tuner is in use
return SourceMap:Key2Index(param)
end,
--
-- Encode(self, param)
-- Seema V. Nikam
-- map the source index into a source label to be used by the device
--
Encode = function(self, param)
if param == self.Param.ST or param == self.Param.AMFM then
return param
elseif param == self.Param.Tuner then
debug("verbose","Line:578: In SI Encode: Band value is",Commands.Band.Value)
debug("verbose","Line:579: next statement is checking above value with ST")
if Commands.Band.Value == 'ST' then
return self.Param.ST
else
return self.Param.AMFM
end
else
return SourceMap:Index2Key(param)
end
end,
},
Band = {
Command = 'TMHD',
Set = function(self, param)
param = self:Encode(param)
-- A little bit different on the set function since setting
-- the band may require first switching the source
debug("verbose","Line:597: In band=Command is TMHD: param is: ",param)
if param == self.Param.ST then
-- To select the SIRIUS band, just select the SIRIUS source if
-- necessary
Commands.Source:Set(Commands.Source.Param.ST)
else
-- To select a non-xm band, first make sure the AM/FM
-- tuner is the current real source
Commands.Source:Set(Commands.Source.Param.AMFM)
-- And then use the normal set logic
stream:QueueCommand(self.Command, param)
end
end,
Update = function(self, new)
if self.Value ~= new then
local old = self.Value
debug('verbose', 'Band = '..prettyprint(new))
self.Value = new
State:Event('UpdateBand', new, old)
State:Event('Update', 'Band', new, old)
else
debug('verbose', 'Line:618: Band = '..prettyprint(new)..' (unchanged)')
end
end,
Query = basic_query,
Param = {
AM = 'AM',
FM = 'FM',
ST = 'ST',
Auto = 'AUTO',
Manual = 'MANUAL',
Next = 'NEXT',
Prev = 'PREV',
},
Next = {
AM = 'FM',
FM = 'ST',
ST = 'AM',
},
Prev = {
AM = 'ST',
FM = 'AM',
ST = 'FM',
},
--
-- Decode
-- Seema V. Nikam
-- 06/01/2011
--
-- Decode the return value. The AUTO and MANUAL return values
-- aren't really frequency settings, so filter them out and
-- save the last known mode away for future reference
--
Decode = function(self, param)
if param == self.Param.Auto or param == self.Param.Manual then
self.Mode = param
param = self.Value
elseif Commands.Source.RawValue == 'ST' then
-- If the source is SIRIUS, then the band is always ST
param = self.Param.ST
else
self.RawValue = param
end
return param
end,
--
-- Encode
-- Seema V. Nikam
-- 06/01/2011
--
-- Encode the command parameter, we really only need to worry about Next
-- and Prev
--
Encode = function(self, param)
if param == self.Param.Next then
return self.Next[self.Value]
elseif param == self.Param.Prev then
return self.Prev[self.Value]
else
return param
end
end,
},
Power = {
Command = 'PW',
SetTimeout = 5000,
Set = basic_set,
Query = basic_query,
Param = {
On = 'ON',
Off = 'STANDBY',
},
Encode = encode_toggle,
},
Mute = {
Command = 'MU',
Set = basic_set,
Query = basic_query,
Param = {
On = 'ON',
Off = 'OFF',
},
Encode = encode_toggle,
},
}
------------------------------------------------------------------------------
-- --
-- States, common to all device types --
-- --
------------------------------------------------------------------------------
--
-- event_power(self, new, old)
-- Seema V. Nikam
-- 06/01/2011
--
-- Common UpdatePower event function to be used by all states
--
event_power = function(self, new, old)
debug('verbose', 'Power Event: '..new)
if new == Commands.Power.Param.On then
self.Machine:Set('Power On')
else
self.Machine:Set('Power Off')
end
end
--
-- event_close(self, new, old)
-- Seema V. Nikam
-- 06/01/2011
--
-- Common UpdateClose event function to be used by all states.
-- Actually, we take advantage of the putting a string into
-- the handler as a shortcut for self.Machine:Set(state)
--
event_close = 'Closed'
--
-- In the Closed state the stream is not yet opened, or has
-- been closed
--
StateClosed = {
Open = 'Open',
}
--
-- In the Open state, the stream is open, but we have no contact with
-- the device or have lost contact with the device
--
StateOpen = {
--
-- Enter(self, from)
-- Seema V. Nikam
-- 06/01/2011
--
-- While we're in the open state, every 15 seconds we want to ping
-- the device to see if it's been plugged in yet. To avoid power
-- on spam when the StreamNet device is first powered up, we only
-- issue an immediate power query on the main MUX device.
--
Enter = function(self, from)
if config.func == 'MUX' then
self:StartTimer(5000)
else
self:StartTimer(15000)
end
end,
--
-- Timer(self)
-- Seema V. Nikam
-- 06/01/2011
--
-- Event handler for the Timer event, as promised, when it fires
-- we ping the device with a power query and restart the timer
-- to try again in 15 seconds.
--
Timer = function(self)
Commands.Power:Query()
if config.func == 'MUX' then
self:StartTimer(15000)
else
self:StartTimer(20000)
end
end,
UpdatePower = event_power,
Close = event_close,
}
--
-- In the PowerOn state, the stream is open and the device is powered
-- on and ready for use.
--
StatePowerOn = {
UpdatePower = event_power,
Close = event_close,
--
-- Enter(self, from)
-- Seema V. Nikam
-- 06/01/2011
--
Enter = function(self, from)
-- When we enter the power on state, query each command we care
-- about for it's value if we haven't already seen it
if type(self.StartUpQueries) == 'table' then
for _, entry in pairs(self.StartUpQueries) do
if entry.Value == nil then
entry:Query()
end
end
end
-- And if there's a status attribute specified, then update
-- it with power on
if self.Attribute ~= nil then
status:setField(self.Attribute, '1')
end
end,
}
--
--
-- In the Power Off state, the stream is open and the device is present
-- and communicative, but the power is turned off
--
StatePowerOff = {
--
-- Enter(self, from)
-- Seema V. Nikam
-- 06/01/2011
--
Enter = function(self, from)
-- When we enter the power off state update any specified
-- power attribute
if StatePowerOn.Attribute ~= nil then
status:setField(StatePowerOn.Attribute, '0')
end
end,
--
-- UpdatePower(self, new, old)
-- Seema V. Nikam
-- 06/01/2011
--
-- When we receive a power on event, we set a timer for 4 seconds
-- before we actually enter the PowerOn state to allow the device
-- to stabilize. If we receive a Power Off during that time we
-- cancel the pending state change.
--
UpdatePower = function(self, new, old)
if new == Commands.Power.Param.On then
self:StartTimer(5000)
else
self:CancelTimer()
end
end,
--
-- Timer()
-- Seema V. Nikam
-- 06/01/2011
--
-- If our 4 second timer elapses and the power is still on then
-- move us into the Power On state.
--
Timer = function(self)
if Commands.Power.Value == Commands.Power.Param.On then
self.Machine:Set('Power On')
end
end,
Close = event_close,
}
------------------------------------------------------------------------------
-- Create the common state machine
------------------------------------------------------------------------------
State = createStateMachine('State')
State:Add('Closed', StateClosed)
State:Add('Open', StateOpen)
State:Add('Power On', StatePowerOn)
State:Add('Power Off', StatePowerOff)
State:Set('Closed')
------------------------------------------------------------------------------
-- --
-- Common Functions to all drivers --
-- --
------------------------------------------------------------------------------
--
-- start()
-- Seema V. Nikam
-- 06/01/2011
--
-- common driver entry point, we just need to open the stream, the rest
-- will happen once the stream is opened
--
function start()
stream = createProtocolStream(config.port)
collectgarbage('collect')
debug('verbose', 'Memory Usage: '..collectgarbage('count'))
debug.enableWatchdog()
end
function stop()
debug.disableWatchdog()
end
--
-- createProtocolStream(comm)
-- Seema V. Nikam
-- 06/01/2011
--
-- Open the stream specified as requested by the caller. This
-- will also store the stream handle away for future usage and
-- insure that the appropriate asynchronous callback is registered
--
function createProtocolStream(comm)
-- We use the port parameter from the stream, other than that
-- We use hard-wired RS-232 settings
local port
if stream then
port = comm:match('^comm://([^;]*)')
end
if not port then
port = 'default'
end
comm = 'comm://'..port..';baud=9600;parity=none;bits=8;stop=1'
-- create the real stream
debug('stream', 'opening "'..comm..'"')
stream = createStream(comm)
--
-- stream:onOpen()
-- Seema V. Nikam
-- 06/01/2011
--
-- automatically called when the stream is successfully opened
--
function stream:onOpen()
debug('stream', 'opened')
self.isOpened = true
self.locked = false
-- start the async input running
self:startAsyncInput(self.onDataReady, {endString = '\r'})
-- timer that fires when a command times out with no response
self.commandTimer = createTimer(nil, self.CommandTimeout)
-- timer that fires to attempt to lock the stream and send the
-- next command
self.startTimer = createTimer(nil,
function (timer)
self:SendNextCommand()
end
)
State:Event('Open')
end
--
-- stream:onClose()
-- Seema V. Nikam
-- 06/01/2011
--
-- register the onClose function which will be invoked if the
-- stream is ever closed (the only time this will happen is
-- if the z8 crashes)
function stream:onClose()
debug('stream', 'closed')
State:Event('Close', self)
self.isOpened = false
self.locked = false
if self.powerCheckTimer then
self.powerCheckTimer:cancel()
self.powerCheckTimer = nil
end
if self.startTimer then
self.startTimer:cancel()
self.startTimer = nil
end
end
--
-- stream:onDataReady(data)
-- Seema V. Nikam
-- 06/01/2011
--
-- register the async input function, this will get invoked
-- whenver there's a \r terminated string available with the
-- available data
function stream:onDataReady(data)
debug('i/o', '>'..data)
-- strip the trailing space and return
local cmd = data:match('^(.*[^ ]) *\r')
local arg = nil
if cmd == nil then
debug('error', 'Unknown response: "'..data..'"')
return
end
debug('i/o', 'Command="'..cmd..'"')
-- find a command in the Commands table that prefixes
-- this response/event and update it and generate
-- an appropriate update event
for name, info in pairs(Commands) do
if type(info) ~= 'table' then
elseif info.Command == nil then
debug('error', 'Commands.'..name..'.Command = nil')
elseif cmd:sub(1, info.Command:len()) == info.Command then
arg = cmd:sub(info.Command:len() + 1)
com = cmd:sub(1, info.Command:len())
debug('i/o', 'com="'..com..'" arg="'..arg..'"')
local new = arg
local old = info.Value
if info.Decode then
new = info:Decode(new)
end
if new ~= nil then
info.Value = new
State:Event('Update'..name, new, old)
State:Event('Update', name, new, old)
end
end
end
-- attempt to find a matching command in the CommandQueue
-- that we've now completed
self:CompleteCommand(cmd)
end
--
-- stream:SendNextCommand()
-- Seema V. Nikam
-- 06/01/2011
--
-- send the next command in the queue. This will take care of
-- locking and unlocking the stream as required. If the stream
-- can not be locked, then a timer will be set to try again
-- shortly
--
function stream:SendNextCommand()
local cmd = self.CommandQueue[1]
if cmd then
if not self.locked then
if not self:lock(100) then
debug('stream', 'lock failed')
self.startTimer:queue(100)
return
else
debug('stream', 'locked')
self.locked = true
end
end
local line = cmd.cmd..cmd.param
debug('i/o', '<'..line)
-- send the command
self:write(line..'\r');
-- and restart the timer
self.commandTimer:queue(cmd.timeout);
else
-- there are no more commands to send, so unlock the stream
if self.locked then
if not self:unlock() then
debug('stream', 'unlock failed')
self.startTimer:queue(100)
else
debug('stream', 'unlocked')
self.locked = false
end
end
-- and cancel the timer
self.commandTimer:cancel()
end
end
--
-- stream:QueueCommand(cmd, param, timeout, onComplete, retries)
-- Seema V. Nikam
-- 06/01/2011
--
-- queue a command to be sent, we have to put the command in a queue and
-- deal with the response asynchronously because we're using async
-- input, which can only happen when we're not in the driver already.
--
stream.CommandQueue = {}
function stream:QueueCommand(cmd, param, timeout, onComplete, retries)
if type(cmd) ~= 'table' then
cmd = {
cmd=cmd,
param=(param or ""),
Complete=onComplete,
retries=(retries or 3),
timeout=(timeout or 500)
}
end
-- put the command in the queue
table.insert(self.CommandQueue, cmd)
debug('stream', 'Queued = '..#self.CommandQueue..' '..cmd.cmd..' '..cmd.param..' to='..cmd.timeout)
-- start the transmission process if this is the first command
-- in the queue
if #self.CommandQueue == 1 then
self:SendNextCommand()
end
return command
end
--
-- stream:CompleteCommand()
-- Seema V. Nikam
-- 06/01/2011
--
-- check if this is a response for the last transmitted command and
-- remove it from the queue if so.
function stream:CompleteCommand(cmd)
local last = self.CommandQueue[1]
if not last then
return
end
if cmd:sub(1, last.cmd:len()) == last.cmd then
-- last command successfully completed
debug('stream', 'Completed: '..last.cmd..':'..last.param)
-- remove the command from the queue
table.remove(self.CommandQueue, 1)
-- let the caller know it completed if they care
if last.Complete then
last:Complete(cmd:sub(last.cmd:len() + 1))
end
-- send the next command in the queue
self:SendNextCommand()
end
end
--
-- stream:ClearCommandQueue()
-- Seema V. Nikam
-- 06/01/2011
--
-- clear all the queued commands out of the queue, sending each
-- an aborted message if it cares
--
function stream:ClearCommandQueue()
debug('stream', 'Clear Command Queue')
-- notify everybody they've been aborted
for _, cmd in pairs(self.CommandQueue) do
if cmd.Complete then
cmd:Complete(nil, 'abort')
end
end
-- clear the command queue
self.CommandQueue = {}
-- and process it to kill timers and unlock as necessary
self:SendNextCommand()
end
--
-- stream.CommandTimeout(timer)
-- Seema V. Nikam
-- 06/01/2011
--
-- deal with a command that has timed out by taking it out of the queue
-- and calling the Timeout and DriverTimeout routines if defined
--
function stream.CommandTimeout(timer)
local last = stream.CommandQueue[1]
if not last then
return nil
end
debug('error', 'Timeout: '..last.cmd..':'..last.param)
-- let the caller know it timed out if they care
if last.Complete then
last:Complete(nil, 'timeout')
end
-- remove the command from the queue
table.remove(stream.CommandQueue, 1)
-- Since not all commands have a response (they won't respond
-- if nothing changes, we can't purge the queue when we don't
-- get a response, instead we have to try to send the next
-- command since ClearCommandQueue normally does it for us.
-- clear the rest of the command queue
--self:ClearCommandQueue()
stream:SendNextCommand()
end
return stream
end
--
-- interpolate(from, frommin, frommax, tomin, tomax)
-- Seema V. Nikam
-- 06/01/2011
--
-- interpolate a value from in the range frommin..frommax to a
-- value in the range tomin..tomax
--
function interpolate(from, frommin, frommax, tomin, tomax)
if from == nil then
return nil
end
return tomin + ((from - frommin) * (tomax - tomin) / (frommax - frommin))
end
--
-- doHandleToggle
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the real work of all the toggleable commands (#MUTE and #AMP) for
-- now
function doHandleToggle(command, how)
if command ~= Commands.Power then
if State:Get() ~= 'Power On' then
debug('warning', 'ignoring command while power not on')
return
end
else
if State:Get() == 'Closed' then
return
end
end
command:Set(how)
end
--
-- handle_pwr(cmd)
-- Seema V. Nikam
-- 06/07/2011
-- Handle the #PWR command
--
function handle_pwr(cmd)
debug('commands', '#PWR '..(cmd.params[1] or ''))
doHandleToggle(Commands.Power, cmd.params[1])
end
------------------------------------------------------------------------------------------------------------------------------------------------------------
-- --
-- The Multiplexer service (MUX) --
-- --
------------------------------------------------------------------------------------------------------------------------------------------------------------
if config.func == 'MUX' then
status = NSStatus:new()
-- We care about the Source attribute
StatePowerOn.StartUpQueries = {
Commands.Source,
}
-- We want power mirrored into the 'power' attribute
StatePowerOn.Attribute = 'power'
--
-- StatePowerOn:UpdateSource(new, old)
-- Seema V. Nikam
-- 06/01/2011
--
-- When the selected source changes, notify the upper layer
--
function StatePowerOn:UpdateSource(new, old)
-- Let the upper layer (lower?) know about the source selection status
local cmd = '#@'..serviceName..'#MUX_SELECT %'..new
debug('verbose', 'Line:1245:In StatePowerOn: send command: "'..cmd..'"')
_sendAsciiCommand(cmd)
end
--
-- handle_input(cmd)
-- 06/01/2011
-- Seema V. Nikam
--
-- Handle the #INPUT command
--
function handle_input(cmd)
if State:Get() ~= 'Power On' then
return
end
local si = tonumber(cmd.params[1])
if Commands.Source.Value ~= si then
Commands.Source:Set(si)
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------
-- --
-- AVR (renderer) driver --
-- --
------------------------------------------------------------------------------------------------------------------------------------------------------------
elseif config.func == 'AVR' then
function table.mergen(dst, ...)
for _, tab in pairs({...}) do
for _, item in pairs(tab) do
table.insert(dst, item)
end
end
return dst
end
function table.merge(dst, ...)
for _, tab in pairs({...}) do
for key, item in pairs(tab) do
if dst[key] == nil then
dst[key] = item
end
end
end
return dst
end
--
-- decode_level(cmd)
-- 06/01/2011
-- Seema V. Nikam
--
-- standard function for decoding level based commands.
--
function decode_level(self, param)
param = tonumber(param)
-- scale to 0..100
return interpolate(param, self.Param.MinEncode, self.Param.MaxEncode, self.Param.Min, self.Param.Max)
end
--
-- encode_level(cmd)
-- 06/01/2011
-- Seema V. Nikam
--
-- standard function for encoding level based commands.
--
function encode_level(self, param)
if param == self.Param.Up then
return 'UP'
elseif param == self.Param.Down then
return 'DOWN'
end
param = interpolate(param, self.Param.Min, self.Param.Max, self.Param.MinEncode, self.Param.MaxEncode)
debug("verbose","Line:1318:In encode_level:",string.format("%02d", param))
return string.format("%02d", param)
end
--
-- Commands table entries for the Bass and Treble,
-- which only we care about
--
Commands.Volume = {
Command = 'MV',
Set = basic_set,
Query = basic_query,
Param = {
Min = 0,
-- Default = 30,
Max = 100,
MaxEncode = 810,
MinEncode = 000,
Up = "UP",
Down = "DOWN",
},
--
-- Decode(self, param)
-- Seema V. Nikam
-- 06/01/2011
--
-- map the MV parameter into a 0-100 range used by the GUI
--
Decode = function(self, param)
if param:sub(1,4) == 'MAX ' then
-- check for MVMAX command, which seems to be a range change
param = param:sub(5)
if param:len() == 2 then
param = tonumber(param) * 10
else
param = tonumber(param)
end
if self.Param.MaxEncode ~= param then
debug('warning', 'Max Volume Changed: '..param)
self.Param.MaxEncode = param
end
param = self.RawValue
elseif param == '99' then
-- check special case
self.RawValue = 0;
return 0
else
-- convert to integer in the range Min..Max
if param:len() == 2 then
param = tonumber(param) * 10
else
param = tonumber(param)
end
end
self.RawValue = param
-- scale to 0..100
return interpolate(param, self.Param.MinEncode, self.Param.MaxEncode, self.Param.Min, self.Param.Max)
end,
--
-- Encode(self, param)
-- Seema V. Nikam
-- 06/01/2011
--
-- Map 0-100 into the values used by the device
--
Encode = function(self, param)
-- check special case
if param == 0 then
return '99'
elseif param == self.Param.Up then
return 'UP'
elseif param == self.Param.Down then
return 'DOWN'
end
-- scale to Min..Max
param = interpolate(param, self.Param.Min, self.Param.Max, self.Param.MinEncode, self.Param.MaxEncode)
-- round to nearest 0 or 5
param = (param * 2 + 4) / 10 * 5
-- convert to string
param = string.format('%03d', param)
-- lop trailing 0 off
if param:sub(3, 3) == '0' then
param = param:sub(1, 2)
end
return param
end,
}
Commands.Surround = {
Command = 'MS',
Set = basic_set,
SetTimeout = 2000, -- Setting Surround mode can be slow
Query = basic_query,
Decode = function(self, param)
return SurroundMode:Key2Index(param)
end,
Encode = function(self, param)
if param == nil or param == "" then
param = "NEXT"
elseif type(param) == "string" then
param = param:upper()
end
if param == "NEXT" then
param = (self.Value or 0) + 1
if param > #SurroundMode then
param = 1
end
elseif param == "PREV" then
param = (self.Value or (#SurroundMode + 1)) - 1
if param <= 0 then
param = #SurroundMode
end
end
return SurroundMode:Index2Key(param)
end,
}
Commands.Menu = {
Command = 'MNMEN ',
Set = function(self, param)
-- Since the MNMEN command has no response/value, we
-- attempt to track whether the menu is up or not so
-- we can toggle it
param = self:Encode(param)
basic_set(self, param)
self.Value = param
end,
Query = basic_query,
Param = {
On = "ON",
Off = "OFF",
},
Encode = encode_toggle,
}
Commands.Nav = {
Command = 'MNC',
Set = basic_set,
Param = {
UP = "UP",
DN = "DN",
LT = "LT",
RT = "RT",
},
}
Commands.Enter = {
Command = 'MNENT',
Set = basic_set,
Encode = function(self, param)
return ""
end,
}
Commands.NightTime = {
Command = "PSNIGHT ",
Set = basic_set,
Query = basic_query,
Param = {
On = "HI",
Off = "OFF",
},
Encode = encode_toggle,
}
Commands.Bass = {
Command = 'PSBAS ',
Set = basic_set,
Query = basic_query,
Param = {
Min = 0,
Default = 50,
Max = 100,
MinEncode = 44,
MaxEncode = 56,
Up = "UP",
Down = "DOWN",
},
decode = decode_level,
encode = encode_level,
}
Commands.Treble = {
Command = 'PSTRE ',
Set = basic_set,
Query = basic_query,
Param = {
Min = 0,
Default = 50,
Max = 100,
MinEncode = 44,
MaxEncode = 56,
Up = "UP",
Down = "DOWN",
},
decode = decode_level,
encode = encode_level,
}
--
-- A table of things we can change with #LEVEL_SET, #LEVEL_UP and #LEVEL_DN.
-- Each entry maps a #LEVEL_xxx parameter to a Commands table entry
--
Levels = {
vol = Commands.Volume,
bass = Commands.Bass,
treb = Commands.Treble,
}
--
-- A list of the toggleable attributes that we care about, really this is
-- just a list of values to ask for at power up
--
Toggles = {
mute = Commands.Mute,
}
--
-- A list of other attributes that we care about
--
Attributes = {
Commands.Surround
}
-- Attributes we care about is all those in Levels and Toggles
StatePowerOn.StartUpQueries = table.mergen({}, Levels, Toggles, Attributes)
-- We want power mirrored into the 'pwrOn' attribute
StatePowerOn.Attribute = 'ampOn'
-- use the renderer report as the status report
status = rendererReportInstance()
--
-- StatePowerOn:UpdateVolume
-- 06/01/2011
-- Seema V. Nikam
--
-- When we receive a volume update, update the 'vol' attribute
--
function StatePowerOn:UpdateVolume(new, old)
debug('verbose', 'volume = '..Commands.Volume.Value)
status:setField('vol', Commands.Volume.Value)
end
--
-- StatePowerOn:UpdateSurround
-- 06/01/2011
-- Seema V. Nikam
--
-- When we receive a surround mode update, update the 'surround' attribute
--
function StatePowerOn:UpdateSurround(new, old)
local label = SurroundMode:Index2Label(new)
debug('verbose', 'surround = '..new..' label="'..(label or 'nil')..'"')
status:setField('surround', new)
status:setField('surroundLabel', label)
end
--
-- StatePowerOn:UpdateBass
-- 06/01/2011
-- Seema V. Nikam
--
-- When we receive a bass update, update the 'bass' attribute
--
function StatePowerOn:UpdateBass(new, old)
debug('verbose', 'bass = '..Commands.Bass.Value)
status:setField('bass', Commands.Bass.Value)
end
--
-- StatePowerOn:UpdateTreble
-- 06/01/2011
-- Seema V. Nikam
--
-- When we receive a treble update, update the 'treb' attribute
--
function StatePowerOn:UpdateTreble(new, old)
debug('verbose', 'treble = '..Commands.Treble.Value)
status:setField('treb', Commands.Treble.Value)
end
local OnOffMap={
ON = '1',
OFF = '0'
}
-- StatePowerOn:UpdateMute
-- 06/01/2011
-- Seema V. Nikam
--
-- When we receive a mute update, update the 'mute' attribute
--
function StatePowerOn:UpdateMute(new, old)
local report = OnOffMap[Commands.Mute.Value] or Commands.Mute.Value
debug('verbose', 'mute=',report)
status:setField('mute', report)
end
--
-- handle_surround
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the #SURROUND command
--
function handle_surround(cmd)
cmd.params[1] = cmd.params[1] or ''
debug('commands', '#SURROUND '..cmd.params[1])
Commands.Surround:Set(cmd.params[1])
end
function handle_nav(cmd)
debug('commands', '#NAV '..(cmd.params[1] or ''))
Commands.Nav:Set(cmd.params[1])
end
function handle_menu(cmd)
debug('commands', '#MENU')
Commands.Menu:Set()
end
function handle_enter(cmd)
debug('commands', '#ENTER')
Commands.Enter:Set()
end
function handle_nighttime(cmd)
cmd.params[1] = cmd.params[1] or ''
debug('commands', '#NIGHTTIME '..cmd.params[1])
Commands.NightTime:Set(cmd.params[1])
end
--
-- handle_level_set
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the level_set command by looking the parameter up in
-- the Levels table and passing it off for doHandleLevel to
-- do the real work
--
function handle_level_set(cmd)
debug('commands', '#LEVEL_SET '..(cmd.params[1] or '')..' '..cmd.params[2])
local param = cmd.params[1]:lower()
local level = cmd.params[2]:lower()
local command = Levels[param]
if command == nil then
debug("error", "unknown argument to level_set: "..param)
return
end
doHandleLevel(command, level)
end
--
-- handle_level_up
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the level_up command by looking the parameter up in
-- the Levels table and passing it off for doHandleLevel to
-- do the real work
--
function handle_level_up(cmd)
debug('commands', '#LEVEL_UP '..(cmd.params[1] or ''))
local param = cmd.params[1]:lower()
local command = Levels[param]
if command == nil then
debug("error", "unknown argument to level_up: "..param)
return
end
doHandleLevel(command, command.Param.Up)
end
--
-- handle_level_dn
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the level_dn command by looking the parameter up in
-- the Levels table and passing it off for doHandleLevel to
-- do the real work
--
function handle_level_dn(cmd)
debug('commands', '#LEVEL_DN '..(cmd.params[1] or ''))
local param = cmd.params[1]:lower()
local command = Levels[param]
if command == nil then
debug("error", "unknown argument to level_dn: "..param)
return
end
doHandleLevel(command, command.Param.Down)
end
--
-- handle_amp
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the amp command by passing it off to doHandleToggle
-- to do the real work
--
function handle_amp(cmd)
debug('commands', '#AMP '..(cmd.params[1] or ''))
doHandleToggle(Commands.Power, cmd.params[1])
end
--
-- handle_mute
-- Seema V. Nikam
-- 06/01/2011
--
-- Handle the mute command by passing it off to doHandleToggle
-- to do the real work
--
function handle_mute(cmd)
debug('commands', '#MUTE '..(cmd.params[1] or ''))
doHandleToggle(Commands.Mute, cmd.params[1])
end
--
-- doHandleLevel
-- Seema V. Nikam
-- 06/01/2011
--
-- handle the real work of all the #LEVEL_* commands
--
function doHandleLevel(command, level)
if State:Get() ~= "Power On" then
return
end
if level == "default" then
level = command.Param.Default
elseif level ~= command.Param.Up and level ~= command.Param.Down then
level = tonumber(level)
end
if level ~= nil then
command:Set(level)
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------
-- --
-- AM/FM/ Tuner Module --
-- --
------------------------------------------------------------------------------------------------------------------------------------------------------------
elseif config.func == 'Tuner' then
--
-- Commands table entries for the Band and Frequency,
-- which only we care about
--
Commands.Tune = {
Command = 'TF',
--
-- Set
-- 06/01/2011
-- Seema V. Nikam
--
-- When setting a frequency, we first have to insure that the
-- proper band is selected
--
Set = function(self, param)
debug("verbose", "param=", param)
param = self:Encode(param)
local band = nil
local timeout
--======================================================================
debug("verbose","what is this:", param:sub(1,2))
if param:sub(1,2) == 'ST' then
band = Commands.Band.Param.ST
debug("verbose","Line:1788: Set to ST",band)
timeout = 2500
elseif param:sub(1,2) == 'HD' then
band = Commands.Band.Param.AMFM
debug("verbose","Line:1792: Set to HDRADIO",band)
timeout = 2500
elseif tonumber(param:sub(3)) > 50000 then
debug("verbose","Line:1795: this is that compared value:", tonumber(param:sub(3)))
-- tuning AM
band = Commands.Band.Param.AM
else
-- tuning FM
band = Commands.Band.Param.FM
end
debug("verbose","Line:1802: this is band value:", band)
-- Check for band
if Commands.Band.Value ~= band then
debug("verbose","Line:1804: what is this band value?", (Commands.Band.Value))
Commands.Band:Set(band)
end
--======================================================================
stream:QueueCommand(self.Command, param, timeout)
end,
--
-- Query
-- Seema V. Nikam
-- 06/01/2011
--
-- Query the right setting depending on the real selected source
--
Query = function(self, param)
debug("verbose","Line:1819: In Command TF: Query func:")
if Commands.Source.RawValue == 'ST' then
debug("verbose","Line:1821: Commands.Source.RawValue is",Commands.Source.RawValue)
stream:QueueCommand(self.Command..'ST', Commands.Query)
else
debug("verbose","Line:1824: Commands.Source.RawValue is",Commands.Source.RawValue)
stream:QueueCommand(self.Command..'HD', Commands.Query)
end
end,
Param = {
Up = 'UP',
Down = 'DOWN',
Next = 'NEXT',
Prev = 'PREV',
Band = {
AM = { Min = 52000, Delta = 1000, Max = 171000},
FM = { Min = 8750, Delta = 20, Max = 10790},
ST = { Min = 1, Delta = 1, Max = 256},
}
},
--
-- Decode
-- Seema V. Nikam
-- 06/01/2011
--
-- When parsing the return value we have to filter out the Up/Down
-- verification commands by turning them into the current value.
-- Otherwise we just turn the 6-digit value into a string rep of
-- the current frequency.
--
Decode = function(self, param)
if param == nil then
elseif param == self.Param.Up or param == self.Param.Down then
param = self.Value
else
local band = param:sub(1, 2)
local tune = param:sub(3)
if band == 'ST' then
-- SIRIUS (XM) channel needs no conversion
param = tonumber(tune)
debug("verbose","Line:1861: param value in Command TF: Decode() : ",param)
self.LastST = param
else
self.RawValue = tonumber(tune)
debug("verbose","Line:1865: RawValue in Command TF: Decode() : ",self.RawValue)
if(self.RawValue == nil) then
debug('error', 'nil RawValue')
else
if(self.RawValue > 50000) then
-- AM - strip leading zeroes and two trailing digits
-- (which should both be zero)
param = tune / 100
else
-- FM - strip leading zeroes and insert the decimal
local left, right = tune:match('^0*(%d-)(%d%d)$')
param = left..'.'..right
end
self.LastHD = param
end
end
end
return param
end,
--
-- Encode
-- Seema V. Nikam
-- 06/01/2011
--
-- Encode a string representation of the tune request into the rep
-- required by the device
--
Encode = function(self, param)
-- filter out Up/Down requests since we use the external
-- representation already
if param == self.Param.Up or param == self.Param.Down then
if Commands.Source.RawValue == 'ST' then
debug("verbose","Line:1897: RawValue in Command TF: Encode() :",Commands.Source.RawValue)
return 'ST'..param
else
return 'HD'..param
end
end
if param == self.Param.Next then
if Commands.Source.RawValue == 'ST' then
return 'ST'..self.Param.Up
end
if self.RawValue then
local band = self.Param.Band[Commands.Band.Value]
param = self.RawValue - band.Delta
if param < band.Min then
param = band.Max
end
return string.format('HD%06d', param)
else
return 'HD'..Commands.Query
end
elseif param == self.Param.Prev then
if Commands.Source.RawValue == 'ST' then
return 'ST'..self.Param.Down
end
if self.RawValue then
local band = self.Param.Band[Commands.Band.Value]
param = self.RawValue + band.Delta
if param > band.Max then
param = band.Min
end
return string.format('HD%06d', param)
else
return 'HD'..Commands.Query
end
end
--
if type(param) == 'number' then
param = tostring(param)
end
--
local left, dot, right = param:match('0*(%d+)(%.?)(%d*)')
--
if dot == '.' then
-- if there's a dot, then it's FM and we just have to put
-- the two halves together
if right:len() == 1 then
return string.format('HD%04d%d0', tonumber(left), tonumber(right))
else
return string.format('HD%04d%02d', tonumber(left), tonumber(right))
end
else
-- There's no dot, so we have to figure out which band
-- is being requested by inference
local num = tonumber(left)
--
if num < 256 then
-- SIRIUS
return string.format('ST%03d', num)
elseif left:sub(left:len()) == '0' then
-- AM always ends in 0
return string.format('HD%04d00', num)
elseif num >= 875 and num <= 1079 then
-- xxx.x FM
return string.format('HD%05d0', num)
elseif num >= 8750 and num <= 10790 then
-- xxx.xx FM
return string.format('HD%06d', num)
else
debug('error', 'Unknown Frequency Request: "'..param..'"')
return Commands.Query
end
end
end,
}
Commands.Preset = {
Command = 'TPHD',
Set = basic_set,
Query = basic_query,
Param = {
Up = 'UP',
Down = 'DOWN',
Off = 'OFF',
},
Decode = function(self, param)
if param == self.Param.Off then
param = nil
end
end,
}
Commands.STAll = {
Command = 'ST?',
Query = function (self)
return stream:QueueCommand(self.Command, '')
end
}
Commands.ChannelName = {
Command = 'STCH NAME ',
Query = basic_query,
}
Commands.Artist = {
Command = 'STARTIST ',
Query = basic_query,
}
Commands.Title = {
Command = 'STTITLE ',
Query = basic_query,
}
Commands.Antenna = {
Command = 'STSIGNAL ',
Query = basic_query,
Param = {
GOOD = 100,
WEAK = 66,
MARGINAL = 33,
NOSIGNAL = 0,
},
--
-- Decode
-- Seema V. Nikam
-- 06/01/2011
--
-- Translate the string param returned into a number appropriate
-- for GUI.
--
Decode = function(self, param)
return self.Param[param] or self.Param.NOSIGNAL
end,
}
-- An engine to reliably save the current state of the tuner
-- and restore it when we're done scanning the SIRIUS channels
SaveStates = {
{ Status = 'Power', Set = Commands.Power.Param.On },
{ Status = 'Mute', Set = Commands.Mute.Param.On },
{ Status = 'Source', Set = 7 },
{ Status = 'Tune', Set = 0 },
--
-- Reset
-- Seema V. Nikam
-- 06/01/2011
--
-- Forget all saved state information
--
Reset = function(self)
for _, info in ipairs(self) do
info.Restore = nil
end
end,
--
-- Save
-- Seema V. Nikam
-- 06/01/2011
--
-- Save all state information, if the state was successfully
-- saved, true is returned. If the state was not successfully
-- saved, a command is issued for the needed state info and
-- false is returned. If false is returned, Save should be
-- called again in a short period (500ms seems to work)
--
Save = function(self)
if not self:Lock() then
return false
end
for _, info in ipairs(self) do
local state = Commands[info.Status]
if state.Value == nil then
state:Query()
return false
elseif info.Set ~= nil then
if info.Restore == nil then
debug('save', 'Save: '..info.Status..' = '..state.Value)
info.Restore = state.Value
end
if info.Set ~= state.Value then
debug('save', 'Set: '..info.Status..' = '..info.Set..' ('..state.Value..')')
state:Set(info.Set)
return false
end
end
end
return true
end,
--
-- Restore
-- Seema V. Nikam
-- 06/01/2011
--
-- Restore the saved state, no attempt is made to verify that the
-- state is successfully restored
--
Restore = function(self)
for _, info in ripairs(self) do
local command = Commands[info.Status]
if info.Set ~= nil and info.Restore ~= nil then
debug('save', 'Restore: '..info.Status..' = '..info.Restore)
command:Set(info.Restore)
end
end
self:Unlock()
end,
--
-- Saved
-- Seema V. Nikam
-- 06/01/2011
--
-- returns true iff the state is completely saved
--
Saved = function(self)
for _, info in ipairs(self) do
if info.Set ~= nil and info.Restore == nil then
return false
end
end
return true
end,
--
-- Relevant
-- Seema V. Nikam
-- 06/01/2011
--
-- returns true iff we care about the state named in the parameter
--
Relevant = function(self, param)
for _, info in ipairs(self) do
if info.Status == param then
return true
end
end
return false
end,
--
-- Lock
-- Seema V. Nikam
-- 06/01/2011
--
Lock = function(self, param)
if not self.Locked then
if not stream:lock(200) then
return false
end
self.Locked = true
end
return true
end,
--
-- Unlock
-- Seema V. Nikam
-- 06/01/2011
--
Unlock = function(self, param)
if self.Locked then
self.Locked = false
stream:unlock()
end
end,
}
--
-- We need some states to deal with the channel list (no categories are
-- available from the Denon, Bad Denon! *smack*)
--
StatePreRefresh = {
Enter = function(self, from)
status:setField('controlState', 'REFRESH')
self:Save()
end,
Timer = function(self)
self:Save()
end,
Save = function(self)
if SaveStates:Save() then
self.Machine:Set('Refresh')
else
self:StartTimer(5000);
end
end,
Update = function(self, name, new, old)
if SaveStates:Relevant(name) then
self:Save()
end
if name == 'Source' then
Commands.Tune:Query()
debug("verbose","Line:2185: StatePreRefresh: Source RawValue", Commands.Source.RawValue)
if Commands.Source.RawValue == 'ST' then
Commands.STAll:Query()
end
end
end,
Exit = function(self, to)
if to ~= 'Refresh' then
status:setField('controlState', 'STOP')
end
end,
}
StateRefresh = {
-- Add Channel, GetNextChannel, UpdateChannelName, UpdateTune
Enter = function(self, from)
status:setField('controlState', 'REFRESH')
Channels = {}
self.Channels = {}
self.NextChannel = 0
self.Channel = nil
self.ChannelName = nil
self:GetNextChannel()
end,
AddChannel = function(self, channel, name)
debug('scan', 'Add Channel #'..channel..' "'..name..'"')
self.Channels[channel] = { Channel = channel, ChannelName = name }
end,
GetNextChannel = function(self)
self.NextChannel = self.NextChannel + 1
if self.NextChannel >= 256 then
self.Machine:Set('Post-Refresh')
end
self.ChannelName = nil
self.Channel = nil
self.Retries = 0
Commands.Tune:Set(self.NextChannel)
self:StartTimer(3000)
end,
UpdateChannelName = function(self, new, old)
-- The second time we get a valid channel name we've got the info for
-- the tuned channel. This is a valid new channel if it's the one
-- we requested
if new ~= '' then
self.ChannelName = new
if self.Channel == self.NextChannel then
debug('scan', 'Add Channel: "'..self.ChannelName..'" Retries='..self.Retries)
self:AddChannel(self.Channel, self.ChannelName)
self:GetNextChannel()
elseif self.Channel then
debug('scan', 'Incorrect Channel #'..(self.Channel or 'nil')..' ~ '..self.NextChannel)
self:GetNextChannel()
end
else
self.ChannelName = nil
end
self:StartTimer(3000)
end,
UpdateTune = function(self, new, old)
self.Channel = new
self:StartTimer(5000)
end,
Timer = function(self)
if self.Channel == nil then
debug('scan', 'Timeout on channel number, invalid channel')
-- If we haven't gotten a channel number by the time everything
-- is quiet, then this is an invalid channel, so just skip it
self:GetNextChannel()
elseif self.Retries < 10 then
-- Sometimes the tuner takes awhile to come up with the channel
-- name. We should be on a valid channel since the channel #
-- has been updated as expected. In this case we need to ask
-- for the channel name until we get it. (Perhaps we should
-- just set the channel name to some predetermined string and
-- wait for an update?)
debug('scan', 'Retry Info Request')
self.Retries = self.Retries + 1
Commands.STAll:Query()
self:StartTimer(5000)
else
-- Sometimes the tuner just gets completely lost and doesn't
-- seem to know what channel it's on, much less what the
-- channel name is. In this case we back up a couple of
-- channels and restart the process
if Commands.Artist.Value and Commands.Artist.Value ~= '' then
debug('scan', 'Channel Name Not Available, Fabricating')
self:AddChannel(self.Channel, 'SIRIUS Channel '..self.Channel)
else
debug('scan', 'Channel Name Not Available, Skipping')
end
self:GetNextChannel()
end
end,
Exit = function(self, to)
Channels = {}
for i = 1, 256 do
if self.Channels[i] then
table.insert(Channels, self.Channels[i])
end
end
if to ~= 'Post-Refresh' then
status:setField('controlState', 'STOP')
end
end,
}
StatePostRefresh = {
Enter = function(self, from)
status:setField('controlState', 'REFRESH')
SaveStates:Restore()
self:StartTimer(5000)
end,
Timer = function(self)
Commands.Power:Query()
self:StartTimer(5000)
end,
UpdatePower = event_power,
Exit = function(self, to)
status:setField('controlState', 'PLAY')
end,
}
State:Add('Pre-Refresh', StatePreRefresh)
State:Add('Refresh', StateRefresh)
State:Add('Post-Refresh', StatePostRefresh)
-- At power up we need to know the band and the frequency
StatePowerOn.StartUpQueries = { Commands.Band, Commands.Tune, Commands.Source }
-- put power state in the 'power' attribute
StatePowerOn.Attribute = 'power'
-- use the song report as our status
status = songReportInstance()
--
-- Info
-- Seema V. Nikam
-- 06/01/2011
--
-- Update the AM/FM song report
--
function UpdateAMFMInfo()
debug("verbose","Line 2329: In UpdateAMFMInfo", Commands.Band.Value)
local caption = (Commands.Band.Value or '')..' '..(Commands.Tune.Value or '')
-- these are the AM/FM screen values
status:setField('band', Commands.Band.Value or '?')
status:setField('caption', caption)
-- these are the SIRIUS (XM) screen values we make up as we go
status:setField('song', '')
status:setField('channelNum', '')
status:setField('category', '')
status:setField('artist', '')
status:setField('channel', caption)
status:setField('strength', '')
end
--
-- UpdateSTInfo
-- Seema V. Nikam
-- 06/01/2011
--
-- Update the SIRIUS song report
--
function UpdateSTInfo()
-- update the band
status:setField('band', Commands.Band.Value)
-- channel information
status:setField('channelNum', Commands.Tune.Value)
status:setField('channel', Commands.ChannelName.Value)
debug("verbose","Line:2355: In UpdateSTInfo",Commands.Band.Value, Commands.Tune.Value, Commands.ChannelName.Value)
-- for XM we set the artwork and use the channel name
-- local CommandsTuneValue = Commands.Tune.Value or ''
--
-- Previously done in Denon_AVR 3808
-- local strArtwork = artworkNameLookup[CommandsTuneValue] or 'def_src_img_1.jpg'
-- status:setField('artwork', 'http://'..ipAddress..'/'..strArtwork)
--
---------------------------------------------------------------------------------------------------------------------------
-- setting a artwork for SIRIUS
-- Dan's suggested: 06/21/2011
-- Give SIRIUS artworkName here: as done in PolkXM
local CommandsChannelValue = Commands.ChannelName.Value or ''
if CommandsChannelValue~=nil then
local strArtwork= "SIRIUS/"..CommandsChannelValue:gsub("[ /]","_"):gsub("^xL_","")..".swf"
end
-- set the caption to the channel # and name
status:setField('caption',
CommandsTuneValue..' '..(Commands.ChannelName.Value or ''))
-- song information
status:setField('song', Commands.Title.Value or '')
status:setField('artist', Commands.Artist.Value or '')
status:setField('category', '') -- 3808 doesn't support categories
-- signal strength
status:setField('strength', Commands.Antenna.Value or '0')
end
--
-- UpdateInfo
-- Seema V. Nikam
-- 06/01/2011
--
-- Update the song report
--
function UpdateInfo()
debug("verbose","Line:2389: In UpdateInfo(): If Commands.Source.RawValue is ST then call UpdateSTInfo() else call UpdateAMFMInfo(): ",Commands.Source.RawValue)
if Commands.Source.RawValue == 'ST' then
UpdateSTInfo()
else
UpdateAMFMInfo()
end
end
--
-- StatePowerOn:UpdateSource
-- Seema V. Nikam
-- 06/01/2011
--
-- When the source changes, force an update of the tuning
--
function StatePowerOn:UpdateSource(new, old)
-- Since the channel/frequency hasn't actually changed, we won't
-- get an update, so explicitly query for the tuning information
Commands.Tune:Query()
if new == 8 then
-- Update the band as necessary as well
local new = Commands.Band.Value
debug("verbose","Line:2412: band value in StatePowerOn:UpdateSource",new)
if Commands.Source.RawValue == 'ST' then
new = Commands.Band.Param.ST
elseif Commands.Source.RawValue == 'HD' then
--new = Commands.Band.RawValue
new = Commands.Band.Param.AMFM
end
Commands.Band:Update(new)
end
-- And query the SIRIUS information if it's appropriate just to be
-- sure we have current information
if Commands.Source.RawValue == 'ST' then
Commands.STAll:Query()
end
end
--
-- StatePowerOn:UpdateBand
-- Seema V. Nikam
-- 06/01/2011
--
-- When the band changes, update the attribute
--
function StatePowerOn:UpdateBand(new, old)
UpdateInfo()
end
--
-- StatePowerOn:UpdateTune
-- Seema V. Nikam
-- 06/01/2011
--
function StatePowerOn:UpdateTune(new, old)
debug('verbose', 'channel = '..(new or 'nil'))
UpdateInfo()
end
--
-- StatePowerOn:UpdatePreset
-- Seema V. Nikam
-- 06/01/2011
--
-- When the band changes, update the attribute
--
function StatePowerOn:UpdatePreset(new, old)
status:setField('preset', new)
end
--
-- StatePowerOn:UpdateChannelName
-- Seema V. Nikam
-- 06/01/2011
--
function StatePowerOn:UpdateChannelName(new, old)
debug('verbose', 'channel name = '..(new or 'nil'))
UpdateInfo()
end
--
-- StatePowerOn:UpdateArtist
-- Seema V. Nikam
-- 06/01/2011
--
function StatePowerOn:UpdateArtist(new, old)
debug('verbose', 'artist = '..(new or 'nil'))
UpdateInfo()
end
--
-- StatePowerOn:UpdateTitle
-- Seema V. Nikam
-- 06/01/2011
--
function StatePowerOn:UpdateTitle(new, old)
debug('verbose', 'title = '..(new or 'nil'))
UpdateInfo()
end
--
-- StatePowerOn:UpdateAntenna
-- Seema V. Nikam
-- 06/01/2011
--
function StatePowerOn:UpdateAntenna(new, old)
debug('antenna', 'antenna = '..(new or 'nil'))
UpdateInfo()
end
-- function handle_band(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #BAND commands
function handle_band(command)
debug('verbose', command)
if State:Get() ~= 'Power On' then
return
end
local band = command.params[1]:upper()
debug("verbose","Band: ",band)
if band == 'NEXT' then
band = Commands.Band.Param.Next
elseif band == 'PREV' then
band = Commands.Band.Param.Prev
else
band = Commands.Band.Param[band]
end
Commands.Band:Set(band)
end
--
-- function handle_tune(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #TUNE commands
function handle_tune(command)
local dir = command.params[1]:upper()
debug('verbose', 'Line:2522: In_Handle_tune: Tuning what:?', dir)
if( dir == "DN" ) then
Commands.Tune:Set(Commands.Tune.Param.Next)
elseif( dir == "UP" ) then
Commands.Tune:Set(Commands.Tune.Param.Prev)
else
Commands.Tune:Set(dir)
end
end
--
-- function handle_tune(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #CHANNEL commands
handle_channel = handle_tune
-- function handle_seek(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #SEEK commands
function handle_seek(command)
local dir = command.params[1]:upper()
if( dir == "DN" ) then
Commands.Tune:Set(Commands.Tune.Param.Down)
elseif( dir == "UP" ) then
Commands.Tune:Set(Commands.Tune.Param.Up)
else
Commands.Tune:Set(dir)
end
end
--
-- function handle_scan(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #SCAN commands
handle_scan = handle_seek
--
-- function handle_preset(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #PRESET UP|DN|ad commands a is A..G, d is 1..8
function handle_preset(command)
local dir = command.params[1]:upper()
if( dir == "DN" ) then
Commands.Preset:Set(Commands.Preset.Param.Up)
elseif( dir == "UP" ) then
Commands.Preset:Set(Commands.Preset.Param.Down)
else
Commands.Preset:Set(command.params[1])
end
end
--
-- function handle_next(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #NEXT commands
function handle_next(command)
Commands.Preset:Set(Commands.Preset.Param.Up)
end
--
-- function handle_prev(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #PREV commands
function handle_prev(command)
Commands.Preset:Set(Commands.Preset.Param.Down)
end
--
-- function handle_key(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #KEY commands
function handle_key(command)
debug('key', '#KEY '..prettyprint(command.params))
if( command == nil or command.params[1] == nil) then
return
end
if not szInput then
szInput = ""
end
if( szInput:len() > 6 ) then
szInput = szInput:sub(2,-1)
end
szInput = szInput..command.params[1]:upper()
end
--
-- function handle_key(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #CLEAR commands
function handle_clear( command )
debug('key', '#CLEAR '..prettyprint(command.params))
szInput = nil
end
--
-- function handle_enter(command)
-- Seema V. Nikam
-- 06/01/2011
-- Receive and process #ENTER commands
function handle_enter(command)
debug('key', '#ENTER '..prettyprint(command.params))
if szInput then
debug("verbose", "szInput is "..szInput)
Commands.Tune:Set(szInput)
end
-- clear out szInput
szInput = nil
end
--
-- function handle_menu_list(command)
-- Seema V. Nikam
-- 06/01/2011
-- handler for #MENU_LIST indexStart, indexEnd, path, searchParams
-- path format: "PRESET"|"MEDIA"|"MEDIA2" [ ">" [ ">" [...] ] ]; ex: "MEDIA>ALLDISCS>Disc_10>Title_5>Chapter_2"
function handle_menu_list(aCommand)
if (#aCommand.params > 4) then
debug("warning", "Extended #MENU_LIST params received: ", aCommand.params)
end
if State:Get() ~= 'Power On' then
return
end
local nStart = tonumber(aCommand.params[1])
local nEnd = tonumber(aCommand.params[2])
local root = aCommand.vPath[1]:upper()
if (root == "PRESETS") then
-- default preset handling is sufficient
defaultHandleMenuListPresets(aCommand, nStart, nEnd)
elseif (root == "MEDIA" or root == "MEDIA2") then
handleMenuListMedia(aCommand, nStart, nEnd)
else
aCommand:sendFinalMenuResp()
end
end
function handleMenuListMedia(aCommand, nStart, nEnd)
local vPath = aCommand.vPath
local strDisplayPath = vPath[1]
local response = aCommand.menuResponse
if #vPath ~= 1 then
aCommand:sendFinalMenuResp()
return
end
response.disppath = 'All SIRIUS Channels'
-- put a note symbol next to this item
aCommand.replyTag = 'song'
for nIndex = nStart, nEnd do
-- look up the channel
local channel = Channels[nIndex]
if(channel == nil) then
response.error = "Unknown channel "..(nIndex or "nil")
debug("error", response.error);
return aCommand:sendFinalMenuResp(response)
end
-- use the channel number and name as the display string
response.display = channel.Channel..' '..channel.ChannelName
-- never any children
response.children = 0
-- use the index as the itemnum
response.itemnum = tostring(nIndex)
-- use the channel # as the ID
response.id = tostring(channel.Channel)
-- send a response
aCommand:sendMenuResp(response)
if(nIndex == #Channels) then
--we have sent the entire list
return aCommand:sendFinalMenuResp(response)
end
end
end
-- function handle_menu_sel(command)
-- Seema V. Nikam
-- 06/01/2011
-- handler for #MENU_SEL path
-- path format: see handle_menu_list
handle_menu_sel = function(command)
if (#command.params > 1) then
debug("warning", "Extended #MENU_SEL params received: ", command.params)
end
if State:Get() ~= 'Power On' then
return
end
local root = command.vPath[1]:upper()
if root == "PRESETS" then
-- handle "#MENU_SEL {{presets>...}}" commands, where the
-- presets are stored in the table passed by the dealer setup
command:handlePresetMenuSel(command.vPath[2])
end
end
-- function handle_menu_set(command)
-- Seema V. Nikam
-- 06/01/2011
-- handler for #MENU_SET index
handle_menu_set = function(command)
if (#command.params > 1) then
debug("warning", "Extended #MENU_SET params received: ", command.params)
end
if State:Get() ~= 'Power On' then
return
end
local strDisplay = (Commands.Band.Value or "")..szFreq
command:handlePresetMenuSet(strDisplay, "#TUNE "..szFreq)
-- permanently save presets
presetSaveOverrides()
end
--
-- function handle_refresh(command)
-- Seema V. Nikam
-- 06/01/2011
--
-- Handler for #REFRESH command, just starts the refresh process
--
function handle_refresh(command)
if State:Get() == 'Power On' or State:Get() == 'Power Off' then
State:Set('Pre-Refresh')
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Excluded from Denon AVR 3311CI, was not implemented even in Denon AVR 3808CI driver --
-- XM Tuner Module --
-- --
------------------------------------------------------------------------------------------------------------------------------------------------------------
--elseif config.func == 'XM' then
else
debug('error', 'Unknown Service Function: '..config.func)
end