fulFYm+$FRD!C8N2QMyk&~(i>gAcW=oO*J5Sq{iPLgh+hWz zXUOPqRdV4vPnEU1sqniwX;Jpm^HW71m}@h(oeIZ&jD8cLhM`M;3ioYmNmT9VjC( zflEYtZ>NdtArF%}2oQrO+PPU=t=87$gcxNvhh!rhkD!ZjC25@#mhR4b{^1fGMOhR> zv}Xi|2~E@V4#YFE=uKE}p%72dx3RybA13(+nbwT$$Wf_a0k`WAZkF(ZkiM>|lej0{ zr?S3_O}xvEJVc?4A^d;MxCFmxG%x#%saBosWut^pZNzv5K33Uy)fmJ70krqS>l$RM zF>J8@M&a$kq%mc@ZoFBzQY->4$M$3PIC_T*!>wPJQpu;~q5S|ac}fEn=s5UYck&8f z3*lUf)>nuUTUjC9#mBleHJ;_Vz8hC<~7trsKOx4rinwF59F-t&LI<2 zmV>Snf0n-oBtONIl8_7p^H7l5i7H&Y-=BaQ@X&OA31*%2EW%|is0mUOZ( TCLK_*! UJ$ul zX2m=+Zg8@M(|U4T_ag#sk3|0@LFC9{5^!CZT5YC_B_DX)T$C}xqAXgBxwW}?fSR_6 zQ0VP=Pa95d2-;cD6kC>8NsIYA_HuP;O%nID)y4Ph+M2aAf6ux|fE0rz%UZQ8O0$S~ zj=O05Z71->Ob`(Fj2&C&QP*iBny_}-$hyu~JSRHOb_V!;h9?zFjcZWVMdOSyVq7o= zP wFcgTB@ca}f!uwxamW^ru6s#dM!FX1PT|M7|`;_06HUXoA1P4sx?;(rnUXl1*D? za}LsQcvff!mjvlb3FX1;G@Ru5_2?5=1xXFj__;8CFo~M{5DO}HkY23V1Pp0AKg8oe zRs^RT{aiypdIu2x@;C&T#5>Cx}_arrXE}_0tjqFpqse@?BjLa{gMjF{qOBZTyG@ zt_Zu;1m-sp92V(5IB!H3b4J2WzH;P APXu#;AY#2JygDv%Qa1>iw(0AwjPPDM zhUwbUP{zrbOw+^0yoQbg^PYoiyX`REbizvQ!`j-?J>E~O;C50%F7wu{nHsJeW@c`g zH_YuH|A_y7_Wfx>#lhg>q>=83`S2Np`qnPmtU8Nh`eu!7qI87csYJw{SPVIIu9rY{ zEF{7~Qz2_CSMRKfSfruv*V|cG2{XPKCgcEn`3F3rvXxOZ&K@%tgGL{O=5pbpA<@re zmtsRFv}Hd2121$o$y*Q@$btUNJx`=BCYZT{I2DCOuhZFtYZu{GK-wpeY@Ogufn2RK zhq^&-g^WeGDL{4%5P4Z3d0TcL@;U*TU3TLN6bmuUi=?ey;8R{NAZy5O+);3S?foj1 z{3|mD2r{5W$?TZZ50o*4P`Hq61KCo!3!wpIY6l}Bo;sf-yicuI6^=hJt#9MSdV)f8 zW)PXU4m6)27m?1UloAt7d^LrHvnkCO+c+#irXp>P;+v`aaWBq(xvtRtXyKbqLP#g> zGmy_nQ)+l#hw}wD=D0-i%=8U3LY#8p5{qdzB;SF14 9&Kvp9uOK% zG<_4`_bWVd%?A2v8{D+RLcz=Eri#GT08+bC6lF>xwSuOa m6<(Lq-(tGSmz eT%Pth}|7}dE$oxi<_KDO*2?knqgtLN$}ew zY%Dxf+F1NS>*?D)w474Ofy6ZWYXkV5FvvY{8|b3K7Q^P_>m7Q|+Wx}Y)A;w|{tMTI zR1go0$N$*bh7psuKl)s*2=xO0)?NUU;1Ene@xmRLguy>Iflut@y ={nE?-KuDzbhdVOB`>LfViwB?LehM6elu^D2psYvd z5HIveuxn{{T1hmyIZvua6%T CqOcZANJ^+lbD)(L@GKV|LjLQC5!ppM*#3jZemheHS- ~kA6eW+o7Pf0p`W}_(wL$W7qY9=h5M4hbLCQbw0ysOsytclLOD8qX1s*o z(qMO+!7Zv*9i5J{Cf94^TH*HmgD{x=)Iys=Vi{waAKuN%W!ZdnttKBkw%)7{If{DQ dB3|DJ3JTNR)NFQ(SW8gT2Q$?CKU5ej{y(2TL(>2N diff --git a/Server Plugin/Resources/CONSTANTS.py b/Server Plugin/Resources/CONSTANTS.py deleted file mode 100644 index 7ea7e41..0000000 --- a/Server Plugin/Resources/CONSTANTS.py +++ /dev/null @@ -1,683 +0,0 @@ -from collections import OrderedDict - - -# Constants for B&O telegram protocols -# ######################################################################################## -# Config data (set on initialisation) -rooms = [] -available_sources = [] - -standby_state = [ - {'key': 'onOffState', 'value': False}, - {'key': 'playState', 'value': 'Standby'}, - {'key': 'source', 'value': 'Standby'}, - {'key': 'nowPlaying', 'value': 'Unknown'}, - {'key': 'channelTrack', 'value': 0}, - {'key': 'mute', 'value': True}, - {'key': 'volume', 'value': 0}, -] - -gw_all_stb = [ - {'key': 'AudioRenderers', 'value': ''}, - {'key': 'VideoRenderers', 'value': ''}, - {'key': 'nAudioRenderers', 'value': 0}, - {'key': 'nVideoRenderers', 'value': 0}, - {'key': 'currentAudioSource', 'value': 'Unknown'}, - {'key': 'currentAudioSourceName', 'value': 'Unknown'}, - {'key': 'nowPlaying', 'value': 'Unknown'}, -] - -# ######################################################################################## -# Source Types - -source_type_dict = dict( - [ - ("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "DVD2", "CAMERA", - "SAT/DTV", "PC", "WEB", "DOORCAM", "PHOTO", "USB2", "WEBMEDIA", "AV.IN", - "HOMEMEDIA", "DVB_RADIO", "DNLA", "RECORDINGS", "CAMERA", "USB", "DNLA-DMR", "YOUTUBE", - "HOME.APP", "HDMI_1", "HDMI_2", "HDMI_3", "HDMI_4", "HDMI_5", "HDMI_6", - "HDMI_7", "HDMI_8", "MATRIX_1", "MATRIX_2", "MATRIX_3", "MATRIX_4", "MATRIX_5", - "MATRIX_6", "MATRIX_7", "MATRIX_8", "MATRIX_9", "MATRIX_10", "MATRIX_11", - "MATRIX_12", "MATRIX_13", "MATRIX_14", "MATRIX_15", "MATRIX_16", "PERSONAL_1", - "PERSONAL_2", "PERSONAL_3", "PERSONAL_4", "PERSONAL_5", "PERSONAL_6", "PERSONAL_7", - "PERSONAL_8")), - ("Audio Sources", ("RADIO", "A.AUX", "A.TAPE/A.MEM", "CD", "PHONO/N.RADIO", "A.TAPE2/N.MUSIC", - "SERVER", "SPOTIFY", "CD2/JOIN", "TUNEIN", "DVB_RADIO", "LINE.IN", "BLUETOOTH", - "MUSIC", "AIRPLAY", "SPOTIFY", "DEEZER", "QPLAY")) - ] -) - -# ######################################################################################## -# Beo4 Commands -beo4_srcdict = OrderedDict( - [ - # Source selection: - (0x0C, "Standby"), - (0x47, "Sleep"), - (0x80, "TV"), - (0x81, "Radio"), - (0x82, "V.Aux/DTV2"), - (0x83, "A.Aux"), - (0x84, "Media"), - (0x85, "V.Tape/V.Mem"), - (0x86, "DVD"), - (0x87, "Camera"), - (0x88, "Text"), - (0x8A, "Sat/DTV"), - (0x8B, "PC"), - (0x8C, "Web"), - (0x8D, "Doorcam"), - (0x8E, "Photo"), - (0x90, "USB2"), - (0x91, "A.Tape/A.Mem"), - (0x92, "CD"), - (0x93, "Phono/N.Radio"), - (0x94, "A.Tape2/N.Music"), - (0x95, "Server"), - (0x96, "Spotify"), - (0x97, "CD2/Join"), - (0xBF, "AV"), - ] -) - -beo4_commanddict = OrderedDict( - [ - # Source selection: - (0x0C, "Standby"), - (0x47, "Sleep"), - (0x80, "TV"), - (0x81, "Radio"), - (0x82, "V.Aux/DTV2"), - (0x83, "A.Aux"), - (0x84, "Media"), - (0x85, "V.Tape/V.Mem"), - (0x86, "DVD"), - (0x87, "Camera"), - (0x88, "Text"), - (0x8A, "Sat/DTV"), - (0x8B, "PC"), - (0x8C, "Web"), - (0x8D, "Doorcam"), - (0x8E, "Photo"), - (0x90, "USB2"), - (0x91, "A.Tape/A.Mem"), - (0x92, "CD"), - (0x93, "Phono/N.Radio"), - (0x94, "A.Tape2/N.Music"), - (0x95, "Server"), - (0x96, "Spotify"), - (0x97, "CD2/Join"), - (0xBF, "AV"), - (0xFA, "P-IN-P"), - # Digits: - (0x00, "Digit-0"), - (0x01, "Digit-1"), - (0x02, "Digit-2"), - (0x03, "Digit-3"), - (0x04, "Digit-4"), - (0x05, "Digit-5"), - (0x06, "Digit-6"), - (0x07, "Digit-7"), - (0x08, "Digit-8"), - (0x09, "Digit-9"), - # Source control: - (0x1E, "Step Up"), - (0x1F, "Step Down"), - (0x32, "Rewind"), - (0x33, "Return"), - (0x34, "Wind"), - (0x35, "Go/Play"), - (0x36, "Stop"), - (0xD4, "Yellow"), - (0xD5, "Green"), - (0xD8, "Blue"), - (0xD9, "Red"), - # Sound and picture control - (0x0D, "Mute"), - (0x1C, "P.Mute"), - (0x2A, "Format"), - (0x44, "Sound/Speaker"), - (0x5C, "Menu"), - (0x60, "Volume Up"), - (0x64, "Volume Down"), - (0xDA, "Cinema_On"), - (0xDB, "Cinema_Off"), - # Other controls: - (0xF7, "Stand"), - (0x0A, "Clear"), - (0x0B, "Store"), - (0x0E, "Reset"), - (0x14, "Back"), - (0x15, "MOTS"), - (0x20, "Goto"), - (0x28, "Show Clock"), - (0x2D, "Eject"), - (0x37, "Record"), - (0x3F, "Select"), - (0x46, "Sound"), - (0x7F, "Exit"), - (0xC0, "Shift-0/Edit"), - (0xC1, "Shift-1/Random"), - (0xC2, "Shift-2"), - (0xC3, "Shift-3/Repeat"), - (0xC4, "Shift-4/Select"), - (0xC5, "Shift-5"), - (0xC6, "Shift-6"), - (0xC7, "Shift-7"), - (0xC8, "Shift-8"), - (0xC9, "Shift-9"), - # Continue functionality: - (0x70, "Rewind Repeat"), - (0x71, "Wind Repeat"), - (0x72, "Step_UP Repeat"), - (0x73, "Step_DW Repeat"), - (0x75, "Go Repeat"), - (0x76, "Green Repeat"), - (0x77, "Yellow Repeat"), - (0x78, "Blue Repeat"), - (0x79, "Red Repeat"), - (0x7E, "Key Release"), - # Functions: - (0x40, "Guide"), - (0x43, "Info"), - (0x0F, "Function_1"), - (0x10, "Function_2"), - (0x11, "Function_3"), - (0x12, "Function_4"), - (0x19, "Function_5"), - (0x1A, "Function_6"), - (0x21, "Function_7"), - (0x22, "Function_8"), - (0x23, "Function_9"), - (0x24, "Function_10"), - (0x25, "Function_11"), - (0x26, "Function_12"), - (0x27, "Function_13"), - (0x39, "Function_14"), - (0x3A, "Function_15"), - (0x3B, "Function_16"), - (0x3C, "Function_17"), - (0x3D, "Function_18"), - (0x3E, "Function_19"), - (0x4B, "Function_20"), - (0x4C, "Function_21"), - (0x50, "Function_22"), - (0x51, "Function_23"), - (0x7D, "Function_24"), - (0xA5, "Function_25"), - (0xA6, "Function_26"), - (0xA9, "Function_27"), - (0xAA, "Function_28"), - (0xDD, "Function_29"), - (0xDE, "Function_30"), - (0xE0, "Function_31"), - (0xE1, "Function_32"), - (0xE2, "Function_33"), - (0xE6, "Function_34"), - (0xE7, "Function_35"), - (0xF2, "Function_36"), - (0xF3, "Function_37"), - (0xF4, "Function_38"), - (0xF5, "Function_39"), - (0xF6, "Function_40"), - # Cursor functions: - (0x13, "Select"), - (0xCA, "Cursor_Up"), - (0xCB, "Cursor_Down"), - (0xCC, "Cursor_Left"), - (0xCD, "Cursor_Right"), - # Light/Control commands - (0x9B, "Light"), - (0x9C, "Command"), - (0x58, "Light Timeout"), - # Dummy for 'Listen for all commands' - (0xFF, " "), - ] -) -BEO4_CMDS = {v.upper(): k for k, v in beo4_commanddict.items()} - -# BeoRemote One Commands -beoremoteone_commanddict = OrderedDict( - [ - # Source, (Cmd, Unit) - ("Standby", (0x0C, 0)), - ("TV", (0x80, 0)), - ("RADIO", (0x81, 0)), - ("TUNEIN", (0x81, 1)), - ("DVB_RADIO", (0x81, 2)), - ("AV.IN", (0x82, 0)), - ("LINE.IN", (0x83, 0)), - ("A.AUX", (0x83, 1)), - ("BLUETOOTH", (0x83, 2)), - ("HOMEMEDIA", (0x84, 0)), - ("DNLA", (0x84, 1)), - ("RECORDINGS", (0x85, 0)), - ("CAMERA", (0x87, 0)), - ("FUTURE.USE", (0x89, 0)), - ("USB", (0x90, 0)), - ("A.MEM", (0x91, 0)), - ("CD", (0x92, 0)), - ("N.RADIO", (0x93, 0)), - ("A.TAPE2/N.MUSIC", (0x94, 0)), - ("MUSIC", (0x94, 0)), - ("DNLA-DMR", (0x94, 1)), - ("AIRPLAY", (0x94, 2)), - ("SPOTIFY", (0x96, 0)), - ("DEEZER", (0x96, 1)), - ("QPLAY", (0x96, 2)), - ("JOIN", (0x97, 0)), - ("WEBMEDIA", (0x8C, 0)), - ("YOUTUBE", (0x8C, 1)), - ("HOME.APP", (0x8C, 2)), - ("HDMI_1", (0xCE, 0)), - ("HDMI_2", (0xCE, 1)), - ("HDMI_3", (0xCE, 2)), - ("HDMI_4", (0xCE, 3)), - ("HDMI_5", (0xCE, 4)), - ("HDMI_6", (0xCE, 5)), - ("HDMI_7", (0xCE, 6)), - ("HDMI_8", (0xCE, 7)), - ("MATRIX_1", (0xCF, 0)), - ("MATRIX_2", (0xCF, 1)), - ("MATRIX_3", (0xCF, 2)), - ("MATRIX_4", (0xCF, 3)), - ("MATRIX_5", (0xCF, 4)), - ("MATRIX_6", (0xCF, 5)), - ("MATRIX_7", (0xCF, 6)), - ("MATRIX_8", (0xCF, 7)), - ("MATRIX_9", (0xD0, 0)), - ("MATRIX_10", (0xD0, 1)), - ("MATRIX_11", (0xD0, 2)), - ("MATRIX_12", (0xD0, 3)), - ("MATRIX_13", (0xD0, 4)), - ("MATRIX_14", (0xD0, 5)), - ("MATRIX_15", (0xD0, 6)), - ("MATRIX_16", (0xD0, 7)), - ("PERSONAL_1", (0xD1, 0)), - ("PERSONAL_2", (0xD1, 1)), - ("PERSONAL_3", (0xD1, 2)), - ("PERSONAL_4", (0xD1, 3)), - ("PERSONAL_5", (0xD1, 4)), - ("PERSONAL_6", (0xD1, 5)), - ("PERSONAL_7", (0xD1, 6)), - ("PERSONAL_8", (0xD1, 7)), - ("TV.ON", (0xD2, 0)), - ("MUSIC.ON", (0xD3, 0)), - ("PATTERNPLAY", (0xD3, 1)), - ] -) - -beoremoteone_keydict = OrderedDict( - [ - (0x0C, "Standby"), - # Digits: - (0x00, "Digit-0"), - (0x01, "Digit-1"), - (0x02, "Digit-2"), - (0x03, "Digit-3"), - (0x04, "Digit-4"), - (0x05, "Digit-5"), - (0x06, "Digit-6"), - (0x07, "Digit-7"), - (0x08, "Digit-8"), - (0x09, "Digit-9"), - # Source control: - (0x1E, "Step Up"), - (0x1F, "Step Down"), - (0x32, "Rewind"), - (0x33, "Return"), - (0x34, "Wind"), - (0x35, "Go/Play"), - (0x36, "Stop"), - (0xD4, "Yellow"), - (0xD5, "Green"), - (0xD8, "Blue"), - (0xD9, "Red"), - # Sound and picture control - (0x0D, "Mute"), - (0x1C, "P.Mute"), - (0x2A, "Format"), - (0x44, "Sound/Speaker"), - (0x5C, "Menu"), - (0x60, "Volume Up"), - (0x64, "Volume Down"), - (0xDA, "Cinema_On"), - (0xDB, "Cinema_Off"), - # Other controls: - (0xF7, "Stand"), - (0x0A, "Clear"), - (0x0B, "Store"), - (0x0E, "Reset"), - (0x14, "Back"), - (0x15, "MOTS"), - (0x20, "Goto"), - (0x28, "Show Clock"), - (0x2D, "Eject"), - (0x37, "Record"), - (0x3F, "Select"), - (0x46, "Sound"), - (0x7F, "Exit"), - (0xC0, "Shift-0/Edit"), - (0xC1, "Shift-1/Random"), - (0xC2, "Shift-2"), - (0xC3, "Shift-3/Repeat"), - (0xC4, "Shift-4/Select"), - (0xC5, "Shift-5"), - (0xC6, "Shift-6"), - (0xC7, "Shift-7"), - (0xC8, "Shift-8"), - (0xC9, "Shift-9"), - # Continue functionality: - (0x70, "Rewind Repeat"), - (0x71, "Wind Repeat"), - (0x72, "Step_UP Repeat"), - (0x73, "Step_DW Repeat"), - (0x75, "Go Repeat"), - (0x76, "Green Repeat"), - (0x77, "Yellow Repeat"), - (0x78, "Blue Repeat"), - (0x79, "Red Repeat"), - (0x7E, "Key Release"), - # Functions: - (0x40, "Guide"), - (0x43, "Info"), - # Cursor functions: - (0x13, "Select"), - (0xCA, "Cursor_Up"), - (0xCB, "Cursor_Down"), - (0xCC, "Cursor_Left"), - (0xCD, "Cursor_Right"), - # Light/Control commands - (0x9B, "Light"), - (0x9C, "Command"), - (0x58, "Light Timeout") - ] -) - -# ######################################################################################## -# Source Activity -sourceactivitydict = OrderedDict( - [ - (0x00, "Unknown"), - (0x01, "Stop"), - (0x02, "Play"), - (0x03, "Wind"), - (0x04, "Rewind"), - (0x05, "Record Lock"), - (0x06, "Standby"), - (0x07, "Load/No Media"), - (0x08, "Still Picture"), - (0x14, "Scan Forward"), - (0x15, "Scan Reverse"), - (0xFF, "None"), - ] -) - -# ######################################################################################## -# ##### MasterLink (not MLGW) Protocol packet constants -ml_telegram_type_dict = dict( - [ - (0x0A, "COMMAND"), - (0x0B, "REQUEST"), - (0x14, "RESPONSE"), - (0x2C, "INFO"), - (0x5E, "CONFIG"), - ] -) - -ml_command_type_dict = dict( - [ - (0x04, "MASTER_PRESENT"), - # REQUEST_DISTRIBUTED_SOURCE: seen when a device asks what source is being distributed - # subtypes seen 01:request 04:no source 06:has source (byte 13 is source) - (0x08, "REQUEST_DISTRIBUTED_SOURCE"), - (0x0D, "BEO4_KEY"), - (0x10, "STANDBY"), - (0x11, "RELEASE"), # when a device turns off - (0x20, "MLGW_REMOTE_BEO4"), - # REQUEST_LOCAL_SOURCE: Seen when a device asks what source is playing locally to a device - # subtypes seen 02:request 04:no source 05:secondary source 06:primary source (byte 11 is source) - # byte 10 is bitmask for distribution: 0x01: coaxial cable - 0x02: MasterLink ML_BUS - - # 0x08: local screen - (0x30, "REQUEST_LOCAL_SOURCE"), - (0x3C, "TIMER"), - (0x40, "CLOCK"), - (0x44, "TRACK_INFO"), - (0x45, "GOTO_SOURCE"), - # LOCKMANAGER_COMMAND: Lock to Determine what device issues source commands - # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0 - (0x5C, "LOCK_MANAGER_COMMAND"), - (0x6C, "DISTRIBUTION_REQUEST"), - (0x82, "TRACK_INFO_LONG"), - # Source Status - # byte 10:source - byte 13: 80 when DTV is turned off. 00 when it's on - # byte 18H 17L: source medium - byte 19: channel/track - byte 21:activity - # byte 22: 01: audio source 02: video source ff:undefined - byte 23: picture identifier - (0x87, "STATUS_INFO"), - (0x94, "VIDEO_TRACK_INFO"), - # - # ----------------------------------------------------------------------- - # More packets that we see on the bus, with a guess of the type - # DISPLAY_SOURCE: Message sent with a payload showing the displayed source name. - # subtype 3 has the printable source name starting at byte 10 of the payload - (0x06, "DISPLAY_SOURCE"), - # START_VIDEO_DISTRIBUTION: Sent when a locally playing source starts being distributed on coaxial cable - (0x07, "START_VIDEO_DISTRIBUTION"), - # EXTENDED_SOURCE_INFORMATION: message with 6 subtypes showing information about the source. - # Printable info at byte 14 of the payload - # For Radio: 1: "" 2: Genre 3: Country 4: RDS info 5: Associated beo4 button 6: "Unknown" - # For A.Mem: 1: Genre 2: Album 3: Artist 4: Track name 5: Associated beo4 button 6: "Unknown" - (0x0B, "EXTENDED_SOURCE_INFORMATION"), - (0x96, "PC_PRESENT"), - # PICTURE AND SOUND STATUS - # byte 0: bit 0-1: sound status - bit 2-3: stereo mode (can be 0 in a 5.1 setup) - # byte 1: speaker mode (see below) - # byte 2: audio volume - # byte 3: picture format identifier (see below) - # byte 4: bit 0: screen1 mute - bit 1: screen2 mute - bit 2: screen1 active - - # bit 3: screen2 active - bit 4: cinema mode - (0x98, "PICTURE_AND_SOUND_STATUS"), - # Unknown commands - seen on power up and initialisation - ######################################################### - # On power up all devices send out a request key telegram. If - # no lock manager is allocated the devices send out a key_lost telegram. The Video Master (or Power - # Master in older implementations) then asserts a NEW_LOCKmANAGER telegram and assumes responsibility - # for LOCKMANAGER_COMMAND telegrams until a key transfer occurs. - # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0 - (0x12, "KEY_LOST"), # ? - # Unknown command with payload of length 1. - # bit 0: unknown - # bit 1: unknown - (0xA0, "NEW_LOCKMANAGER"), # ? - # Unknown command with payload of length 2 - # bit 0: unknown - # bit 1: unknown - # bit 2: unknown - ] -) - -ml_command_type_request_key_subtype_dict = dict( - [ - (0x01, "Request Key"), - (0x02, "Transfer Key"), - (0x03, "Transfer Impossible"), - (0x04, "Key Received"), - (0x05, "Timeout"), - (0xFF, "Undefined"), - ] -) - -ml_activity_dict = dict( - [ - (0x01, "Request Source"), - (0x02, "Request Source"), - (0x04, "No Source"), - (0x06, "Source Active"), - ] -) - -ml_device_dict = dict( - [ - (0xC0, "VIDEO MASTER"), - (0xC1, "AUDIO MASTER"), - (0xC2, "SOURCE CENTER/SLAVE DEVICE"), - (0x81, "ALL AUDIO LINK DEVICES"), - (0x82, "ALL VIDEO LINK DEVICES"), - (0x83, "ALL LINK DEVICES"), - (0x80, "ALL"), - (0xF0, "MLGW"), - (0x29, "SYSTEM CONTROLLER/TIMER"), - # Power Master exists in older (pre 1996?) ML implementations. Later revisions enforced the Video Master - # as lock key manager for the system and the concept was phased out. If your system is older than 2000 - # you may see this device type on the network. - # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0 - (0xFF, "POWER MASTER"), # ? - ] -) - -ml_pictureformatdict = dict( - [ - (0x00, "Not known"), - (0x01, "Known by decoder"), - (0x02, "4:3"), - (0x03, "16:9"), - (0x04, "4:3 Letterbox middle"), - (0x05, "4:3 Letterbox top"), - (0x06, "4:3 Letterbox bottom"), - (0xFF, "Blank picture"), - ] -) - -ml_selectedsourcedict = dict( - [ - (0x00, "NONE"), - (0x0B, "TV"), - (0x15, "V.TAPE/V.MEM"), - (0x16, "DVD2"), - (0x1F, "SAT/DTV"), - (0x29, "DVD"), - (0x33, "V.AUX/DTV2"), - (0x3E, "DOORCAM"), - (0x47, "PC"), - (0x6F, "RADIO"), - (0x79, "A.TAPE/A.MEM"), - (0x7A, "A.TAPE2/N.MUSIC"), - (0x8D, "CD"), - (0x97, "A.AUX"), - (0xA1, "PHONO/N.RADIO"), - # Dummy for 'Listen for all sources' - (0xFE, "ALL"), # have also seen 0xFF as "all" - (0xFF, "ALL"), - ] -) - -ml_trackinfo_subtype_dict = dict([(0x05, "Current Source"), (0x07, "Change Source"), ]) - -ml_sourcekind_dict = dict([(0x01, "audio source"), (0x02, "video source"), (0xFF, "undefined")]) - -ml_selectedsource_type_dict = dict( - [ - ("VIDEO", (0x0B, 0x1F)), - ("VIDEO_PAUSABLE", (0x15, 0x16, 0x29, 0x33)), - ("AUDIO", (0x6F, 0x97)), - ("AUDIO_PAUSABLE", (0x8D, 0x79, 0x7A, 0xA1, 0x8D)), - ("ALL", (0xFE, 0xFF)), - ("OTHER", (0x47, 0x3E)), - ] -) - -# ######################################################################################## -# ##### MLGW Protocol packet constants -mlgw_payloadtypedict = dict( - [ - (0x01, "Beo4 Command"), - (0x02, "Source Status"), - (0x03, "Picture and Sound Status"), - (0x04, "Light and Control command"), - (0x05, "All standby notification"), - (0x06, "BeoRemote One control command"), - (0x07, "BeoRemote One source selection"), - (0x20, "MLGW virtual button event"), - (0x30, "Login request"), - (0x31, "Login status"), - (0x32, "Change password request"), - (0x33, "Change password response"), - (0x34, "Secure login request"), - (0x36, "Ping"), - (0x37, "Pong"), - (0x38, "Configuration change notification"), - (0x39, "Request Serial Number"), - (0x3A, "Serial Number"), - (0x40, "Location based event"), - ] -) -MLGW_PL = {v.upper(): k for k, v in mlgw_payloadtypedict.items()} - -destselectordict = OrderedDict( - [ - (0x00, "Video Source"), - (0x01, "Audio Source"), - (0x05, "Peripheral Video Source (V.TAPE/V.MEM/DVD)"), - (0x06, "Secondary Peripheral Video Source (V.TAPE2/V.MEM2/DVD2)"), - (0x0F, "All Products"), - (0x1B, "MLGW"), - ] -) -CMDS_DEST = {v.upper(): k for k, v in destselectordict.items()} - -mlgw_secsourcedict = dict([(0x00, "V.TAPE/V.MEM"), (0x01, "V.TAPE2/DVD2/V.MEM2")]) -mlgw_linkdict = dict([(0x00, "Local/Default Source"), (0x01, "Remote Source/Option 4 Product")]) - -mlgw_virtualactiondict = dict([(0x01, "PRESS"), (0x02, "HOLD"), (0x03, "RELEASE")]) - -# for '0x03: Picture and Sound Status' -mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")]) - -mlgw_speakermodedict = dict( - [ - (0x01, "Center channel"), - (0x02, "2 channel stereo"), - (0x03, "Front surround"), - (0x04, "4 channel stereo"), - (0x05, "Full surround"), - (0xFD, " "), # Dummy for 'Listen for all modes' - ] -) - -mlgw_screenmutedict = dict([(0x00, "not muted"), (0x01, "muted")]) -mlgw_screenactivedict = dict([(0x00, "not active"), (0x01, "active")]) -mlgw_cinemamodedict = dict([(0x00, "Cinema mode off"), (0x01, "Cinema mode on")]) -mlgw_stereoindicatordict = dict([(0x00, "Mono"), (0x01, "Stereo")]) - -# for '0x04: Light and Control command' -mlgw_lctypedict = dict([(0x01, "LIGHT"), (0x02, "CONTROL")]) - -# for '0x31: Login Status -mlgw_loginstatusdict = dict([(0x00, "OK"), (0x01, "FAIL")]) - -# ######################################################################################## -# ##### BeoLink Gateway Protocol packet constants -blgw_srcdict = dict( - [ - ("TV", "TV"), - ("DVD", "DVD"), - ("RADIO", "RADIO"), - ("TP1", "A.TAPE/A.MEM"), - ("TP2", "A.TAPE2/N.MUSIC"), - ("CD", "CD"), - ("PH", "PHONO/N.RADIO"), - ] -) - -blgw_devtypes = OrderedDict( - [ - ("*", "All"), - ("SYSTEM", "System"), - ("AV renderer", "AV Renderer"), - ("BUTTON", "Button"), - ("Dimmer", "Dimmer"), - ("GPIO", "GPIO"), - ("Thermostat 1 setpoint", "Thermostat 1 setpoint"), - ("Thermostat 2 setpoints", "Thermostat 2 setpoints") - ] -) diff --git a/Server Plugin/Resources/CONSTANTS.pyc b/Server Plugin/Resources/CONSTANTS.pyc deleted file mode 100644 index 358305200847381094b8a8feaa012391cf6c47bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21752 zcmeHv2Y6i7mH*Sd#*~<1(?TD*Ay~4C Plb z?+`lafh1%@dhZFng%oN+2?R(2+1+F}|J46xQ}+DMow@HljUc=G?f?7d+Yb!a=l473 z_H)a7?+p4kJMXl8*PrsEUhSW2`0rKxU*%6bdLHq=O QJ;qjqfAH?j*%_Dq5;&nWE*2Rwz1F(Q%57SF}>m35r%JN-FA7)U7C`D6MF $S z6!j`PQPEmOeTw=O4JgVe8dP+WB41HfQBKj2qG3hr6s=dZLD9*IPEmBKqSF+eu4qKj z8H)0XHYyrbM2Z4MV~WNVZBk?uovCQEqJp9cMMXs=MU#roQdCw{QB+klrD%(yt%|lO znpSkSqPHqKN71>8-lpj7iq2DXzM=~hU8v|HMHef&MA4;+E>m>5qAL_#spu+2S1Y 32Nd0^=r% vZAjj z`l_O@Df+siZz%evqHig>Q_)?D?pEVh$$YUl7n?5fyx>jVne)60Jue04*jZ+g7sox~ zI(vC^uLuJk-6z62kM0-Y2#+2R;V_RL6k)wb4~dZR=wT5yc=T-%4)^F05svieJ0dLf z=({59@6q=}C^gadML5c%M@2ZRi5?T-B##~!q1;4Ih>-Q@NfA!==qVA7_UH#9oZ`_B zMHuqvX%SBK=ot}C^XOR-Doym92#0#~BN5)>(eol4 y(Ge7 zk6sp`)uW$?(B{!kMQHcvXCicX^oj^eJbG1xPLF;r!cvd^QG{h4{gVjGJ^E)6R(SLa z5svlfmm(bJ(Z7gryhr~k!b*>RCBg|F{T~rldGu=$k{ plOw+ zq^2%S-I`LG(wbIl>d~}DQ?I5IHLca-CrVFum7ex#>en=&DWhpn(@C0qO<7GjO+%W7 zHLcU*ywZ4;?Is)adQBTNovi5;O{ePd?L;+SRHtb=UDJrBGc@HjZPYZXi8KY8#x#v< z+N8;7I#bhTO$AL8nu?lAnkF@!rKzl`qR01^0vAYuRZUZxwrJX_X`7~LJw8{oyNmX0 zO>fn7j;3?<_#EN)5dLkN-md99P3LR6K+}bqF4A gQ{%RR;NGEJ9jxH zny%7xwWez{y+e=hAU1o6&9$1Y({#P28#LXh=_XA#Yq~|#J2kya)4MgjN7H*Xy-(Bo zHGM$St$KXEWZzq|-=^t FAIQ`5Kf`0mn-{diH!#=2j0W8Gg| zJ+A4Adi95j_DM}o)$I=x<_DU7SZ59w=4nmO)R`lMc~;YNb>>K6ex&L7I C<(R`b=M%EsR2V7j^;|Ggz& zm #HAKh#id(}CdOiXcwC}c~iFf|cWcVRI(MW*BjMKrKnsWRgUKWr$7@Vhc!oM9{6 z47&wvH#$`=2gRybXKi!$nw1{NPXv{nStW%etZk*Ln8^)SFi^wI$)WWvsoZdz3{ZbM z)tju$7ey{Pm~I(v?oao(q=r*%GLS4tM}
uSvlU~GlEclt12RWzGF&$rBq%l9HR2~zy*6)Zpf4$N z 65v5wD=^D1}!!{oCudK1i_>^58U~6&d>A4VJ=`;U#U~uWThIuwtMx?Pi-@Nowud* z2W0Z*VrWtPaC0s{8DI$q6Xp^o%%!lDWy>2wId3il#>SNkwpGm)EZCKtuc9JzB@^Z< z*kBV~7i=_F1E6oIQmH(epD=hTx667{sai7EGBnq5QlAktfM =yb%Q-C(35M2Eq2Mu;T_bw!9y zgSJM9r3P({5X%fsqX@Cw99So^)nGC)G-(b30W%>|d<2;`Mlvpbu+ DlRW6zi zU^I6fv`bi!DtbySa-enA)!Axk(mV--mTw3Oh0<2@6fnBhQx1Znc^VL9b@Ab8o&mzh z`0Q+$Nm2Fl!DseoWJkMM@X^qs+|{M>M80ZL&?5_ Zk{9zmSnE((I Qwz^r0Yrmkm-?{uac5L4G% z94i^@YT}fur;0qyrQ%4d*{y+VGq`ib?b;3QdU3A9?9;$4G5a=fo#x;MZmBt>fm>z{ zZQz!h!@yxU!Zt59hc}X~=7>hJ%^cZCwwvXRWQSSNNG>tQHj z+m z%gx#bvTd>HYb0CEU?bUPPHH6E&AE+ahq=9xTw*@bNOqb#8p);RV~yl8^L!(@+`Q00 zwl6j>Hj=I8rAD&Nyxd5(n^zji4)bawxy1ask?b_TX(X4L-!_uV%<>vPdWXEE& zJCo+y&|rCYPn9dB@(7;^kJOE8l$;6QnL+jiV^#BAkk|-nmcAL^R5kAbi|w{g;ClhF zWp sgkKLiCB8nFvZ#Rr6QS$aY-5P*^#NI|R-`tadE>3Kog+ymLJ7wEetO zm3MBFcdqizY4Xld-kBcWx89( S+mm8 zof*jHk^{MH^W?PbkjD`}c#oDQCRhFvt;9`gN4s~f<|i*d-8_^{o6Dg>XXS=22UP7+ zF1sae+2(t!Hs43`D3ZrGku164=FEV39B>Ct Wn!ZXf7u_gsXdd C{LzGvs%trSPtFrekDndV{?Q^Gu|=tn8=z(#dSP ziUX# wepXS?2dITBFV$_X0O}+HKOgBHv)yl=h( -kV1j7NAFl0DpyZaX5T0BdMnCwemj+#owfI2OaANBS}YJyJCLRc rHqwm&N(~+=#V~R#@GSmFe}l^gzmup_K0P<1#l$`Z(BK zAAR#qHm{<Om?^lL_Fl=O$-!=2n=aF9Ydy%yh4 b6negWDgwTH%ViB=Owuy`GKvY z_G$;4Bs+Hv;i6;3M3V0wTWskvND1q8(yrtXzg4Lda6ju3iEehswBM5LOAe sM%(}E+Z?WxjH6btODyV8LUehHuPA7=(+Wg+Ff{nFfMSBJ7u(fl= z@(TQti9}yetp??drEQ4`L&U?6Ygk;tPe`+vZ!A@Lca{C4tB@~lPE6YU!|o25fwZ)f zpTONqG=rftB_wAfxedu*IFWhe!wIkSqBGacuNcRJI)f7|k5`F_+Aa)Y3*I5t2yqU6 zbg<4Lt5{~jzHF3bG?*O9CcFC5qLdjFrAQ+&CBMu_`_ctIBsvncQ_*ghatI{w@^q?F zk)>k0p5Sv6twm~;xU1Up6Dk}l N^CLZ>Qugv*f_ 8M_-9g!bI6F zBGbx@blTF~Fmw^lSeuK G3BF;O$xZzB ztdNKrCl*E*JKmTdRlzeqJ3y&Od|gZYjf`#fU3;6nY(Gle5UXRbT%xJbYQ^9ndt=_} z_snu+-f)!K!MB4~8%SAyeDBK_T2jGSeyZShS8N@%oy8uHEg8Nt^TyCoA8y$paU;vh z8eNm=OUe6%fl@Vrk31v>c*|vv;cnhg_+ZC+<*!0=2({G_Z*ctSrzG3_>N4+Qi3)G_ z*eAuVW2POJ@#-mTj5Ra!XJ({&-UuSqOGD)T4L&a?yj(7=C@)m)tFXibAA^a~*qA+Q z xp#az9EA^FCVpE{1C* <{@Vjei zM9*Mvrh@%2$Ln#Tg#9|v%KLwHl3xT@ !?1p9`~e)IBi4Ei{3e7U}3t@`^T!8v9hdhl7x7th1{Tt9&XJH8a=AHtG6+SET> z`G?k-`WGwzFjyas JtNu{Zf|L2?X|MM#>Fv>?Id-^cmpw;^dq(t%_N zl1?Ppt$Z05ysPn7AUPHZ-o5z8BUy au+f52RX6uVwlL`68RuwRhPgL zer4gMfG0V48KCdr6)ah=v)^ZIo-1Jl|FiHaz^sE;1LhpW`vZT-!D|8Gr#A0(EZKv{ zHk2RKg9)p;9+sFp3vYlWHmcZn9ILw#Iy@8cXSV3a9IL&F75*?M7TyfF&cRz)3TK;F zv9-DrR_mP=#`2Ac{9iDUf5dcSf4AS~>|`^vZeIIRXW6DW+iF>?YgrQ3w#~9S&bC{2 zPMqzqES7Ghge8_mn<8wdWw(#BOD&7dDPp_KvUn gS!A42loIx)4@FfH#?XBEI7E3B{> |7^y)p+fuP9|5iM9w_0pR#{sf_r|l- zKL^?@_r;a VDMKRz68m;&m6e#%eL%FzDJZcF-B(1*Zvl@pC;BPTs 7Ip)oGZx~s^{Wo<28iBR?R-FV!omfBXt;&@ z0HUoH?hA+pT6i$v*$y58_*MrG1w6;W!vN29@NhtM$rg14ARODmBLUBIa5>=l4z2*a zz(Ki(Ug+R)&|c)A+)Xcba3!>tIJg$@QV070FLQ7Z@Nx%F0=&XObjH8Z!P^0^a_}R7 zS37tI;580@4DcNeJ`Z@UgD(JH=irNg*E{$UATC*UtX>AZ(ZN>$Z*uTez?&WXHQ+4{ z{s!=!4*nMKT@L;Z@ZApn9`HR5?hY7kQr`xAuhTvP_&x`}1NeRizYF*Q2T`bhtAi-T zzs <2i9>iqL_0SQw|DUSEJYeAFsxT#Sg*ja zUV&k~0>gR*hV=>z>wPp{ufVWgfnmJ@!+HgV^$HB@6&ThlFs%2nc)bF{dIg5{3JmKN z7}hH=tXE)IufVY0$K&-rF$)Zvf%O}Cf@2iDEHAe63iHSn`i(2Ug!TES@UYkcT2ddN zH?I6V5)$k8#+ASB6K`Dk?INjQKz88-57#6AYp(o%7gsYLRnOmnlW@cKPjwK-jQ>#w zam++EFB~)e2B*a_BTq|_fE`;ZPMpYDffL7G6{C?n))Y+h^#R=T0^f-D6dI z##FU9AFIOU)4zig3+15=7kdlwbmxEEK~&*?!ogzzKk4A NEGEeB;9?{pB;=-=g_jO5)8;@;`s;~*Yi{Cgc-0C=B+w*ubppj;cI zyjpAwKTO-{f r&221(4eqhJyP$)cN5g!q3d1X^XuywEVW=zna`M`tKi2Sk z81BW%>_I@*2!7#=8pDCC(S OrgD;y(a8F3s;6yl03CDVqzARb6$BaEzx>~>hi zaTAU;vU-`}#v*Lgku{OdVVj7>A6Xr 3uQ6jz_8G5chQIitHR9sNGv>vCHwx+ ze&@6-20!xAsvSny7{jlq)@~*2;8Yg&0KVD55@5=~X_l;WMt;U+GoB6AuAI#H-Imo} z&5FpEDLkUqzsC)&w S-T;WdBXCH$7k8B*utdPS_F`dC9pd2 zBe-5PX3MJFE) hVpb$(-a^;zXxx`oNxLzMwx6{iFRPPPfVr)O(Kf__Su @$- zMuk~B8Fm!+TJd4lOjgMOZ1%d5V%AnB>%a!virqn7%$PM!u-)rNk6AmMwBiA=I5Mki zg$^DN4JNayHmLUIq%m|%qB*UVtSbycqm>OXvt}JA3|qx)p=Q>klePY1oLION5RdB? zE@SCG7kg&ym%|==h`sF=y6h1#HAu8g;9nLxT~UBVjj@$TXXsm9;}m0vu;uSkb^N zn@harXwLdKBCTwEkc4R2jN(TamH%e2mMsN4l- RAGstc7H*58uqtrbr?1_16z_3XT0I*w{j>a{oyOv9KdIO-A9dDe*^ deehT|PhU&FWVL5=qQ`mnI#+Zo46W4! M%;vN)HN8-m` zC$wzgF`P?&$}n0g$S*rN7T9Ob{@kcs;;8W|ZhrYKc~i)9$p45vgDMhqw>n>_{lp|! z=Koa%hOibNJ%aEy`~RgF-#U^n6qwj3VQ~2Ijr>@`7Bo_wo(x8W#PPF{1Gq;j =OAI#u;AJG52|G|sxjC4Y((rI*8zNzdrfO-7 z^+aK$%E8&=<@`jXktm5ZwZbEcg|jGXq1q!{+el? x?abz+|bZy}RI4V`b-e)N6Z?zFdDhixXQdngazY&*{ z043Q$L~DeX*_e@IRH9E^Nx0zHk|gpJKSbkOL9MIUZfq|`-T1i1N=$vEvEuCLa^2Z6 z1{*=}{3Ck>4)ymPoi4vFI*tctst~NiMqc>^pNG56o!c~5&u ^&!x=Qr&=S1Zl` z7sTkFxlLepovXEG>A&)4-kkq>9xk6-(C}wAZZ^x=?HbFo%xu=Paj%vCKg)+@T1m~* zd-2fh*!2JN&w@ET(qYTOzRbq`zZ>hYHj(Cu2R6( 2D~x5}NdOjcg+mu}4YV_IYy_s6BZ^!#Q2>nth4qW^ahi1n`0;E=@0p V^K+QRAI*O-0^yu-?QyJ|{sTVQZ%qII diff --git a/Server Plugin/Resources/MLCLI_CLIENT.py b/Server Plugin/Resources/MLCLI_CLIENT.py deleted file mode 100644 index 3ceef3a..0000000 --- a/Server Plugin/Resources/MLCLI_CLIENT.py +++ /dev/null @@ -1,462 +0,0 @@ -import indigo -import asynchat -import socket -import time -import logging -from collections import OrderedDict - -import Resources.CONSTANTS as CONST - - -class MLCLIClient(asynchat.async_chat): - """Client to monitor raw packet traffic on the Masterlink network via the undocumented command line interface - of the Bang & Olufsen Gateway.""" - def __init__(self, host_address='blgw.local', port=23, user='admin', pwd='admin', name='ML_CLI', - debug=False, cb=None): - asynchat.async_chat.__init__(self) - - self.debug = debug - - self._host = host_address - self._port = int(port) - self._user = user - self._pwd = pwd - self.name = name - self.is_connected = False - - self._i = 0 - self._header_lines = 6 - self._received_data = "" - self.last_sent = '' - self.last_sent_at = time.time() - self.last_received = '' - self.last_received_at = time.time() - self.last_message = {} - - self.isBLGW = False - - # Optional callback function - if cb: - self.messageCallBack = cb - else: - self.messageCallBack = None - - # ######################################################################################## - # ##### Open Socket and connect to B&O Gateway - self.client_connect() - - # ######################################################################################## - # ##### Client functions - def collect_incoming_data(self, data): - self._received_data += data - - def found_terminator(self): - self.last_received = self._received_data - self.last_received_at = time.time() - - telegram = self._received_data - self._received_data = "" - - # Clear login process lines before processing telegrams - if self._i <= self._header_lines: - self._i += 1 - if self._i == self._header_lines - 1: - indigo.server.log("\tAuthenticated! Gateway type is " + telegram[0:4] + "\n", level=logging.DEBUG) - if telegram[0:4] != "MLGW": - self.isBLGW = True - - # Process telegrams and return json data in human readable format - if self._i > self._header_lines: - if "---- Logging" in telegram: - # Pong telegram - header = telegram - payload = [] - message = OrderedDict([('payload_type', 'Pong'), ('State_Update', dict([('CONNECTION', 'Online')]))]) - self.is_connected = True - if self.messageCallBack: - self.messageCallBack(self.name, header, str(list(payload)), message) - else: - # ML protocol message detected - items = telegram.split()[1:] - if len(items): - telegram = bytearray() - for item in items: - try: - telegram.append(int(item[:-1], base=16)) - except (ValueError, TypeError): - # abort if invalid character found - if self.debug: - indigo.server.log('Invalid character ' + str(item) + ' found in telegram: ' + - ''.join(items) + '\nAborting!', level=logging.ERROR) - break - - # Decode any telegram with a valid 9 byte header, excluding typy 0x14 (regular clock sync pings) - if len(telegram) >= 9: - # Header: To_Device/From_Device/1/Type/To_Source/From_Source/0/Payload_Type/Length - header = telegram[:9] - payload = telegram[9:] - message = self._decode(telegram) - self._report(header, payload, message) - - def client_connect(self): - indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING) - self.set_terminator(b'\r\n') - # Create the socket - try: - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - except socket.error, e: - indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR) - self.handle_close() - # Now connect - try: - self.connect((self._host, self._port)) - except socket.gaierror, e: - indigo.server.log("\tError with address: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.timeout, e: - indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.error, e: - indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR) - self.handle_close() - else: - self.is_connected = True - indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG) - - def handle_connect(self): - indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING) - self.send_cmd(self._pwd) - self.send_cmd("_MLLOG ONLINE") - - def handle_close(self): - indigo.server.log(self.name + ": Closing socket", level=logging.ERROR) - self.is_connected = False - self.close() - - def send_cmd(self, telegram): - try: - self.push(telegram + "\r\n") - except socket.timeout, e: - indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.error, e: - indigo.server.log("Error sending data: " + str(e), level=logging.ERROR) - self.handle_close() - else: - self.last_sent = telegram - self.last_sent_at = time.time() - indigo.server.log(self.name + " >>-SENT--> : " + telegram, level=logging.INFO) - time.sleep(0.2) - - def _report(self, header, payload, message): - self.last_message = message - if self.messageCallBack: - self.messageCallBack(self.name, str(list(header)), str(list(payload)), message) - - def ping(self): - if self.debug: - indigo.server.log(self.name + " >>-SENT--> : Ping", level=logging.DEBUG) - self.push('\n') - - # ######################################################################################## - # ##### Utility functions - @staticmethod - def _hexbyte(byte): - resultstr = hex(byte) - if byte < 16: - resultstr = resultstr[:2] + "0" + resultstr[2] - return resultstr - - def _hexword(self, byte1, byte2): - resultstr = self._hexbyte(byte2) - resultstr = self._hexbyte(byte1) + resultstr[2:] - return resultstr - - def _dictsanitize(self, d, s): - result = d.get(s) - if result is None: - result = self._hexbyte(s) - return str(result) - - @staticmethod - def _get_type(d, s): - rev_dict = {value: key for key, value in d.items()} - for i in range(len(list(rev_dict))): - if s in list(rev_dict)[i]: - return rev_dict.get(list(rev_dict)[i]) - - # ######################################################################################## - # ##### Decode Masterlink Protocol packet to a serializable dict - def _decode(self, telegram): - # Decode header - message = OrderedDict() - self._get_device_info(message, telegram) - if 'Device' not in message: - # If ML telegram has been matched to a Masterlink node in the devices list then the 'from_device' - # key is redundant - it will always be identical to the 'Device' key - message["from_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[1]) - message["from_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[5]) - message["to_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[0])) - message["to_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[4]) - message["type"] = self._dictsanitize(CONST.ml_telegram_type_dict, telegram[3]) - message["payload_type"] = self._dictsanitize(CONST.ml_command_type_dict, telegram[7]) - message["payload_len"] = telegram[8] + 1 - message["State_Update"] = OrderedDict() - - # RELEASE command signifies product standby - if message.get("payload_type") in ["RELEASE", "STANDBY"]: - message["State_Update"]["state"] = 'Standby' - - # source status info - # TTFF__TYDSOS__PTLLPS SR____LS______SLSHTR__ACSTPI________________________TRTR______ - if message.get("payload_type") == "STATUS_INFO": - message["State_Update"]["nowPlaying"] = 'Unknown' - - if telegram[8] < 27: - c_trk = telegram[19] - else: - c_trk = telegram[36] * 256 + telegram[37] - - message["State_Update"]["nowPlayingDetails"] = OrderedDict( - [ - ("local_source", telegram[13]), - ("type", self._dictsanitize(CONST.ml_sourcekind_dict, telegram[22])), - ("channel_track", c_trk), - ("source_medium_position", self._hexword(telegram[18], telegram[17])), - ("picture_format", self._dictsanitize(CONST.ml_pictureformatdict, telegram[23])) - ] - ) - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[10]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[10] - self._get_channel_track(message) - message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[21]) - - # display source information - if message.get("payload_type") == "DISPLAY_SOURCE": - _s = "" - for i in range(0, telegram[8] - 5): - _s = _s + chr(telegram[i + 15]) - message["State_Update"]["display_source"] = _s.rstrip() - - # extended source information - if message.get("payload_type") == "EXTENDED_SOURCE_INFORMATION": - message["State_Update"]["info_type"] = telegram[10] - _s = "" - for i in range(0, telegram[8] - 14): - _s = _s + chr(telegram[i + 24]) - message["State_Update"]["info_value"] = _s - - # beo4 command - if message.get("payload_type") == "BEO4_KEY": - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[10]) - self._get_source_name(source, message) - message["State_Update"] = OrderedDict( - [ - ("source", source), - ("sourceID", telegram[10]), - ("source_type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[10])), - ("command", self._dictsanitize(CONST.beo4_commanddict, telegram[11])) - ] - ) - - # audio track info long - if message.get("payload_type") == "TRACK_INFO_LONG": - message["State_Update"]["nowPlaying"] = 'Unknown' - message["State_Update"]["nowPlayingDetails"] = OrderedDict( - [ - ("type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])), - ("channel_track", telegram[12]), - ] - ) - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[11] - self._get_channel_track(message) - message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[13]) - - # video track info - if message.get("payload_type") == "VIDEO_TRACK_INFO": - message["State_Update"]["nowPlaying"] = 'Unknown' - message["State_Update"]["nowPlayingDetails"] = OrderedDict( - [ - ("source_type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[13])), - ("channel_track", telegram[11] * 256 + telegram[12]) - ] - ) - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[13]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[13] - self._get_channel_track(message) - message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[14]) - - # track change info - if message.get("payload_type") == "TRACK_INFO": - message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_trackinfo_subtype_dict, telegram[9]) - - # Change source - if message["State_Update"].get("subtype") == "Change Source": - message["State_Update"]["prev_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11]) - message["State_Update"]["prev_sourceID"] = telegram[11] - message["State_Update"]["prev_source_type"] = self._get_type( - CONST.ml_selectedsource_type_dict, telegram[11]) - if len(telegram) > 18: - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[22]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[22] - - # Current Source - if message["State_Update"].get("subtype") == "Current Source": - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[11] - message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[11]) - message["State_Update"]["state"] = 'Unknown' - else: - message["State_Update"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9]) - - # goto source - if message.get("payload_type") == "GOTO_SOURCE": - message["State_Update"]["nowPlaying"] = 'Unknown' - message["State_Update"]["nowPlayingDetails"] = OrderedDict( - [ - ("source_type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])), - ("channel_track", telegram[12]) - ] - ) - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[11] - if telegram[12] not in [0, 255]: - self._get_channel_track(message) - # Device sending goto source command is playing - message["State_Update"]["state"] = 'Play' - - # remote request - if message.get("payload_type") == "MLGW_REMOTE_BEO4": - message["State_Update"]["command"] = self._dictsanitize(CONST.beo4_commanddict, telegram[14]) - message["State_Update"]["destination"] = self._dictsanitize(CONST.destselectordict, telegram[11]) - - # request_key - if message.get("payload_type") == "LOCK_MANAGER_COMMAND": - message["State_Update"]["subtype"] = self._dictsanitize( - CONST.ml_command_type_request_key_subtype_dict, telegram[9]) - - # request distributed audio source - if message.get("payload_type") == "REQUEST_DISTRIBUTED_SOURCE": - message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_activity_dict, telegram[9]) - if message["State_Update"].get('subtype') == "Source Active": - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[13]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[13] - message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[13]) - - # request local audio source - if message.get("payload_type") == "REQUEST_LOCAL_SOURCE": - message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_activity_dict, telegram[9]) - if message["State_Update"].get('subtype') == "Source Active": - source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11]) - self._get_source_name(source, message) - message["State_Update"]["source"] = source - message["State_Update"]["sourceID"] = telegram[11] - message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[11]) - - # request local audio source - if message.get("payload_type") == "PICTURE_AND_SOUND_STATUS": - message["State_Update"]["sound_status"] = OrderedDict( - [ - ("mute_status", self._dictsanitize(CONST.mlgw_soundstatusdict, telegram[10])), - ("speaker_mode", self._dictsanitize(CONST.mlgw_speakermodedict, telegram[11])), - # ("stereo_mode", self._dictsanitize(CONST.mlgw_stereoindicatordict, telegram[9])) - ] - ) - # message["State_Update"]["picture_status"] = OrderedDict() - - message['State_Update']['source'] = 'Unknown' - message['State_Update']['sourceName'] = 'Unknown' - message["State_Update"]["state"] = 'Unknown' - message["volume"] = int(telegram[12]) - - return message - - @staticmethod - def _get_device_info(message, telegram): - # Loop over the device list - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - # Get properties - node_props = node.pluginProps - - # Skip netlink devices with no ml_id - if node_props['mlid'] == 'NA': - continue - - # identify if the mlid is a number or a text string - try: - ml_id = int(node_props['mlid'], base=16) - except ValueError: - # If it is a text mlid then loop over the dictionary and get the numeric key - for item in CONST.ml_device_dict.items(): - if item[1] == node_props['mlid']: - ml_id = int(item[0]) - - if ml_id == int(telegram[1]): # Match ML_ID - try: - message["Zone"] = node_props['zone'].upper() - except KeyError: - pass - message["Room"] = node_props['room'].upper() - message["Type"] = "AV RENDERER" - message["Device"] = node.name - break - - def _get_channel_track(self, message): - try: - node = indigo.devices[message['Device']] - # Get properties - node_props = node.pluginProps - source_name = message["State_Update"]["sourceName"].strip().replace(" ", "_") - if self.debug: - indigo.server.log('searching device ' + node.name + ' channel list for source ' + source_name, - level=logging.DEBUG) - if 'channels' in node_props['sources'][source_name]: - for channel in node_props['sources'][source_name]['channels']: - if self.debug: - indigo.server.log(source_name + " Channel " + channel[0][1:] + " = " + channel[1], - level=logging.DEBUG) - if int(channel[0][1:]) == int( - message["State_Update"]['nowPlayingDetails']["channel_track"]): - message["State_Update"]["nowPlaying"] = channel[1] - if self.debug: - indigo.server.log("Current Channel: " + channel[1], level=logging.DEBUG) - return - - # If source list exhausted then return Unknown - message["State_Update"]["nowPlaying"] = 'Unknown' - except KeyError: - message["State_Update"]["nowPlaying"] = 'Unknown' - - @staticmethod - def _get_device_name(dev): - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - # Get properties - node_props = node.pluginProps - if node_props['mlid'] == dev: - return node.name - return dev - - @staticmethod - def _get_source_name(source, message): - if CONST.available_sources: - for src in CONST.available_sources: - if str(src[0]) == str(source): - message["State_Update"]["sourceName"] = src[1] - return - # If source list exhausted then return Unknown - message["State_Update"]["sourceName"] = 'Unknown' diff --git a/Server Plugin/Resources/MLCLI_CLIENT.pyc b/Server Plugin/Resources/MLCLI_CLIENT.pyc deleted file mode 100644 index b9b8d8d2f759cb47635896cd4298c705a94bdf96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16403 zcmdU0TWlQHc|Nnt+fuws)QXZUjci$=FCr!Ru1K<$+?7I`T*_xD(^jeumODdo$mQ;G zW>ykYIY2ABD2nzW=tJGYEz%}P(e$CnL(?KaZ)n<}4+Z*O6nQ966nSicz6I&`{by!( zNy?CrA^ ?yAH~l|@{hOcuv|Lf~pEUlzjwgJ8!o@#JRh7Eas$1%g zm9(eSom5g!t2^nW-lgtzNj;^i-D;~x-RV(Q469dd^r<@;<))Qqsf~WM3UFQOW2GJ` z_2HdE{9tLa8z1+7hCjtg3)Sclip9Y70?*C+l}O$f=4a>UW~;tei(1(CVcZO!@IO#^ zz*}%7mZ+cviul*1Jd|1GcB_wj)tzqT_7DMJ>Gn#cSGj#s=~He-DnPhjD*ehmBo$yf zAe8~-4oYQEc|*z_V*L@wG_2fVX&+JUh*U;pz-TgHOu1vyepuQMC+$a+dqmpDrF}eU zKPs @$rO9Jl>~Etmgs`K!y;ZOIQ9ZDO@_oBeu55ad9R=mpRlj1_Yj(8m*$d?` z@`9>g+q7$5biW>K+V}jjbTw;kz0%wQO+DAH)VH?EHP^;S&-T$2td=WYzl}fjRT(~0 zuC3YU?8RzxHS}us&2r@3FYio+{lI9Yx^{o6TCbF=eipA0_q6P80ZtEU3-b=Pufh%E zlfpBLC;T@Qky0Drjg)#Ql(Zi9u;!%ILrW@M1okjRDzhEE=t8AeJ?xT7pL&P^XvwIk zUn;^^Y{6~`TxXE1z!vP8sSK-VL~V>HFe2MWllHOQ?T3|b1{_I%kE@5hD)=EwPCTk= zU>7syC|fp;5hf*|G}(pq6#)SJO)_AqN=;6%MlzJcomyqR90BY2#gSL`IgXEwbsQn2 z>#a1`q~@&G!zfB)04Qr(8ucKqHA63m_(`X6-<3+OyyeNL8|7-~MFX4{I+c2@=2fug z5jVo|qe1?+?v){O4!1vyhS(B#70 7Aadx&B(+zI5Tz#i>ivx2nxGzZOo LRm zrf2Jrvsx5Rm%QMf7ua#L_QHA-(8Fm_dz76*acQctqkDlBkq%!(p;9BMOe$jyTN(2g zb@P`kcv6{kk2R3WbXB-KK3w=Lp71aVFl*Na{ _+lr2|1C)F44L0WQ{<`|8N9~*;o*P*e9aGTwFrGH- zraOvB7Uo&Y+n&`GO{nNNvE1xe!B0RDi#VN7!GBt98Un2`g21e-dl(PEo{T@Ag>6k} z`V2u^*pyoU{Q;h3)QPAp#~oLj-6SAhQMW-jQ#%df@vEoefglxMPm?6HL?A;NWVoIJ zG|~Aq(nU>G 2^K?aPooGUV=b#bOe-Q1ZsO zR n&0)LqZi1COR;lFKnlwhy(0wDL`3m1T6i*HTZ6CSm)Lw`GhRYX@FEb0>t29IzF1 za~baIYiJAeD3mo|4bzOJ$E-2ys5NOFwuVt3NcC7f))_HxFQrE?Dq{^=X|x;`qj$*~ zNROrZt$vuj3Pp%&p2V5O6aE=mT5hIM;2UWuhLp;JX(-O3tg_I@AUbrjXkRqrL;(4Q zI@&od!^FefIK=JW7*LN=&{MTp)y3CAQBr@PEND8b{W0i5d_h!90)D6kewdoks#)rT zoo58TQMX3nbW`z(Dy;p;d}35Sp{zZh=y{4y{E2}uCJ<)!LV(eEB9dO`GC$r6q3 ;?ixVa23O&+&<%~Lb33$L$A&*M|MbodD*7y*!moI znA<(rA5xu&2-=mvgF}XCVV#E}s3~fhjOo1ZN9%U%9ioTsO{OH{a0(LNn2sLZTi32P zIhmRxo?M3#AmH{$^wX!$#K>Y2aGg1~n5g+lu0&6 Xy zoGacGHy18@hmeS8E$i|evnrz+1#y|1yWz|g3rlifE-lW!>y(ysA-5nJ$fKUp*Z3J) zl64&Qs-9D+*6|UNGhU7yOxDW243`d)0YuQRV31Lxmr(0)3@L8jL03Ea8Dfbi6ils) z)?g}a9fy*6&Kg4P8L16+ltWM~qDr_vDsxdKmr-dmFMI_!MrpM{En&P9If`zGJ0ji+ z;tk?5bsPu{!{s7qYn&epYj-*bQ&UrhtL0p_Fh9R|(_SpjV{472*uzO=9sL#J)rSJ7 zGuNqXxtf#3ofpe;(Dz69Jz+{CgJ0)rcx+bIgf;S*y?6s%t-YWYN1TUFAhx8-sC0{d zA~lYj)uGO*Z$-tVF}E1f7G1VyAp~tzA*7!a@!UgbdJZC}B1W30R1pY~FOj4#4!!jv zum?2~3fnNKa!Mp&T88r{c-euA4BFx_18xU-jY;|h=0FrUUGqDzda#8?5MWEW1E9N@ zFR^-Z+DRlNBfC)hEd!xnOwwn2A>csrL y2u(oYW!xgE&P;#n=9T} zG`?)r^BR-fOc8&bW37bZpeq8s1SEe0PskI2f{!=8M=1|mXRQip$A|i17EkyNia0W8 z&fPeC-jMAV7QtB_?Ada2z{K&ZFezSnu*G?{wkr-oLD01A?W2RS*{`EbqI-tAGaN!R zbe _I4fpRwa{ zZnqfRf{+;5p=s@x@SU8Q_Od_O+9+JQ9C{hW7e~Vy@y1d=1KKT0KW8XSf0e>1tV=0} z9RmTuhd4f<0s?O;6 1N4VIK;W8o=>y!m4nEQ~~^d7LUY4$%P<} ziE0^k6 0jm$JO^32TU5hUkNaKV z8uQ+;m2K0EYZsc)hXO0@q8Z#Mr8fIia9+e6ajt}%;5#rDRVBHP+*TAVDBOk^zCMX2 zO+t#AY->p2OjpWKPA!HB4?Hc!19uTX3d^{T@xSK%Io4*_5RrxfSSE3r$cB7`q@X3` z_hS%NnDG(fQxxo!daytdMP!*oObidY@^@chj28RQ>Hv)=7{#6DKxo`u9i;yDv|7eC z#cW*RUZi6zXy(JyD11TT?zsA;6!nUUX#(Ewq||l~rea9U&YUW@K~ooGFE_m%VS7XY zn+N`EClSai+$9A28`u64YY`_dqvUE?7q~c@@&Ff@o_-TGO#`kgC=8E+l1h6hM0x`6 zo t5&Qx0G85Cft| zhJb+yrPqw=1ku+5k?HKj`3>; GJ %l2O_AcaDj ?#)H3=u#>mRax2E=Q-L!mbIZfalYq+4Ue9qS|e(u*Vb$tD7 znqN=8`^x_BzAE4S-*kI>pFnc5#w53ZJN|+Y1NidgHQC|AGy}M3ix&b4vG4Vc-Iwbu z_l?%>OZ3{HxR0aX6!iYJYd1#|2g8w_Uu!`mM}mxR30mgzFrmO>n9EFiD(1q_1kjV1 z3vv^yp%JjkW0>o*tnB;U`&YJ~js_U%XjTS15`o1cv*>u@Dh-IIS|x@)?J6%mX_a4= zRW^Ft>}6EWm0c6BLQ=6fP>LGws%X4xGV=#L?U{x#&2}XSzHOwwV`Huxso&2S$&?)Q zY?cf{z51-;mGu6v8142{ s7)^_`A2#?1HmYkUoA zqiqAgecO#*cUEK&SJ_xM=??{!AdU?%Shp=sUNC;AuLBNj2_ %zz0Y+HThS%6^*73FLq8 zfQ&1-nA*lCp)z<3vEPx2pJm$2e&X>H_c7MH`(_%(`hUrLCu12v2|2-7+n;6j^2)p* z@zwsH7yZtezi7GG{%ZeT^cMvEpZ1fviyejv85-C;<9v|07d!flchqA1-TL>|F7bnB z(ji3e?RtY~Mv}EbYXylUopr)krODfHg-t3%-n-``(~U RY&`m&Wd0jV3J% z$105E4M9{-#_{x*G{vLRY?5{)2Ke;AHIEX0AKQ89mn3f>+d#j_Mj2C>FXT!Esh5^= z#r({NrXJzO4)F*g6f(EN-105umTs4D>xZx}XWy=E*6R0blAeIdt!jA(5h$P6mp&6N z{Ie_+mg+x`np_1+)+TNgnQ`Xx!0R)nY;GHIBUAIL4pK^yg2!ev%)!m5-`sK DGMiL#MQOThA6uZVhi=q|^10w^81S7Z7o&g%6er#e5+jPZ09xg`DKRaNGRaYF!RX zKB4;zGz-O~U5>nh$V_4JRp;HpheGLi;gV=WO2_H4J~vVl7MFAmH?G_XXMVAG)8{R# zq&~hsmoF?j?QZ#0`&ANVWb?x23c-imgIQ31&9h5F3&SlumBwV^h(`N;E-xSIXpz;) z4a96S2$0PZLkfxR?V9VYBKgO?YzyR@i%W|!sr;W2YcefaN1!tK$kBy`#ifEnipUff zIigJ6z}`nkFnXT1%?r6=?q)$dvx^I;=8?JumM^?_yHHwku!T!{Zszt Rhp-0aeAU2p)I%`Aoa1)r@ZlAS0P%}{tcY Qy8{n_KOBc-Qw)$g4=Z&}EV_6<*VreU&4(Q4`2BT&?Rh z_E0{NYE@bi=G>~r3c|z_$kAv*nyTdA8XP3jz!+{PK*`*VhbIX^vZ)3TX%0X!6Sg}f zq=9@m^BG~DO=O(PB)x)Wp)qF0B5EXbHwFePM=&5HK?{js9h!j6W*Cyg{Cj@1LlAnC zAE4T+tOq8_hbXh(&@c*01asfo+n5Wlt$6iUlSK(qynDeU(LW-cv5c4nNhF8SMwv{W zG?Yc)lhGS$+r2~zyzM5&IS6n$ov;J~1}E$3B=g#R%3aMh+#{e3{n=p{jeI5~AZxYr zxy-d&o*0rZDp<&-{x!Zb0PBQ$3;!MT_>z7bNd66;aDXNfX%Wc3O4~@8IEROS$E?Bh zxHZ}}j#P>fBvu?vrBj2}Nn9&EW1Ua)@_vY8tg{%^W1T`z7PYasK8iZWqyd?;UhW1Q zyq&a8?U`#@U) bmV`@Q7C`6n-}D8KyZU*t{)gnLlliMr8m{~VczH< z#|)Ym@gC{JB^yIG7S1orHI2J+WEtbC{X0nivL4LPXqp^igg)|j&ohXEU<4v46EOfn z4g?^Yd&L0)sO^CNmYH&fFatjaFN`rc+&5vuVYP!Z3@|_#4#yRSRp7T})RpDhsN{bU zh5sT70s#XNl4}n{R1RS{PGJZRtCc_k0y @s3a(_IXi1=ZY~s??{d%}p hY z(Wr 3nLP-eKU*?=YV)4f; z3MfS8@oNF#O`q>wZ%6V3Bm;GlEF-tYq9X)>8jTed2rEv5umuFvlJDvp=s57I;+sJ7 z6Uab#4i%N2z%LL+te!M8t)LdHBi0F&Y3mpSy$f&s_`fAYVmUD@{7nGZabqDOgIa20 zB9aF!A54uFBy(>|c8~}PLyO?YKFJ2pg7vdtaro 2R0aY@(miSp&sg6(LL#$1q`>3TkNXrwZu?WnJl2Q^cl;4R zS+1RiOb>`!`2->@0q}M=C0xXnwyr3GA*U4mhj^Ep5nD D2y?4!cIX#kHC~> zte2#A3??>%1e`-i%7*N6SGXyBX7PkM6b~+oT$udc*Z`iB#0E6M0Ge ZtPZ#m zB3z<{KwC-jmVQ19q5ld9m}|c&)U@pS-?7H6H>#J&ikk|}-rm0hzenE&+JDVGA}^{G zbSs>uLq>mcNnLnk4_s6tXeeYrY4zYnT+iQqlca~`WFQ18m0AZcl64Yq13I`ZPy)Dq zhDN;tP_ipj9wvI!(IKJ`|Gbfm&{hvd_K1|2L^=GI@8QH>UP02a5t7iP2*@D1#3D|z zEn!eO@SE|c!1$lI8T1v5Pka)haVS-kU+Ic!bL0$#-*9rn9G*p+SiK`!$8qq}TgNd; zq$klYKkd`cvv!jO^OZGYR3__hiM8u2@at?7x8ZTbui*T;UUyB-#R~g*3Y8O=`0e_e ze5H76@{cCoZQ@GpVBTtLaulN#lCa%_-+v5S!_Q^XnG2bTOgH`x !|DG6G!N)n diff --git a/Server Plugin/Resources/MLCONFIG.py b/Server Plugin/Resources/MLCONFIG.py deleted file mode 100644 index c1fc0bc..0000000 --- a/Server Plugin/Resources/MLCONFIG.py +++ /dev/null @@ -1,298 +0,0 @@ -import indigo -import asyncore -import json -import requests -import logging -from requests.auth import HTTPDigestAuth, HTTPBasicAuth -from collections import OrderedDict - -import Resources.CONSTANTS as CONST - - -class MLConfig: - - def __init__(self, host_address='blgw.local', user='admin', pwd='admin', debug=False): - self.debug =debug - - self._host = host_address - self._user = user - self._pwd = pwd - - self._download_data() - - def _download_data(self): - try: - indigo.server.log('Downloading configuration data from Gateway...', level=logging.WARNING) - url = 'http://' + self._host + '/mlgwpservices.json' - # try Basic Auth next (this is needed for the BLGW) - response = requests.get(url, auth=HTTPBasicAuth(self._user, self._pwd)) - - if response.status_code == 401: - # try Digest Auth first (this is needed for the MLGW) - response = requests.get(url, auth=HTTPDigestAuth(self._user, self._pwd)) - - if response.status_code == 401: - return - else: - # Once logged in successfully download and process the configuration data - configurationdata = json.loads(response.text) - self.configure_mlgw(configurationdata) - except ValueError: - pass - - def configure_mlgw(self, data): - if "BeoGateway" not in indigo.devices.folders: - indigo.devices.folder.create("BeoGateway") - folder_id = indigo.devices.folders.getId("BeoGateway") - - # Check to see if any devices already exist to avoid duplication - _nodes = [] - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - _nodes.append(int(node.address)) - - indigo.server.log('Processing Gateway configuration data...\n', level=logging.WARNING) - # Check to see if gateway device exists and create one if not - try: - gw = indigo.device.create( - protocol=indigo.kProtocol.Plugin, - name="Bang and Olufsen Gateway", - description="Automatically generated device for BeoGateway plugin:\n" - " - Please do not delete or rename!\n" - " - Editing device properties for advanced users only!", - deviceTypeId="BOGateway", - pluginId='uk.co.lukes_plugins.BeoGateway.plugin', - folder=folder_id, - address=1 - ) - except ValueError: - gw = indigo.devices['Bang and Olufsen Gateway'] - - try: - gateway_type = 'blgw' - gw.replacePluginPropsOnServer( - { - 'serial_no': data['sn'], - 'project': data['project'], - 'installer': str(data['installer']['name']), - 'contact': str(data['installer']['contact']), - 'isBLGW': 'BLGW' - } - ) - except KeyError: - gateway_type = 'mlgw' - gw.replacePluginPropsOnServer( - { - 'serial_no': data['sn'], - 'project': data['project'], - 'isBLGW': 'MLGW' - } - ) - - # Replace States - gw.updateStatesOnServer(CONST.gw_all_stb) - - # Loop over the config data to find the rooms, devices and sources in the installation - for zone in data["zones"]: - # Get rooms - if int(zone['number']) == 240: - continue - room = OrderedDict() - room['Room_Number'] = zone['number'] - if gateway_type == 'blgw': - # BLGW arranges rooms within zones - room['Zone'] = str(zone['name']).split('/')[0] - room['Room_Name'] = str(zone['name']).split('/')[1] - elif gateway_type == 'mlgw': - # MLGW has no zoning concept - devices are arranged in rooms only - room['Room_Name'] = str(zone['name']) - - # Get products - for product in zone["products"]: - device = OrderedDict() - - # Device identification - device['Device'] = str(product["name"]) - device['MLN'] = product["MLN"] - device['ML_ID'] = '' - try: - device['Serial_num'] = str(product["sn"]) - except KeyError: - device['Serial_num'] = 'NA' - - # Physical location - if gateway_type == 'blgw': - # BLGW arranges rooms within zones - device['Zone'] = str(zone['name']).split('/')[0] - device['Room'] = str(zone['name']).split('/')[1] - elif gateway_type == 'mlgw': - # MLGW has no zoning concept - devices are arranged in rooms only - device['Room'] = str(zone['name']) - device['Room_Number'] = str(zone["number"]) - - # Source information - device['Sources'] = dict() - - for source in product["sources"]: - src_name = str(source["name"]).strip().replace(' ', '_') - device['Sources'][src_name] = dict() - for selectCmd in source["selectCmds"]: - if gateway_type == 'blgw': - # get source information from the BLGW config file - if str(source['sourceId']) == '': - source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper() - else: - source_id = str(source['sourceId'].split(':')[0]) - source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper() - device['Sources'][src_name]['source'] = source_id - device['Sources'][src_name]['uniqueID'] = str(source['sourceId']) - else: - # MLGW config file is structured differently - source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper() - device['Sources'][src_name]['source'] = source_id - source_tuple = (str(source_id), str(source["name"])) - device['Sources'][src_name]['BR1_cmd'] = \ - dict([('command', int(selectCmd["cmd"])), ('unit', int(selectCmd["unit"]))]) - - # Establish the channel list for sources with favourites lists - if 'channels' in source: - device['Sources'][src_name]['channels'] = [] - for channel in source['channels']: - c_num = 'c' - if gateway_type == 'blgw': - num = channel['selectSEQ'][::2] - else: - num = channel['selectSEQ'][:-1] - for n in num: - c_num += str(n) - c = (c_num, str(channel['name'])) - device['Sources'][src_name]['channels'].append(c) - - if source_tuple not in CONST.available_sources: - CONST.available_sources.append(source_tuple) - - # Create indigo devices to represent the B&O AV renderers in the installation - if int(device['MLN']) not in _nodes: - if self.debug: - indigo.server.log("New Device! Creating Indigo Device " + device['Device'], - level=logging.DEBUG) - - node = indigo.device.create( - protocol=indigo.kProtocol.Plugin, - name=device['Device'], - description="Automatically generated device for BeoGateway plugin:\n" - "- Device data sourced from gateway config:\n" - "- Please do not delete or rename!\n" - "- Editing device properties for advanced users only!", - deviceTypeId="AVrenderer", - pluginId='uk.co.lukes_plugins.BeoGateway.plugin', - folder=folder_id, - address=device['MLN'], - props={ - 'serial_no': device['Serial_num'], - 'mlid': 'NA', - 'zone': room['Zone'], - 'room': device['Room'], - 'roomnum': device['Room_Number'], - 'sources': device['Sources'] - } - ) - - # Update the device states - node.updateStatesOnServer(CONST.standby_state) - node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff) - else: - # if node already exists, update the properties in case they have been updated - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - if int(node.address) == int(device['MLN']): - if self.debug: - indigo.server.log("Old Device! Updating Properties for " + device['Device'] + "\n", - level=logging.DEBUG) - # Update the name of the device - node.name = device['Device'] - node.description = "Automatically generated device for BeoGateway plugin:\n" \ - " - Device data sourced from gateway config:\n" \ - " - Please do not delete or rename!\n" \ - " - Editing device properties for advanced users only!" - node.replaceOnServer() - # Update the properties of the device - node_props = node.pluginProps - node_props.update( - { - 'serial_no': device['Serial_num'], - 'zone': room['Zone'], - 'room': device['Room'], - 'roomnum': device['Room_Number'], - 'sources': device['Sources'] - } - ) - node.replacePluginPropsOnServer(node_props) - - # Update the device states - node.stateListOrDisplayStateIdChanged() - node.updateStatesOnServer(CONST.standby_state) - node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff) - indigo.device.moveToFolder(node.id, value=folder_id) - break - - # Keep track of the room data - CONST.rooms.append(room) - - # Report details of the configuration - n_devices = indigo.devices.len(filter="uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer") - 1 - indigo.server.log('Found ' + str(n_devices) + ' AV Renderers!', level=logging.DEBUG) - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - indigo.server.log('\tMLN ' + str(node.address) + ': ' + str(node.name), level=logging.INFO) - indigo.server.log('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Type, Name]:', - level=logging.DEBUG) - for i in range(len(CONST.available_sources)): - indigo.server.log('\t\t' + str(list(CONST.available_sources[i])), level=logging.INFO) - indigo.server.log('\tDone!\n', level=logging.DEBUG) - - @staticmethod - def get_masterlink_id(mlgw, mlcli): - # Identify the MasterLink ID of products - indigo.server.log("Finding MasterLink ID of products:", level=logging.WARNING) - if mlgw.is_connected and mlcli.is_connected: - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - indigo.server.log("Finding MasterLink ID of product " + node.name, level=logging.WARNING) - # Ping the device with a light timeout to elicit a ML telegram containing its ML_ID - mlgw.send_beo4_cmd(int(node.address), - CONST.CMDS_DEST.get("AUDIO SOURCE"), - CONST.BEO4_CMDS.get("LIGHT TIMEOUT")) - node_props = node.pluginProps - if node_props['serial_no'] in [None, 'NA', '']: - # If this is a MasterLink product it has no serial number... - # loop to until expected response received from ML Command Line Interface - test = True - while test: - try: - if mlcli.last_message['from_device'] == "MLGW" and \ - mlcli.last_message['payload_type'] == "MLGW_REMOTE_BEO4" and \ - mlcli.last_message['State_Update']['command'] == "Light Timeout": - - if node_props['mlid'] == 'NA': - node_props['mlid'] = mlcli.last_message.get('to_device') - node_props['serial_no'] = 'NA' - node.replacePluginPropsOnServer(node_props) - - indigo.server.log("\tMasterLink ID of product " + - node.name + " is " + node_props['mlid'] + ".\n", - level=logging.DEBUG) - test = False - except KeyError: - asyncore.loop(count=1, timeout=0.2) - - else: - # If this is a NetLink product then it has a serial number and no ML_ID - indigo.server.log("\tNetworkLink ID of product " + node.name + " is " + - node_props['serial_no'] + ". No MasterLink ID assigned.\n", - level=logging.DEBUG) - - # ######################################################################################## - # Utility Functions - @staticmethod - def _srcdictsanitize(d, s): - result = d.get(s) - if result is None: - result = s - return str(result) diff --git a/Server Plugin/Resources/MLCONFIG.pyc b/Server Plugin/Resources/MLCONFIG.pyc deleted file mode 100644 index d8c3b2575b98ec125dc4191f038e20dad465cc40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9245 zcmd5?+ix7#c|WsDF1ah-#fw%KdnilxMwXY7tjIP)JLXL?6LKjJDKTZB4u(6! Q(f}l-`q9{=0p+Er_`2+e;pvY7E+J`>np=h6q7HEIJ@9grT z$aQL gILj_809`_BkX>`Uj zYBSU6XVqr5)9+H7UDD5}pj$O^YBQ%AJ!-Q@8HAWsL9c4$)n;Bl {lzOhzZ*BGyfwfW(kn#T;|0<;ny6FI#PuABTF8DPsNoQN>PTJodnsXAr%8`=6 zmFLi`#;zB8?t))U(_TEUEYC;bwqH9Yx*LOY_$Di8JdoQV5r=yqF{8c*nm}Kch>^H1 z>2xda7+Wx=M;>#WdXC3lWogWl0>EwswP)oZsyac+;~cl)hgJ6YB~kPECU?*rcBw<9 zY;+9ipv_1JZB{zc*1O0ea&%2dt&|8!fV w)H5NUs3EgcOQGx6BJ<9zJGaYsrXB{Znja=pb6)ho zNxf&zLD`oDdSVKKOT92nrq;apsTZ5=VarRR7N93nknL*a{?dbT^FU;6+kWV$woQ!@ zIVdBa$s71&s#Flto5NSQ|4VcZK3Azybwkx5c<4b!rCCK@8-@Y-Lo)SlH30>fI5Je4 zQ}rIo2`Y=@IGLBWpq^*A &k-QsB|p2!~ZyI;?gJD*k%~Y(qnRhx^2ODoGi{Ewlyhhc&Y*HqeT-kf;IXb{sX# z4&TdV(VM_~>IDf6a3@Wh(^FH)dGx0mQ0FEz>ZuQ|m+J}SLRLC3JpYft*wP4|z#iDG z$WOXl4<)1m1yL;}Q2avRy1y}NRhB9bEbbEF#ok^E_MOO%nwMIXn8j@t?O2RENu9Kn z*wx7OEQ~2ZL-3(~QoFV4rrv%kTHm4Dvq?ir+kET
zF4_zx2yE}M+8{K!4`6LDGdQApKPC<1U|wJi&>CrPSU0HZLvmyED7RO+dF2+A3uD7g zf}2B(#T``c5IkkiQMcL%UpqFDf1` _10aS(Y)qx&sy-ns+y5-beV0^m;YpY~(mVw_E^7>~h}-$4vG;S4)CJxd zNAOg7#UYsXnwmL;aNs!LpQbaU43nCwdQrKr^BC7uMJ}FHGheV^NH4gmX2LfV0Tu<| zld}K`gG-d!|Eq4E^9J$$)9$0ZN;Pf;#RDKojlm%VdJPz2;5tsg({(lDzNy%IOU)cy z=qv~0fG3AbYGxz6bj<00%#hPx>r4p`kLkUU{Q`1GFJS%e^kTt{7wBC%3y?6l1V*Cy zs>Q-#@IAdqaq!`7HM5^)Naam3WG8cUPIkz}LBi^nxU_y*x$h{q%-iLtPo3UhJnRFE zo0&swnIa4`bJQ<_(2{L-5d%!#(u(n>h^MEEch(irr`KfqHl}g+K eq9_4Ji$Go858b%ouT(SLp5 I1U* z57iM|(6l-lKE>?E-G> Fl>PW!nm* |LRy89Zsi@N(Zx;NoqRWGj< z+$P#PUK4ik`MO*cZ>suRnnOF8{c_v$*H3Pxiw3rn8J+v+3}fgUf7Rj2T+azt?w@ew zfrN!;IMe0;VA>_+enQ|jJQ0q#LHjb;ds`iislzc PDcpy9zslinV{a$3K^?j!;cl ~IJZo=%P+8K|5I5B5GR88km11zgI8ofsF5IH!mX$YS~~_x8m3l8 zx+TNdYxSOrA6B_Fz1_A1ARstAd=Uqn@a~Jt7M|QwM-!*e0HT0(w+-uXLd-?rojL!% zPM>de;umcx5{s`Pz^X{ld6MEJw=me+Emxy*(AxD9yQ#CF^2w629xBg1j=hlCY@B=z zn;ymy!taFPd^>D^NmySlmkZV)R!b<`jH5KFMuEi1q0{grFr0G$!U 3^A(ssf%$gT^NFqGfbUkJR=5`dDCzzWW^XY>54ze}RNj%ey z%rHu^Jn&M_#A?t4L?*dp(e+c3(}o3Xo5&b^FA=bg`_u`mIEsmAVn$(bFe#a;UcP?N z^p@O|A@AI3n<1KLVd#>ZT*uUJ#A(S_wxa;NmOQ1M1^ZG|V3NRmK4sXPgmNGd@}0m2 zVa!i}cils|fv6y#O~I))AY>1zgBik@TYj*ibEHJN9FwGkofS?phxsfDkpwX*3R{gW z9P_W}ieRiLYS os6+NBVB7ij0cj7DQf>=&USPU~<^Y16P*q zr3KN^wKh?$hLBpB6|@Obs9e)YxAfZyxZq(*YYPYz6Tpc12GTI*Y hxlfnLRXW@;Wxn)9)Td=84&u1Dx`Gx-~=`)d0BP8+r 7VTfpHPdXoN1it1X)l1kJcf`sLR4uuN{i}g{%%ew5kU}0kJZqH+#aBo^ z|H`a_-M9Ju<-GcTb0gIJrz`}B9&Y Hh0@ot&tjcHRNECB zO{A~gQDT)iNzAHLT2BOv>IjUAe!~aeGV3&(KvgWp4^xZxg~h6j#R85s%7*E_tkuVe zwd_K=`i9^Uy1-^#;lhh(Vy_uERZkC750cdoUZPXhNzz!X@Y~*j)Kie{!r@YHyfqYR zypz@B9ZGd;>*6J9&uq9zJ4v@JvQkJ&P(svx(q-kA8vq{n5dA?Lk8}uqJBh1Yk~nY( z{%4*HZh6sNRE5NA9PdXZ63k&~lkU5-PpUdjM^d&n)kNx>a_Yk3+@l9lV1^?P-K_)Q zNxrjSa*DR4hEwy7xj}W=6`Eyy&C`{nhtV@HUftfd=zuI*SjwxpP0sOdmaX;0DLfV# z>7!B;Q*!>YpQNktf)7492l^y;9^S9!xk6$idg`r5_w^knp3fC>Xx_S{7NC?$m07CX zU)59GEkgl-mq?&4hH({bQ#3^Dp$-2*4Z)R%06{jfCl>{-Ml!kW=j;?JZ#^ck4KyOZ zW*qS%vbH$-Esn~C5La!60;k+IZVvq<_QOL7SyTTR3eazALdFH`^qF03q;@t4ji zv_9n#bcn>!#_4c_yqrV3V_{t9)3jdMQr>0Y_BOuBW#Vd#7~{rRHiv@V#Y{1iHTv+$ z899vb)1T=_p)V`_tZ@#XF? Q+ z(B79F#p<`v`-br*pk6la>T)Li1@&{D9Qzk=j5)uTZjVKcZ@h#~dG@XOXG_ z&XAPzQSd}55~Vv99{$ouN0kX{Hsm-sG89GP!$+E z<^@ww6jxEx=u+6YvG+NuaT8Ma{3#|3cutm*#+5FPqEz@?WelwP4!#?EADv?J#TSnP zgC #Pxg+)Amc& zyBs8#rgI?g>gY`6hGT+YTZ=2J>x=f>;_6*lCHH_Wk(y^MW1BwFukEDfy5I1kR%$&W zNIJhX>Uck1?fEa_lbO){n|`9>oN_^XS%gKY&WGrLQw_ZKqaX6$-+eQ=M35CPeHO*L zzs`Fzp`6NQC3- c^>$_Zz*QiSF zT77>bAhLC2&~Pn>a}pN$=m|&WR~FXng~hdXi| OY<>AoJ<1>+g z!-wM@f|I|3-+p{1yV#evQd9y4FXG0_=+MQYm1>TDy$ZA6?N#wL4q}9^N?yCdsdZkB z6N-q``V;oH(TI9Ubl^&>b7H*eZUwJ2v#kioU&noxP@NPm);{pEB=*b^TOuf1XWzEn zK%5nLwr%|kbJhSGF0~j;i0U$y^s9)ub|TmM3r_KX6Lpi`tM_)_qDX0a@>V~W8O!HK z^Zzh-LnAJ9GKsSOj7^cvI2-08;tD$Nmdku)EpLwH?Xvh^l*d>xV7*JHBa$(7{*OUf s(w5oE`kHnbOk((C(TfOLuRQa_6U}JV_z0?`66R41%k*a));O5`AEtagH2?qr diff --git a/Server Plugin/Resources/MLGW_CLIENT.py b/Server Plugin/Resources/MLGW_CLIENT.py deleted file mode 100644 index e10f46c..0000000 --- a/Server Plugin/Resources/MLGW_CLIENT.py +++ /dev/null @@ -1,431 +0,0 @@ -import indigo -import asynchat -import socket -import time -import logging -from collections import OrderedDict - -import Resources.CONSTANTS as CONST - - -class MLGWClient(asynchat.async_chat): - """Client to interact with a B&O Gateway via the MasterLink Gateway Protocol - http://mlgw.bang-olufsen.dk/source/documents/mlgw_2.24b/MlgwProto0240.pdf""" - - def __init__(self, host_address='blgw.local', port=9000, user='admin', pwd='admin', name='MLGW_Protocol', - debug=False, cb=None): - asynchat.async_chat.__init__(self) - - self.debug = debug - - self._host = host_address - self._port = int(port) - self._user = user - self._pwd = pwd - self.name = name - self.is_connected = False - - self._received_data = bytearray() - self.last_sent = '' - self.last_sent_at = time.time() - self.last_received = '' - self.last_received_at = time.time() - self.last_message = {} - - # Optional callback function - if cb: - self.messageCallBack = cb - else: - self.messageCallBack = None - - # Expose dictionaries via API - self.BEO4_CMDS = CONST.BEO4_CMDS - self.BEORMT1_CMDS = CONST.beoremoteone_commanddict - self.CMDS_DEST = CONST.CMDS_DEST - self.MLGW_PL = CONST.MLGW_PL - - # ######################################################################################## - # ##### Open Socket and connect to B&O Gateway - self.client_connect() - - # ######################################################################################## - # ##### Client functions - def collect_incoming_data(self, data): - self.is_connected = True - self._received_data = bytearray(data) - - bit1 = int(self._received_data[0]) # Start of Header == 1 - bit2 = int(self._received_data[1]) # Message Type - bit3 = int(self._received_data[2]) # Payload length - bit4 = int(self._received_data[3]) # Spare Bit/End of Header == 0 - - payload = bytearray() - for item in self._received_data[4:bit3 + 4]: - payload.append(item) - - if bit1 == 1 and len(self._received_data) == bit3 + 4 and bit4 == 0: - self.found_terminator(bit2, payload) - else: - if self.debug: - indigo.server.log("Incomplete Telegram Received: " + str(list(self._received_data)) + " - Ignoring!\n", - level=logging.ERROR) - self._received_data = "" - - def found_terminator(self, msg_type, payload): - self.last_received = str(list(self._received_data)) - self.last_received_at = time.time() - - header = self._received_data[0:4] - self._received_data = "" - self._decode(msg_type, header, payload) - - def _decode(self, msg_type, header, payload): - message = OrderedDict() - payload_type = self._dictsanitize(CONST.mlgw_payloadtypedict, msg_type) - - if payload_type == "MLGW virtual button event": - virtual_btn = payload[0] - if len(payload) < 1: - virtual_action = self._getvirtualactionstr(0x01) - else: - virtual_action = self._getvirtualactionstr(payload[1]) - - message["payload_type"] = payload_type - message["button"] = virtual_btn - message["action"] = virtual_action - - elif payload_type == "Login status": - if payload == 0: - indigo.server.log("\tAuthentication Failed: Incorrect Password", level=logging.ERROR) - self.handle_close() - message['Connected'] = "False" - return - else: - indigo.server.log("\tLogin successful to " + self._host, level=logging.DEBUG) - self.is_connected = True - message["payload_type"] = payload_type - message['Connected'] = "True" - self.get_serial() - - elif payload_type == "Pong": - self.is_connected = True - message = OrderedDict([('payload_type', 'Pong'), ('State_Update', dict([('CONNECTION', 'Online')]))]) - - elif payload_type == "Serial Number": - sn = '' - for c in payload: - sn += chr(c) - message["payload_type"] = payload_type - message['serial_Num'] = sn - - elif payload_type == "Source Status": - self._get_device_info(message, payload) - message["payload_type"] = payload_type - message["MLN"] = payload[0] - message["State_Update"] = OrderedDict() - message["State_Update"]["nowPlaying"] = 'Unknown' - message["State_Update"]["nowPlayingDetails"] = OrderedDict( - [ - ("channel_track", self._hexword(payload[4], payload[5])), - ("source_medium_position", self._hexword(payload[2], payload[3])), - ("picture_format", self._getdictstr(CONST.ml_pictureformatdict, payload[7])), - ] - ) - source = self._getselectedsourcestr(payload[1]).upper() - self._get_source_name(source, message) - message["State_Update"]["source"] = source - self._get_channel_track(message) - message["State_Update"]["state"] = self._getdictstr(CONST.sourceactivitydict, payload[6]) - - elif payload_type == "Picture and Sound Status": - self._get_device_info(message, payload) - message["payload_type"] = payload_type - message["MLN"] = payload[0] - message["State_Update"] = OrderedDict() - message["State_Update"]["sound_status"] = OrderedDict( - [ - ("mute_status", self._getdictstr(CONST.mlgw_soundstatusdict, payload[1])), - ("speaker_mode", self._getdictstr(CONST.mlgw_speakermodedict, payload[2])), - ("stereo_mode", self._getdictstr(CONST.mlgw_stereoindicatordict, payload[9])), - ] - ) - message["State_Update"]["picture_status"] = OrderedDict( - [ - ("screen1_mute", self._getdictstr(CONST.mlgw_screenmutedict, payload[4])), - ("screen1_active", self._getdictstr(CONST.mlgw_screenactivedict, payload[5])), - ("screen2_mute", self._getdictstr(CONST.mlgw_screenmutedict, payload[6])), - ("screen2_active", self._getdictstr(CONST.mlgw_screenactivedict, payload[7])), - ("cinema_mode", self._getdictstr(CONST.mlgw_cinemamodedict, payload[8])), - ] - ) - message["State_Update"]["state"] = 'Unknown' - message["volume"] = int(payload[3]) - - elif payload_type == "All standby notification": - message["payload_type"] = payload_type - message["command"] = "All Standby" - - elif payload_type == "Light and Control command": - if CONST.rooms: - for room in CONST.rooms: - if room['Room_Number'] == payload[0]: - try: - message["Zone"] = room['Zone'].upper() - except KeyError: - pass - message["Room"] = room['Room_Name'].upper() - message["Type"] = self._getdictstr(CONST.mlgw_lctypedict, payload[1]).upper() + " COMMAND" - message["Device"] = 'Beo4/BeoRemote One' - message["payload_type"] = payload_type - message["room_number"] = str(payload[0]) - message["command"] = self._getbeo4commandstr(payload[2]) - - if message != '': - self._report(header, payload, message) - - def client_connect(self): - indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING) - self.set_terminator(b'\r\n') - # Create the socket - try: - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - except socket.error, e: - indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR) - self.handle_close() - # Now connect - try: - self.connect((self._host, self._port)) - except socket.gaierror, e: - indigo.server.log("\tError with address: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.timeout, e: - indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.error, e: - indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR) - self.handle_close() - else: - self.is_connected = True - indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG) - - def handle_connect(self): - login = [] - for c in self._user: - login.append(c) - login.append(0x00) - for c in self._pwd: - login.append(c) - - indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING) - self._send_cmd(CONST.MLGW_PL.get("LOGIN REQUEST"), login) - - def handle_close(self): - indigo.server.log(self.name + ": Closing socket", level=logging.ERROR) - self.is_connected = False - self.close() - - def _report(self, header, payload, message): - self.last_message = message - if self.messageCallBack: - self.messageCallBack(self.name, str(list(header)), str(list(payload)), message) - - # ######################################################################################## - # ##### mlgw send functions - - # send_cmd command to mlgw - def _send_cmd(self, msg_type, payload): - # Construct header - telegram = [1, msg_type, len(payload), 0] - # append payload - for p in payload: - telegram.append(p) - - try: - self.push(str(bytearray(telegram))) - except socket.timeout, e: - indigo.server.log("\tSocket connection to timed out: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.error, e: - indigo.server.log("Error sending data: " + str(e), level=logging.ERROR) - self.handle_close() - else: - self.last_sent = str(bytearray(telegram)) - self.last_sent_at = time.time() - if msg_type != CONST.MLGW_PL.get("PING"): - indigo.server.log( - self.name + " >>-SENT--> " - + self._getpayloadtypestr(msg_type) - + ": " - + str(list(telegram)), - level=logging.INFO) - else: - if self.debug: - indigo.server.log( - self.name + " >>-SENT--> " - + self._getpayloadtypestr(msg_type) - + ": " - + str(list(telegram)), - level=logging.DEBUG - ) - - # Sleep to allow msg to arrive - time.sleep(0.2) - - # Ping the gateway - def ping(self): - self._send_cmd(CONST.MLGW_PL.get("PING"), "") - - # Get serial number of mlgw - def get_serial(self): - if self.is_connected: - # Request serial number - self._send_cmd(CONST.MLGW_PL.get("REQUEST SERIAL NUMBER"), "") - - # send_cmd Beo4 command to mlgw - def send_beo4_cmd(self, mln, dest, cmd, sec_source=0x00, link=0x00): - payload = [ - mln, # byte[0] MLN - dest, # byte[1] Dest-Sel (0x00, 0x01, 0x05, 0x0f) - cmd, # byte[2] Beo4 Command - sec_source, # byte[3] Sec-Source - link] # byte[4] Link - self._send_cmd(CONST.MLGW_PL.get("BEO4 COMMAND"), payload) - - # send_cmd BeoRemote One command to mlgw - def send_beoremoteone_cmd(self, mln, cmd, network_bit=0x00): - payload = [ - mln, # byte[0] MLN - cmd, # byte[1] Beo4 Command - 0x00, # byte[2] AV (needs to be 0) - network_bit] # byte[3] Network_bit (0 = local source, 1 = network source) - self._send_cmd(CONST.MLGW_PL.get("BEOREMOTE ONE CONTROL COMMAND"), payload) - - # send_cmd BeoRemote One Source Select to mlgw - def send_beoremoteone_select_source(self, mln, cmd, unit, network_bit=0x00): - payload = [ - mln, # byte[0] MLN - cmd, # byte[1] Beoremote One Command - unit, # byte[2] Unit - 0x00, # byte[3] AV (needs to be 0) - network_bit] # byte[4] Network_bit (0 = local source, 1 = network source) - self._send_cmd(CONST.MLGW_PL.get("BEOREMOTE ONE SOURCE SELECTION"), payload) - - def send_virtualbutton(self, button, action): - payload = [ - button, # byte[0] Button number - action] # byte[1] Action ID - self._send_cmd(CONST.MLGW_PL.get("MLGW VIRTUAL BUTTON EVENT"), payload) - - # ######################################################################################## - # ##### Utility functions - - @staticmethod - def _hexbyte(byte): - resultstr = hex(byte) - if byte < 16: - resultstr = resultstr[:2] + "0" + resultstr[2] - return resultstr - - def _hexword(self, byte1, byte2): - resultstr = self._hexbyte(byte2) - resultstr = self._hexbyte(byte1) + resultstr[2:] - return resultstr - - def _dictsanitize(self, d, s): - result = d.get(s) - if result is None: - result = "UNKNOWN (type=" + self._hexbyte(s) + ")" - return str(result) - - # ######################################################################################## - # ##### Decode MLGW Protocol packet to readable string - - # Get message string for mlgw packet's payload type - def _getpayloadtypestr(self, payloadtype): - result = CONST.mlgw_payloadtypedict.get(payloadtype) - if result is None: - result = "UNKNOWN (type = " + self._hexbyte(payloadtype) + ")" - return str(result) - - def _getbeo4commandstr(self, command): - result = CONST.beo4_commanddict.get(command) - if result is None: - result = "CMD = " + self._hexbyte(command) - return result - - def _getvirtualactionstr(self, action): - result = CONST.mlgw_virtualactiondict.get(action) - if result is None: - result = "Action = " + self._hexbyte(action) - return result - - def _getselectedsourcestr(self, source): - result = CONST.ml_selectedsourcedict.get(source) - if result is None: - result = "SRC = " + self._hexbyte(source) - return result - - def _getspeakermodestr(self, source): - result = CONST.mlgw_speakermodedict.get(source) - if result is None: - result = "mode = " + self._hexbyte(source) - return result - - def _getdictstr(self, mydict, mykey): - result = mydict.get(mykey) - if result is None: - result = self._hexbyte(mykey) - return result - - @staticmethod - def _get_source_name(source, message): - if CONST.available_sources: - for src in CONST.available_sources: - if src[1] == source: - message["State_Update"]["sourceName"] = src[0] - return - # If source list exhausted then return Unknown - message["State_Update"]["sourceName"] = 'Unknown' - - @staticmethod - def _get_device_info(message, payload): - # Loop over the device list - for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'): - # Get properties - node_props = node.pluginProps - - if int(node.address) == int(payload[0]): # Match MLN - try: - message["Zone"] = node_props['zone'].upper() - except KeyError: - pass - message["Room"] = node_props['room'].upper() - message["Type"] = "AV RENDERER" - message["Device"] = node.name - break - - def _get_channel_track(self, message): - try: - node = indigo.devices[message['Device']] - # Get properties - node_props = node.pluginProps - source_name = message["State_Update"]["sourceName"].strip().replace(" ", "_") - if self.debug: - indigo.server.log('searching device ' + node.name + ' channel list for source ' + source_name, - level=logging.DEBUG) - if 'channels' in node_props['sources'][source_name]: - for channel in node_props['sources'][source_name]['channels']: - if self.debug: - indigo.server.log(source_name + " Channel " + channel[0][1:] + " = " + channel[1], - level=logging.DEBUG) - if int(channel[0][1:]) == int( - message["State_Update"]['nowPlayingDetails']["channel_track"]): - message["State_Update"]["nowPlaying"] = channel[1] - if self.debug: - indigo.server.log("Current Channel: " + channel[1], level=logging.DEBUG) - return - - # If source list exhausted then return Unknown - message["State_Update"]["nowPlaying"] = 'Unknown' - except KeyError: - message["State_Update"]["nowPlaying"] = 'Unknown' diff --git a/Server Plugin/Resources/MLGW_CLIENT.pyc b/Server Plugin/Resources/MLGW_CLIENT.pyc deleted file mode 100644 index d6c86c83dffb4e44c889f349f52c53af9e2a5de0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17589 zcmdU0ZERduT0VDX?06i1$BE;#NpAY-e#B1Ply g1!9-n)@&wJkYJ D70;QQImI)k+GA?H=4P*P(zXtn?LKoeZ~UwY9JAeTHqczo+&AU} zV}9o5VRpDO)q~C}|AC*vl!It;7{PMn2T|ZJRLV)xkN0a=-&mWkR)R2T555n{SMbCa zP%FT&HB=4~5rh~a{&FTjc*giW=Kdjbv&Z >q>l4=HlU_ 89%QG#@nw*zwr+% zf`JYwGGP2cMFx#Oq{xu*hZPw%{)qlY&3(|y#>dQk&~Q}gM-(43{ GgE$ z^|bL_6?2WZGW8g#_bmjLvTjm$D`65urE=oFS4p x$^65rdltTs+Fn7!5!q5l3&9>2N5G* z6kENQ$r5}rc;@lMKS7Wfvkm6Un7bm0bGMhVV%FSs6v?s8yBU&;>BvPEB8SY~oFaYZ zE(#zeZ@`O;95%@SS(tHff~`1Yl3}ww+)4)n*!0o8>0_p1D;_a-51Hr(4B5`O3Bh!oTO zfJCSl7t8f949ZF1CwU~S? dDCd6c%gOs|9gf+6=Hh#-#E2Qnh-eRNhf9g?eZQKEGU8S=BJEEG}Ot z&R<(tNsgn^dQgvoT0IF+5EH7^N}=ylH?a#8^{%a+x1~8pRx2(nqNPK4vn+a5^DRq- zwMa5W(rW;5P~A{lBzUpp`w>R09MPLp7P+|;pteWad8*BoF0W4^Gz#KxA~tiivL2P9 z-I=*YqgpAKl1e>vSDFnrHgi3I1U3>--JNSD^_p^LmO{U>S$Aj8&7Pm0ow;6ZZdSs0 z=1NdcrGL7j0ybl2z79P>@m&d`+d<@}$zEXPJD$;`=dUg;7FMSlyB_I4;>XV;Fqx4| zK9hHboxJ_c4B$WC@ML?PflNL-;OIA3=KMHOiv2vE_$g?6a5hE8F$tvFX-RIDv_$$g z6ux67A ~PJhfPDA?)92G&$Jqa7Qj~5V ~@>XGGU7PgG0deAW7 zE?cj@ssC|el}@z+%|y1U);GbbR$nc8-m)jD@*YE$1}4SAv94E=^GZQvwiS7y6}h0s z8l~N8y=1jnB?)Q=T&|L%m>0I)D)cKfIF=HWkScu<8CqaNvC15A^b4VT#2L+;ca9+T zlr!j*$qgh9ABoo*A`q`cdlxoM%WRgZK&$OsYk$+0hyq|`u>6^O;5oZ*YNOo-^P>r* zpowg{E3uQ59~reA#5MHJF*wgajB|{!aRw6zv@%6MDA)a9N)@%Z)C#qDvzY8Q0`E&K zNAB4QN ;;TY#QmS_YERp#utrBHIT92?MkSdI^%iL8o}&Vy2++RK=G zScSGW`3C8YwU~s`!cyS{i-$$oF(!IPx&uX(2-Ot=en9u2z7|f}5{d}#P*pi|i6}`* z>k{z^V!h^4_(wVRc1XJGv}1dt9{W4e9(oLD9~uct$f_ Y7ZnYV5^YhCmKD#UEY zL6vW-rK4);M;VR{w6*kyM(?U(SBKS(nM-z6`7#WO0P86>3CzzdAbvJ9uH`vlCQ0i@ zur*~2CIQUf$vkN@Pqi~oEAuEfu X2yvf?_YC$d9Epeo94N>mj2&Zlb@>-PsY-JPrwX*W5d7?gDzeWZ4T^g@PXEc43Ji z`DHU{d4-L?Y%Z;Zqb8R;VRrgW^urv%h-2Pi!7p_eyl5`nigWl|16Jwq&6Ao1!5Bs& z4)nilCfM>N4Eq%g`!cw$J(UEkfMv0Wj^$ppJP9s9GFRS`gL6ekC~(B=WKFc*!%o2u z*rT9A!N1s=2%Bg#U4hOm#FGdKUSaJ M}C zN{C#qmUfBF2qR%@V(`^s0{6mBg#)Y*)5FlkHmC-RwZN}5YsE%Au8?>gyb84ry9f4G zGYX0u^@uhLaRZdQF`a%Ef&`R|H7VO Qy(*)x5oWy+#HAUx>SI(qtw9Yz8fVt|{MQj?|rBzIJV{u%Ogc zK(v_14)){)8kL@mX?GcIaQF+s?Mm6wH)21b`jHpIdzGyc3)$C2zP`>^GP;L{h7phg z&GEPdFGl67f%gcL#}Uv8RcxVpjDn@S=UTZ%_ykfpPvtd sXPYLbe81>r%ElO;94QH2A8$&dA-y`}C_Ig_7;%b~ Y!=R!*aKryNm zc%xGp$o|YTI z{~^2&IoZrf=cIEsJL!xlg(X|L+03KPqsX)WZG6a?P`~UgkCNj~7Dm&clXFfvPpijq z=LGV~Br&N?j-b+rf6uy$tOwY7WMMr`06R=z5eNqau_k=@g=E9Dj27527T@m5-?}a5 zFH9K-_colrz{G3eJTMPl2L*dRG0r`}VIbACGx(6uL+Y%t%@4`8VXyKN3nlFWuOeYY zp-gBD378QW23`D>?P6427-wG>*3kR>F1}}5fTaU$c4dDHhyDwL47#Uze}4-wlfH18 zkDGTO^Oz>w+#LexV2D&dDzXTL(ef?C1O+MB0e&E^p<~o7C2mald)}ptx 2~(rdXts8m*j2b zI^0-nxKk4OM2z`#u&9Kwb{(2b$(6BhOVPF|mk0M`?J&|kYja* ny-Z6`y zI4__RjWz{2oS^E?y _?`98+l6tUo->#Z{w9~HSgRHa19_>)GStpuh-nj&RO1Sf_m~>ed!SwXBl@Q{itIKaJ6 rCi-^#JUA6p5%`-mNI z9+8-}@SGL>zs0Cyq9ucUYs4AF4udc=X*q+8-{y>$@Y2>~ |bjs_*L3@hhKt`$Z z;6FotA3ZB}2}!Vm`nl-N14(vtmDX!Y`eC2Wpas;yqB03B)+EgZM|F6vbvWRJmlFDC z3_B+FHx5CAJf9>kljiZnZz7 r_EXlP}&5S6A=Wr0Odn~gB}BaKjFp4z(75~ zKyn2GGEuc*4O57Pb2go4wIG(x(4Fw4qZTZR`iK2307ZRa3;zgsVVR*dHnPp94&*ua zQL{Zp|0Dd_7(bmtKie5D`3jdZaInVM(KdMN16V}+@}}B}PXHWpG7~~B;We@3aT9(2 zf6~~bZ48P97YT63L#}75G6ak4DIONp#N6K=gj?#}(8X&1aTK-ig*u$NhDTx-UD+Vl zfj8u&axYyMoi&vu7{6f>amX&ENV>{`~c;Q-naWnmuk8s@Q18TlUyS=Y1cs#IG>e zr?=YXv!vep28+=8v&TCGW}O|y`!&8&)3q(MF4G++e>x&k_e+J>mvv+)bYeX`)gWk0 zk>{lZt=%S*t&c6Hr<;N=4ey&sI_P5ks~F>t@uY|5$1{W2i}qrdq0CVnnjd%axoIFA z2tNPk%2alIxY0 daK?hxhvAY#m87hFLj86wBOBcM~Kxj|qsE$1jV&4iqG#ia& zRB-$e5JOCDIy-rrHqT>*mNt<+DQF$B0ianpml7#V9mS%PY>E_4xhsp_(%e TxE2aylty7aPx zxQ^yqK`Mw>Zdz_byB9x*`Pi)l92%L J)C?twBveZcARzhpw1T}lbZ{(!mr z*Fg{#veI4$tBYtF=m_xbC2#cx^!k+>tE vH zMeo#UZ4WWOL=uys$noHIyvniKCN8<1mV_?V!a;%vczy?t`14dm*fU+A{|OtNoInZe zpLkml1q&7KjXL2IB0FcVezSEPfymn=z0K>QazNlhWV4z810Hm8_F)P<#^K1Xkcdn$ z@J-W8-o9_uCL;m0 M;0zLzGf~uz{n4AUe#GxXaMPa{>>Xs$to1;!*$LEEJ z8JqQfz#@2Ta4_$O)-b%KHpc Dw=TQ2gpz1GRpkLWb)ngA} z;{OGz=F!ufZt(0SEKhTEKE3DGfDV@TYK<=P&OI#T-M~Px*`zxb&PE2D|0D7eSFCvR zdudDXU8~}tq5C4k=aZI4=??|xHZV{)RsSDIOY@kQBlghNyG=rUCWk&OWNdo{(_2}b zClF!dQ-tN_K{(F+Z5~g29>Hfq2YYX`g&Qnn5ZFSyI5W^rw2yXd-0n_f<(j=CL-1AG z-3fLd3YNr kt&c0AJbwzw&Y@4*2no zOPQcFp#P*UokGxW9WiV8YNjg{eFIRe2nQ58UTtrIoymJ6&rJch%Dg3{+0$ojZKHTC z-NrS)(mL*8rMI2z8i|eHXA@kqI4U1>ln`N 3{{09oE6ZyC0>?0EaFZ7bX*yz}{$KTxJ+A!`kKH-CY1?EDerpK0x1C23!obcXx5C z9|t&9TrshCcTupXyCClW<6wkU`f%A6z2E{oUh?MBbu6&fm`lplE5zpzHg~4W_33JJ zCy0v;`#sq-uKu;YC$mY@b8kmDmB5|KNM0xzeP6}hLhmbR!uycH&m*w67vIJKc41-B zgOjzyoOJ}9(^6euus7=Lw{!S{Cz2n^_Jz+x8?n7GMlG3sW#`>uOPGp1d9X trzMEiu_Ip~9}8-e4m0 zK(;3>{)bsb6Vx)4I0yz6_B6Q2@h4YS`hiN1iEfEcb(;#uY?fE_xf(oT`eaQXwsn27 z243rj+Q7GWJG~~l;oSKfF>0Cy9-CZv_mB0l3%W!H|I+CImGDW)1pLkTM0*mSh@76l zm)UwBG?Sm|4eZcW6q3P&bXNShVMKSwDJ~zYAlhhUkJ>xt=>AQS$j%R6h@k5Sb>&m_ zeQegK?4TOcPSDC)cp7J>1CqB7A#VKmq$`^@=UI1Z9P3gLk4rIne};D%p02`TOkEh` zYufUbPW5z`)ZRyO(@UK$Ka+EDT@-hr=nJ=3rgZ5iEfGs 9dF!2 >+s~uDCyBR>Rn>Mro3Ngu*raz3GA8GCybE^yx(E)y9~a^;QI*dy?0!yuat51 zd#mnSX9*oS9=O+dG=V)@Y~BU~B4F=_3?L82 14: - message['Payload']['cmd' + str(k - 9)] = self._dictsanitize( - CONST.beo4_commanddict, int(s[k], base=16)) - return message - - @staticmethod - def _decode_action(telegram, message): - message['Zone'] = telegram[0].upper() - message['Room'] = telegram[1].upper() - message['Type'] = telegram[2].upper() - message['Device'] = telegram[3] - message['State_Update'] = OrderedDict() - if message.get('Type') == 'BUTTON': - if telegram[4].split('=')[0] == '_SET STATE?STATE': - message['State_Update']['STATE'] = telegram[4].split('=')[1] - if message['State_Update'].get('STATE') == '0': - message['State_Update']['Status'] = "Off" - else: - message['State_Update']['Status'] = "On" - else: - message['State_Update']['STATE'] = telegram[4] - - if message.get('Type') == 'DIMMER': # e.g. DownstairsHallwayDIMMERWall LightSTATE_UPDATE?LEVEL=5 - if telegram[4].split('=')[0] == '_SET STATE?LEVEL': - message['State_Update']['LEVEL'] = telegram[4].split('=')[1] - if message['State_Update'].get('LEVEL') == '0': - message['State_Update']['Status'] = "Off" - else: - message['State_Update']['Status'] = "On" - else: - message['State_Update']['STATE'] = telegram[4] - return message - - @staticmethod - def _decode_command(telegram, message): - message['Zone'] = telegram[0].upper() - message['Room'] = telegram[1].upper() - message['Type'] = telegram[2].upper() - message['Device'] = telegram[3] - message['State_Update'] = OrderedDict() - if message.get('Type') == 'BUTTON': - if telegram[4].split('=')[0] == '_SET STATE?STATE': - message['State_Update']['STATE'] = telegram[4].split('=')[1] - if message['State_Update'].get('STATE') == '0': - message['State_Update']['Status'] = "Off" - else: - message['State_Update']['Status'] = "On" - else: - message['State_Update']['STATE'] = telegram[4] - - if message.get('Type') == 'DIMMER': - if telegram[4].split('=')[0] == '_SET STATE?LEVEL': - message['State_Update']['LEVEL'] = telegram[4].split('=')[1] - if message['State_Update'].get('LEVEL') == '0': - message['State_Update']['Status'] = "Off" - else: - message['State_Update']['Status'] = "On" - else: - message['State_Update']['STATE'] = telegram[4] - return message - - def _decode_trigger(self, telegram, message): - message['Zone'] = telegram[0].upper() - message['Room'] = telegram[1].upper() - message['Type'] = telegram[2].upper() - message['Device'] = telegram[3] - message['State_Update'] = OrderedDict() - - if message.get('Type') == 'BUTTON': - if telegram[4].split('=')[0] == 'STATE_UPDATE?STATE': - message['State_Update']['STATE'] = telegram[4].split('=')[1] - if message['State_Update'].get('STATE') == '0': - message['State_Update']['Status'] = "Off" - else: - message['State_Update']['Status'] = "On" - else: - message['State_Update']['STATE'] = telegram[4] - - if message.get('Type') == 'DIMMER': - if telegram[4].split('=')[0] == 'STATE_UPDATE?LEVEL': - message['State_Update']['LEVEL'] = telegram[4].split('=')[1] - if message['State_Update'].get('LEVEL') == '0': - message['State_Update']['Status'] = "Off" - else: - message['State_Update']['Status'] = "On" - else: - message['State_Update']['STATE'] = telegram[4] - - if message.get('Type') == 'AV RENDERER': - if telegram[4][:5] == 'Light': - state = telegram[4][6:].split('&') - message['State_Update']['type'] = 'Light Command' - for s in state: - message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1].title() - if message['State_Update'].get('command') == ' Cmd': - message['State_Update']['command'] = self._dictsanitize(CONST.beo4_commanddict, - int(s[13:].strip())).title() - elif telegram[4][:7] == 'Control': - state = telegram[4][6:].split('&') - message['State_Update']['type'] = 'Control Command' - for s in state: - message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1] - if message['State_Update'].get('command') == ' cmd': - message['State_Update']['command'] = self._dictsanitize(CONST.beo4_commanddict, - int(s[13:].strip())).title() - elif telegram[4] == 'All standby': - message['State_Update']['command'] = telegram[4] - - else: - state = telegram[4][13:].split('&') - for s in state: - if s.split('=')[0] == 'sourceUniqueId': - src = s.split('=')[1].split(':')[0].upper() - message['State_Update']['source'] = self._srcdictsanitize(CONST.blgw_srcdict, src) - message['State_Update'][s.split('=')[0]] = s.split('=')[1] - elif s.split('=')[0] == 'nowPlayingDetails': - message['State_Update']['nowPlayingDetails'] = OrderedDict() - details = s.split('=')[1].split(';') - if len(details) > 1: - for d in details: - if d.split(':')[0].strip() in ['track number', 'channel number']: - message['State_Update']['nowPlayingDetails']['channel_track'] \ - = d.split(':')[1].strip() - else: - message['State_Update']['nowPlayingDetails'][d.split(':')[0].strip()] \ - = d.split(':')[1].strip() - else: - message['State_Update'][s.split('=')[0]] = s.split('=')[1] - return message - - def _report(self, header, telegram, message): - self.last_message = message - if self.messageCallBack: - self.messageCallBack(self.name, ''.join(header).upper(), ''.join(telegram), message) - - def client_connect(self): - indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING) - self.set_terminator(b'\r\n') - # Create the socket - try: - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - except socket.error, e: - indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR) - self.handle_close() - # Now connect - try: - self.connect((self._host, self._port)) - except socket.gaierror, e: - indigo.server.log("\tError with address: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.timeout, e: - indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.error, e: - indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR) - self.handle_close() - else: - self.is_connected = True - indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG) - - def handle_connect(self): - indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING) - self._send_cmd(self._pwd) - self._send_cmd("MONITOR") - - def handle_close(self): - indigo.server.log(self.name + ": Closing socket", level=logging.ERROR) - self.is_connected = False - self.close() - - def _send_cmd(self, telegram): - try: - self.push(telegram + "\r\n") - except socket.timeout, e: - indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR) - self.handle_close() - except socket.error, e: - indigo.server.log("Error sending data: " + str(e), level=logging.ERROR) - self.handle_close() - else: - self.last_sent = telegram - self.last_sent_at = time.time() - indigo.server.log(self.name + " >>-SENT--> : " + telegram, level=logging.INFO) - time.sleep(0.2) - - def toggle_events(self): - try: - self.push('e') - except socket.error, e: - indigo.server.log("Error sending data: " + str(e), level=logging.ERROR) - self.handle_close() - - def toggle_macros(self): - try: - self.push('m') - except socket.error, e: - indigo.server.log("Error sending data: " + str(e), level=logging.ERROR) - self.handle_close() - - def toggle_commands(self): - try: - self.push('c') - except socket.error, e: - indigo.server.log("Error sending data: " + str(e), level=logging.ERROR) - self.handle_close() - - def ping(self): - self._send_cmd('') - - # ######################################################################################## - # ##### Utility functions - @staticmethod - def _hexbyte(byte): - resultstr = hex(byte) - if byte < 16: - resultstr = resultstr[:2] + "0" + resultstr[2] - return resultstr - - def _dictsanitize(self, d, s): - result = d.get(s) - if result is None: - result = "UNKNOWN (type=" + self._hexbyte(s) + ")" - return str(result) - - @staticmethod - def _srcdictsanitize(d, s): - result = d.get(s) - if result is None: - result = s - return str(result) diff --git a/Server Plugin/Resources/MLtn_CLIENT.pyc b/Server Plugin/Resources/MLtn_CLIENT.pyc deleted file mode 100644 index 7be73e356338e1c7243d645d2f2d5ad2ac46ca7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15209 zcmeHOU2GjmR<7>rzw6ksoj9?bNhUp$nQ$|k*vT-X(S}Td|B~^HeVwiAOa?DQ?@f2d zZu|PS-Q7;?B>|yiq+RVIMj#NoVpm8!Ac3?GydsT+goHpmu!uL1c;JnP +4`?C<^8+0Uz<%KjDb|1aZ7c98h^wNy>1 z LJ#8@GgW@SX z$$uaTz_zeTCXqo34)NQe0;FTg?^I8_)pDouyVyUN^t&a~t^6Lz^eDe5nW74MmEX(! z6G-5QydKG4`;^}&)%um+FPWptKPs64 pTdnPP!U6X;4F(ynRA(Z`4*Ej@KfuS_?<; znsSP&{zh0&DfNZ UCg-!^~jP;2gyCM zIYyS_D?TC9tsZqqrbj(O1(XyO=AIc*6lNfNq3c5UrWJ&5G6O0dRBM9@n#G!syeU7n zxBR#YO@-l{_zCr>Tg89Plnqa+IuzE_Imwc>QPQMJS4tfiUl|0Tu*_npN|nZ#qZq2m zZrxk08jY0~S6=zebwf;#>xwA-;NJF% zzf0$f$#d&w3-%C zRD)Ky309R*PeV{A%|@|{!H}2~ ~DE zRlRlTupB}0$eJfhJ0D1i0+R|!P`fXbDJHk-`!R^i7m*TWBSNA8AtOy%T3S;Q-n|l1 zB}wurauf66y|^0hPE2lY) {(7=IVa95;<>P ze)G!sn-jNd+bdx`nV1TqY!!`%Qo($fn2zcxq?t@qg7`rYJ6W*~l4u*$lL_LOJ3T)) zTV5L9+|}J+it }`J{ ^rz;o3Fp*iu*-K%^(;Re%AX)?m0S;?QG(qBTLCwJx=8sra9aIAJeriqm|8 zP{pLY0&B8fQ1PZp4QTYJy6G<`fmWmIwu*l$TohIO7lfO&BWev0CV+_kM__`myw;9_ z2g}MRppPEKrazXZ0|Gu W#9SjKvyWlQmPplPto$`o9XMI#UajKfM12&_dbkSDMuTEQwa068-| zb?26WfNI^Z1+G_%fPAdx`hgeuf%pelroql^97l1Bii=~B1{=v?*Kq>s{b;-HyRgwv z@G9*1TPRCjMWU=eYrsBc9|HuOME)ee;2iSD3k7S)>bE* ){Su(xX@ma#W*4BFquHXLgu+)>A`k5C1*X@O|Ci% z7oFLQ&h$m6lx%tzE;?UxE;yI)FC}@n3&Bb(Hp< I5Lv}>uvx2mfxdt*8jlhVQ$PS0yJ0FZ zWKEgylt{lB0=VI(ac~SExMp}lC$Eta5{!bq2jI{r$-ia{8&m;9a0g0UPhu(cFOn&~ z8js5G7t|>J4L3=vahizx2R~ZMgiPk{9CGB*G@|o(l8Z=`b<{dzy$VNT(CW4N?LO-S zo}ui2=7;fP-aXDJQJ8MzG@j%iV1NNQG`WH;z#3o=L8bt_=#X{vXV0n8i5gu%A5;c} z0C50AfGU7UkGi&_2~9H}-y#rk;TmB1t?WC{Zw%4eJB4dozuHm+iw~`1bwm$+AZmfG zsdN_*^L$PXuKd0I>>A|M0Ly<9NHP|;_E$IK0G7a3V;sa60hTes1#-vL*flJNCk87y z%-^=4o(Kw|Yp4Kj0hTi#C)Q)UBU~L834UU6RKVdbFp`hVa508BQqU8XPfF##%0L C^Ss}e6_A6{J*!3`@sVp=+QKh21XU^}2bx_L{1(u@YCxOL z%v=+Io!c1zJMdf(=82a@cT}H8B5-iKx?78?zTn{Hoy$V(vY_F;Y7&G5;DoULEUJlv z4-P;c0Ynn`4m_r?T65~#8}|@m#KG1!0=(obDjigZepw}``wrrlx14DMuENFA?q(1Y z<3e$o=raoBrIEWZUk+&tWM5_mUC}>*`3M>D$>1_Vp+qulS_!;M6|q90pg&Q69$ODG zd~d@K2^u9Lu%2Ra0g3sx8fy|ThJdWZ)%r@HCs~2yT^~C_Nfn`V_)QZDPcN1$OJc!% ziUAnx!FysgDZGb~=4OP2dXA*%n{tl>v9D*$r8Hm kUaS zx8zO94GR$57LWdb4yd5)7z_eIPOQN-mcbk}%Q}FIC |8r|lFM-39SGHCqA?-7y!*KG_MNPoKjV+(`8^cDtBv@n=hv@I L@&0|GNS|~&kqxWGy6IzXVPRIc6Plm>aCTk@Nltd) zWDOgLAW7^s34)l&udzN4`x?9j)_)g|iB4_0R>N?>`mGai!p>S_aI}W(6ZW3BFIQTo z?hQres+AxW4i#5>L*@5E!Hclc^eVJL)|jT7A*)LR6;=D1FiUEkwYjb|kz8*uf!m^V zgrvQREFQgyFKsJGZ?Opt@C&wl-UG`&!t?VjAL2z19=bZyZ2eU@8AM0ei_8E)p=^V> zO+$q;8Y LJ)i! zX&|JM9l6GMUfnaE9b3lp #xoxWNo?;NYw)LGtdzoJnHo7`9e>79z(te`k>w+jDgozzOW1$La9pUMC=gy}r znfdq=^%%zo(3(A?5Uueqsr4?LN_On|%n{4yd^{y+_C<*G;lcIx5Tdk%5S!lJ8t-f$ z?avlWBRn1lgnkhx^7*EXk(k&s_s;E^hUch%wsG6nsA=93hN_bvI(oJ|GplE{#IweK z3|CfDiE$-TJD)Jxn`hykNH|3#EKzN~_sHPYnMTmc`XK|sEa `J7H7 zO%UfAW@ Rh-mX=5aRxFIq}8xy zqRL-NB;t;7dYT3Q3F#pO#qn>xA0Sb3R^+CvD|Y>hG?ks&rf=Q!3`BfsnAS|hU5g$D zvHloc>tAE?4JN%vq#mMWY{yDCki;H?=tj6s_U}p5Z9Yg?FNeL)3auftG|=}+msT6Z zMD957X-EVM68|JQoaejmgT22a^K{Xac=+Pu@Ac~Uy$xF<5(&J8D4@@}f_CP8JbNFs zP83EEDU4a?5i5L!u>#_BM(bzoQen!Tuso_DAMT-uqeE7Ju)szf3=$!6$ayo%@D*T& zxD^%5=B=2+ti;^az?o2o;wh!in-B~V&BmDA-_g7Tp MBiL5sn{4EcV9x_< zoX2B0qGd5I8$rT2z}-D1)#R!RjTR1xv4LUpWOo`TyLg3jh{N3 1a#H{nsY94|Xw9ANts&63Dl57Mhn!V~bK`S>tQSDoyd z3cgV7ixsKoWS4Y! 74=2y666Gwjq zRrEzBmzWfpcuZ&t^*9m%&byPkJXgMDY*3K495ox8!Epty8C7PGI6!GTdDERM&o0Rp zaAk4&1GloIXD1f~@B^N@OKw){MXfLA9cJT@v$2(GXzGdm#P~6K{Ra7?rO*>dO#Dfa ziG2 Nciu*hQA%qcPv*7Yo4!EJT@cZT5 zit$1Q75SACqJ>$$Koh 4L 0zvE>WS1lpqe8XjgN_@Be3GiPK zn`UkqW)8*=9v@Z! __e>kCkagTP?NlSEn)3ofi)G3txbrJfp3w=yTsb!$2b%$7YlhLt*by!~ zsu3^f!3a=Woa@&wRd8+a(xvOpRcGb<-{Zf(y-{Mk03F5Q-*&R9zlDtc9VTrG=drA* za0F@n9VYD5T;ifx6z@&Q&6RI18i&3X1e+yJW{a2pDP9jb3}|kyzeBy`?KEW-u(3{X zHjqp(9 %+|rP+BBom$0462aXqrjKIZz uZ6t8(Fn0JF=9V`ku#RgbSL8?pcVpp9)1brA7#l&2x_Bq~ zUwSY&G3VhtXcOVcX4Go1BRDHuU|%l~e;HRVw`;fz7$0`f?ZII1_aQsv{2P=X3l#46 z^ !*Yb>jU!&|%LV4yg&i}B3Q)KpOMQkE5DQgHz|1qi@ zb}{`Xc-&}+$)=c!R$2bYO?D4lmw)(#Kc1pr;JO=;zm3bmu7tC$>qnmJnh;8)K;K)x z&g3m7SD4&HB6p#=s~mc`sJt5a`W8RE$K-t`A24B9uYZq;_{el3^&c{!5fsSP-)Bx# zR5roMeN)1cJDj`Gkgz&}M=twz;YQbhHE^z2D2^7L;`xEo1E-6##m?dwp3dSxv0HvR z2fB-0r9O^We9unizQu&wUS@ZRG_ya^!S+_7@v5*PhtK)nJ&nsPy~*-YMS!yLW)2Xz do;8_!5u_*zdCjQcbuulWM(8v@gZ4n-{{VnjyuAPb diff --git a/Server Plugin/Resources/Notify.app/Contents/Info.plist b/Server Plugin/Resources/Notify.app/Contents/Info.plist deleted file mode 100644 index 429fb28..0000000 --- a/Server Plugin/Resources/Notify.app/Contents/Info.plist +++ /dev/null @@ -1,78 +0,0 @@ - - - - diff --git a/Server Plugin/Resources/Notify.app/Contents/MacOS/applet b/Server Plugin/Resources/Notify.app/Contents/MacOS/applet deleted file mode 100644 index 792791262270b07af372dbea68d4be2bb805af53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99064 zcmeI*du$ZP9S87P+c?)^%t2|=2oNp_2x#RssfkP;WzY77Lm=$fPGlmgVeh==F5Iho zdtmd*84DChoPrXO{7I`+0hOkxMQYlr!8B=U`6J*eYST8VXi5`DZ3;@G)- -CFBundleAllowMixedLocalizations -- CFBundleDevelopmentRegion -en -CFBundleExecutable -applet -CFBundleIconFile -applet -CFBundleIdentifier -com.apple.ScriptEditor.id.Notify -CFBundleInfoDictionaryVersion -6.0 -CFBundleName -Notify -CFBundlePackageType -APPL -CFBundleShortVersionString -1.0 -CFBundleSignature -aplt -LSMinimumSystemVersionByArchitecture -- -x86_64 -10.6 -LSRequiresCarbon -- NSAppleEventsUsageDescription -This script needs to control other applications to run. -NSAppleMusicUsageDescription -This script needs access to your music to run. -NSCalendarsUsageDescription -This script needs access to your calendars to run. -NSCameraUsageDescription -This script needs access to your camera to run. -NSContactsUsageDescription -This script needs access to your contacts to run. -NSHomeKitUsageDescription -This script needs access to your HomeKit Home to run. -NSMicrophoneUsageDescription -This script needs access to your microphone to run. -NSPhotoLibraryUsageDescription -This script needs access to your photos to run. -NSRemindersUsageDescription -This script needs access to your reminders to run. -NSSiriUsageDescription -This script needs access to Siri to run. -NSSystemAdministrationUsageDescription -This script needs access to administer this system to run. -OSAAppletShowStartupScreen -- OSAAppletStayOpen -- WindowState -- -bundleDividerCollapsed -- bundlePositionOfDivider -0.0 -dividerCollapsed -- eventLogLevel -2 -name -ScriptWindowState -positionOfDivider -419 -savedFrame -523 274 700 678 0 0 1680 1025 -selectedTab -description -Gt5lV~KWTOzJ3Bl3*|Yzf*|S%F_^J@1LJ{IN$}5G?C>uCWhgxc@p nFoL7m0zba+U%13j>rt +D{PDf7#xw m}nA2X+cl8gbcc>M6skm%fiacf5jo9oPA{alM#)udSi3kgu2KZW}?l9c4L> z$%)@Dhm=_}vzg{4x9TRn4k@oa-V~y-mf9<|So1DRcd)XeKGMmN{j-C7Lt8fIA-7 z{p0cb^w*`_obmnari|B6HXhgg*YkM(-=UOA{YLX^a>x4|UB|(7|9HGvhiAv _^B1VjW}60__UTmie&gOfIOqO;SE`nTM%R1q z*nfgIGO44)8=R>5oT(-gQMIQ(*+X@Xxs-SwuYKd<^xKsOhn_z^vgyP!x#n(a3sMr* zpQb~N{SIEC63<~7rT=w!|Nm|_4}R`AC4NTx|AmsLM^J>?*`KqFl)5evHFdLJeTe^6 zY|EHia%xM)G&&7)Ya(Xk>g|6a+^#-qn7KqIol`?4J=_WKeVY9D5ew*NOZV~2$=$hS zeRDneC!)@Uiz#F0w+hbtTk6&?P#pmQ2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izzz^w^9E+YH? z6dAmDIr7x5Kx8N%+<#^8C(7w}DkI8=k^Liou8Itv4nzi5iu~WXG|KA(_n#T%)yN+m zq De`m0tpmK$A7?UB91Rv<#1cs=0q;jg^L{k-7*k-@(NBWJ>wML?mu?;48? z?Ycb53uGgMuLr67#G>`IRCHodyM64SqY&$c<}al}sB>Zw=LdN$q Erben+*qr*)&U&EqXH9l1XJVX(Mg5 zCURNbip2$;)aYbe)=0M+y?Q=r75nhH$ZSm-eF=-o9Ey~wI5iltyk*F^q4O>7us=Qh zu_k(pozCyn8u*fplpdhe9M jlRV`^~tYedTmX|amoua^&ay+Rb6rK^2Qa7_t^Kx0Rj+!00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*#kPX*3h`QU1eyZ)Wmt ^M2p$?iaPs=?xbUc zl9cVC)#1m&9c^}V$)UJ-zNEFZ!s9eiM=86)-Cd>U?CZ7CHsRKJJv}F0W6vR_W%OB2 z*{PR~*SgAi7+&|!!F7JFVpNGju}lSNZ+Va_HkHrsh&vwd6TrcB|9Am+JTkOH%6xho z?VlhM>D~;rl#lm{J06>IaNR$i(>i%%w-+c$*&6B!$(FYDosOR7zBJ11D9hQ46Wj7` z2U2d%_}H#Hp0hIoU7$^xQ%zJ;^3QYfcd>5G`nBQKHQqZdl+BCk9AoaqJRkdg+pR)0 z3Q@4{SrFn$YWtE~f0F8TROids{zSpuV_`suZmLhG)J}=R8 G7un htQ?D;Es`YTj~+S#A8jFh@A5jAzQUwz2b zQ^vN8xh1EzWK5&eFt;XRMy|fsYgZpN%v>Uq&Z(i29`4QfK284nhz0cLMfdTZnV#FZ zWqor!J%1wVT)3DrevYFBAp(>yb?f|IIObF0^P2|f_v&D9GXeq-fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=pSZyIj#J0RE5)J0GeaGs{ Tyqr;W*R}+I`WPFEmY`jGr3W$M@CE|!VS}BfQpfR}b_>Ryq>T`(u(wf>6!3yo4 zqGEwlu6D}2#CD$hr2d@O4)faLM#?#T-CbwQWnPoatCNHF3T3JkmqG^%?vmYt-HHPQ zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=pSVCB-LaigHznRb!2kJf+K-?Z z{B3eC?6O8*h1a|m=Q@Q3P#UOBw&=-ZOD2`gq>Z%In#g5!D;5{BEo-D(jb1&Uw2EhW z2t~?NTpE2wEN>YyZs LU$++_SnqivG^VH(aX%4lq(x9jy z-3oguF*?uu9G? 1O8(PfWW%{L-6;XT(-M`_oGs4qP(+@!5TYfBQxC;W?T98Oz?#J5KI?{LKSr ztDbItagRvsJar^_p#SWw;5LlY%pAp-jes$GE<$Isczf<_m UH~-N5eZBked)pWOe9^<=-_hmt&;S4c diff --git a/Server Plugin/Resources/Notify.app/Contents/PkgInfo b/Server Plugin/Resources/Notify.app/Contents/PkgInfo deleted file mode 100644 index 3253614..0000000 --- a/Server Plugin/Resources/Notify.app/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPLaplt \ No newline at end of file diff --git a/Server Plugin/Resources/Notify.app/Contents/Resources/Scripts/main.scpt b/Server Plugin/Resources/Notify.app/Contents/Resources/Scripts/main.scpt deleted file mode 100644 index c478d35fc04038aeb4ea0260d1a41cc1a2a6454b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1116 zcmaJ>*-lhJ5IuKcV7lo$Ga$0c`c8&V@Iez3-^9c>!x|?JV3;Awo1fq(sPFy@i|jBC z2(s@YvU#i?Mp2k>lRDjXZ*_ImIhFgcBh~%%{*%VG*0#8{0DvDM;lPFG$OcCC&P#c3 zAK!&9Uy2Yi0&XJr(nB#yjDYtj=Yzix=7p9;#S!WsNGE!SA;-dE3fc%b1NqQ%&2t2C zsMm?~i|6S?I@x1GwOA>P5%?Q=rg;VvNBA ZVOwpuxj&dKm-&yB0?j4enTDZq27^=5L+h!D_quG`EQ~Wmv1RFkOak1enknF zxQGTcGONY?cP0Za!S(qCO`d=kph<3Lh6|k6%-m2*bK$z~n)8L!apguSyg|gB<1A-1 z=L%$-y_s#MP_`% Y3D*BB507~p@d z{LcyWzdi>H0Pwl--*U!3@;{0IO#d_Me_!r@y8r9-e;b2<0|Ne^$Bz;K%zu*r0YE`O z004h<0FVU)BqS96ug8xG04V^Vu%oG=v#AMzkq3diy{Vm)f~YuwtEr=t eCHMc)dfXg*C aD;TneD1b&`6Ii5N1*R1NqzS`I;m&Mtg@mx;D+gZs z > z&zi`F{dNGIYUDc=8|q;y5UlVPLB|)R4!I~qg~lF$j6+PL3joM)4&)yb76sY!;gcl2 z0}73IGEjhvo5aeS9go3q@Abl${&b<@Dza(87ndr8Pw(Rv0UNBOsshlu~O6kyf5 z5+y-2ZI<|b(nCY}m*YS)ONyKWdC1dvx;^<>1c|S5afcwOx2v%t9l01H9G5BOOSyZ& zyHb#IR3pL5>Ou1GYrJnPgCz?KlLXS=HSCo)63dkAKxmra{k9baR}ItsKngn>L`>>6 zGyMTZdOj&{(+Q+>v|PoxlWp@iuT})%qeZB`{%%#;*C;xPq>N2|z%D4vs<^VSPi(AA zbCm{c2EV;gbpn*bfFMDF{9p>|hLJzGkF}v+i#v$JxmM$&>E62^yTKSXiqmtoJA8Fl zP7TE{ulR70LXR+H-RGyc;Flo)(lw+8&WR7}W0AMfCmFjPp|m=r^=x*yw`%spy^L$; zeen}IQJtDEOY9hsTyP@r=ufv<>wQMF5n7&1w}OAjejlE-DgOL!((fkyK1u(tJW2k$ zsqM49N~gzFeSLh4+O2k0it`=bqCuA};Q8y2oD!p0ylj(j`utp7TO650ft28SmP~Qn zUFrXx;<@`QqwJ@ff>jHN)liaO^ySLvHiA&V!^@Q)%GDZc` yoyzi8o)_%8w&?KFJ2-q3T)}&b=%iW_pk=~0 zcH(Anlp5=uiQ(hNuVuS(pRYL51{|<<^}P3W#&lk(?E=~(^-g?WpOm&!UpX*4 _a#3b4Q_+@(88gM^>!3K9oP1}57XG4DgNv5^v@<17D zZ;sxE`U$MwweNM=QpC$NduQ7xO--MxSMyG2Zsoi~F5G@n)&`0O1>d-G3B?e{ST(qs z2!$0*!9HEn(e^dYK~lXZEg^;X{RECz(w=8b31a~9r6p$
#?f2E!BU4xzSFSRVq?{MJek2QLF9Z7khtBzNR`VRet!;u#Mu3QP+%X!^+K+9 zarSCa`vbP!z*bkOg(K`~4pA#zLG=UUI8=`H{8SJ%Eb$MA&MlxyXWkV*##D0Hb3N6c z);6c{^@XfVRBfhc{Lcy|;5+e=&(RHvEDC1I!TI~i_IW_cR(gidiTA#Xwl;x>6}aDW z4DJJ&XN^3KkEUm@Mhlv>>yq(n1Zt)044*|>5^hnB0xIOuR2Dv>7Z?*8Pe+GA0S<9)!7 z3rtgB@ d63l#!XcI4|`JhbcD5sjEMDRDjIOO&D|Ci9aI8^DV2J}eTI=#MsV zYZH2x*%l2kV=0Ux?ebg3W&w`>D6KCRba)aR- rsv18a~<$5{sP){iYmvfFLlIlMz5WzRot$Y z1Ur=Y0(U|doGNh?0|=)`?@C k>vLkw1+&r|x%vExLMUpv>0tFkF@JP4|zFutaXGD^;w9nF(NO4 Wj&~?s*teFMp2V)R&gGP}q1~KReBix|D4)<25zDcEhQsK^q=9Y05 zsU@ude(|1^opChBT1QZvrc1i5BXcP5NBcJ1ScrPixqI1n`nL7&8D9UyDKRLW2$OfD z>)&C)!?$C&;SQt?*iZN$v}k{Kznl49d-8Hh0A^sdx6I_KwQu`C4O_-*h8&5=3zOFK zw=6UgSrTsq6^_2aLT60(vwX;G3OehZZ{E}(LB>Ix