Add files via upload

This commit is contained in:
LukeSpad 2021-11-25 19:18:52 +00:00 committed by GitHub
parent 82af7d348c
commit 338287f477
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 490 additions and 355 deletions

View file

@ -6,7 +6,8 @@ import json
import urllib import urllib
from collections import OrderedDict from collections import OrderedDict
import Resources.CONSTANTS as const import Resources.CONSTANTS as CONST
class BLHIPClient(asynchat.async_chat): class BLHIPClient(asynchat.async_chat):
"""Client to interact with a Beolink Gateway via the Home Integration Protocol """Client to interact with a Beolink Gateway via the Home Integration Protocol
@ -32,7 +33,7 @@ class BLHIPClient(asynchat.async_chat):
self.last_received_at = time.time() self.last_received_at = time.time()
self.last_message = {} self.last_message = {}
#Optional callback function # Optional callback function
if cb: if cb:
self.messageCallBack = cb self.messageCallBack = cb
else: else:
@ -87,35 +88,40 @@ class BLHIPClient(asynchat.async_chat):
return return
if len(telegram) > 4: if len(telegram) > 4:
state = telegram[4].replace('?','&') state = telegram[4].replace('?', '&')
state = state.split('&')[1:] state = state.split('&')[1:]
message = OrderedDict() message = OrderedDict()
message['Zone'] = telegram[0][2:].upper() message['Zone'] = telegram[0][2:].upper()
message['Room'] = telegram[1].upper() message['Room'] = telegram[1].upper()
message['Type'] = telegram[2].upper() message['Type'] = telegram[2].upper()
message['Device'] = telegram[3] message['Device'] = telegram[3]
message['State_Update'] = OrderedDict() message['State_Update'] = OrderedDict()
for s in state: for s in state:
if s.split('=')[0] == "nowPlayingDetails": if s.split('=')[0] == "nowPlayingDetails":
playDetails = s.split('=') play_details = s.split('=')
if len(playDetails[1]) >0: if len(play_details[1]) > 0:
playDetails = playDetails[1].split('; ') play_details = play_details[1].split('; ')
message['State_Update']["nowPlayingDetails"] = OrderedDict() message['State_Update']["nowPlayingDetails"] = OrderedDict()
for p in playDetails: for p in play_details:
if p.split(': ')[0] in ['track number','channel number']: if p.split(': ')[0] in ['track number', 'channel number']:
message['State_Update']["nowPlayingDetails"]['channel_track'] = p.split(': ')[1] message['State_Update']["nowPlayingDetails"]['channel_track'] = p.split(': ')[1]
else: else:
message['State_Update']["nowPlayingDetails"][p.split(': ')[0]] = p.split(': ')[1] message['State_Update']["nowPlayingDetails"][p.split(': ')[0]] = p.split(': ')[1]
elif s.split('=')[0] == "sourceUniqueId": elif s.split('=')[0] == "sourceUniqueId":
src = s.split('=')[1].split(':')[0].upper() src = s.split('=')[1].split(':')[0].upper()
message['State_Update']['source'] = self._srcdictsanitize(const._blgw_srcdict, src) message['State_Update']['source'] = self._srcdictsanitize(CONST.blgw_srcdict, src)
message['State_Update'][s.split('=')[0]] = s.split('=')[1] message['State_Update'][s.split('=')[0]] = s.split('=')[1]
else: else:
message['State_Update'][s.split('=')[0]] = s.split('=')[1] message['State_Update'][s.split('=')[0]] = s.split('=')[1]
# call function to find channel details if type = Legacy
if 'nowPlayingDetails' in message['State_Update'] \
and message['State_Update']['nowPlayingDetails']['type'] == 'Legacy':
self._get_channel_track(message)
if message.get('Type') == 'BUTTON': if message.get('Type') == 'BUTTON':
if message['State_Update'].get('STATE') == '0': if message['State_Update'].get('STATE') == '0':
message['State_Update']['Status'] = 'Off' message['State_Update']['Status'] = 'Off'
@ -131,7 +137,7 @@ class BLHIPClient(asynchat.async_chat):
self._report(header, state, message) self._report(header, state, message)
def _report(self, header, payload, message): def _report(self, header, payload, message):
#Report messages, excluding regular clock pings from gateway # Report messages, excluding regular clock pings from gateway
self.last_message = message self.last_message = message
if message.get('Device').upper() != 'CLOCK': if message.get('Device').upper() != 'CLOCK':
self.log.debug(self.name + "\n" + str(json.dumps(message, indent=4))) self.log.debug(self.name + "\n" + str(json.dumps(message, indent=4)))
@ -141,25 +147,24 @@ class BLHIPClient(asynchat.async_chat):
def client_connect(self): def client_connect(self):
self.log.info('Connecting to host at %s, port %i', self._host, self._port) self.log.info('Connecting to host at %s, port %i', self._host, self._port)
self.set_terminator(b'\r\n') self.set_terminator(b'\r\n')
#Create the socket # Create the socket
try: try:
socket.setdefaulttimeout(3)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, e: except socket.error, e:
self.log.info("Error creating socket: %s" % e) self.log.info("Error creating socket: %s" % e)
self.handle_close() self.handle_close()
#Now connect # Now connect
try: try:
self.connect((self._host, self._port)) self.connect((self._host, self._port))
except socket.gaierror, e: except socket.gaierror, e:
self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e)) self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
else: else:
self.is_connected = True self.is_connected = True
self.log.info("\tConnected to B&O Gateway") self.log.info("\tConnected to B&O Gateway")
@ -175,25 +180,25 @@ class BLHIPClient(asynchat.async_chat):
self.is_connected = False self.is_connected = False
self.close() self.close()
def send_cmd(self,telegram): def send_cmd(self, telegram):
try: try:
self.push(telegram.encode("ascii") + "\r\n") self.push(telegram.encode("ascii") + "\r\n")
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
else: else:
self.last_sent = telegram self.last_sent = telegram
self.last_sent_at = time.time() self.last_sent_at = time.time()
self.log.info(self.name + " >>-SENT--> : " + telegram) self.log.info(self.name + " >>-SENT--> : " + telegram)
time.sleep(0.2) time.sleep(0.2)
def query(self, zone='*',room='*',dev_type='*',device='*'): def query(self, zone='*', room='*', dev_type='*', device='*'):
query = "q " + zone + "/" + room + "/" + dev_type + '/' + device query = "q " + zone + "/" + room + "/" + dev_type + '/' + device
#Convert to human readable string # Convert to human readable string
if zone == '*': if zone == '*':
zone = ' in all zones.' zone = ' in all zones.'
else: else:
@ -214,7 +219,7 @@ class BLHIPClient(asynchat.async_chat):
self.log.info(self.name + ": sending state update request for" + device + dev_type + room + zone) self.log.info(self.name + ": sending state update request for" + device + dev_type + room + zone)
self.send_cmd(query) self.send_cmd(query)
def statefiler(self, zone='*',room='*',dev_type='*',device='*'): def statefiler(self, zone='*', room='*', dev_type='*', device='*'):
s_filter = "f " + zone + "/" + room + "/" + dev_type + '/' + device s_filter = "f " + zone + "/" + room + "/" + dev_type + '/' + device
self.send_cmd(s_filter) self.send_cmd(s_filter)
@ -226,9 +231,22 @@ class BLHIPClient(asynchat.async_chat):
def ping(self): def ping(self):
self.query('Main', 'global', 'SYSTEM', 'BeoLink') self.query('Main', 'global', 'SYSTEM', 'BeoLink')
def _srcdictsanitize(self, d, s): # Utility Functions
@staticmethod
def _srcdictsanitize(d, s):
result = d.get(s) result = d.get(s)
if result == None: if result is None:
result = s result = s
return str(result) return str(result)
@staticmethod
def _get_channel_track(message):
# Check device list for channel name information
if CONST.devices:
for device in CONST.devices:
if device['Device'] == message['Device']:
if 'channels' in device['Sources'][message["State_Update"]["source"]]:
for channel in device['Sources'][message["State_Update"]["source"]]['channels']:
if channel['number'] == int(message["State_Update"]['nowPlayingDetails']["channel_track"]):
message["State_Update"]["nowPlaying"] = channel['name']
break

View file

@ -1,13 +1,13 @@
# Constants for B&O telegram protocols # Constants for B&O telegram protocols
# ######################################################################################## # ########################################################################################
### Config data (set on initialisation) # Config data (set on initialisation)
gateway = dict() gateway = dict()
rooms = [] rooms = []
devices = [] devices = []
available_sources = [] available_sources = []
# ######################################################################################## # ########################################################################################
### Beo4 Commands # Beo4 Commands
beo4_commanddict = dict( beo4_commanddict = dict(
[ [
# Source selection: # Source selection:
@ -124,10 +124,10 @@ beo4_commanddict = dict(
) )
BEO4_CMDS = {v.upper(): k for k, v in beo4_commanddict.items()} BEO4_CMDS = {v.upper(): k for k, v in beo4_commanddict.items()}
### BeoRemote One Commands # BeoRemote One Commands
beoremoteone_commanddict = dict( beoremoteone_commanddict = dict(
[ [
#Source, (Cmd, Unit) # Source, (Cmd, Unit)
("TV", (0x80, 0)), ("TV", (0x80, 0)),
("RADIO", (0x81, 0)), ("RADIO", (0x81, 0)),
("TUNEIN", (0x81, 1)), ("TUNEIN", (0x81, 1)),
@ -189,13 +189,13 @@ beoremoteone_commanddict = dict(
("PERSONAL_8", (0xD1, 7)), ("PERSONAL_8", (0xD1, 7)),
("TV.ON", (0xD2, 0)), ("TV.ON", (0xD2, 0)),
("MUSIC.ON", (0xD3, 0)), ("MUSIC.ON", (0xD3, 0)),
("PATTERNPLAY",(0xD3, 1)), ("PATTERNPLAY", (0xD3, 1)),
] ]
) )
# ######################################################################################## # ########################################################################################
# Source Activity # Source Activity
_sourceactivitydict = dict( sourceactivitydict = dict(
[ [
(0x00, "Unknown"), (0x00, "Unknown"),
(0x01, "Stop"), (0x01, "Stop"),
@ -214,7 +214,7 @@ _sourceactivitydict = dict(
# ######################################################################################## # ########################################################################################
# ##### MasterLink (not MLGW) Protocol packet constants # ##### MasterLink (not MLGW) Protocol packet constants
_ml_telegram_type_dict = dict( ml_telegram_type_dict = dict(
[ [
(0x0A, "COMMAND"), (0x0A, "COMMAND"),
(0x0B, "REQUEST"), (0x0B, "REQUEST"),
@ -224,7 +224,7 @@ _ml_telegram_type_dict = dict(
] ]
) )
_ml_command_type_dict = dict( ml_command_type_dict = dict(
[ [
(0x04, "MASTER_PRESENT"), (0x04, "MASTER_PRESENT"),
# REQUEST_DISTRIBUTED_SOURCE: seen when a device asks what source is being distributed # REQUEST_DISTRIBUTED_SOURCE: seen when a device asks what source is being distributed
@ -242,7 +242,7 @@ _ml_command_type_dict = dict(
(0x3C, "TIMER"), (0x3C, "TIMER"),
(0x40, "CLOCK"), (0x40, "CLOCK"),
(0x44, "TRACK_INFO"), (0x44, "TRACK_INFO"),
# LOCK_MANAGER_COMMAND: Lock to Determine what device issues source commands # LOCKmANAGER_COMMAND: Lock to Determine what device issues source commands
# reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0 # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
(0x45, "GOTO_SOURCE"), (0x45, "GOTO_SOURCE"),
(0x5C, "LOCK_MANAGER_COMMAND"), (0x5C, "LOCK_MANAGER_COMMAND"),
@ -280,13 +280,13 @@ _ml_command_type_dict = dict(
######################################################### #########################################################
# On power up all devices send out a request key telegram. If # 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 # 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_LOCK_MANAGER telegram and assumes responsibility # Master in older implementations) then asserts a NEW_LOCKmANAGER telegram and assumes responsibility
# for LOCK_MANAGER_COMMAND telegrams until a key transfer occurs. # for LOCKmANAGER_COMMAND telegrams until a key transfer occurs.
(0x12, "KEY_LOST"), #? (0x12, "KEY_LOST"), # ?
# Unknown command with payload of length 1. # Unknown command with payload of length 1.
# bit 0: unknown # bit 0: unknown
# bit 1: unknown # bit 1: unknown
(0xA0, "NEW_LOCK_MANAGER"), #? (0xA0, "NEW_LOCKMANAGER"), # ?
# Unknown command with payload of length 2 # Unknown command with payload of length 2
# bit 0: unknown # bit 0: unknown
# bit 1: unknown # bit 1: unknown
@ -294,7 +294,7 @@ _ml_command_type_dict = dict(
] ]
) )
_ml_command_type_request_key_subtype_dict = dict( ml_command_type_request_key_subtype_dict = dict(
[ [
(0x01, "Request Key"), (0x01, "Request Key"),
(0x02, "Transfer Key"), (0x02, "Transfer Key"),
@ -305,7 +305,7 @@ _ml_command_type_request_key_subtype_dict = dict(
] ]
) )
_ml_activity_dict = dict( ml_activity_dict = dict(
[ [
(0x01, "Request Source"), (0x01, "Request Source"),
(0x02, "Request Source"), (0x02, "Request Source"),
@ -314,24 +314,24 @@ _ml_activity_dict = dict(
] ]
) )
_ml_device_dict = dict( ml_device_dict = dict(
[ [
(0xC0, "VIDEO_MASTER"), (0xC0, "VIDEO MASTER"),
(0xC1, "AUDIO_MASTER"), (0xC1, "AUDIO MASTER"),
(0xC2, "SOURCE_CENTER"), (0xC2, "SOURCE CENTER/SLAVE DEVICE"),
(0x81, "ALL_AUDIO_LINK_DEVICES"), (0x81, "ALL AUDIO LINK DEVICES"),
(0x82, "ALL_VIDEO_LINK_DEVICES"), (0x82, "ALL VIDEO LINK DEVICES"),
(0x83, "ALL_LINK_DEVICES"), (0x83, "ALL LINK DEVICES"),
(0x80, "ALL"), (0x80, "ALL"),
(0xF0, "MLGW"), (0xF0, "MLGW"),
# Power Master exists in older (pre 1996?) ML implementations. Later revisions enforced the Video Master # 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 # 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. # you may see this device type on the network.
(0xFF, "POWER_MASTER"), #? (0xFF, "POWER MASTER"), # ?
] ]
) )
_ml_pictureformatdict = dict( ml_pictureformatdict = dict(
[ [
(0x00, "Not known"), (0x00, "Not known"),
(0x01, "Known by decoder"), (0x01, "Known by decoder"),
@ -344,7 +344,7 @@ _ml_pictureformatdict = dict(
] ]
) )
_ml_selectedsourcedict = dict( ml_selectedsourcedict = dict(
[ [
(0x00, "NONE"), (0x00, "NONE"),
(0x0B, "TV"), (0x0B, "TV"),
@ -367,9 +367,9 @@ _ml_selectedsourcedict = dict(
] ]
) )
_ml_trackinfo_subtype_dict = dict([(0x05, "Current Source"),(0x07, "Change Source"),]) ml_trackinfo_subtype_dict = dict([(0x05, "Current Source"), (0x07, "Change Source"), ])
_ml_selectedsource_type_dict = dict( ml_selectedsource_type_dict = dict(
[ [
("VIDEO", (0x0B, 0x1F)), ("VIDEO", (0x0B, 0x1F)),
("VIDEO_PAUSABLE", (0x15, 0x16, 0x29, 0x33)), ("VIDEO_PAUSABLE", (0x15, 0x16, 0x29, 0x33)),
@ -382,7 +382,7 @@ _ml_selectedsource_type_dict = dict(
# ######################################################################################## # ########################################################################################
# ##### MLGW Protocol packet constants # ##### MLGW Protocol packet constants
_mlgw_payloadtypedict = dict( mlgw_payloadtypedict = dict(
[ [
(0x01, "Beo4 Command"), (0x01, "Beo4 Command"),
(0x02, "Source Status"), (0x02, "Source Status"),
@ -405,9 +405,9 @@ _mlgw_payloadtypedict = dict(
(0x40, "Location based event"), (0x40, "Location based event"),
] ]
) )
MLGW_PL = {v.upper(): k for k, v in _mlgw_payloadtypedict.items()} MLGW_PL = {v.upper(): k for k, v in mlgw_payloadtypedict.items()}
_destselectordict = dict( destselectordict = dict(
[ [
(0x00, "Video Source"), (0x00, "Video Source"),
(0x01, "Audio Source"), (0x01, "Audio Source"),
@ -416,41 +416,41 @@ _destselectordict = dict(
(0x1B, "MLGW"), (0x1B, "MLGW"),
] ]
) )
CMDS_DEST = {v.upper(): k for k, v in _destselectordict.items()} 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_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_linkdict = dict([(0x00, "Local/Default Source"), (0x01, "Remote Source/Option 4 Product")])
_mlgw_virtualactiondict = dict([(0x01, "PRESS"), (0x02, "HOLD"), (0x03, "RELEASE")]) mlgw_virtualactiondict = dict([(0x01, "PRESS"), (0x02, "HOLD"), (0x03, "RELEASE")])
### for '0x03: Picture and Sound Status' # for '0x03: Picture and Sound Status'
_mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")]) mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")])
_mlgw_speakermodedict = dict( mlgw_speakermodedict = dict(
[ [
(0x01, "Center channel"), (0x01, "Center channel"),
(0x02, "2ch stereo"), (0x02, "2ch stereo"),
(0x03, "Front surround"), (0x03, "Front surround"),
(0x04, "4ch stereo"), (0x04, "4ch stereo"),
(0x05, "Full surround"), (0x05, "Full surround"),
(0xFD, "<all>"), # Dummy for 'Listen for all modes' (0xFD, "<all>"), # Dummy for 'Listen for all modes'
] ]
) )
_mlgw_screenmutedict = dict([(0x00, "not muted"), (0x01, "muted")]) mlgw_screenmutedict = dict([(0x00, "not muted"), (0x01, "muted")])
_mlgw_screenactivedict = dict([(0x00, "not active"), (0x01, "active")]) mlgw_screenactivedict = dict([(0x00, "not active"), (0x01, "active")])
_mlgw_cinemamodedict = dict([(0x00, "Cinemamode=off"), (0x01, "Cinemamode=on")]) mlgw_cinemamodedict = dict([(0x00, "Cinema mode off"), (0x01, "Cinema mode on")])
_mlgw_stereoindicatordict = dict([(0x00, "Mono"), (0x01, "Stereo")]) mlgw_stereoindicatordict = dict([(0x00, "Mono"), (0x01, "Stereo")])
### for '0x04: Light and Control command' # for '0x04: Light and Control command'
_mlgw_lctypedict = dict([(0x01, "LIGHT"), (0x02, "CONTROL")]) mlgw_lctypedict = dict([(0x01, "LIGHT"), (0x02, "CONTROL")])
### for '0x31: Login Status # for '0x31: Login Status
_mlgw_loginstatusdict = dict([(0x00, "OK"), (0x01, "FAIL")]) mlgw_loginstatusdict = dict([(0x00, "OK"), (0x01, "FAIL")])
# ######################################################################################## # ########################################################################################
# ##### BeoLink Gateway Protocol packet constants # ##### BeoLink Gateway Protocol packet constants
_blgw_srcdict = dict( blgw_srcdict = dict(
[ [
("TV", "TV"), ("TV", "TV"),
("DVD", "DVD"), ("DVD", "DVD"),

View file

@ -5,7 +5,8 @@ import time
import json import json
from collections import OrderedDict from collections import OrderedDict
import Resources.CONSTANTS as const import Resources.CONSTANTS as CONST
class MLCLIClient(asynchat.async_chat): class MLCLIClient(asynchat.async_chat):
"""Client to monitor raw packet traffic on the Masterlink network via the undocumented command line interface """Client to monitor raw packet traffic on the Masterlink network via the undocumented command line interface
@ -57,7 +58,7 @@ class MLCLIClient(asynchat.async_chat):
telegram = self._received_data telegram = self._received_data
self._received_data = "" self._received_data = ""
#clear login process lines before processing telegrams # Clear login process lines before processing telegrams
if self._i <= self._header_lines: if self._i <= self._header_lines:
self._i += 1 self._i += 1
if self._i == self._header_lines - 1: if self._i == self._header_lines - 1:
@ -65,32 +66,43 @@ class MLCLIClient(asynchat.async_chat):
if telegram[0:4] != "MLGW": if telegram[0:4] != "MLGW":
self.isBLGW = True self.isBLGW = True
#Process telegrams and return json data in human readable format # Process telegrams and return json data in human readable format
if self._i > self._header_lines: if self._i > self._header_lines:
items = telegram.split()[1:] if "---- Logging" in telegram:
if len(items): # Pong telegram
telegram=bytearray() header = telegram
for item in items: payload = []
try: message = OrderedDict([('payload_type', 'Pong'), ('CONNECTION', 'Online')])
telegram.append(int(item[:-1],base=16)) self.is_connected = True
except: if self.messageCallBack:
#abort if invalid character found self.messageCallBack(self.name, header, str(list(payload)), message)
break 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:
# abort if invalid character found
self.log.debug('Invalid character ' + str(item) + ' found in telegram: ' +
''.join(items) + '\nAborting!')
break
#Decode any telegram with a valid 9 byte header, excluding typy 0x14 (regular clock sync pings) # Decode any telegram with a valid 9 byte header, excluding typy 0x14 (regular clock sync pings)
if len(telegram) >= 9 and telegram[7] != 0x14: if len(telegram) >= 9 and telegram[7] != 0x14:
#Header: To_Device/From_Device/1/Type/To_Source/From_Source/0/Payload_Type/Length # Header: To_Device/From_Device/1/Type/To_Source/From_Source/0/Payload_Type/Length
header = telegram[:9] header = telegram[:9]
payload = telegram[9:] payload = telegram[9:]
message = self._decode(telegram) message = self._decode(telegram)
self._report(header, payload, message) self._report(header, payload, message)
def client_connect(self): def client_connect(self):
self.log.info('Connecting to host at %s, port %i', self._host, self._port) self.log.info('Connecting to host at %s, port %i', self._host, self._port)
self.set_terminator(b'\r\n') self.set_terminator(b'\r\n')
# Create the socket # Create the socket
try: try:
socket.setdefaulttimeout(3)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, e: except socket.error, e:
self.log.info("Error creating socket: %s" % e) self.log.info("Error creating socket: %s" % e)
@ -101,12 +113,12 @@ class MLCLIClient(asynchat.async_chat):
except socket.gaierror, e: except socket.gaierror, e:
self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e)) self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
else: else:
self.is_connected = True self.is_connected = True
self.log.info("\tConnected to B&O Gateway") self.log.info("\tConnected to B&O Gateway")
@ -121,15 +133,15 @@ class MLCLIClient(asynchat.async_chat):
self.is_connected = False self.is_connected = False
self.close() self.close()
def send_cmd(self,telegram): def send_cmd(self, telegram):
try: try:
self.push(telegram + "\r\n") self.push(telegram + "\r\n")
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
else: else:
self.last_sent = telegram self.last_sent = telegram
self.last_sent_at = time.time() self.last_sent_at = time.time()
@ -149,7 +161,8 @@ class MLCLIClient(asynchat.async_chat):
# ######################################################################################## # ########################################################################################
# ##### Utility functions # ##### Utility functions
def _hexbyte(self, byte): @staticmethod
def _hexbyte(byte):
resultstr = hex(byte) resultstr = hex(byte)
if byte < 16: if byte < 16:
resultstr = resultstr[:2] + "0" + resultstr[2] resultstr = resultstr[:2] + "0" + resultstr[2]
@ -162,12 +175,13 @@ class MLCLIClient(asynchat.async_chat):
def _dictsanitize(self, d, s): def _dictsanitize(self, d, s):
result = d.get(s) result = d.get(s)
if result == None: if result is None:
result = self._hexbyte(s) result = self._hexbyte(s)
self.log.debug("UNKNOWN (type=" + result + ")") self.log.debug("UNKNOWN (type=" + result + ")")
return str(result) return str(result)
def _get_type(self, d, s): @staticmethod
def _get_type(d, s):
rev_dict = {value: key for key, value in d.items()} rev_dict = {value: key for key, value in d.items()}
for i in range(len(list(rev_dict))): for i in range(len(list(rev_dict))):
if s in list(rev_dict)[i]: if s in list(rev_dict)[i]:
@ -175,127 +189,195 @@ class MLCLIClient(asynchat.async_chat):
# ######################################################################################## # ########################################################################################
# ##### Decode Masterlink Protocol packet to a serializable dict # ##### Decode Masterlink Protocol packet to a serializable dict
def _decode(self, telegram): def _decode(self, telegram):
# Decode header # Decode header
message = OrderedDict() message = OrderedDict()
if const.devices: if CONST.devices:
for device in const.devices: for device in CONST.devices:
if device['ML_ID'] == telegram[1]: if device['ML_ID'] == telegram[1]:
message["Zone"] = device["Zone"].upper() message["Zone"] = device["Zone"].upper()
message["Room"] = device["Room"].uppr() message["Room"] = device["Room"].uppr()
message["Type"] = "AV RENDERER" message["Type"] = "AV RENDERER"
message["Device"] = device["Device"] message["Device"] = device["Device"]
message["from_device"] = self._dictsanitize(const._ml_device_dict, telegram[1]) break
message["from_source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[5]) message["from_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[1])
message["to_device"] = self._dictsanitize(const._ml_device_dict, telegram[0]) message["from_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[5])
message["to_source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[4]) message["to_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[0])
message["type"] = self._dictsanitize(const._ml_telegram_type_dict, telegram[3]) message["to_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[4])
message["payload_type"] = self._dictsanitize(const._ml_command_type_dict, telegram[7]) 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["payload_len"] = telegram[8] + 1
message["payload"] = OrderedDict() message["State_Update"] = OrderedDict()
# source status info # source status info
# TTFF__TYDSOS__PTLLPS SR____LS______SLSHTR__ACSTPI________________________TRTR______ # TTFF__TYDSOS__PTLLPS SR____LS______SLSHTR__ACSTPI________________________TRTR______
if message.get("payload_type") == "STATUS_INFO": if message.get("payload_type") == "STATUS_INFO":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[10]) message["State_Update"]["nowPlaying"] = ''
message["payload"]["sourceID"] = telegram[10] message["State_Update"]["nowPlayingDetails"] = OrderedDict()
message["payload"]["source_type"] = telegram[22] message["State_Update"]["nowPlayingDetails"]["local_source"] = telegram[13]
message["payload"]["local_source"] = telegram[13] message["State_Update"]["nowPlayingDetails"]["type"] = telegram[22]
message["payload"]["source_medium"] = self._hexword(telegram[18], telegram[17]) if telegram[8] < 27:
message["payload"]["channel_track"] = ( message["State_Update"]["nowPlayingDetails"]["channel_track"] = telegram[19]
telegram[19] if telegram[8] < 27 else (telegram[36] * 256 + telegram[37]) else:
) message["State_Update"]["nowPlayingDetails"]["channel_track"] = telegram[36] * 256 + telegram[37]
message["payload"]["picture_identifier"] = self._dictsanitize(const._ml_pictureformatdict, telegram[23]) message["State_Update"]["nowPlayingDetails"]["source_medium_position"] = \
message["payload"]["state"] = self._dictsanitize(const._sourceactivitydict, telegram[21]) self._hexword(telegram[18], telegram[17])
message["State_Update"]["nowPlayingDetails"]["picture_identifier"] = \
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(telegram, message)
message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[21])
# display source information # display source information
if message.get("payload_type") == "DISPLAY_SOURCE": if message.get("payload_type") == "DISPLAY_SOURCE":
_s = "" _s = ""
for i in range(0, telegram[8] - 5): for i in range(0, telegram[8] - 5):
_s = _s + chr(telegram[i + 15]) _s = _s + chr(telegram[i + 15])
message["payload"]["display_source"] = _s.rstrip() message["State_Update"]["display_source"] = _s.rstrip()
# extended source information # extended source information
if message.get("payload_type") == "EXTENDED_SOURCE_INFORMATION": if message.get("payload_type") == "EXTENDED_SOURCE_INFORMATION":
message["payload"]["info_type"] = telegram[10] message["State_Update"]["info_type"] = telegram[10]
_s = "" _s = ""
for i in range(0, telegram[8] - 14): for i in range(0, telegram[8] - 14):
_s = _s + chr(telegram[i + 24]) _s = _s + chr(telegram[i + 24])
message["payload"]["info_value"] = _s message["State_Update"]["info_value"] = _s
# beo4 command # beo4 command
if message.get("payload_type") == "BEO4_KEY": if message.get("payload_type") == "BEO4_KEY":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[10]) source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[10])
message["payload"]["sourceID"] = telegram[10] self._get_source_name(source, message)
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[10]) message["State_Update"]["source"] = source
message["payload"]["command"] = self._dictsanitize(const.beo4_commanddict, telegram[11]) message["State_Update"]["sourceID"] = telegram[10]
message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[10])
message["State_Update"]["command"] = self._dictsanitize(CONST.beo4_commanddict, telegram[11])
# audio track info long # audio track info long
if message.get("payload_type") == "TRACK_INFO_LONG": if message.get("payload_type") == "TRACK_INFO_LONG":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[11]) message["State_Update"]["nowPlaying"] = ''
message["payload"]["sourceID"] = telegram[11] message["State_Update"]["nowPlayingDetails"] = OrderedDict()
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[11]) message["State_Update"]["nowPlayingDetails"]["type"] = \
message["payload"]["channel_track"] = telegram[12] self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
message["payload"]["state"] = self._dictsanitize(const._sourceactivitydict, telegram[13]) message["State_Update"]["nowPlayingDetails"]["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(telegram, message)
message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[13])
# video track info # video track info
if message.get("payload_type") == "VIDEO_TRACK_INFO": if message.get("payload_type") == "VIDEO_TRACK_INFO":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[13]) message["State_Update"]["nowPlaying"] = ''
message["payload"]["sourceID"] = telegram[13] message["State_Update"]["nowPlayingDetails"] = OrderedDict()
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[13]) message["State_Update"]["nowPlayingDetails"]["source_type"] = \
message["payload"]["channel_track"] = telegram[11] * 256 + telegram[12] self._get_type(CONST.ml_selectedsource_type_dict, telegram[13])
message["payload"]["state"] = self._dictsanitize(const._sourceactivitydict, telegram[14]) message["State_Update"]["nowPlayingDetails"]["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(telegram, message)
message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[14])
# track change info # track change info
if message.get("payload_type") == "TRACK_INFO": if message.get("payload_type") == "TRACK_INFO":
message["payload"]["subtype"] = self._dictsanitize(const._ml_trackinfo_subtype_dict, telegram[9]) message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_trackinfo_subtype_dict, telegram[9])
if message["payload"].get("subtype") == "Change Source": if message["State_Update"].get("subtype") == "Change Source":
message["payload"]["prev_source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[11]) message["State_Update"]["prev_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
message["payload"]["prev_sourceID"] = telegram[11] message["State_Update"]["prev_sourceID"] = telegram[11]
message["payload"]["prev_source_type"] = self._get_type( message["State_Update"]["prev_source_type"] = self._get_type(
const._ml_selectedsource_type_dict, telegram[11]) CONST.ml_selectedsource_type_dict, telegram[11])
if len(telegram) > 18: if len(telegram) > 18:
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[22]) source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[22])
message["payload"]["sourceID"] = telegram[22] self._get_source_name(source, message)
if message["payload"].get("subtype") == "Current Source": message["State_Update"]["source"] = source
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[11]) message["State_Update"]["sourceID"] = telegram[22]
message["payload"]["sourceID"] = telegram[11]
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[11]) 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])
else: else:
message["payload"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9]) message["State_Update"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9])
# goto source # goto source
if message.get("payload_type") == "GOTO_SOURCE": if message.get("payload_type") == "GOTO_SOURCE":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[11]) message["State_Update"]["nowPlaying"] = ''
message["payload"]["sourceID"] = telegram[11] message["State_Update"]["nowPlayingDetails"] = OrderedDict()
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[11]) message["State_Update"]["nowPlayingDetails"]["source_type"] = \
message["payload"]["channel_track"] = telegram[12] self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
message["State_Update"]["nowPlayingDetails"]["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(telegram, message)
# remote request # remote request
if message.get("payload_type") == "MLGW_REMOTE_BEO4": if message.get("payload_type") == "MLGW_REMOTE_BEO4":
message["payload"]["command"] = self._dictsanitize(const.beo4_commanddict, telegram[14]) message["State_Update"]["command"] = self._dictsanitize(CONST.beo4_commanddict, telegram[14])
message["payload"]["destination"] = self._dictsanitize(const._destselectordict, telegram[11]) message["State_Update"]["destination"] = self._dictsanitize(CONST.destselectordict, telegram[11])
# request_key # request_key
if message.get("payload_type") == "LOCK_MANAGER_COMMAND": if message.get("payload_type") == "LOCK_MANAGER_COMMAND":
message["payload"]["subtype"] = self._dictsanitize( message["State_Update"]["subtype"] = self._dictsanitize(
const._ml_command_type_request_key_subtype_dict, telegram[9]) CONST.ml_command_type_request_key_subtype_dict, telegram[9])
# request distributed audio source # request distributed audio source
if message.get("payload_type") == "REQUEST_DISTRIBUTED_SOURCE": if message.get("payload_type") == "REQUEST_DISTRIBUTED_SOURCE":
message["payload"]["subtype"] = self._dictsanitize(const._ml_activity_dict, telegram[9]) message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_activity_dict, telegram[9])
if message["payload"].get('subtype') == "Source Active": if message["State_Update"].get('subtype') == "Source Active":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[13]) source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[13])
message["payload"]["sourceID"] = telegram[13] self._get_source_name(source, message)
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[13]) 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 # request local audio source
if message.get("payload_type") == "REQUEST_LOCAL_SOURCE": if message.get("payload_type") == "REQUEST_LOCAL_SOURCE":
message["payload"]["subtype"] = self._dictsanitize(const._ml_activity_dict, telegram[9]) message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_activity_dict, telegram[9])
if message["payload"].get('subtype') == "Source Active": if message["State_Update"].get('subtype') == "Source Active":
message["payload"]["source"] = self._dictsanitize(const._ml_selectedsourcedict, telegram[11]) source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
message["payload"]["sourceID"] = telegram[11] self._get_source_name(source, message)
message["payload"]["source_type"] = self._get_type(const._ml_selectedsource_type_dict, telegram[11]) 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])
return message return message
@staticmethod
def _get_channel_track(telegram, message):
# Check device list for channel name information
if CONST.devices:
for device in CONST.devices:
# Loop over devices to find source list for this specific device
if device['ML_ID'] == telegram[1]:
if 'channels' in device['Sources'][message["State_Update"]["source"]]:
for channel in device['Sources'][message["State_Update"]["source"]]['channels']:
if channel['number'] == message["State_Update"]["nowPlayingDetails"]["channel_track"]:
message["State_Update"]["nowPlaying"] = channel['name']
break
# If device is a NetLink device and has no ML_ID, seek a generic solution from the first device that has
# this source available: This could give an incorrect response if a link room device has a different
# favorites list to the Audio Master!
for device in CONST.devices:
if message["State_Update"]["source"] in device['Sources']:
if 'channels' in device['Sources'][message["State_Update"]["source"]]:
for channel in device['Sources'][message["State_Update"]["source"]]['channels']:
if channel['number'] == message["State_Update"]["nowPlayingDetails"]["channel_track"]:
message["State_Update"]["nowPlaying"] = channel['name']
break
@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]
break

View file

@ -4,34 +4,35 @@ import asyncore
import json import json
from collections import OrderedDict from collections import OrderedDict
import Resources.CONSTANTS as const import Resources.CONSTANTS as CONST
class MLConfig: class MLConfig:
def __init__(self, host_address='blgw.local', user='admin', pwd='admin'): def __init__(self, host_address='blgw.local', user='admin', pwd='admin'):
self.log = logging.getLogger('Config:') self.log = logging.getLogger('Config')
self.log.setLevel('INFO') self.log.setLevel('INFO')
self._host = host_address self._host = host_address
self._user = user self._user = user
self._pwd = pwd self._pwd = pwd
self._downloadData() self._download_data()
def _downloadData(self): def _download_data(self):
self.log.info('Downloading configuration data from Gateway...') self.log.info('Downloading configuration data from Gateway...')
url = 'http://' + self._host + '/mlgwpservices.json' url = 'http://' + self._host + '/mlgwpservices.json'
auth = (self._user, self._pwd) auth = (self._user, self._pwd)
response = requests.get(url, auth=auth) response = requests.get(url, auth=auth)
configurationdata = json.loads(response.text) configurationdata = json.loads(response.text)
self.configureMLGW((configurationdata)) self.configure_mlgw(configurationdata)
def configureMLGW(self, data): def configure_mlgw(self, data):
self.log.info('Processing Gateway configuration data...\n') self.log.info('Processing Gateway configuration data...\n')
const.gateway['Serial_Number'] = data['sn'] CONST.gateway['Serial_Number'] = data['sn']
const.gateway['Project'] = data['project'] CONST.gateway['Project'] = data['project']
const.gateway['Installer'] = str(data['installer']['name']) CONST.gateway['Installer'] = str(data['installer']['name'])
const.gateway['Contact'] = str(data['installer']['contact']) CONST.gateway['Contact'] = str(data['installer']['contact'])
for zone in data["zones"]: for zone in data["zones"]:
if int(zone['number']) == 240: if int(zone['number']) == 240:
@ -58,72 +59,73 @@ class MLConfig:
device['Sources'][str(source["name"])] = OrderedDict() device['Sources'][str(source["name"])] = OrderedDict()
for selectCmd in source["selectCmds"]: for selectCmd in source["selectCmds"]:
source_id = str(source['sourceId'].split(':')[0]) source_id = str(source['sourceId'].split(':')[0])
source_id = self._srcdictsanitize(const._blgw_srcdict, source_id).upper() source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
device['Sources'][str(source["name"])]['source'] = source_id device['Sources'][str(source["name"])]['source'] = source_id
device['Sources'][str(source["name"])]['uniqueID'] = str(source['sourceId']) device['Sources'][str(source["name"])]['uniqueID'] = str(source['sourceId'])
source_tuple = (str(source["name"]), source_id) source_tuple = (str(source["name"]), source_id)
cmd_tuple = (source_id,(int(selectCmd["cmd"]),int(selectCmd["unit"]))) cmd_tuple = (source_id, (int(selectCmd["cmd"]), int(selectCmd["unit"])))
device['Sources'][str(source["name"])]['BR1_cmd'] = cmd_tuple device['Sources'][str(source["name"])]['BR1_cmd'] = cmd_tuple
if source.has_key('channels'): if 'channels' in source:
device['Sources'][str(source["name"])][ device['Sources'][str(source["name"])]['channels'] = []
'channel_track'] = OrderedDict()
for channel in source['channels']: for channel in source['channels']:
c = OrderedDict()
c_num = '' c_num = ''
num = channel['selectSEQ'][::2] num = channel['selectSEQ'][::2]
for n in num: for n in num:
c_num += str(n) c_num += str(n)
device['Sources'][str(source["name"])]['channel_track'][c_num] = OrderedDict() c['number'] = int(c_num)
device['Sources'][str(source["name"])]['channel_track'][c_num]['name'] = channel['name'] c['name'] = channel['name']
device['Sources'][str(source["name"])]['channel_track'][c_num]['icon'] = channel['icon'] c['icon'] = channel['icon']
device['Sources'][str(source["name"])]['channels'].append(c)
if source_tuple not in const.available_sources: if source_tuple not in CONST.available_sources:
const.available_sources.append(source_tuple) CONST.available_sources.append(source_tuple)
const.devices.append(device) CONST.devices.append(device)
const.rooms.append(room) CONST.rooms.append(room)
self.log.info('Found ' + str(len(const.devices)) + ' AV Renderers!') self.log.info('Found ' + str(len(CONST.devices)) + ' AV Renderers!')
for i in range(len(const.devices)): for i in range(len(CONST.devices)):
self.log.info('\tMLN ' + str(const.devices[i].get('MLN')) + ': ' + str(const.devices[i].get('Device'))) self.log.info('\tMLN ' + str(CONST.devices[i].get('MLN')) + ': ' + str(CONST.devices[i].get('Device')))
self.log.info('\tFound ' + str(len(const.available_sources)) + ' Available Sources [Name, Type]:') self.log.info('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Name, Type]:')
for i in range(len(const.available_sources)): for i in range(len(CONST.available_sources)):
self.log.info('\t\t' + str(list(const.available_sources[i]))) self.log.info('\t\t' + str(list(CONST.available_sources[i])))
self.log.info('Done!\n') self.log.info('Done!\n')
self.log.debug(json.dumps(const.gateway, indent=4)) self.log.debug(json.dumps(CONST.gateway, indent=4))
self.log.debug(json.dumps(const.rooms, indent=4)) self.log.debug(json.dumps(CONST.rooms, indent=4))
self.log.debug(json.dumps(const.devices, indent=4)) self.log.debug(json.dumps(CONST.devices, indent=4))
def get_masterlink_id(self, mlgw, mlcli): def get_masterlink_id(self, mlgw, mlcli):
self.log.info("Finding MasterLink ID of products:") self.log.info("Finding MasterLink ID of products:")
if mlgw.is_connected and mlcli.is_connected: if mlgw.is_connected and mlcli.is_connected:
for device in const.devices: for device in CONST.devices:
self.log.info("Finding MasterLink ID of product " + device.get('Device')) self.log.info("Finding MasterLink ID of product " + device.get('Device'))
# Ping the device with a light timeout to elicit a ML telegram containing its ML_ID # Ping the device with a light timeout to elicit a ML telegram containing its ML_ID
mlgw.send_beo4_cmd(int(device.get('MLN')), mlgw.send_beo4_cmd(int(device.get('MLN')),
const.CMDS_DEST.get("AUDIO SOURCE"), CONST.CMDS_DEST.get("AUDIO SOURCE"),
const.BEO4_CMDS.get("LIGHT TIMEOUT")) CONST.BEO4_CMDS.get("LIGHT TIMEOUT"))
if device.get('Serial_num') is None: if device.get('Serial_num') is None:
# If this is a MasterLink product it has no serial number... # If this is a MasterLink product it has no serial number...
# loop to until expected response received from ML Command Line Interface # loop to until expected response received from ML Command Line Interface
while not mlcli.last_message.has_key('to_device') and mlcli.last_message[ while 'to_device' not in mlcli.last_message and mlcli.last_message['from_device'] == \
'from_device'] == "MLGW" and mlcli.last_message[ "MLGW" and mlcli.last_message['payload_type'] == \
'payload_type'] == "MLGW_REMOTE_BEO4" and mlcli.last_message[ "MLGW_REMOTE_BEO4" and mlcli.last_message['payload']['command'] == "LIGHT TIMEOUT":
'payload']['command'] == "LIGHT TIMEOUT":
asyncore.loop(count=1, timeout=0.2) asyncore.loop(count=1, timeout=0.2)
device['ML_ID'] = mlcli.last_message.get('to_device') device['ML_ID'] = mlcli.last_message.get('to_device')
self.log.info("\tMasterLink ID of product " + self.log.info("\tMasterLink ID of product " +
device.get('Device') + " is " + device.get('ML_ID') + ".\n") device.get('Device') + " is " + device.get('ML_ID') + ".\n")
else: else:
# If this is a NetLink product then it has a serial number and no ML_ID # If this is a NetLink product then it has a serial number and no ML_ID
device['ML_ID'] = 'NA' device['ML_ID'] = 'NA'
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " + self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
device.get('Serial_num') + ". No MasterLink ID assigned.\n") device.get('Serial_num') + ". No MasterLink ID assigned.\n")
def _srcdictsanitize(self, d, s): @staticmethod
def _srcdictsanitize(d, s):
result = d.get(s) result = d.get(s)
if result == None: if result is None:
result = s result = s
return str(result) return str(result)

View file

@ -5,7 +5,8 @@ import time
import json import json
from collections import OrderedDict from collections import OrderedDict
import Resources.CONSTANTS as const import Resources.CONSTANTS as CONST
class MLGWClient(asynchat.async_chat): class MLGWClient(asynchat.async_chat):
"""Client to interact with a B&O Gateway via the MasterLink Gateway Protocol """Client to interact with a B&O Gateway via the MasterLink Gateway Protocol
@ -19,7 +20,7 @@ class MLGWClient(asynchat.async_chat):
self._host = host_address self._host = host_address
self._port = int(port) self._port = int(port)
self._user = user self._user = user
self._pwd = pwd self._pwd = pwd
self.name = name self.name = name
self.is_connected = False self.is_connected = False
@ -36,10 +37,11 @@ class MLGWClient(asynchat.async_chat):
else: else:
self.messageCallBack = None self.messageCallBack = None
#Expose dictionaries via API # Expose dictionaries via API
self.BEO4_CMDS = const.BEO4_CMDS self.BEO4_CMDS = CONST.BEO4_CMDS
self.CMDS_DEST = const.CMDS_DEST self.BEORMT1_CMDS = CONST.beoremoteone_commanddict
self.MLGW_PL = const.MLGW_PL self.CMDS_DEST = CONST.CMDS_DEST
self.MLGW_PL = CONST.MLGW_PL
# ######################################################################################## # ########################################################################################
# ##### Open Socket and connect to B&O Gateway # ##### Open Socket and connect to B&O Gateway
@ -52,10 +54,10 @@ class MLGWClient(asynchat.async_chat):
self.log.debug(data) self.log.debug(data)
self._received_data = bytearray(data) self._received_data = bytearray(data)
bit1 = int(self._received_data[0]) #Start of Header == 1 bit1 = int(self._received_data[0]) # Start of Header == 1
bit2 = int(self._received_data[1]) #Message Type bit2 = int(self._received_data[1]) # Message Type
bit3 = int(self._received_data[2]) #Payload length bit3 = int(self._received_data[2]) # Payload length
bit4 = int(self._received_data[3]) #Spare Bit/End of Header == 0 bit4 = int(self._received_data[3]) # Spare Bit/End of Header == 0
payload = bytearray() payload = bytearray()
for item in self._received_data[4:bit3 + 4]: for item in self._received_data[4:bit3 + 4]:
@ -77,8 +79,7 @@ class MLGWClient(asynchat.async_chat):
def _decode(self, msg_type, header, payload): def _decode(self, msg_type, header, payload):
message = OrderedDict() message = OrderedDict()
payload_type = self._dictsanitize(const._mlgw_payloadtypedict, msg_type) payload_type = self._dictsanitize(CONST.mlgw_payloadtypedict, msg_type)
message["Payload_type"] = payload_type
if payload_type == "MLGW virtual button event": if payload_type == "MLGW virtual button event":
virtual_btn = payload[0] virtual_btn = payload[0]
@ -87,6 +88,7 @@ class MLGWClient(asynchat.async_chat):
else: else:
virtual_action = self._getvirtualactionstr(payload[1]) virtual_action = self._getvirtualactionstr(payload[1])
message["payload_type"] = payload_type
message["button"] = virtual_btn message["button"] = virtual_btn
message["action"] = virtual_action message["action"] = virtual_action
@ -99,75 +101,89 @@ class MLGWClient(asynchat.async_chat):
else: else:
self.log.info("\tLogin successful to %s", self._host) self.log.info("\tLogin successful to %s", self._host)
self.is_connected = True self.is_connected = True
message["payload_type"] = payload_type
message['Connected'] = "True" message['Connected'] = "True"
self.get_serial() self.get_serial()
elif payload_type == "Pong": elif payload_type == "Pong":
self.is_connected = True self.is_connected = True
message['CONNECTION'] = 'Online' message["payload_type"] = payload_type
message['CONNECTION'] = 'Online'
elif payload_type == "Serial Number": elif payload_type == "Serial Number":
sn = '' sn = ''
for c in payload: for c in payload:
sn += chr(c) sn += chr(c)
message['Serial_Num'] = sn message["payload_type"] = payload_type
message['serial_Num'] = sn
elif payload_type == "Source Status": elif payload_type == "Source Status":
if const.rooms and const.devices: if CONST.rooms and CONST.devices:
for device in const.devices: for device in CONST.devices:
if device['MLN'] == payload[0]: if device['MLN'] == payload[0]:
name = device['Device'] name = device['Device']
for room in const.rooms: for room in CONST.rooms:
if name in room['Products']: if name in room['Products']:
message["Zone"] = room['Zone'].upper() message["Zone"] = room['Zone'].upper()
message["Room"] = room['Room_Name'].upper() message["Room"] = room['Room_Name'].upper()
message["Type"] = 'AV RENDERER' message["Type"] = 'AV RENDERER'
message["Device"] = name message["Device"] = name
message["payload_type"] = payload_type
message["MLN"] = payload[0] message["MLN"] = payload[0]
message["Source"] = self._getselectedsourcestr(payload[1]).upper() message["State_Update"] = OrderedDict()
message["Source_medium_position"] = self._hexword(payload[2], payload[3]) message["State_Update"]["nowPlaying"] = ''
message["Source_position"] = self._hexword(payload[4], payload[5]) message["State_Update"]["nowPlayingDetails"] = OrderedDict()
message["Picture_format"] = self._getdictstr(const.ml_pictureformatdict, payload[7]) message["State_Update"]["nowPlayingDetails"]["channel_track"] = \
message["State"] = self._getdictstr(const._sourceactivitydict, payload[6]) self._hexword(payload[4], payload[5])
message["State_Update"]["nowPlayingDetails"]["source_medium_position"] = \
self._hexword(payload[2], payload[3])
message["State_Update"]["nowPlayingDetails"]["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": elif payload_type == "Picture and Sound Status":
if const.rooms and const.devices: if CONST.rooms and CONST.devices:
for device in const.devices: for device in CONST.devices:
if device['MLN'] == payload[0]: if device['MLN'] == payload[0]:
name = device['Device'] name = device['Device']
for room in const.rooms: for room in CONST.rooms:
if name in room['Products']: if name in room['Products']:
message["Zone"] = room['Zone'].upper() message["Zone"] = room['Zone'].upper()
message["Room"] = room['Room_Name'].upper() message["Room"] = room['Room_Name'].upper()
message["Type"] = 'AV RENDERER' message["Type"] = 'AV RENDERER'
message["Device"] = name message["Device"] = name
message["payload_type"] = payload_type
message["MLN"] = payload[0] message["MLN"] = payload[0]
message["Sound_status"] = self._getdictstr(const.mlgw_soundstatusdict, payload[1]) message["State_Update"] = OrderedDict()
message["Speaker_mode"] = self._getdictstr(const._mlgw_speakermodedict, payload[2]) message["State_Update"]["sound_status"] = self._getdictstr(CONST.mlgw_soundstatusdict, payload[1])
message["Stereo_mode"] = self._getdictstr(const._mlgw_stereoindicatordict, payload[9]) message["State_Update"]["speakermode"] = self._getdictstr(CONST.mlgw_speakermodedict, payload[2])
message["Volume"] = int(payload[3]) message["State_Update"]["stereo_mode"] = self._getdictstr(CONST.mlgw_stereoindicatordict, payload[9])
message["Screen1_mute"] = self._getdictstr(const._mlgw_screenmutedict, payload[4]) message["State_Update"]["screen1_mute"] = self._getdictstr(CONST.mlgw_screenmutedict, payload[4])
message["Screen1_active"] = self._getdictstr(const._mlgw_screenactivedict, payload[5]) message["State_Update"]["screen1_active"] = self._getdictstr(CONST.mlgw_screenactivedict, payload[5])
message["Screen2_mute"] = self._getdictstr(const._mlgw_screenmutedict, payload[6]) message["State_Update"]["screen2_mute"] = self._getdictstr(CONST.mlgw_screenmutedict, payload[6])
message["Screen2_active"] = self._getdictstr(const._mlgw_screenactivedict, payload[7]) message["State_Update"]["screen2_active"] = self._getdictstr(CONST.mlgw_screenactivedict, payload[7])
message["Cinema_mode"] = self._getdictstr(const._mlgw_cinemamodedict, payload[8]) message["State_Update"]["cinema_mode"] = self._getdictstr(CONST.mlgw_cinemamodedict, payload[8])
message["volume"] = int(payload[3])
elif payload_type == "All standby notification": elif payload_type == "All standby notification":
message["Command"] = "All Standby" message["payload_type"] = payload_type
message["command"] = "All Standby"
elif payload_type == "Light and Control command": elif payload_type == "Light and Control command":
if const.rooms: if CONST.rooms:
for room in const.rooms: for room in CONST.rooms:
if room['Room_Number'] == payload[0]: if room['Room_Number'] == payload[0]:
message["Zone"] = room['Zone'].upper() message["Zone"] = room['Zone'].upper()
message["Room"] = room['Room_Name'].upper() message["Room"] = room['Room_Name'].upper()
message["Type"] = self._getdictstr(const._mlgw_lctypedict, payload[1]).upper() + " COMMAND" message["Type"] = self._getdictstr(CONST.mlgw_lctypedict, payload[1]).upper() + " COMMAND"
message["Device"] = 'Beo4/BeoRemote One' message["Device"] = 'Beo4/BeoRemote One'
message["Room number"] = str(payload[0]) message["payload_type"] = payload_type
message["Command"] = self._getbeo4commandstr(payload[2]) message["room_number"] = str(payload[0])
message["command"] = self._getbeo4commandstr(payload[2])
if message != '': if message != '':
self._report(header, payload, message) self._report(header, payload, message)
@ -177,7 +193,6 @@ class MLGWClient(asynchat.async_chat):
self.set_terminator(b'\r\n') self.set_terminator(b'\r\n')
# Create the socket # Create the socket
try: try:
socket.setdefaulttimeout(3)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, e: except socket.error, e:
self.log.info("Error creating socket: %s" % e) self.log.info("Error creating socket: %s" % e)
@ -188,18 +203,18 @@ class MLGWClient(asynchat.async_chat):
except socket.gaierror, e: except socket.gaierror, e:
self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e)) self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
else: else:
self.is_connected = True self.is_connected = True
self.log.info("\tConnected to B&O Gateway") self.log.info("\tConnected to B&O Gateway")
def handle_connect(self): def handle_connect(self):
login=[] login = []
for c in self._user: for c in self._user:
login.append(c) login.append(c)
login.append(0x00) login.append(0x00)
@ -207,7 +222,7 @@ class MLGWClient(asynchat.async_chat):
login.append(c) login.append(c)
self.log.info("\tAttempting to Authenticate...") self.log.info("\tAttempting to Authenticate...")
self._send_cmd(const.MLGW_PL.get("LOGIN REQUEST"), login) self._send_cmd(CONST.MLGW_PL.get("LOGIN REQUEST"), login)
def handle_close(self): def handle_close(self):
self.log.info(self.name + ": Closing socket") self.log.info(self.name + ": Closing socket")
@ -223,22 +238,22 @@ class MLGWClient(asynchat.async_chat):
# ######################################################################################## # ########################################################################################
# ##### mlgw send_cmder functions # ##### mlgw send_cmder functions
## send_cmd command to mlgw # send_cmd command to mlgw
def _send_cmd(self, msg_type, payload): def _send_cmd(self, msg_type, payload):
#construct header # Construct header
telegram = [1,msg_type,len(payload),0] telegram = [1, msg_type, len(payload), 0]
#append payload # append payload
for p in payload: for p in payload:
telegram.append(p) telegram.append(p)
try: try:
self.push(str(bytearray(telegram))) self.push(str(bytearray(telegram)))
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
else: else:
self.last_sent = str(bytearray(telegram)) self.last_sent = str(bytearray(telegram))
self.last_sent_at = time.time() self.last_sent_at = time.time()
@ -253,53 +268,52 @@ class MLGWClient(asynchat.async_chat):
time.sleep(0.2) time.sleep(0.2)
def ping(self): def ping(self):
self._send_cmd(const.MLGW_PL.get("PING"), "") self._send_cmd(CONST.MLGW_PL.get("PING"), "")
## Get serial number of mlgw # Get serial number of mlgw
def get_serial(self): def get_serial(self):
if self.is_connected: if self.is_connected:
# Request serial number # Request serial number
self._send_cmd(const.MLGW_PL.get("REQUEST SERIAL NUMBER"), "") self._send_cmd(CONST.MLGW_PL.get("REQUEST SERIAL NUMBER"), "")
## send_cmd Beo4 command to mlgw # send_cmd Beo4 command to mlgw
def send_beo4_cmd(self, mln, dest, cmd, sec_source=0x00, link=0x00): def send_beo4_cmd(self, mln, dest, cmd, sec_source=0x00, link=0x00):
payload = [] payload = [
payload.append(mln) # byte[0] MLN mln, # byte[0] MLN
payload.append(dest) # byte[1] Dest-Sel (0x00, 0x01, 0x05, 0x0f) dest, # byte[1] Dest-Sel (0x00, 0x01, 0x05, 0x0f)
payload.append(cmd) # byte[2] Beo4 Command cmd, # byte[2] Beo4 Command
payload.append(sec_source) # byte[3] Sec-Source sec_source, # byte[3] Sec-Source
payload.append(link) # byte[4] Link link] # byte[4] Link
self._send_cmd(const.MLGW_PL.get("BEO4 COMMAND"), payload) self._send_cmd(CONST.MLGW_PL.get("BEO4 COMMAND"), payload)
## send_cmd BeoRemote One command to mlgw # send_cmd BeoRemote One command to mlgw
def send_beoremoteone_cmd(self, mln, cmd, network_bit=0x00): def send_beoremoteone_cmd(self, mln, cmd, network_bit=0x00):
payload = [] payload = [
payload.append(mln) # byte[0] MLN mln, # byte[0] MLN
payload.append(cmd) # byte[1] Beo4 Command cmd, # byte[1] Beo4 Command
payload.append(0x00) # byte[2] AV (needs to be 0) 0x00, # byte[2] AV (needs to be 0)
payload.append(network_bit) # byte[3] Network_bit (0 = local source, 1 = network source) network_bit] # byte[3] Network_bit (0 = local source, 1 = network source)
self._send_cmd(const.MLGW_PL.get("BEOREMOTE ONE CONTROL COMMAND"), payload) self._send_cmd(CONST.MLGW_PL.get("BEOREMOTE ONE CONTROL COMMAND"), payload)
## send_cmd BeoRemote One Source Select to mlgw # send_cmd BeoRemote One Source Select to mlgw
def send_beoremoteone_select_source(self, mln, cmd, unit, network_bit=0x00): def send_beoremoteone_select_source(self, mln, cmd, unit, network_bit=0x00):
payload = [] payload = [
payload.append(mln) # byte[0] MLN mln, # byte[0] MLN
payload.append(cmd) # byte[1] Beormyone Command cmd, # byte[1] Beoremote One Command self.BEORMT1_CMD.get('cmd')[0]
payload.append(unit) # byte[2] Unit unit, # byte[2] Unit self.BEORMT1_CMD.get('cmd')[1]
payload.append(0x00) # byte[3] AV (needs to be 0) 0x00, # byte[3] AV (needs to be 0)
payload.append(network_bit) # byte[4] Network_bit (0 = local source, 1 = network source) network_bit] # byte[4] Network_bit (0 = local source, 1 = network source)
self._send_cmd(const.MLGW_PL.get("BEOREMOTE ONE SOURCE SELECTION"), payload) self._send_cmd(CONST.MLGW_PL.get("BEOREMOTE ONE SOURCE SELECTION"), payload)
## send_cmd Beo4 commmand and store the source name # send_cmd Beo4 commmand and store the source name
def send_beo4_select_source(self, mln, dest, source, sec_source=0x00, link=0x00): def send_beo4_select_source(self, mln, dest, source, sec_source=0x00, link=0x00):
beolink_source = self._dictsanitize(const.beo4_commanddict, source).upper()
self.send_beo4_cmd(mln, dest, source, sec_source, link) self.send_beo4_cmd(mln, dest, source, sec_source, link)
# ######################################################################################## # ########################################################################################
# ##### Utility functions # ##### Utility functions
@staticmethod
def _hexbyte(self, byte): def _hexbyte(byte):
resultstr = hex(byte) resultstr = hex(byte)
if byte < 16: if byte < 16:
resultstr = resultstr[:2] + "0" + resultstr[2] resultstr = resultstr[:2] + "0" + resultstr[2]
@ -312,51 +326,66 @@ class MLGWClient(asynchat.async_chat):
def _dictsanitize(self, d, s): def _dictsanitize(self, d, s):
result = d.get(s) result = d.get(s)
if result == None: if result is None:
result = "UNKNOWN (type=" + self._hexbyte(s) + ")" result = "UNKNOWN (type=" + self._hexbyte(s) + ")"
return str(result) return str(result)
# ######################################################################################## # ########################################################################################
# ##### Decode MLGW Protocol packet to readable string # ##### Decode MLGW Protocol packet to readable string
## Get message string for mlgw packet's payload type # Get message string for mlgw packet's payload type
#
def _getpayloadtypestr(self, payloadtype): def _getpayloadtypestr(self, payloadtype):
result = const._mlgw_payloadtypedict.get(payloadtype) result = CONST.mlgw_payloadtypedict.get(payloadtype)
if result == None: if result is None:
result = "UNKNOWN (type=" + self._hexbyte(payloadtype) + ")" result = "UNKNOWN (type=" + self._hexbyte(payloadtype) + ")"
return str(result) return str(result)
def _getmlnstr(self, mln):
result = "MLN=" + str(mln)
return result
def _getbeo4commandstr(self, command): def _getbeo4commandstr(self, command):
result = const.beo4_commanddict.get(command) result = CONST.beo4_commanddict.get(command)
if result == None: if result is None:
result = "Cmd=" + self._hexbyte(command) result = "Cmd=" + self._hexbyte(command)
return result return result
def _getvirtualactionstr(self, action): def _getvirtualactionstr(self, action):
result = const._mlgw_virtualactiondict.get(action) result = CONST.mlgw_virtualactiondict.get(action)
if result == None: if result is None:
result = "Action=" + self._hexbyte(action) result = "Action=" + self._hexbyte(action)
return result return result
def _getselectedsourcestr(self, source): def _getselectedsourcestr(self, source):
result = const.ml_selectedsourcedict.get(source) result = CONST.ml_selectedsourcedict.get(source)
if result == None: if result is None:
result = "Src=" + self._hexbyte(source) result = "Src=" + self._hexbyte(source)
return result return result
def _getspeakermodestr(self, source): def _getspeakermodestr(self, source):
result = const._mlgw_speakermodedict.get(source) result = CONST.mlgw_speakermodedict.get(source)
if result == None: if result is None:
result = "mode=" + self._hexbyte(source) result = "mode=" + self._hexbyte(source)
return result return result
def _getdictstr(self, mydict, mykey): def _getdictstr(self, mydict, mykey):
result = mydict.get(mykey) result = mydict.get(mykey)
if result == None: if result is None:
result = self._hexbyte(mykey) result = self._hexbyte(mykey)
return result 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]
break
@staticmethod
def _get_channel_track(message):
# Check device list for channel name information
if CONST.devices:
for device in CONST.devices:
if device['Device'] == message['Device']:
if 'channels' in device['Sources'][message["State_Update"]["source"]]:
for channel in device['Sources'][message["State_Update"]["source"]]['channels']:
if channel['number'] == int(message["State_Update"]['nowPlayingDetails']["channel_track"]):
message["State_Update"]["nowPlaying"] = channel['name']
break

View file

@ -5,7 +5,8 @@ import time
import json import json
from collections import OrderedDict from collections import OrderedDict
import Resources.CONSTANTS as const import Resources.CONSTANTS as CONST
class MLtnClient(asynchat.async_chat): class MLtnClient(asynchat.async_chat):
"""Client to monitor network activity on a Masterlink Gateway via the telnet monitor""" """Client to monitor network activity on a Masterlink Gateway via the telnet monitor"""
@ -74,10 +75,9 @@ class MLtnClient(asynchat.async_chat):
self.last_received_at = 0 self.last_received_at = 0
def _decode(self, items): def _decode(self, items):
time_stamp = ''.join([items[1],' at ',items[0]])
header = items[3][:-1] header = items[3][:-1]
telegramStarts = len(''.join(items[:4])) + 4 telegram_starts = len(''.join(items[:4])) + 4
telegram = self._received_data[telegramStarts:].replace('!','').split('/') telegram = self._received_data[telegram_starts:].replace('!', '').split('/')
message = OrderedDict() message = OrderedDict()
if header == 'integration_protocol': if header == 'integration_protocol':
@ -125,22 +125,23 @@ class MLtnClient(asynchat.async_chat):
message['Payload']['to_MLN'] = int(s[k], base=16) message['Payload']['to_MLN'] = int(s[k], base=16)
if k == 11: if k == 11:
message['Payload']['Destination'] = self._dictsanitize( message['Payload']['Destination'] = self._dictsanitize(
const._mlgw_payloaddestdict, int(s[k], base=16)) CONST.destselectordict, int(s[k], base=16))
if k == 12: if k == 12:
message['Payload']['Command'] = self._dictsanitize( message['Payload']['Command'] = self._dictsanitize(
const.beo4_commanddict, int(s[k], base=16)).upper() CONST.beo4_commanddict, int(s[k], base=16)).upper()
if k == 13: if k == 13:
message['Payload']['Sec-Source'] = self._dictsanitize( message['Payload']['Sec-Source'] = self._dictsanitize(
const._mlgw_secsourcedict, int(s[k], base=16)) CONST.mlgw_secsourcedict, int(s[k], base=16))
if k == 14: if k == 14:
message['Payload']['Link'] = self._dictsanitize( message['Payload']['Link'] = self._dictsanitize(
const._mlgw_linkdict, int(s[k], base=16)) CONST.mlgw_linkdict, int(s[k], base=16))
if k > 14: if k > 14:
message['Payload']['cmd' + str(k - 9)] = self._dictsanitize( message['Payload']['cmd' + str(k - 9)] = self._dictsanitize(
const.beo4_commanddict, int(s[k], base=16)) CONST.beo4_commanddict, int(s[k], base=16))
return message return message
def _decode_action(self, telegram, message): @staticmethod
def _decode_action(telegram, message):
message['Zone'] = telegram[0].upper() message['Zone'] = telegram[0].upper()
message['Room'] = telegram[1].upper() message['Room'] = telegram[1].upper()
message['Type'] = telegram[2].upper() message['Type'] = telegram[2].upper()
@ -167,7 +168,8 @@ class MLtnClient(asynchat.async_chat):
message['State_Update']['STATE'] = telegram[4] message['State_Update']['STATE'] = telegram[4]
return message return message
def _decode_command(self, telegram, message): @staticmethod
def _decode_command(telegram, message):
message['Zone'] = telegram[0].upper() message['Zone'] = telegram[0].upper()
message['Room'] = telegram[1].upper() message['Room'] = telegram[1].upper()
message['Type'] = telegram[2].upper() message['Type'] = telegram[2].upper()
@ -228,7 +230,7 @@ class MLtnClient(asynchat.async_chat):
for s in state: for s in state:
message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1].title() message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1].title()
if message['State_Update'].get('command') == ' Cmd': if message['State_Update'].get('command') == ' Cmd':
message['State_Update']['command'] = self._dictsanitize(const.beo4_commanddict, message['State_Update']['command'] = self._dictsanitize(CONST.beo4_commanddict,
int(s[13:].strip())).title() int(s[13:].strip())).title()
elif telegram[4][:7] == 'Control': elif telegram[4][:7] == 'Control':
state = telegram[4][6:].split('&') state = telegram[4][6:].split('&')
@ -236,7 +238,7 @@ class MLtnClient(asynchat.async_chat):
for s in state: for s in state:
message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1] message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1]
if message['State_Update'].get('command') == ' cmd': if message['State_Update'].get('command') == ' cmd':
message['State_Update']['command'] = self._dictsanitize(const.beo4_commanddict, message['State_Update']['command'] = self._dictsanitize(CONST.beo4_commanddict,
int(s[13:].strip())).title() int(s[13:].strip())).title()
elif telegram[4] == 'All standby': elif telegram[4] == 'All standby':
message['State_Update']['command'] = telegram[4] message['State_Update']['command'] = telegram[4]
@ -246,7 +248,7 @@ class MLtnClient(asynchat.async_chat):
for s in state: for s in state:
if s.split('=')[0] == 'sourceUniqueId': if s.split('=')[0] == 'sourceUniqueId':
src = s.split('=')[1].split(':')[0].upper() src = s.split('=')[1].split(':')[0].upper()
message['State_Update']['source'] = self._srcdictsanitize(const._blgw_srcdict, src) message['State_Update']['source'] = self._srcdictsanitize(CONST.blgw_srcdict, src)
message['State_Update'][s.split('=')[0]] = s.split('=')[1] message['State_Update'][s.split('=')[0]] = s.split('=')[1]
elif s.split('=')[0] == 'nowPlayingDetails': elif s.split('=')[0] == 'nowPlayingDetails':
message['State_Update']['nowPlayingDetails'] = OrderedDict() message['State_Update']['nowPlayingDetails'] = OrderedDict()
@ -274,7 +276,6 @@ class MLtnClient(asynchat.async_chat):
self.set_terminator(b'\r\n') self.set_terminator(b'\r\n')
# Create the socket # Create the socket
try: try:
socket.setdefaulttimeout(3)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, e: except socket.error, e:
self.log.info("Error creating socket: %s" % e) self.log.info("Error creating socket: %s" % e)
@ -285,12 +286,12 @@ class MLtnClient(asynchat.async_chat):
except socket.gaierror, e: except socket.gaierror, e:
self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e)) self.log.info("\tError with address %s:%i - %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("\tError opening connection to %s:%i - %s" % (self._host, self._port, e))
self.handle_close()
else: else:
self.is_connected = True self.is_connected = True
self.log.info("\tConnected to B&O Gateway") self.log.info("\tConnected to B&O Gateway")
@ -305,28 +306,28 @@ class MLtnClient(asynchat.async_chat):
self.is_connected = False self.is_connected = False
self.close() self.close()
def _send_cmd(self,telegram): def _send_cmd(self, telegram):
try: try:
self.push(telegram +"\r\n") self.push(telegram + "\r\n")
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
except socket.timeout, e: except socket.timeout, e:
self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e)) self.log.info("\tSocket connection to %s:%i timed out- %s" % (self._host, self._port, e))
self.handle_close() self.handle_close()
except socket.error, e:
self.log.info("Error sending data: %s" % e)
self.handle_close()
else: else:
self.last_sent = telegram self.last_sent = telegram
self.last_sent_at = time.time() self.last_sent_at = time.time()
self.log.info(self.name + " >>-SENT--> : " + telegram) self.log.info(self.name + " >>-SENT--> : " + telegram)
time.sleep(0.2) time.sleep(0.2)
def toggleEvents(self): def toggle_events(self):
self._send_cmd('e') self._send_cmd('e')
def toggleMacros(self): def toggle_macros(self):
self._send_cmd('m') self._send_cmd('m')
def toggleCommands(self): def toggle_commands(self):
self._send_cmd('c') self._send_cmd('c')
def ping(self): def ping(self):
@ -334,7 +335,8 @@ class MLtnClient(asynchat.async_chat):
# ######################################################################################## # ########################################################################################
# ##### Utility functions # ##### Utility functions
def _hexbyte(self, byte): @staticmethod
def _hexbyte(byte):
resultstr = hex(byte) resultstr = hex(byte)
if byte < 16: if byte < 16:
resultstr = resultstr[:2] + "0" + resultstr[2] resultstr = resultstr[:2] + "0" + resultstr[2]
@ -342,12 +344,13 @@ class MLtnClient(asynchat.async_chat):
def _dictsanitize(self, d, s): def _dictsanitize(self, d, s):
result = d.get(s) result = d.get(s)
if result == None: if result is None:
result = "UNKNOWN (type=" + self._hexbyte(s) + ")" result = "UNKNOWN (type=" + self._hexbyte(s) + ")"
return str(result) return str(result)
def _srcdictsanitize(self, d, s): @staticmethod
def _srcdictsanitize(d, s):
result = d.get(s) result = d.get(s)
if result == None: if result is None:
result = s result = s
return str(result) return str(result)

1
Resources/__init__.py Normal file
View file

@ -0,0 +1 @@
# init