mirror of
https://github.com/LukeSpad/BeoGateway.git
synced 2024-12-23 21:51:51 +00:00
Add files via upload
This commit is contained in:
parent
7d7fcc9f5e
commit
4688f11252
7 changed files with 366 additions and 112 deletions
111
Resources/ASBridge.py
Normal file
111
Resources/ASBridge.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/python
|
||||
import os
|
||||
from Foundation import NSAppleScript
|
||||
from ScriptingBridge import SBApplication
|
||||
|
||||
''' Module defining a MusicController class for Apple Music, enables:
|
||||
basic transport control of the player,
|
||||
query of player status,
|
||||
playing existing playlists, and
|
||||
running external appleScripts for more complex control
|
||||
reporting messages in the notification centre'''
|
||||
|
||||
PLAYSTATE = dict([
|
||||
(1800426320, "Play"),
|
||||
(1800426352, "Pause"),
|
||||
(1800426323, "Stop"),
|
||||
(1800426310, "Wind"),
|
||||
(1800426322, "Rewind")
|
||||
])
|
||||
|
||||
|
||||
class MusicController(object):
|
||||
|
||||
def __init__(self):
|
||||
self.app = SBApplication.applicationWithBundleIdentifier_("com.apple.Music")
|
||||
|
||||
# Player information
|
||||
def get_current_track_info(self):
|
||||
name = str(self.app.currentTrack().name())
|
||||
album = str(self.app.currentTrack().album())
|
||||
artist = str(self.app.currentTrack().artist())
|
||||
return [name, album, artist]
|
||||
|
||||
def get_current_play_state(self):
|
||||
return PLAYSTATE.get(self.app.playerState())
|
||||
|
||||
def get_current_track_position(self):
|
||||
return self.app.playerPosition()
|
||||
|
||||
# ########################################################################################
|
||||
# Transport Controls
|
||||
def playpause(self):
|
||||
self.app.playpause()
|
||||
|
||||
def play(self):
|
||||
if PLAYSTATE.get(self.app.playerState()) in ['Wind', 'Rewind']:
|
||||
self.app.resume()
|
||||
elif PLAYSTATE.get(self.app.playerState()) == 'Pause':
|
||||
self.app.playpause()
|
||||
elif PLAYSTATE.get(self.app.playerState()) == 'Stop':
|
||||
playlist = self.app.sources().objectWithName_("Library")
|
||||
playlist.playOnce_(None)
|
||||
|
||||
def pause(self):
|
||||
if PLAYSTATE.get(self.app.playerState()) == 'Play':
|
||||
self.app.pause()
|
||||
|
||||
def stop(self):
|
||||
if PLAYSTATE.get(self.app.playerState()) != 'Stop':
|
||||
self.app.stop()
|
||||
|
||||
def next_track(self):
|
||||
self.app.nextTrack()
|
||||
|
||||
def previous_track(self):
|
||||
self.app.previousTrack()
|
||||
|
||||
def wind(self, time):
|
||||
# Native wind function can be a bit annoying
|
||||
# I provide an alternative below that skips a set number of seconds forwards
|
||||
# self.app.wind()
|
||||
self.set_current_track_position(time)
|
||||
|
||||
def rewind(self, time):
|
||||
# Native rewind function can be a bit annoying
|
||||
# I provide an alternative below that skips a set number of seconds back
|
||||
# self.app.rewind()
|
||||
self.set_current_track_position(time)
|
||||
|
||||
# ########################################################################################
|
||||
# More complex playback control functions
|
||||
def set_current_track_position(self, time, mode='Relative'):
|
||||
if mode == 'Relative':
|
||||
# Set playback position in seconds relative to current position
|
||||
self.app.setPlayerPosition_(self.app.playerPosition() + time)
|
||||
elif mode == 'Absolute':
|
||||
# Set playback position in seconds from the start of the track
|
||||
self.app.setPlayerPosition_(time)
|
||||
|
||||
def play_playlist(self, playlist):
|
||||
self.app.stop()
|
||||
playlist = self.app.sources().objectWithName_("Library").playlists().objectWithName_(playlist)
|
||||
playlist.playOnce_(None)
|
||||
|
||||
# ########################################################################################
|
||||
# Accessory functions
|
||||
@staticmethod
|
||||
def run_script(script):
|
||||
# Run an external applescript file
|
||||
script = 'run script ("' + script + '" as POSIX file)'
|
||||
s = NSAppleScript.alloc().initWithSource_(script)
|
||||
s.executeAndReturnError_(None)
|
||||
|
||||
@staticmethod
|
||||
def notify(message):
|
||||
# Post message in notification center
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
script = 'tell application "' + path + '/Notify.app" to notify("BeoGateway", "' + \
|
||||
message + '")'
|
||||
s = NSAppleScript.alloc().initWithSource_(script)
|
||||
s.executeAndReturnError_(None)
|
|
@ -173,7 +173,7 @@ class BLHIPClient(asynchat.async_chat):
|
|||
self.log.info("\tAttempting to Authenticate...")
|
||||
self.send_cmd(self._user)
|
||||
self.send_cmd(self._pwd)
|
||||
self.statefiler()
|
||||
self.statefilter()
|
||||
|
||||
def handle_close(self):
|
||||
self.log.info(self.name + ": Closing socket")
|
||||
|
@ -219,7 +219,7 @@ class BLHIPClient(asynchat.async_chat):
|
|||
self.log.info(self.name + ": sending state update request for" + device + dev_type + room + zone)
|
||||
self.send_cmd(query)
|
||||
|
||||
def statefiler(self, zone='*', room='*', dev_type='*', device='*'):
|
||||
def statefilter(self, zone='*', room='*', dev_type='*', device='*'):
|
||||
s_filter = "f " + zone + "/" + room + "/" + dev_type + '/' + device
|
||||
self.send_cmd(s_filter)
|
||||
|
||||
|
@ -241,12 +241,16 @@ class BLHIPClient(asynchat.async_chat):
|
|||
|
||||
@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
|
||||
try:
|
||||
# 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
|
||||
except KeyError:
|
||||
pass
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
# Constants for B&O telegram protocols
|
||||
# ########################################################################################
|
||||
# Config data (set on initialisation)
|
||||
gateway = dict()
|
||||
gateway = dict([("Current Audio Source", ''), ("Current Video Source", '')])
|
||||
rooms = []
|
||||
devices = []
|
||||
available_sources = []
|
||||
|
||||
# ########################################################################################
|
||||
# Source Types
|
||||
|
||||
source_type_dict = dict(
|
||||
[
|
||||
("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "CAMERA",
|
||||
"SAT/DTV", "PC", "WEB", "DOORCAM", "PHOTO", "USB2", "WEBMEDIA", "AV.IN",
|
||||
"HOMEMEDIA", "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_commanddict = dict(
|
||||
|
@ -18,7 +38,7 @@ beo4_commanddict = dict(
|
|||
(0x82, "V.Aux/DTV2"),
|
||||
(0x83, "A.Aux"),
|
||||
(0x84, "Media"),
|
||||
(0x85, "V.Tape/V.Mem/DVD2"),
|
||||
(0x85, "V.Tape/V.Mem"),
|
||||
(0x86, "DVD"),
|
||||
(0x87, "Camera"),
|
||||
(0x88, "Text"),
|
||||
|
@ -350,9 +370,9 @@ ml_selectedsourcedict = dict(
|
|||
(0x0B, "TV"),
|
||||
(0x15, "V.TAPE/V.MEM"),
|
||||
(0x16, "DVD2"),
|
||||
(0x1F, "DTV"),
|
||||
(0x1F, "SAT/DTV"),
|
||||
(0x29, "DVD"),
|
||||
(0x33, "V.AUX"),
|
||||
(0x33, "V.AUX/DTV2"),
|
||||
(0x3E, "DOORCAM"),
|
||||
(0x47, "PC"),
|
||||
(0x6F, "RADIO"),
|
||||
|
@ -369,6 +389,8 @@ ml_selectedsourcedict = dict(
|
|||
|
||||
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)),
|
||||
|
|
|
@ -84,7 +84,7 @@ class MLCLIClient(asynchat.async_chat):
|
|||
for item in items:
|
||||
try:
|
||||
telegram.append(int(item[:-1], base=16))
|
||||
except TypeError:
|
||||
except (ValueError, TypeError):
|
||||
# abort if invalid character found
|
||||
self.log.debug('Invalid character ' + str(item) + ' found in telegram: ' +
|
||||
''.join(items) + '\nAborting!')
|
||||
|
@ -192,37 +192,35 @@ class MLCLIClient(asynchat.async_chat):
|
|||
def _decode(self, telegram):
|
||||
# Decode header
|
||||
message = OrderedDict()
|
||||
if CONST.devices:
|
||||
for device in CONST.devices:
|
||||
if device['ML_ID'] == telegram[1]:
|
||||
message["Zone"] = device["Zone"].upper()
|
||||
message["Room"] = device["Room"].uppr()
|
||||
message["Type"] = "AV RENDERER"
|
||||
message["Device"] = device["Device"]
|
||||
break
|
||||
message["from_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[1])
|
||||
self._get_device_info(message, telegram)
|
||||
message["from_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[1]))
|
||||
message["from_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[5])
|
||||
message["to_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[0])
|
||||
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"] = ''
|
||||
message["State_Update"]["nowPlaying"] = 'Unknown'
|
||||
message["State_Update"]["nowPlayingDetails"] = OrderedDict()
|
||||
message["State_Update"]["nowPlayingDetails"]["local_source"] = telegram[13]
|
||||
message["State_Update"]["nowPlayingDetails"]["type"] = telegram[22]
|
||||
message["State_Update"]["nowPlayingDetails"]["type"] = \
|
||||
self._dictsanitize(CONST.ml_sourcekind_dict, telegram[22])
|
||||
if telegram[8] < 27:
|
||||
message["State_Update"]["nowPlayingDetails"]["channel_track"] = telegram[19]
|
||||
else:
|
||||
message["State_Update"]["nowPlayingDetails"]["channel_track"] = telegram[36] * 256 + telegram[37]
|
||||
message["State_Update"]["nowPlayingDetails"]["source_medium_position"] = \
|
||||
self._hexword(telegram[18], telegram[17])
|
||||
message["State_Update"]["nowPlayingDetails"]["picture_identifier"] = \
|
||||
message["State_Update"]["nowPlayingDetails"]["picture_format"] = \
|
||||
self._dictsanitize(CONST.ml_pictureformatdict, telegram[23])
|
||||
source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[10])
|
||||
self._get_source_name(source, message)
|
||||
|
@ -257,7 +255,7 @@ class MLCLIClient(asynchat.async_chat):
|
|||
|
||||
# audio track info long
|
||||
if message.get("payload_type") == "TRACK_INFO_LONG":
|
||||
message["State_Update"]["nowPlaying"] = ''
|
||||
message["State_Update"]["nowPlaying"] = 'Unknown'
|
||||
message["State_Update"]["nowPlayingDetails"] = OrderedDict()
|
||||
message["State_Update"]["nowPlayingDetails"]["type"] = \
|
||||
self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
|
||||
|
@ -271,7 +269,7 @@ class MLCLIClient(asynchat.async_chat):
|
|||
|
||||
# video track info
|
||||
if message.get("payload_type") == "VIDEO_TRACK_INFO":
|
||||
message["State_Update"]["nowPlaying"] = ''
|
||||
message["State_Update"]["nowPlaying"] = 'Unknown'
|
||||
message["State_Update"]["nowPlayingDetails"] = OrderedDict()
|
||||
message["State_Update"]["nowPlayingDetails"]["source_type"] = \
|
||||
self._get_type(CONST.ml_selectedsource_type_dict, telegram[13])
|
||||
|
@ -286,6 +284,8 @@ class MLCLIClient(asynchat.async_chat):
|
|||
# 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]
|
||||
|
@ -297,6 +297,7 @@ class MLCLIClient(asynchat.async_chat):
|
|||
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)
|
||||
|
@ -308,7 +309,7 @@ class MLCLIClient(asynchat.async_chat):
|
|||
|
||||
# goto source
|
||||
if message.get("payload_type") == "GOTO_SOURCE":
|
||||
message["State_Update"]["nowPlaying"] = ''
|
||||
message["State_Update"]["nowPlaying"] = 'Unknown'
|
||||
message["State_Update"]["nowPlayingDetails"] = OrderedDict()
|
||||
message["State_Update"]["nowPlayingDetails"]["source_type"] = \
|
||||
self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
|
||||
|
@ -374,6 +375,27 @@ class MLCLIClient(asynchat.async_chat):
|
|||
message["State_Update"]["nowPlaying"] = channel['name']
|
||||
return
|
||||
|
||||
def _get_device_info(self, message, telegram):
|
||||
if CONST.devices:
|
||||
for device in CONST.devices:
|
||||
if device['ML_ID'] == self._dictsanitize(CONST.ml_device_dict, telegram[1]):
|
||||
try:
|
||||
message["Zone"] = device['Zone'].upper()
|
||||
except KeyError:
|
||||
pass
|
||||
message["Room"] = device["Room"].upper()
|
||||
message["Type"] = "AV RENDERER"
|
||||
message["Device"] = device["Device"]
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def _get_device_name(dev):
|
||||
if CONST.devices:
|
||||
for device in CONST.devices:
|
||||
if device['ML_ID'] == dev:
|
||||
return device['Device']
|
||||
return dev
|
||||
|
||||
@staticmethod
|
||||
def _get_source_name(source, message):
|
||||
if CONST.available_sources:
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import logging
|
||||
import requests
|
||||
import asyncore
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from requests.auth import HTTPDigestAuth, HTTPBasicAuth
|
||||
from collections import OrderedDict
|
||||
|
||||
import Resources.CONSTANTS as CONST
|
||||
|
@ -20,48 +22,95 @@ class MLConfig:
|
|||
self._download_data()
|
||||
|
||||
def _download_data(self):
|
||||
self.log.info('Downloading configuration data from Gateway...')
|
||||
url = 'http://' + self._host + '/mlgwpservices.json'
|
||||
auth = (self._user, self._pwd)
|
||||
response = requests.get(url, auth=auth)
|
||||
configurationdata = json.loads(response.text)
|
||||
self.configure_mlgw(configurationdata)
|
||||
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
|
||||
|
||||
def configure_mlgw(self, data):
|
||||
self.log.info('Processing Gateway configuration data...\n')
|
||||
CONST.gateway['Serial_Number'] = data['sn']
|
||||
CONST.gateway['Project'] = data['project']
|
||||
CONST.gateway['Installer'] = str(data['installer']['name'])
|
||||
CONST.gateway['Contact'] = str(data['installer']['contact'])
|
||||
try:
|
||||
CONST.gateway['Installer'] = str(data['installer']['name'])
|
||||
CONST.gateway['Contact'] = str(data['installer']['contact'])
|
||||
gateway_type = 'blgw'
|
||||
except KeyError:
|
||||
gateway_type = 'mlgw'
|
||||
|
||||
for zone in data["zones"]:
|
||||
if int(zone['number']) == 240:
|
||||
continue
|
||||
room = OrderedDict()
|
||||
room['Room_Number'] = zone['number']
|
||||
room['Zone'] = str(zone['name']).split('/')[0]
|
||||
room['Room_Name'] = str(zone['name']).split('/')[1]
|
||||
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'])
|
||||
|
||||
room['Products'] = []
|
||||
|
||||
for product in zone["products"]:
|
||||
device = OrderedDict()
|
||||
room['Products'].append(str(product["name"]))
|
||||
# Device identification
|
||||
device['Device'] = str(product["name"])
|
||||
device['MLN'] = product["MLN"]
|
||||
device['ML_ID'] = ''
|
||||
device['Serial_num'] = str(product["sn"])
|
||||
device['Zone'] = str(zone["name"]).split('/')[0]
|
||||
device['Room'] = str(zone["name"]).split('/')[1]
|
||||
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'])
|
||||
device['Room_Number'] = str(zone["number"])
|
||||
# Source logging parameters for managing notifications
|
||||
device['Sources'] = OrderedDict()
|
||||
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()
|
||||
|
||||
for source in product["sources"]:
|
||||
device['Sources'][str(source["name"])] = OrderedDict()
|
||||
for selectCmd in source["selectCmds"]:
|
||||
source_id = str(source['sourceId'].split(':')[0])
|
||||
source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
|
||||
device['Sources'][str(source["name"])]['source'] = source_id
|
||||
device['Sources'][str(source["name"])]['uniqueID'] = str(source['sourceId'])
|
||||
if gateway_type == 'blgw':
|
||||
# get source information from the BLGW config file
|
||||
source_id = str(source['sourceId'].split(':')[0])
|
||||
source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
|
||||
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
|
||||
source_tuple = (str(source["name"]), source_id)
|
||||
cmd_tuple = (source_id, (int(selectCmd["cmd"]), int(selectCmd["unit"])))
|
||||
device['Sources'][str(source["name"])]['BR1_cmd'] = cmd_tuple
|
||||
|
@ -70,7 +119,10 @@ class MLConfig:
|
|||
for channel in source['channels']:
|
||||
c = OrderedDict()
|
||||
c_num = ''
|
||||
num = channel['selectSEQ'][::2]
|
||||
if gateway_type == 'blgw':
|
||||
num = channel['selectSEQ'][::2]
|
||||
else:
|
||||
num = channel['selectSEQ'][:-1]
|
||||
for n in num:
|
||||
c_num += str(n)
|
||||
c['number'] = int(c_num)
|
||||
|
@ -98,7 +150,7 @@ class MLConfig:
|
|||
|
||||
def get_masterlink_id(self, mlgw, mlcli):
|
||||
self.log.info("Finding MasterLink ID of products:")
|
||||
if mlgw.is_connected and mlcli.is_connected:
|
||||
if mlgw.is_connected and mlcli.is_connected and CONST.devices:
|
||||
for device in CONST.devices:
|
||||
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
|
||||
|
@ -106,23 +158,33 @@ class MLConfig:
|
|||
CONST.CMDS_DEST.get("AUDIO SOURCE"),
|
||||
CONST.BEO4_CMDS.get("LIGHT TIMEOUT"))
|
||||
|
||||
if device.get('Serial_num') is None:
|
||||
if device.get('Serial_num') in [None, '']:
|
||||
# If this is a MasterLink product it has no serial number...
|
||||
# loop to until expected response received from ML Command Line Interface
|
||||
while 'to_device' not in mlcli.last_message and mlcli.last_message['from_device'] == \
|
||||
"MLGW" and mlcli.last_message['payload_type'] == \
|
||||
"MLGW_REMOTE_BEO4" and mlcli.last_message['payload']['command'] == "LIGHT TIMEOUT":
|
||||
asyncore.loop(count=1, timeout=0.2)
|
||||
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')
|
||||
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)
|
||||
|
||||
device['ML_ID'] = mlcli.last_message.get('to_device')
|
||||
self.log.info("\tMasterLink ID of product " +
|
||||
device.get('Device') + " is " + device.get('ML_ID') + ".\n")
|
||||
else:
|
||||
# If this is a NetLink product then it has a serial number and no ML_ID
|
||||
device['ML_ID'] = 'NA'
|
||||
self.log.info("\tNetworkLink ID of product " + device.get('Device') + " is " +
|
||||
device.get('Serial_num') + ". No MasterLink ID assigned.\n")
|
||||
|
||||
self.log.debug(json.dumps(device, indent=4))
|
||||
|
||||
# ########################################################################################
|
||||
# Utility Functions
|
||||
@staticmethod
|
||||
def _srcdictsanitize(d, s):
|
||||
result = d.get(s)
|
||||
|
|
|
@ -118,20 +118,11 @@ class MLGWClient(asynchat.async_chat):
|
|||
message['serial_Num'] = sn
|
||||
|
||||
elif payload_type == "Source Status":
|
||||
if CONST.rooms and CONST.devices:
|
||||
for device in CONST.devices:
|
||||
if device['MLN'] == payload[0]:
|
||||
name = device['Device']
|
||||
for room in CONST.rooms:
|
||||
if name in room['Products']:
|
||||
message["Zone"] = room['Zone'].upper()
|
||||
message["Room"] = room['Room_Name'].upper()
|
||||
message["Type"] = 'AV RENDERER'
|
||||
message["Device"] = name
|
||||
self._get_device_info(message, payload)
|
||||
message["payload_type"] = payload_type
|
||||
message["MLN"] = payload[0]
|
||||
message["State_Update"] = OrderedDict()
|
||||
message["State_Update"]["nowPlaying"] = ''
|
||||
message["State_Update"]["nowPlaying"] = 'Unknown'
|
||||
message["State_Update"]["nowPlayingDetails"] = OrderedDict()
|
||||
message["State_Update"]["nowPlayingDetails"]["channel_track"] = \
|
||||
self._hexword(payload[4], payload[5])
|
||||
|
@ -146,27 +137,26 @@ class MLGWClient(asynchat.async_chat):
|
|||
message["State_Update"]["state"] = self._getdictstr(CONST.sourceactivitydict, payload[6])
|
||||
|
||||
elif payload_type == "Picture and Sound Status":
|
||||
if CONST.rooms and CONST.devices:
|
||||
for device in CONST.devices:
|
||||
if device['MLN'] == payload[0]:
|
||||
name = device['Device']
|
||||
for room in CONST.rooms:
|
||||
if name in room['Products']:
|
||||
message["Zone"] = room['Zone'].upper()
|
||||
message["Room"] = room['Room_Name'].upper()
|
||||
message["Type"] = 'AV RENDERER'
|
||||
message["Device"] = name
|
||||
self._get_device_info(message, payload)
|
||||
message["payload_type"] = payload_type
|
||||
message["MLN"] = payload[0]
|
||||
message["State_Update"] = OrderedDict()
|
||||
message["State_Update"]["sound_status"] = self._getdictstr(CONST.mlgw_soundstatusdict, payload[1])
|
||||
message["State_Update"]["speakermode"] = self._getdictstr(CONST.mlgw_speakermodedict, payload[2])
|
||||
message["State_Update"]["stereo_mode"] = self._getdictstr(CONST.mlgw_stereoindicatordict, payload[9])
|
||||
message["State_Update"]["screen1_mute"] = self._getdictstr(CONST.mlgw_screenmutedict, payload[4])
|
||||
message["State_Update"]["screen1_active"] = self._getdictstr(CONST.mlgw_screenactivedict, payload[5])
|
||||
message["State_Update"]["screen2_mute"] = self._getdictstr(CONST.mlgw_screenmutedict, payload[6])
|
||||
message["State_Update"]["screen2_active"] = self._getdictstr(CONST.mlgw_screenactivedict, payload[7])
|
||||
message["State_Update"]["cinema_mode"] = self._getdictstr(CONST.mlgw_cinemamodedict, payload[8])
|
||||
message["State_Update"] = OrderedDict([("sound_status", OrderedDict()), ("picture_status", OrderedDict())])
|
||||
message["State_Update"]["sound_status"]["mute_status"] = \
|
||||
self._getdictstr(CONST.mlgw_soundstatusdict, payload[1])
|
||||
message["State_Update"]["sound_status"]["speakermode"] = \
|
||||
self._getdictstr(CONST.mlgw_speakermodedict, payload[2])
|
||||
message["State_Update"]["sound_status"]["stereo_mode"] = \
|
||||
self._getdictstr(CONST.mlgw_stereoindicatordict, payload[9])
|
||||
message["State_Update"]["picture_status"]["screen1_mute"] = \
|
||||
self._getdictstr(CONST.mlgw_screenmutedict, payload[4])
|
||||
message["State_Update"]["picture_status"]["screen1_active"] = \
|
||||
self._getdictstr(CONST.mlgw_screenactivedict, payload[5])
|
||||
message["State_Update"]["picture_status"]["screen2_mute"] = \
|
||||
self._getdictstr(CONST.mlgw_screenmutedict, payload[6])
|
||||
message["State_Update"]["picture_status"]["screen2_active"] = \
|
||||
self._getdictstr(CONST.mlgw_screenactivedict, payload[7])
|
||||
message["State_Update"]["picture_status"]["cinema_mode"] = \
|
||||
self._getdictstr(CONST.mlgw_cinemamodedict, payload[8])
|
||||
message["volume"] = int(payload[3])
|
||||
|
||||
elif payload_type == "All standby notification":
|
||||
|
@ -177,7 +167,10 @@ class MLGWClient(asynchat.async_chat):
|
|||
if CONST.rooms:
|
||||
for room in CONST.rooms:
|
||||
if room['Room_Number'] == payload[0]:
|
||||
message["Zone"] = room['Zone'].upper()
|
||||
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'
|
||||
|
@ -236,7 +229,7 @@ class MLGWClient(asynchat.async_chat):
|
|||
self.messageCallBack(self.name, str(list(header)), str(list(payload)), message)
|
||||
|
||||
# ########################################################################################
|
||||
# ##### mlgw send_cmder functions
|
||||
# ##### mlgw send functions
|
||||
|
||||
# send_cmd command to mlgw
|
||||
def _send_cmd(self, msg_type, payload):
|
||||
|
@ -267,6 +260,7 @@ class MLGWClient(asynchat.async_chat):
|
|||
# Sleep to allow msg to arrive
|
||||
time.sleep(0.2)
|
||||
|
||||
# Ping the gateway
|
||||
def ping(self):
|
||||
self._send_cmd(CONST.MLGW_PL.get("PING"), "")
|
||||
|
||||
|
@ -337,31 +331,31 @@ class MLGWClient(asynchat.async_chat):
|
|||
def _getpayloadtypestr(self, payloadtype):
|
||||
result = CONST.mlgw_payloadtypedict.get(payloadtype)
|
||||
if result is None:
|
||||
result = "UNKNOWN (type=" + self._hexbyte(payloadtype) + ")"
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
result = "mode = " + self._hexbyte(source)
|
||||
return result
|
||||
|
||||
def _getdictstr(self, mydict, mykey):
|
||||
|
@ -376,16 +370,40 @@ class MLGWClient(asynchat.async_chat):
|
|||
for src in CONST.available_sources:
|
||||
if src[1] == source:
|
||||
message["State_Update"]["sourceName"] = src[0]
|
||||
break
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _get_device_info(message, payload):
|
||||
if CONST.rooms and CONST.devices:
|
||||
for device in CONST.devices:
|
||||
try:
|
||||
if device['MLN'] == payload[0]:
|
||||
name = device['Device']
|
||||
for room in CONST.rooms:
|
||||
if name in room['Products']:
|
||||
try:
|
||||
message["Zone"] = room['Zone'].upper()
|
||||
except KeyError:
|
||||
pass
|
||||
message["Room"] = room['Room_Name'].upper()
|
||||
message["Type"] = 'AV RENDERER'
|
||||
message["Device"] = name
|
||||
return
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@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
|
||||
try:
|
||||
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']
|
||||
return
|
||||
except KeyError:
|
||||
pass
|
||||
|
|
|
@ -66,13 +66,12 @@ class MLtnClient(asynchat.async_chat):
|
|||
self.handle_close()
|
||||
|
||||
else:
|
||||
self._decode(items)
|
||||
try:
|
||||
self._decode(items)
|
||||
except IndexError:
|
||||
self.log.debug(str(list(items)))
|
||||
|
||||
self._received_data = ""
|
||||
self.last_sent = ''
|
||||
self.last_sent_at = 0
|
||||
self.last_received = ''
|
||||
self.last_received_at = 0
|
||||
|
||||
def _decode(self, items):
|
||||
header = items[3][:-1]
|
||||
|
@ -80,6 +79,10 @@ class MLtnClient(asynchat.async_chat):
|
|||
telegram = self._received_data[telegram_starts:].replace('!', '').split('/')
|
||||
message = OrderedDict()
|
||||
|
||||
if telegram[0] == 'Monitor events ( keys: M, E, C, (spc), Q ) ----':
|
||||
self.toggle_commands()
|
||||
self.toggle_macros()
|
||||
|
||||
if header == 'integration_protocol':
|
||||
message = self._decode_ip(telegram, message)
|
||||
|
||||
|
@ -322,13 +325,25 @@ class MLtnClient(asynchat.async_chat):
|
|||
time.sleep(0.2)
|
||||
|
||||
def toggle_events(self):
|
||||
self._send_cmd('e')
|
||||
try:
|
||||
self.push('e')
|
||||
except socket.error, e:
|
||||
self.log.info("Error sending data: %s" % e)
|
||||
self.handle_close()
|
||||
|
||||
def toggle_macros(self):
|
||||
self._send_cmd('m')
|
||||
try:
|
||||
self.push('m')
|
||||
except socket.error, e:
|
||||
self.log.info("Error sending data: %s" % e)
|
||||
self.handle_close()
|
||||
|
||||
def toggle_commands(self):
|
||||
self._send_cmd('c')
|
||||
try:
|
||||
self.push('c')
|
||||
except socket.error, e:
|
||||
self.log.info("Error sending data: %s" % e)
|
||||
self.handle_close()
|
||||
|
||||
def ping(self):
|
||||
self._send_cmd('')
|
||||
|
|
Loading…
Reference in a new issue