From: Matthew W. <mat...@us...> - 2006-05-30 20:06:27
|
Kirk, I've updated the repository with your new version. It is in rev 460. Matt Kirk Bauer wrote: > I have updated Musica.pm to support at least some of the new command set > found in Netstream's newest Musica whole-house audio system (5602 > series). I have to say that I am VERY impressed by the reliability > improvements and feature enhancements found in this latest version of > their system. My music system is finally working as reliably as I would > like. > > > ------------------------------------------------------------------------ > > =begin comment > @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ > > File: > Musica.pm > > Description: > Allows control of the Musica whole-house audio system by Netstreams over the > RS232 port. This system has excellent controllability through Misterhouse > and provides 6 zones and 4 sources with very nice keypads. > > http://www.netstreams.com > > If you are interested in installing a Netstreams Musica system yourself, > contact me and I can put you in contact with somebody who can give you > a good price. > > Note that you must use a null-modem cable between the Musica system and your > Misterhouse computer. > > This module uses Rev 2.0 of the Musica RS-232 protocol. > > Author: > Kirk Bauer > ki...@ka... > > You can get the most current version of this file and other files related > whole-house music/speech setup here: > http://www.linux.kaybee.org:81/tabs/whole_house_audio/ > > License: > This free software is licensed under the terms of the GNU public license. > > Important Note: > Because the new FM-Tuner keypads (and possibly other newer keypads?) take > *forever (minutes) to respond to the StatVer command, their version is > stored in the persistant Misterhouse %Save hash. This means that if you > change the physical keypad, you'll need to stop Misterhouse, edit > data_dir/mh_temp.saved_states.persistent, and search for 'Musica' and > remove or modify the entry for the zone you chaged. Then start Misterhouse > back up. If you simply remove a keypad, just take the entry for that zone > out of your .mht file -- you don't need to worry about %Save. > > Also, some keypads never respond to the StatVer command Misterhouse sends > to try to find the version of each keypad. Watch your print log and if > You see StatVer being re-send over and over again for a specific keypad, > you may want to specify its version in your .mht file as so: > > MUSICA_ZONE, music_kitchen, Musica, 1, 40822 > > My keypads (version 40822) do respond to StatVer after 2-3 minutes and > then this version is stored by the Musica module in the persistent %Save > hash. > > The only keypad versions I know are: > 30419: MU4601 keypad > 40822: MU4602 FM-Tuner keypad > > Initialization: > You can define any number of Musica systems, but each one requires its own > serial port. To begin with, come up with a name for your object such as > $Musica. Then add an entry to your mh.private.ini file: > > Musica_serial_port=/dev/ttyS9 > > If you wanted to define two Musica systems, you would just choose two unique > names: > > Musica1_serial_port=/dev/ttyS9 > Musica2_serial_port=/dev/ttyS10 > > Once you have specified which serial port to use, simply define the main > object and your zone objects in your .mht file: > > MUSICA, Musica > MUSICA_ZONE, music_kitchen, Musica, 1 > MUSICA_ZONE, music_master_bed, Musica, 2 > > This would define the object $Musica, with zone 1 named $music_kitchen, and > zone 2 named $music_master_bed. If you have more than one system to > control, just use the same names as you used in your .ini file: > > MUSICA, Musica1 > MUSICA, Musica2 > MUSICA_ZONE, music_kitchen, Musica1, 1 > MUSICA_ZONE, music_master_bed, Musica1, 2 > MUSICA_ZONE, music_patio, Musica2, 1 > MUSICA_ZONE, music_dining_room, Musica2, 2 > > Finally, you can define an object representing each of the four Musica > sources if you so desire. This is only necessary if you need to do > something like start or stop an MP3 player or turn something on via X10 when > a source is accessed, if you want to watch for users changing the source > labels from keypads, or if you want to change the source labels from > Misterhouse. Finally, if you want to watch for button presses or cause > buttons to be pressed through Misterhouse, these are useful. These are not > required for casual use. > > MUSICA_SOURCE, mrhouse_speech, Musica, 1 > MUSICA_SOURCE, am_fm_tuner, Musica, 2 > MUSICA_SOURCE, music_source3, Musica, 3 > MUSICA_SOURCE, music_source4, Musica, 4 > > Interface Overview: > All objects (the main Musica object, the zone objects, and the source objects) > will return various states (as documented below) when somebody performs any > kind of action from a keypad. > > If somebody changes the volume in a specific zone, for example, the object > for that zone will have a state of 'volume_changed'. You can watch for > those states as follows: > > if ($state = state_now $music_kitchen) { > print_log "Kitchen keypad new state: $state"; > } > > Note that when changes are made FROM Misterhouse (using the various > controlling functions such as set_volume()) any resulting object state > changes will have the set_by set to 'misterhouse'. By contrast, new states > initiated by the keypad or Musica remote control have a set_by of 'keypad'. > You can also see that your command took effect by calling get_volume() and > seeing that it returns the volume you set. Note that this function will > return your new value only after your command has been sent to the Musica > system and it has confirmed your command. If there is a large queue of > pending commands this can take several seconds. > > Also, many functions can be called on either the main Musica object or one > only one zone object. You can, for example, call $Musica->set_volume('100%') > to set all zones to 100% volume or call $music_kitchen->set_volume('100%') to > only change the volume of one zone. But even if you change the volume of > all zones using the main object, you must still call get_volume() on the zone > objects since the system as a whole doesn't actually have a volume. > > Finally, if any object returns a state of 'error', it means that an error > was encountered with the initialization of the system (i.e. a zone keypad > was not found) or there was a problem executing one of your commands. > The error will be displayed in the print log but you can also call > get_last_error() on any object to see this error message. > > Controlling either all zones or one zone: > These functions can all be called on the main Musica object to influence > all zones or can be called on one specific zone object: > > set_treble(level): Sets the treble level for the zone. Valid values are -14 > through 14 with 0 being the default (note that the keypads only have a > resolution of two, such as -4, -2, 0, 2, 4, and a value such as 1 will be > the same as 0 or 2). > > set_bass(level): Sets the bass level for the zone. Valid values are -14 > through 14 with 0 being the default (note that the keypads only have a > resolution of two, such as -4, -2, 0, 2, 4, and a value such as 1 will be > the same as 0 or 2). > > set_source(identifier): Changes zone or zones to specified source. The > parameter can be a number 1-4, the letter 'E' for the local expansion port, > and the letter 'F' for the integrated FM tuner. > the label assigned to a specific source (such as 'MP3'), or a source object. > Examples: > $zone1_obj->set_source(1); > $Musica->set_source('E'); > $zone1_obj->set_source('MP3'); > $Musica->set_source($source1_obj); > > set_volume(level): Sets the volume of one or all zones, where the level > specified must range from 0 to 35. Can also be specified as a percentage > where '0%' is off and '100%' is full volume. > > set_balance(level): Sets the balance where -7 is full-left, +7 is full-right, > and 0 is centered. > > mute(): Mutes one or all zones. > > unmute(): Unmutes one or all zones. > > loudness_on(): Turns on loudness in one or all zones. > > loudness_off(): Turns off loudness in one or all zones. > > internal_amp(): Use only the internal amplifier. > > both_amps(): Use both internal and external amplifiers. > > external_amp(): Use only the external amplifier. > > green_backlight(): Set backlight to the color green. > > amber_backlight(): Set backlight to the color amber. > > white_backlight(): Set backlight to the color white (instead of green when applicable). > > blue_backlight(): Set backlight to the color blue (instead of amber when applicable). > > set_backlight_brightness(level): Sets backlight level to a value between > 0 and 8 where 0 is off and 8 is full brightness. Can also be specified > with a percentage where '0%' is off and '100%' is full brightness. > > nudge_volume_down(): Reduces the volume one notch. > > nudge_volume_up(): Increases the volume one notch. > > nudge_bass_down(): Reduces the bass one notch. > > nudge_bass_up(): Increases the bass one notch. > > nudge_treble_down(): Reduces the treble one notch. > > nudge_treble_up(): Increases the treble one notch. > > nudge_balance_left(): Balance shifted one notch to the left. > > nudge_balance_right(): Balance shifted one notch to the right. > > lock_menu(): Locks the menu on the keypad to prevent the user from > entering the setup menu. Source selection and volume can still > be changed. > > unlock_menu(): Unlocks the menu. > > set_preset_label(number, label): Sets FM preset 1-8 to specified numeric > or text label. Label must be one listed in %source_name_to_number_30419. > > set_preset_frequency(number, freq): Sets FM preset 1-8 to specified frequency, > where 8950 is 89.5, for example. > > Controlling the Musica system object: > The following functions allow you to make changes to the Musica system > as a whole from Misterhouse. > > all_off(): Turn off all zones. > > Retrieving data from the Musica system object: > get_object_version(): Returns the version of the Musica object. > get_port_name(): Returns the name of the serial port used for this object. > get_zones(): Returns list of zone objects associated with this system. > get_sources(): Returns list of source objects associated with this system. > get_adc_version(): Returns the version string as reported by the Audio > Distribution Center. > > Monitoring the Musica system object: > Following is a list of states that may be returned by the Musica system: > > error: An error has occurred, call get_last_error() for details. > > Controlling the Musica zone objects using functions: > The following functions allow you to make changes to a specific Musica > zone from Misterhouse. > > set_source(source): Sets the zone to the specified source, where 'source' > is a number between 1 and 4 or E for the local expansion source. Turns > the zone ON if it is not already on. > > turn_off(): Turns off the zone. > nudge_source_up(): Switches to the next source (must already be on). > nudge_source_down(): Switches to the previous source (must already be on). > delay_off(seconds): Automatically turn the zone off in the specified number > of seconds. Timer is cancelled if the user selects a new source or if > this function is called with '0' for the argument. Also can be reset > by calling this function again. Returns current delay if no argument > is provided. > > Controlling the Musica zone objects using set() > The following input states are recognized by the Musica zone objects: > off: Turn zone off > 1: Turn to source 1 > 2: Turn to source 2 > 3: Turn to source 3 > 4: Turn to source 4 > E: Turn to external input > volumeXX: Set volume where XX ranges from 0 to 35 > mute: Mutes the zone > unmute: Unmutes the zone > > Retrieving data from the Musica zone object: > get_musica_obj(): Returns the main musica object. > get_zone_num(): Returns the zone number (from 1 to 6) for this object. > get_keypad_version(): Returns the version number returned by the keypad. > get_last_on_time(): Returns the last time the keypad was turned on > (in Epoch format, as $::Time expresses it) > get_source(): Returns the currently-selected source number, or 0 if off. > get_source_obj(): Returns the currently-selected source object. > get_volume(): Returns the volume, from 0 to 35. > get_bass_level(): Returns the bass level from -14 to 14. > get_treble_level(): Returns the bass level from -14 to 14. > get_balance(): Returns the balance where -7 is full-left, 7 is full-right, > and 0 is centered. > get_loudness(): Returns 1 if on, 0 if off. > get_mute(): Returns 1 if on, 0 if off. > get_blcolor(): Returns 'green' or 'amber' to indicate backlight color. > ('green' == 'white' and 'amber' == 'blue' on applicable keypads) > get_brightness(): Returns backlight brightness from 0 to 8 where 0 means > that the backlight is currently off. > get_audioport(): Returns 1 if the audioport is connected. > get_amp(): Returns 'room_amp', 'both', or 'external_amp'. > get_locked(): Returns 1 if the keypad is locked. > get_overheat(): Returns 1 if the keypad is overheated. > get_button_labels(): Returns a list of the names of the 12 programmable > buttons on the Musica keypad and remote. > press_button(button): Sends the IR code associated with pressing the > specified button for this source. Button can be specified as either > a number from 1 to 12 or as a label as returned by get_button_labels(). > hold_button(button): Sends the IR code associated with holding the > specified button for this source. Button can be specified as either > a number from 1 to 12 or as a label as returned by get_button_labels(). > > Monitoring the Musica zone objects: > You can watch for state changes of the Musica zone object to see when a > user changes the system in a way that affects a particular zone. > > error: An error has occurred, call get_last_error() for details. > zone_on: The zone was turned on from an off state. > zone_off: The zone was turned off from an on state. > source_changed: The zone is already on and a user changed the source. > changed_label_source_X: This zone keypad was used to change the label > of source X (where X is a number from 1 to 4). > volume_changed: Volume of the zone was changed. > bass_changed: Bass level of the zone was changed. > treble_changed: Treble level of the zone was changed. > balance_changed: Balance level of the zone was changed. > loudness_on: Loudness was enabled in the zone > loudness_off: Loudness was disabled in the zone > mute_on: This zone was muted. > mute_off: This zone was unmuted. > color_amber: The backlight color in this zone was changed to amber > (or blue on applicable keypads). > color_green: The backlight color in this zone was changed to green > (or white on applicable keypads). > backlight_on: The backlight was turned on (not given when the backlight > comes on when the zone is turned on). > backlight_off: The backlight was turned off. > brightness_changed: The backlight brightness was changed. > internal_amp: The user has chosen to use the internal amp only. > internal_external_amp: The user has chosen to use the internal amp as well > as an amp connected through the Expansion Interface Module. > external_amp: Only the external amp is being used. > locked: The keypad has been locked > unlocked: The keypad has been unlocked > overheated: The zone keypad has become overheated > heat_normal: The zone keypad is no longer overheated > button_pressed_*: A button was pressed (button names can be found after > this comment section or by calling get_button_labels() on a zone > object). > button_held_*: A button was pressed and held (button names can be found > after this comment section or by calling get_button_labels() on a > zone object). > > Controlling the Musica source object: > The following functions allow you to make changes to a particular source > accessible from the Musica system. > > set_label(label): Sets the global label for this particular source. The > label can either be given as a number between 1 and 30 or as a string. > Only certain strings are allowed, and these strings can be determined > by calling get_source_labels() on a source object. > > Retrieving data from a Musica source object: > get_musica_obj(): Returns the main musica object. > get_source_num(): Returns the numerical source number associated with > this source object. > get_label(): Returns the label for the source as a string. > get_source_labels(): Returns a list of valid source labels. > get_zones(): Returns array of zones currently listening to this source. > get_usage_count(): Returns the number of zones currently listening to > this source. > > Monitoring the Musica source objects: > You can watch for state changes of the Musica source object to see when a > user changes the system in a way that affects a particular source. > > error: An error has occurred, call get_last_error() for details. > label_changed: somebody changed the label for this source from a keypad > (call get_set_by() to find the object name of the keypad used to make > the change). > first_listener: the source now has a listener and it did not before. > no_listeners: the only listener stopped listening and now nobody is using > this particular source. > listener_zone_X: zone X just selected this source. > button_pressed_*: A button was pressed (button names can be found after > this comment section or by calling get_button_labels()). > button_held_*: A button was pressed and held (button names can be found > after this comment section or by calling get_button_labels()). > > Usage Examples: > To better understand the following examples, you should know that I have > an array of MP3 players defined as: > > my @players; > if ($Startup) { > push @players, undef; > foreach ('channel12', 'channel34', 'channel56', 'channel78') { > push @players, new AlsaPlayer($_); > } > } > > Then I create an array of all of the Musica::Source objects (which > are already defined in my .mht file): > > my @musica_sources = ( $music_source1, $music_source2, $music_source3, > $music_source4 ); > > The 'first_listener' and 'no_listeners' states are handy to start/stop > or pause/unpause something like an MP3 player, or even to power on/off > sources or send out IR commands. > > foreach (@musica_sources) { > if (state_now $_ eq 'first_listener') { > $players[$_->get_source_num()]->unpause(); > } > if (state_now $_ eq 'no_listeners') { > $players[$_->get_source_num()]->pause(); > } > } > > To watch for button presses on all keypads and all sources and take > the appropriate action: > > foreach (@musica_sources) { > if (state_now $_ eq 'button_pressed_next') { > $players[$_->get_source_num()]->next_song(); > } > if (state_now $_ eq 'button_pressed_previous') { > $players[$_->get_source_num()]->previous_song(); > } > if (state_now $_ eq 'button_pressed_pause') { > $players[$_->get_source_num()]->pause_toggle(); > } > if (state_now $_ eq 'button_pressed_play') { > $players[$_->get_source_num()]->unpause(); > } > } > > MUSICA SYSTEM BUGS: > [ADC Version M30419/Keypad Version R40822 (FM-Tuner)] > - Keypad takes FOREVER (1-4 minutes) to respond to StatVer. > - When you hold down a button on the keypad often times a button pressed > event is sent right after the button held event. This code will ignore > a press right after a hold. > - Never sends back a copy of the ChangeAmp command nor does it send > an EventData message back. > - Does not respond to various commands like ChangeVol, ChangeBass, etc. > Instead, sends an EventData message showing the changes (bug or protocol > change?) > > [ADC Version M30419/Keypad Version R30419] > If I send the ChangeSrc command at least three seconds after the > ChangeStore command then it works fine. But if the gap is two seconds or > less the zone never turns on like it should. > > [ADC Version M30419/Keypad Version R30419] > ADC never responds to a NudgeSrc to a zone that is off. > Workaround: only sends command if the zone is already on. > > [ADC Version M30419/Keypad Version R30419] > ChangeSwOu command is always responded to with ChangeSwOu/1 whether you > are trying to turn it on or off, and it seems to have no effect? > > [ADC Version M30419/Keypad Version R30419] > The backlight brightness sent in EventData comes in as 0 until you > change the backlight from its original value (or at least that is > what happened to me). > > TODO: > - Detect door/phone muting by watching for volume changes? > - Implement IR_Dn/IR_Up? > - ExeMenu* is not implemented as the ADC does not respond which makes it > not like every other command plus I don't know why you would use it as it > just allows you to navigate the menu system on a keypad but you can > change everything directly through other messages anyways. But, for the > record: > ExeMenu/Zone/Command where Command is: > 1: Menu button > 2: Up Arrow > 3: Down Arrow > 4: Left Arrow > 5: Right Arrow > > @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ > =cut > > use strict; > > package Musica; > > # Object data: > # musica_obj: pointer to itself to allow for cleaner code (since > # all derived classes use this but use common functions) > # port_name: the name of the port registered with Misterhouse > # zones: An array of registered zone objects > # sources: An array of registered source objects > # queue: An array of commands still awaiting a response > # adc_version: The version reported by the Audio Distribution Center > # zone: set to 0 to allow the easy building of messages > > @Musica::ISA = ('Serial_Item'); > > use vars qw( %source_name_to_number @source_number_to_name > %source_name_to_number_30419 @source_number_to_name_30419 > %button_name_to_number @button_number_to_name > %button_name_to_number_40822 @button_number_to_name_40822 > ); > > %source_name_to_number = ( > 'CD' => 1, > 'AUX' => 2, > 'TAPE' => 3, > 'TUNER' => 4, > 'TUNER2' => 5, > 'AM' => 6, > 'FM' => 7, > 'MP3' => 8, > 'BLUES' => 9, > 'CHILDREN' => 10, > 'CLASSIC' => 11, > 'COUNTRY' => 12, > 'DAD' => 13, > 'DANCE' => 14, > 'DVD' => 15, > 'LIGHTS' => 16, > 'INTERNET' => 17, > 'JAZZ' => 18, > 'REQUEST' => 19, > 'MOM' => 20, > 'XMRADIO' => 21, > 'XM RADIO' => 21, > 'POP' => 22, > 'R&B' => 23, > 'RAP' => 24, > 'RADIO' => 25, > 'HD RADIO' => 25, > 'ROCK' => 26, > 'SAT' => 27, > 'SAT2' => 28, > 'SOUL' => 29, > 'WESTERN' => 30 > ); > > %source_name_to_number_30419 = ( > 'CD' => 1, > 'AUX' => 2, > 'TAPE' => 3, > 'TUNER' => 4, > 'TUNER2' => 5, > 'AM' => 6, > 'FM' => 7, > 'MP3' => 8, > 'BLUES' => 9, > 'CHILDREN' => 10, > 'CLASSIC' => 11, > 'COUNTRY' => 12, > 'DAD' => 13, > 'DANCE' => 14, > 'DVD' => 15, > 'LIGHTS' => 16, > 'INTERNET' => 17, > 'JAZZ' => 18, > 'REQUEST' => 19, > 'MOM' => 20, > 'XMRADIO' => 21, > 'XM RADIO' => 21, > 'POP' => 22, > 'R&B' => 23, > 'RAP' => 24, > 'RADIO' => 25, > 'HD RADIO' => 25, > 'ROCK' => 26, > 'SAT' => 27, > 'SAT2' => 28, > 'SOUL' => 29, > 'CD2' => 30, > 'TALK' => 31, > 'NEWS' => 32, > 'SIRIUS' => 33, > 'TRAFFIC' => 34, > 'WEATHER' => 35, > 'SPORTS' => 36, > 'NPR' => 37, > 'DSS' => 38, > 'M SERVER' => 39, > 'M-SERVER' => 39, > 'DISH' => 40, > '' => 41, > 'EXTAUDIO' => 42, > 'MASTER' => 43, > 'BEDROOM' => 44, > 'KITCHEN' => 45, > 'DINING' => 46, > 'LIVING' => 47, > 'FAMILY' => 48, > 'GREAT' => 49, > 'STUDY' => 50, > 'OUTSIDE' => 51, > 'ROOM' => 52, > ); > > @source_number_to_name = ( > '' , > 'CD' , > 'AUX' , > 'TAPE' , > 'TUNER' , > 'TUNER2' , > 'AM' , > 'FM' , > 'MP3' , > 'BLUES' , > 'CHILDREN' , > 'CLASSIC' , > 'COUNTRY' , > 'DAD' , > 'DANCE' , > 'DVD' , > 'LIGHTS' , > 'INTERNET' , > 'JAZZ' , > 'REQUEST' , > 'MOM' , > 'XM RADIO' , > 'POP' , > 'R&B' , > 'RAP' , > 'HD RADIO' , > 'ROCK' , > 'SAT' , > 'SAT2' , > 'SOUL' , > 'WESTERN' > ); > > @source_number_to_name_30419 = ( > '' , > 'CD' , > 'AUX' , > 'TAPE' , > 'TUNER' , > 'TUNER2' , > 'AM' , > 'FM' , > 'MP3' , > 'BLUES' , > 'CHILDREN' , > 'CLASSIC' , > 'COUNTRY' , > 'DAD' , > 'DANCE' , > 'DVD' , > 'LIGHTS' , > 'INTERNET' , > 'JAZZ' , > 'REQUEST' , > 'MOM' , > 'XMRADIO' , > 'POP' , > 'R&B' , > 'RAP' , > 'RADIO' , > 'ROCK' , > 'SAT' , > 'SAT2' , > 'SOUL' , > 'CD2' , > 'TALK' , > 'NEWS' , > 'SIRIUS' , > 'TRAFFIC' , > 'WEATHER' , > 'SPORTS' , > 'NPR' , > 'DSS' , > 'M SERVER' , > 'DISH' , > '' > ); > > %button_name_to_number = ( > 'pause' => 1, > 'stop' => 2, > 'play' => 3, > 'rewind' => 4, > 'up' => 5, > 'forward' => 6, > 'left' => 7, > 'down' => 8, > 'right' => 9, > 'previous' => 10, > 'power' => 11, > 'next' => 12, > ); > > %button_name_to_number_40822 = ( > 'pause' => 1, # 1 > 'stop' => 2, # 9 > 'play' => 3, # 2 > 'shuffle' => 4, # 3 > 'repeat' => 6, # 4 > 'left' => 7, # 7 > 'right' => 9, # 8 > 'previous' => 10, # 5 > 'mode' => 11, # 0 > 'next' => 12, # 6 > ); > > @button_number_to_name = ( > '', > 'pause', > 'stop', > 'play', > 'rewind', > 'up', > 'forward', > 'left', > 'down', > 'right', > 'previous', > 'power', > 'next' > ); > > @button_number_to_name_40822 = ( > '', > 'pause', > 'stop', > 'play', > 'shuffle', > '', > 'repeat', > 'left', > '', > 'right', > 'previous', > 'mode', > 'next' > ); > > my %commands_to_keys = ( > # ChangeSrc seems to be okay to send with the current value > # 'ChangeSrc' => 'source', > 'ChangeTreb' => 'treble', > 'ChangeBass' => 'bass', > 'ChangeBal' => 'balance', > 'ChangeVol' => 'volume', > 'ChangeMute' => 'mute', > 'ChangeLoud' => 'loudness', > 'ChangeAmp' => 'amp', > 'ChangeBaCo' => 'blcolor', > 'ChangeBaLi' => 'brightness', > ); > > # The version of this Misterhouse object > use constant OBJECT_VERSION => '2.0'; > # Maximum number of zones and sources in the system > use constant MAX_ZONES => 6; > use constant MAX_SOURCES => 4; > # Maximum amount of time to wait for a response to a command (in seconds) > use constant MAX_RESPONSE_WAIT => 5; > # How long after a zone turns on should mute/volume changes not be reported? > use constant IGNORE_AFTER_ON => 15; > # How long after a button_held to ignore a button_pressed > use constant IGNORE_BUTTON_PRESSED_AFTER_HELD => 2; > # How long to wait for a zone to turn on > use constant MAX_ZONE_ON_DELAY => 10; > > # How to handle sources... with 5602 system, each zone seems to have its own > # source labels which it sends at startup. > # Set this to 1 to send out the source label when a zone selects a source > use constant SOURCE_SEND_LABELS => 1; > # Set this to 1 to ignore source label changes from keypads > use constant SOURCE_IGNORE_LABLES => 1; > > my %Musica_Systems; > > sub serial_startup { > # Nothing needs to be done here... > } > > sub _check_for_data { > for my $port_name (keys %Musica_Systems) { > if (($Musica_Systems{$port_name}{'last_data_received'} + MAX_RESPONSE_WAIT) < $::Time) { > # It has been a while since we received data for this object... so, call > # the _send_next_cmd() function which will re-send the first item in the > # queue (if there is one present) just to try the command again > if ($Musica_Systems{$port_name}{'object'}->{'queue'}->[0]) { > $Musica_Systems{$port_name}{'object'}->{'waiting_for_zone'} = 0; > if (($Musica_Systems{$port_name}{'object'}->{'resend_count'} < 3) or ($Musica_Systems{$port_name}{'object'}->{'queue'}->[0] =~ /^StatVer/)) { > $Musica_Systems{$port_name}{'object'}->{'resend_count'}++; > $Musica_Systems{$port_name}{'object'}->_report_error("Going to re-send command [" . $Musica_Systems{$port_name}{'object'}->{'queue'}->[0] . "] ($Musica_Systems{$port_name}{'object'}->{'resend_count'} times)"); > $Musica_Systems{$port_name}{'object'}->_send_next_cmd(); > } elsif ($Musica_Systems{$port_name}{'object'}->{'resend_count'} == 3) { > $Musica_Systems{$port_name}{'object'}->{'resend_count'}++; > $Musica_Systems{$port_name}{'object'}->_report_error("Resetting serial connection to re-send command [" . $Musica_Systems{$port_name}{'object'}->{'queue'}->[0] . "] ($Musica_Systems{$port_name}{'object'}->{'resend_count'} times)"); > &::serial_port_reopen($port_name); > } elsif ($Musica_Systems{$port_name}{'object'}->{'resend_count'} < 6) { > $Musica_Systems{$port_name}{'object'}->{'resend_count'}++; > $Musica_Systems{$port_name}{'object'}->_report_error("Going to re-send command [" . $Musica_Systems{$port_name}{'object'}->{'queue'}->[0] . "] ($Musica_Systems{$port_name}{'object'}->{'resend_count'} times)"); > $Musica_Systems{$port_name}{'object'}->_send_next_cmd(); > } else { > $Musica_Systems{$port_name}{'object'}->_critical_error("Re-sent command [" . $Musica_Systems{$port_name}{'object'}->{'queue'}->[0] . '] too many times, dropping'); > $Musica_Systems{$port_name}{'object'}->_found_response(); > } > $Musica_Systems{$port_name}{'last_data_received'} = $::Time; > } > } > for (my $i = 1; $i <= MAX_ZONES; $i++) { > if ($Musica_Systems{$port_name}{'object'}->{'zones'}[$i]) { > if ($Musica_Systems{$port_name}{'object'}->{'zones'}[$i]->{'just_turned_on'}) { > if (($Musica_Systems{$port_name}{'object'}->{'zones'}[$i]->{'on_time'} + MAX_ZONE_ON_DELAY) <= $::Time) { > if ($Musica_Systems{$port_name}{'object'}->{'on_repeat_count'} < 3) { > $Musica_Systems{$port_name}{'object'}->{'on_repeat_count'}++; > $Musica_Systems{$port_name}{'object'}->_report_error("Turning zone $i on again since it did not turn on before"); > $Musica_Systems{$port_name}{'object'}->{'zones'}[$i]->{'on_time'} = $::Time; > $Musica_Systems{$port_name}{'object'}->_queue_cmd("ChangeSrc/$i/$Musica_Systems{$port_name}{'object'}->{'zones'}[$i]->{'just_turned_on'}"); > } elsif ($Musica_Systems{$port_name}{'object'}->{'on_repeat_count'} == 3) { > $Musica_Systems{$port_name}{'object'}->{'on_repeat_count'}++; > $Musica_Systems{$port_name}{'object'}->_report_error("Resetting serial connection to turn zone $i on again since it did not turn on before"); > &::serial_port_reopen($port_name); > } elsif ($Musica_Systems{$port_name}{'object'}->{'on_repeat_count'} < 6) { > $Musica_Systems{$port_name}{'object'}->{'on_repeat_count'}++; > $Musica_Systems{$port_name}{'object'}->_report_error("Turning zone $i on again since it did not turn on before"); > $Musica_Systems{$port_name}{'object'}->{'zones'}[$i]->{'on_time'} = $::Time; > $Musica_Systems{$port_name}{'object'}->_queue_cmd("ChangeSrc/$i/$Musica_Systems{$port_name}{'object'}->{'zones'}[$i]->{'just_turned_on'}"); > } else { > $Musica_Systems{$port_name}{'object'}->_critical_error("Turning zone $i on again since it did not turn on before"); > } > } > } > } > } > &::check_for_generic_serial_data($port_name) if $::Serial_Ports{$port_name}{'object'}; > my $data = $::Serial_Ports{$port_name}{'data_record'}; > next if !$data; > $Musica_Systems{$port_name}{'object'}->_parse_data($data); > $main::Serial_Ports{$port_name}{'data_record'}=''; > $Musica_Systems{$port_name}{'last_data_received'} = $::Time; > } > } > > sub reset { > my ($self) = @_; > $$self{'queue'} = (); > foreach (@{$$self{'zones'}}) { > $_->{'just_turned_on'} = 0; > } > $self->_queue_cmd('EventData/0/1'); > $self->_queue_cmd('EventSrc/1'); > $self->_queue_cmd('EventStore/0/1'); > $self->_queue_cmd('StatVer/0'); > } > > sub new { > my ($class, $port_name) = @_; > my $self = {}; > $$self{'port_name'} = $port_name; > $$self{'zone'} = 0; > &::print_log("$self->{'port_name'}: Netstreams Musica Misterhouse Module Version " . OBJECT_VERSION); > for (my $i = 1; $i <= MAX_ZONES; $i++) { > $$self{'zones'}[$i] = undef; > } > @{$$self{'queue'}} = (); > bless $self, $class; > $$self{'musica_obj'} = $self; > $Musica_Systems{$port_name}{'object'} = $self; > if (1==scalar(keys %Musica_Systems)) { # Add hooks on first call only > &::MainLoop_pre_add_hook(\&Musica::_check_for_data, 1); > } > &::serial_port_create($port_name, $::config_parms{$port_name . "_serial_port"}, 9600); > $self->_queue_cmd('EventData/0/1'); > $self->_queue_cmd('EventSrc/1'); > $self->_queue_cmd('EventStore/0/1'); > $self->_queue_cmd('StatVer/0'); > return $self; > } > > sub _store_zone_source { > my ($self, $zone, $source, $set_by) = @_; > &::print_log("$self->{'port_name'}: _store_zone_source($zone,$source)") if $main::Debug{musica}; > if ($$self{'zones'}[$zone] and ($source ne 'X')) { > my $currsrc = $$self{'zones'}[$zone]->{'source'}; > &::print_log("$self->{'port_name'}: _store_zone_source($zone,$source): $currsrc") if $main::Debug{musica}; > unless ($currsrc eq $source) { > $$self{'timerOff'}->stop() if $$self{'timerOff'}; > if ($currsrc eq '0') { > # Currently off > $$self{'zones'}[$zone]->set_receive('zone_on', $set_by); > &::print_log("$self->{'port_name'}: _store_zone_source($zone,$source): Zone just turned on") if $main::Debug{musica}; > if (($source eq '1') or ($source eq '2') or ($source eq '3') or ($source eq '4')) { > # 5602 keypads don't seem to get the source labels unless they are on, so > # make sure to set the labels whenever a source is selected > if (SOURCE_SEND_LABELS) { > $$self{'sources'}[$source]->set_label($$self{'sources'}[$source]->get_label()); > } > } > } else { > if ($source eq '0') { > if (defined $currsrc) { > &::print_log("$self->{'port_name'}: _store_zone_source($zone,$source): Zone just turned off") if $main::Debug{musica}; > $$self{'zones'}[$zone]->set_receive('zone_off', $set_by); > if ($$self{'timerOff'}) { > $$self{'timerOff'}->set(0); > } > } > } else { > &::print_log("$self->{'port_name'}: _store_zone_source($zone,$source): Zone source changed") if $main::Debug{musica}; > $$self{'zones'}[$zone]->set_receive('source_changed', $set_by); > if (($source eq '1') or ($source eq '2') or ($source eq '3') or ($source eq '4')) { > # 5602 keypads don't seem to get the source labels unless they are on, so > # make sure to set the labels whenever a source is selected > if (SOURCE_SEND_LABELS) { > $$self{'sources'}[$source]->set_label($$self{'sources'}[$source]->get_label()); > } > } > } > } > $$self{'zones'}[$zone]->{'source'} = $source; > if ($$self{'sources'}[$currsrc]) { > $$self{'sources'}[$currsrc]->_zone_not_using($zone); > } > if ($$self{'sources'}[$source]) { > $$self{'sources'}[$source]->_zone_is_using($zone); > } > } > } > } > > sub _found_response { > my ($self) = @_; > &::print_log("$self->{'port_name'}: found response for: [$self->{'queue'}->[0]]") if $main::Debug{musica}; > $self->{'last_received'} = $self->{'next_last_received'}; > $self->{'next_last_received'} = $$self{'queue'}->[0]; > shift @{$$self{'queue'}}; > $self->{'resend_count'} = 0; > $self->_send_next_cmd(); > } > > sub _check_first_cmd { > my ($self, $zone, $cmd) = @_; > return unless $$self{'zones'}[$zone]; > return unless ($$self{'zones'}[$zone]->{'keypad_version'} >= 40822); > return if ($$self{'zones'}[$zone]->{'just_turned_on'}); > my $compare = $$self{'queue'}->[0]; > if ($compare) { > # Now, see if we got a response to the first command in the queue > if ($cmd eq $compare) { > $self->_found_response(); > return 1; > } > } > return 0; > } > > sub _see_if_zone_just_turned_on { > my ($self, $zone, $new_source, $set_by) = @_; > return 0 unless $$self{'zones'}[$zone]; > return 0 unless defined($$self{'zones'}[$zone]->{'source'}); > my $currsrc = $$self{'zones'}[$zone]->{'source'}; > &::print_log("$self->{'port_name'}: Zone $zone exists and source is defined (currsrc=$currsrc, new_source=$new_source), just_turned_on=" . $$self{'zones'}[$zone]->{'just_turned_on'}) if $main::Debug{musica}; > if (($currsrc eq '0') and ($new_source ne '0')) { > # Currently off > unless ($$self{'zones'}[$zone]->{'just_turned_on'}) { > $$self{'zones'}[$zone]->{'on_time'} = $::Time; > &::print_log("$self->{'port_name'}: setting just_turned_on=$new_source (zone $zone)") if $main::Debug{musica}; > $$self{'zones'}[$zone]->{'just_turned_on'} = $new_source; > $$self{'zones'}[$zone]->{'just_turned_on_by'} = $set_by; > $$self{'on_repeat_count'} = 0; > &::print_log("$self->{'port_name'}: Recording that zone $zone was just turned on") if $main::Debug{musica}; > } > return 1; > } > return 0; > } > > sub _parse_data { > my ($self, $data) = @_; > &::print_log("$self->{'port_name'}: parsing serial data: [$data]") if $main::Debug{musica}; > # First, check to see if we got a Busy response which means we need to re-send > # the first command in the queue > if ($data eq 'Busy') { > &::print_log("$self->{'port_name'}: re-sending first command in queue") if $main::Debug{musica}; > $self->_send_next_cmd(); > return; > } > my ($cmd, $value) = ($data =~ m=^([^/]+)/(.*)$=); > unless ($data =~ /\//) { > $cmd = $data; > } > if ($cmd eq 'State') { > $cmd = 'Chang'; > } > my $compare = $$self{'queue'}->[0]; > $compare =~ s/\/.*$//; > if ($compare) { > # Now, see if we got a response to the first command in the queue > if ($cmd eq $compare) { > unless ($compare eq 'ChangeSrc') { > $self->_found_response(); > } > } > } > if ($cmd eq 'StatVer') { > my ($zone, $version) = split /\//, $value; > $version =~ s/^\D//; > if ($zone == 0) { > $$self{'adc_version'} = $version; > &::print_log("$self->{'port_name'}: Netstreams Musica ADC Version: $version"); > } elsif ($$self{'zones'}[$zone]) { > if ($version eq 'X') { > $version = 'Not Present'; > $self->_report_error('Zone keypad not detected by ADC', $zone); > $$self{'zones'}[$zone]->{'present'} = 0; > } else { > $$self{'zones'}[$zone]->{'keypad_version'} = $version; > $main::Save{"Musica-Keypad$zone-Version"} = $version; > &::print_log("$self->{'port_name'}: Netstreams Musica Zone $zone Keypad Version: $version"); > $self->_process_keypad_version($zone); > } > } > } elsif ($cmd eq 'EventPress') { > if (($compare eq 'ExePress') or ($compare eq 'ExeHold')) { > # Both ExePress and ExeHold commands respond with EventPress > $self->_found_response(); > } else { > my ($source, $button, $zone) = split /\//, $value; > my $action = 'button_pressed_'; > my $ignore = 0; > if ($button > 12) { > $button -= 12; > $action = 'button_held_'; > $$self{'last_held_time'} = $main::Time; > $$self{'last_held_button'} = $button; > } elsif ($$self{'last_held_time'} and $$self{'last_held_button'}) { > if (($main::Time - $$self{'last_held_time'}) <= IGNORE_BUTTON_PRESSED_AFTER_HELD) { > if ($$self{'last_held_button'} == $button) { > &::print_log("$self->{'port_name'}: ignoring button press because of recent button hold") if $main::Debug{musica}; > $ignore = 1; > } > } > } > unless ($ignore) { > if ($$self{'zones'}[$zone]) { > if ($$self{'zones'}[$zone]->{'keypad_version'} >= 40822) { > $$self{'zones'}[$zone]->set_receive($action . $button_number_to_name_40822[$button], 'keypad'); > } else { > $$self{'zones'}[$zone]->set_receive($action . $button_number_to_name[$button], 'keypad'); > } > } > if ($$self{'sources'}[$source]) { > if (($$self{'zones'}[$zone]) and ($$self{'zones'}[$zone]->{'keypad_version'} >= 40822)) { > $$self{'sources'}[$source]->set_receive($action . $button_number_to_name_40822[$button], 'keypad'); > } else { > $$self{'sources'}[$source]->set_receive($action . $button_number_to_name[$button], 'keypad'); > } > } > } > } > } elsif ($cmd eq 'ExeLock') { > my ($zone, $lock) = split /\//, $value; > $self->_store_zone_data($zone, 'locked', $lock, ($lock ? 'locked' : 'unlocked')); > } elsif ($cmd eq 'ChangeVol') { > my ($zone, $volume) = split /\//, $value; > $self->_store_zone_data($zone, 'volume', $volume, 'volume_changed'); > } elsif ($cmd eq 'NudgeVol') { > my ($zone, $volume) = split /\//, $value; > $self->_store_zone_nudge($zone, 'volume', $volume, 0, 35, 'volume_changed'); > } elsif ($cmd eq 'ChangeMute') { > my ($zone, $mute) = split /\//, $value; > $self->_store_zone_data($zone, 'mute', $mute, ($mute ? 'mute_on' : 'mute_off')); > } elsif ($cmd eq 'ChangeTreb') { > my ($zone, $treble) = split /\//, $value; > $self->_store_zone_data($zone, 'treble', $treble, 'treble_changed'); > } elsif ($cmd eq 'NudgeTreb') { > my ($zone, $treble) = split /\//, $value; > $self->_store_zone_nudge($zone, 'treble', $treble, 1, 15, 'treble_changed'); > } elsif ($cmd eq 'ChangeBass') { > my ($zone, $bass) = split /\//, $value; > $self->_store_zone_data($zone, 'bass', $bass, 'bass_changed'); > } elsif ($cmd eq 'NudgeBass') { > my ($zone, $bass) = split /\//, $value; > $self->_store_zone_nudge($zone, 'bass', $bass, 1, 15, 'bass_changed'); > } elsif ($cmd eq 'ChangeBal') { > my ($zone, $balance) = split /\//, $value; > $self->_store_zone_data($zone, 'balance', $balance, 'balance_changed'); > } elsif ($cmd eq 'NudgeBal') { > my ($zone, $balance) = split /\//, $value; > $self->_store_zone_nudge($zone, 'balance', $balance, 1, 15, 'balance_changed'); > } elsif ($cmd eq 'ChangeLoud') { > my ($zone, $loudness) = split /\//, $value; > $self->_store_zone_data($zone, 'loudness', $loudness, ($loudness ? 'loudness_on' : 'loudness_off')); > } elsif ($cmd eq 'ChangeAmp') { > my ($zone, $amp) = split /\//, $value; > my $newstate = 'internal_amp'; > if ($amp == 1) { > $newstate = 'internal_external_amp'; > } elsif ($amp == 2) { > $newstate = 'external_amp'; > } > $self->_store_zone_data($zone, 'amp', $amp, $newstate); > } elsif ($cmd eq 'ChangeBaCo') { > my ($zone, $color) = split /\//, $value; > $self->_store_zone_data($zone, 'blcolor', $color, ($color ? 'color_amber' : 'color_green')); > } elsif ($cmd eq 'ChangeBaLi') { > my ($zone, $level) = split /\//, $value; > $self->_store_zone_data($zone, 'brightness', $level, 'brightness_changed'); > } elsif ($cmd eq 'NudgeSrc') { > my ($zone, $source) = split /\//, $value; > $self->_store_zone_source($zone, $source, 'misterhouse'); > } elsif ($cmd eq 'ChangeSrc') { > my ($zone, $source) = split /\//, $value; > if ($zone == 0) { > for (my $i = 1; $i <= MAX_ZONES; $i++) { > $self->_store_zone_source($i, $source, 'misterhouse'); > } > } else { > unless ($self->_see_if_zone_just_turned_on($zone, $source, 'misterhouse')) { > $self->_store_zone_source($zone, $source, 'misterhouse'); > } > } > if ($compare eq 'ChangeSrc') { > $self->_found_response(); > } > } elsif ($cmd eq 'ChangeStore') { > my ($source, $label) = split /\//, $value; > if ($$self{'sources'}[$source]) { > $$self{'sources'}[$source]->{'label'} = $label; > } > } elsif ($cmd eq 'EventStore') { > unless (SOURCE_IGNORE_LABLES) { > my ($zone, @sources) = split /\//, $value; > my $changed = 0; > for (my $i = 1; $i <= MAX_SOURCES; $i++) { > if ($$self{'sources'}[$i]) { > unless ($$self{'sources'}[$i]->{'label'} eq $sources[$i-1]) { > if ($$self{'sources'}[$i]->{'label'}) { > $changed = $i; > $$self{'sources'}[$i]->{'label'} = $sources[$i-1]; > if ($$self{'zones'}[$zone]) { > $$self{'sources'}[$i]->set_receive('label_changed', $$self{'zones'}[$zone]); > } else { > $$self{'sources'}[$i]->set_receive('label_changed', undef); > } > } > } > } > } > if ($$self{'zones'}[$zone]) { > $$self{'zones'}[$zone]->set_receive('changed_label_source_' . $changed, 'keypad') if $changed; > } > } > } elsif ($cmd eq 'EventSrc') { > my (@sources) = split /\//, $value; > my $who = 'keypad'; > if ($self->{'next_last_received'} eq 'AllOff') { > $who = 'misterhouse'; > } > for (my $i = 1; $i <= MAX_ZONES; $i++) { > unless ($self->_see_if_zone_just_turned_on($i, $sources[$i-1], $who)) { > $self->_store_zone_source($i, $sources[$i-1], $who); > } > } > } elsif ($cmd eq 'EventData') { > my ($zone, $volume, $bass, $treble, $balance, $loudness, $mute, $blcolor, > $brightness, $audioport, $amp, $locked, $overheat) = split /\//, $value; > if ($$self{'zones'}[$zone]) { > if ($self->_check_first_cmd($zone, "ChangeVol/$zone/$volume")) { > $self->_store_zone_data($zone, 'volume', $volume, 'volume_changed'); > } elsif ($$self{'zones'}[$zone]->{'volume'} != $volume) { > if (defined($$self{'zones'}[$zone]->{'volume'}) and (($$self{'zones'}[$zone]->{'on_time'} + IGNORE_AFTER_ON) < $::Time)) { > $$self{'zones'}[$zone]->set_receive('volume_changed', 'keypad') > } > $$self{'zones'}[$zone]->{'volume'} = $volume; > } > if ($self->_check_first_cmd($zone, "ChangeBass/$zone/$bass")) { > $self->_store_zone_data($zone, 'bass', $bass, 'bass_changed'); > } elsif ($$self{'zones'}[$zone]->{'bass'} != $bass) { > $$self{'zones'}[$zone]->set_receive('bass_changed', 'keypad') if defined $$self{'zones'}[$zone]->{'bass'}; > $$self{'zones'}[$zone]->{'bass'} = $bass; > } > if ($self->_check_first_cmd($zone, "ChangeTreb/$zone/$treble")) { > $self->_store_zone_data($zone, 'treble', $treble, 'treble_changed'); > } elsif ($$self{'zones'}[$zone]->{'treble'} != $treble) { > $$self{'zones'}[$zone]->set_receive('treble_changed', 'keypad') if defined $$self{'zones'}[$zone]->{'treble'}; > $$self{'zones'}[$zone]->{'treble'} = $treble; > } > if ($self->_check_first_cmd($zone, "ChangeBal/$zone/$balance")) { > $self->_store_zone_data($zone, 'balance', $balance, 'balance_changed'); > } elsif ($$self{'zones'}[$zone]->{'balance'} != $balance) { > $$self{'zones'}[$zone]->set_receive('balance_changed', 'keypad') if defined $$self{'zones'}[$zone]->{'balance'}; > $$self{'zones'}[$zone]->{'balance'} = $balance; > } > if ($self->_check_first_cmd($zone, "ChangeLoud/$zone/$loudness")) { > $self->_store_zone_data($zone, 'loudness', $loudness, ($loudness ? 'loudness_on' : 'loudness_off')); > } elsif ($$self{'zones'}[$zone]->{'loudness'} != $loudness) { > if ($loudness) { > $$self{'zones'}[$zone]->set_receive('loudness_on', 'keypad') if defined $$self{'zones'}[$zone]->{'loudness'}; > } else { > $$self{'zones'}[$zone]->set_receive('loudness_off', 'keypad') if defined $$self{'zones'}[$zone]->{'loudness'}; > } > $$self{'zones'}[$zone]->{'loudness'} = $loudness; > } > if ($self->_check_first_cmd($zone, "ChangeMute/$zone/$mute")) { > $self->_store_zone_data($zone, 'mute', $mute, ($mute ? 'mute_on' : 'mute_off')); > } elsif ($$self{'zones'}[$zone]->{'mute'} != $mute) { > if (defined($$self{'zones'}[$zone]->{'mute'}) and (($$self{'zones'}[$zone]->{'on_time'} + IGNORE_AFTER_ON) < $::Time)) { > if ($mute) { > $$self{'zones'}[$zone]->set_receive('mute_on', 'keypad'); > } else { > $$self{'zones'}[$zone]->set_receive('mute_off', 'keypad'); > } > } > $$self{'zones'}[$zone]->{'mute'} = $mute; > } > if ($self->_check_first_cmd($zone, "ChangeBaCo/$zone/$blcolor")) { > $self->_store_zone_data($zone, 'blcolor', $blcolor, ($blcolor ? 'color_amber' : 'color_green')); > } elsif ($$self{'zones'}[$zone]->{'blcolor'} != $blcolor) { > if ($blcolor) { > $$self{'zones'}[$zone]->set_receive('color_amber', 'keypad') if defined $$self{'zones'}[$zone]->{'blcolor'}; > } else { > $$self{'zones'}[$zone]->set_receive('color_green', 'keypad') if defined $$self{'zones'}[$zone]->{'blcolor'}; > } > $$self{'zones'}[$zone]->{'blcolor'} = $blcolor; > } > if ($self->_check_first_cmd($zone, "ChangeBaLi/$zone/$brightness")) { > $self->_store_zone_data($zone, 'brightness', $brightness, 'brightness_changed'); > } elsif ($$self{'zones'}[$zone]->{'brightness'} != $brightness) { > if (defined $$self{'zones'}[$zone]->{'brightness'}) { > if ($$self{'zones'}[$zone]->{'brightness'} == 0) { > $$self{'zones'}[$zone]->set_receive('backlight_on', 'keypad') > } elsif ($brightness == 0) { > $$self{'zones'}[$zone]->set_receive('backlight_off', 'keypad') > } else { > $$self{'zones'}[$zone]->set_receive('brightness_changed', 'keypad') > } > } > $$self{'zones'}[$zone]->{'brightness'} = $brightness; > } > unless ($$self{'zones'}[$zone]->{'audioport'} == $audioport) { > if ($audioport) { > $$self{'zones'}[$zone]->set_receive('audio_port_connected', 'keypad') if defined $$self{'zones'}[$zone]->{'audioport'}; > } else { > $$self{'zones'}[$zone]->set_receive('audio_port_disconnected', 'keypad') if defined $$self{'zones'}[$zone]->{'audioport'}; > } > $$self{'zones'}[$zone]->{'audioport'} = $audioport; > } > if ($self->_check_first_cmd($zone, "ChangeAmp/$zone/$amp")) { > my $newstate = 'internal_amp'; > if ($amp == 1) { > $newstate = 'internal_external_amp'; > } elsif ($amp == 2) { > $newstate = 'external_amp'; > } > $self->_store_zone_data($zone, 'amp', $amp, $newstate); > } elsif ($$self{'zones'}[$zone]->{'amp'} != $amp) { > if ($amp == 0) { > $$self{'zones'}[$zone]->set_receive('internal_amp', 'keypad') if defined $$self{'zones'}[$zone]->{'amp'}; > } elsif ($amp == 1) { > $$self{'zones'}[$zone]->set_receive('internal_external_amp', 'keypad') if defined $$self{'zones'}[$zone]->{'amp'}; > } else { > $$self{'zones'}[$zone]->set_receive('external_amp', 'keypad') if defined $$self{'zones'}[$zone]->{'amp'}; > } > $$self{'zones'}[$zone]->{'amp'} = $amp; > } > unless ($$self{'zones'}[$zone]->{'locked'} == $locked) { > if ($locked) { > $$self{'zones'}[$zone]->set_receive('locked', 'keypad') if defined $$self{'zones'}[$zone]->{'locked'}; > } else { > $$self{'zones'}[$zone]->set_receive('unlocked', 'keypad') if defined $$self{'zones'}[$zone]->{'locked'}; > } > $$self{'zones'}[$zone]->{'locked'} = $locked; > } > unless ($$self{'zones'}[$zone]->{'overheat'} == $overheat) { > if ($overheat) { > $$self{'zones'}[$zone]->set_receive('overheated', 'keypad') if defined $$self{'zones'}[$zone]->{'overheat'}; > } else { > $$self{'zones'}[$zone]->set_receive('heat_normal', 'keypad') if defined $$self{'zones'}[$zone]->{'overheat'}; > } > $$self{'zones'}[$zone]->{'overheat'} = $overheat; > } > if ($$self{'zones'}[$zone]->{'just_turned_on'}) { > &::print_log("$self->{'port_name'}: Got first EventData after zone $zone was turned on, setting zone_on state") if $main::Debug{musica}; > $self->_store_zone_source($zone, $$self{'zones'}[$zone]->{'just_turned_on'}, $$self{'zones'}[$zone]->{'just_turned_on_by'}); > $$self{'zones'}[$zone]->{'just_turned_on'} = 0; > if ($$self{'waiting_for_zone'} == $zone) { > &::print_log("$self->{'port_name'}: Queue was waiting on this zone, sending next message") if $main::Debug{musica}; > $$self{'waiting_for_zone'} = 0; > $self->_send_next_cmd(); > } > } > } > } > } > > sub _process_keypad_version { > my ($self, $zone_num) = @_; > if ($$self{'zones'}[$zone_num]->{'keypad_version'} < 40822) { > # Use older source names if any keypad is older... > %source_name_to_number = %source_name_to_number_30419; > @source_number_to_name = @source_number_to_name_30419; > } > } > > sub _register_zone { > my ($self, $zone_obj, $zone_num) = @_; > $$self{'zones'}[$zone_num] = $zone_obj; > # Determine version of the keypad > if ($$self{'zones'}[$zone_num]->{'keypad_version'}) { > &::print_log("$self->{'port_name'}: Netstreams Musica Zone $zone_num Keypad Version: " . $$self{'zones'}[$zone_num]->{'keypad_version'} . " (as specified in your .mht file)"); > $self->_process_keypad_version($zone_num); > } else { > if ($main::Save{"Musica-Keypad$zone_num-Version"}) { > $$self{'zones'}[$zone_num]->{'keypad_version'} = $main::Save{"Musica-Keypad$zone_num-Version"}; > &::print_log("$self->{'port_name'}: Netstreams Musica Zone $zone_num Keypad Version: " . $main::Save{"Musica-Keypad$zone_num-Version"} . " (restored from \%Save)"); > $self->_process_keypad_version($zone_num); > } else { > $self->_queue_cmd("StatVer/$zone_num"); > } > } > } > > sub _register_source { > my ($self, $source_obj, $source_num) = @_; > $$self{'sources'}[$source_num] = $source_obj; > } > > sub _send_next_cmd { > my ($self) = @_; > if ($$self{'queue'}->[0]) { > my ($cmd, $zone, $value); > if (($cmd, $zone, $value) = ($$self{'queue'}->[0] =~ /^([^\/]+)\/(\d+)\/(\d+)/)) { > &::print_log("$self->{'port_name'}: checking command '$cmd' '$zone' '$value'") if $main::Debug{musica}; > if ($$self{'zones'}[$zone]) { > # Check to make sure the zone still isn't turning on > &::print_log("$self->{'port_name'}: just_turned_on=$$self{'zones'}[$zone]->{'just_turned_on'} (zone $zone)") if $main::Debug{musica}; > if ($$self{'zones'}[$zone]->{'just_turned_on'}) { > my $moved = ''; > # See if we can find a command for another zone meanwhile > for (my $i = 0; $i <= $#{@{$$self{'queue'}}}; $i++) { > if ($$self{'queue'}->[$i] =~ /^[^\/]+\/(\d+)/) { > if ($$self{'zones'}[$1] and not $$self{'zones'}[$zone]->{'just_turned_on'}) { > $moved = $$self{'queue'}->[$i]; > splice @{$$self{'queue'}}, $i, 1; > unshift @{$$self{'queue'}}, $moved; > } > } > } > if ($moved) { > &::print_log("$self->{'port_name'}: Moved command '$moved' to front of queue since we are waiting on zone $zone to turn on") if $main::Debug{musica}; > $self->_send_next_cmd(); > } else { > # Nothing to send, so we have to wait... > $$self{'waiting_for_zone'} = $zone; > &::print_log("$self->{'port_name'}: Queue stalled while waiting for zone $zone to turn on"); > return; > } > } > # Check to see if this command is necessary since the new keypads will not > # respond with an EventData if these messages don't change anythig > if ($commands_to_keys{$cmd}) { > &::print_log("$self->{'port_name'}: $c... [truncated message content] |