2021-11-23 15:15:08 +00:00
|
|
|
import logging
|
|
|
|
import asyncore
|
|
|
|
import json
|
2021-12-08 13:03:50 +00:00
|
|
|
import time
|
|
|
|
import requests
|
|
|
|
from requests.auth import HTTPDigestAuth, HTTPBasicAuth
|
2021-11-23 15:15:08 +00:00
|
|
|
from collections import OrderedDict
|
|
|
|
|
2021-11-25 19:18:52 +00:00
|
|
|
import Resources.CONSTANTS as CONST
|
|
|
|
|
2021-11-23 15:15:08 +00:00
|
|
|
|
|
|
|
class MLConfig:
|
|
|
|
|
|
|
|
def __init__(self, host_address='blgw.local', user='admin', pwd='admin'):
|
2021-11-25 19:18:52 +00:00
|
|
|
self.log = logging.getLogger('Config')
|
2021-11-23 15:15:08 +00:00
|
|
|
self.log.setLevel('INFO')
|
|
|
|
|
|
|
|
self._host = host_address
|
|
|
|
self._user = user
|
|
|
|
self._pwd = pwd
|
|
|
|
|
2021-11-25 19:18:52 +00:00
|
|
|
self._download_data()
|
2021-11-23 15:15:08 +00:00
|
|
|
|
2021-11-25 19:18:52 +00:00
|
|
|
def _download_data(self):
|
2021-12-08 13:03:50 +00:00
|
|
|
try:
|
|
|
|
self.log.info('Downloading configuration data from Gateway...')
|
|
|
|
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.log.debug(json.dumps(configurationdata, indent=4))
|
|
|
|
self.configure_mlgw(configurationdata)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
2021-11-23 15:15:08 +00:00
|
|
|
|
2021-11-25 19:18:52 +00:00
|
|
|
def configure_mlgw(self, data):
|
2021-11-23 15:15:08 +00:00
|
|
|
self.log.info('Processing Gateway configuration data...\n')
|
2021-11-25 19:18:52 +00:00
|
|
|
CONST.gateway['Serial_Number'] = data['sn']
|
|
|
|
CONST.gateway['Project'] = data['project']
|
2021-12-08 13:03:50 +00:00
|
|
|
try:
|
|
|
|
CONST.gateway['Installer'] = str(data['installer']['name'])
|
|
|
|
CONST.gateway['Contact'] = str(data['installer']['contact'])
|
|
|
|
gateway_type = 'blgw'
|
|
|
|
except KeyError:
|
|
|
|
gateway_type = 'mlgw'
|
2021-11-23 15:15:08 +00:00
|
|
|
|
|
|
|
for zone in data["zones"]:
|
|
|
|
if int(zone['number']) == 240:
|
|
|
|
continue
|
|
|
|
room = OrderedDict()
|
|
|
|
room['Room_Number'] = zone['number']
|
2021-12-08 13:03:50 +00:00
|
|
|
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'])
|
|
|
|
|
2021-11-23 15:15:08 +00:00
|
|
|
room['Products'] = []
|
|
|
|
|
|
|
|
for product in zone["products"]:
|
|
|
|
device = OrderedDict()
|
|
|
|
room['Products'].append(str(product["name"]))
|
2021-12-08 13:03:50 +00:00
|
|
|
# Device identification
|
2021-11-23 15:15:08 +00:00
|
|
|
device['Device'] = str(product["name"])
|
|
|
|
device['MLN'] = product["MLN"]
|
|
|
|
device['ML_ID'] = ''
|
2021-12-08 13:03:50 +00:00
|
|
|
try:
|
|
|
|
device['Serial_num'] = str(product["sn"])
|
|
|
|
except KeyError:
|
|
|
|
device['Serial_num'] = ''
|
|
|
|
|
|
|
|
# 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'])
|
2021-11-23 15:15:08 +00:00
|
|
|
device['Room_Number'] = str(zone["number"])
|
2021-12-08 13:03:50 +00:00
|
|
|
# Source logging parameters for managing notifications
|
2021-11-23 15:15:08 +00:00
|
|
|
device['Sources'] = OrderedDict()
|
2021-12-08 13:03:50 +00:00
|
|
|
device['Current Source'] = 'None'
|
|
|
|
device['Current Source Type'] = 'None'
|
|
|
|
device['Now Playing'] = 'None'
|
|
|
|
device['Channel/Track'] = '0'
|
|
|
|
device['State'] = 'Standby'
|
|
|
|
device['last update'] = time.time()
|
2021-11-23 15:15:08 +00:00
|
|
|
|
|
|
|
for source in product["sources"]:
|
|
|
|
device['Sources'][str(source["name"])] = OrderedDict()
|
|
|
|
for selectCmd in source["selectCmds"]:
|
2021-12-08 13:03:50 +00:00
|
|
|
if gateway_type == 'blgw':
|
|
|
|
# get source information from the BLGW config file
|
2021-12-17 18:45:05 +00:00
|
|
|
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()
|
2021-12-08 13:03:50 +00:00
|
|
|
device['Sources'][str(source["name"])]['source'] = source_id
|
|
|
|
device['Sources'][str(source["name"])]['uniqueID'] = str(source['sourceId'])
|
|
|
|
else:
|
|
|
|
# MLGW config file is structured differently
|
|
|
|
source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper()
|
|
|
|
device['Sources'][str(source["name"])]['source'] = source_id
|
2021-11-23 15:15:08 +00:00
|
|
|
source_tuple = (str(source["name"]), source_id)
|
2021-11-25 19:18:52 +00:00
|
|
|
cmd_tuple = (source_id, (int(selectCmd["cmd"]), int(selectCmd["unit"])))
|
2021-11-23 15:15:08 +00:00
|
|
|
device['Sources'][str(source["name"])]['BR1_cmd'] = cmd_tuple
|
2021-11-25 19:18:52 +00:00
|
|
|
if 'channels' in source:
|
|
|
|
device['Sources'][str(source["name"])]['channels'] = []
|
2021-11-23 15:15:08 +00:00
|
|
|
for channel in source['channels']:
|
2021-11-25 19:18:52 +00:00
|
|
|
c = OrderedDict()
|
2021-11-23 15:15:08 +00:00
|
|
|
c_num = ''
|
2021-12-08 13:03:50 +00:00
|
|
|
if gateway_type == 'blgw':
|
|
|
|
num = channel['selectSEQ'][::2]
|
|
|
|
else:
|
|
|
|
num = channel['selectSEQ'][:-1]
|
2021-11-23 15:15:08 +00:00
|
|
|
for n in num:
|
|
|
|
c_num += str(n)
|
2021-11-25 19:18:52 +00:00
|
|
|
c['number'] = int(c_num)
|
|
|
|
c['name'] = channel['name']
|
|
|
|
c['icon'] = channel['icon']
|
|
|
|
device['Sources'][str(source["name"])]['channels'].append(c)
|
|
|
|
|
|
|
|
if source_tuple not in CONST.available_sources:
|
|
|
|
CONST.available_sources.append(source_tuple)
|
|
|
|
|
|
|
|
CONST.devices.append(device)
|
|
|
|
CONST.rooms.append(room)
|
|
|
|
|
|
|
|
self.log.info('Found ' + str(len(CONST.devices)) + ' AV Renderers!')
|
|
|
|
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('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Name, Type]:')
|
|
|
|
for i in range(len(CONST.available_sources)):
|
|
|
|
self.log.info('\t\t' + str(list(CONST.available_sources[i])))
|
2021-12-17 18:45:05 +00:00
|
|
|
self.log.info('\tDone!\n')
|
2021-11-23 15:15:08 +00:00
|
|
|
|
2021-11-25 19:18:52 +00:00
|
|
|
self.log.debug(json.dumps(CONST.gateway, indent=4))
|
|
|
|
self.log.debug(json.dumps(CONST.rooms, indent=4))
|
|
|
|
self.log.debug(json.dumps(CONST.devices, indent=4))
|
2021-11-23 15:15:08 +00:00
|
|
|
|
|
|
|
def get_masterlink_id(self, mlgw, mlcli):
|
|
|
|
self.log.info("Finding MasterLink ID of products:")
|
2021-12-08 13:03:50 +00:00
|
|
|
if mlgw.is_connected and mlcli.is_connected and CONST.devices:
|
2021-11-25 19:18:52 +00:00
|
|
|
for device in CONST.devices:
|
2021-11-23 15:15:08 +00:00
|
|
|
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
|
|
|
|
mlgw.send_beo4_cmd(int(device.get('MLN')),
|
2021-11-25 19:18:52 +00:00
|
|
|
CONST.CMDS_DEST.get("AUDIO SOURCE"),
|
|
|
|
CONST.BEO4_CMDS.get("LIGHT TIMEOUT"))
|
2021-11-23 15:15:08 +00:00
|
|
|
|
2021-12-08 13:03:50 +00:00
|
|
|
if device.get('Serial_num') in [None, '']:
|
2021-11-23 15:15:08 +00:00
|
|
|
# If this is a MasterLink product it has no serial number...
|
|
|
|
# loop to until expected response received from ML Command Line Interface
|
2021-12-08 13:03:50 +00:00
|
|
|
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":
|
|
|
|
|
|
|
|
device['ML_ID'] = mlcli.last_message.get('to_device')
|
2021-12-17 18:45:05 +00:00
|
|
|
device['Serial_num'] = 'NA'
|
2021-12-08 13:03:50 +00:00
|
|
|
self.log.info("\tMasterLink ID of product " +
|
|
|
|
device.get('Device') + " is " + device.get('ML_ID') + ".\n")
|
|
|
|
test = False
|
|
|
|
except KeyError:
|
|
|
|
asyncore.loop(count=1, timeout=0.2)
|
|
|
|
|
2021-11-23 15:15:08 +00:00
|
|
|
else:
|
|
|
|
# If this is a NetLink product then it has a serial number and no ML_ID
|
2021-12-17 18:45:05 +00:00
|
|
|
if device['Device'] == 'BeoMaster 7000':
|
|
|
|
device['ML_ID'] = "AUDIO MASTER"
|
|
|
|
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
|
|
|
|
device.get('Serial_num') + ". MasterLink ID manually assigned to AUDIO MASTER.\n")
|
|
|
|
else:
|
|
|
|
device['ML_ID'] = 'NA'
|
|
|
|
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
|
|
|
|
device.get('Serial_num') + ". No MasterLink ID assigned.\n")
|
2021-11-23 15:15:08 +00:00
|
|
|
|
2021-12-08 13:03:50 +00:00
|
|
|
self.log.debug(json.dumps(device, indent=4))
|
|
|
|
|
|
|
|
# ########################################################################################
|
|
|
|
# Utility Functions
|
2021-11-25 19:18:52 +00:00
|
|
|
@staticmethod
|
|
|
|
def _srcdictsanitize(d, s):
|
2021-11-23 15:15:08 +00:00
|
|
|
result = d.get(s)
|
2021-11-25 19:18:52 +00:00
|
|
|
if result is None:
|
2021-11-23 15:15:08 +00:00
|
|
|
result = s
|
|
|
|
return str(result)
|