diff --git a/Server Plugin/Actions.xml b/Server Plugin/Actions.xml
deleted file mode 100644
index b807e45..0000000
--- a/Server Plugin/Actions.xml
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
- http://
-
- Send Beo4 Source Selection Command
- send_beo4_src
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send Beo4 Key
- send_beo4_key
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send BeoRemote One Source Selection Command
- send_br1_src
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send BeoRemote One Key
- send_br1_key
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Request Device State Update
- request_state_update
-
-
-
- Request BLGW Home Integration Protocol State Updates
- send_hip_query
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send BLGW Home Integration Protocol Command
- send_hip_cmd
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send Free Text Home Integration Protocol Command
- send_hip_cmd2
-
-
-
-
-
-
-
-
- Send Virtual Button
- send_virtual_button
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Post Message in Notification Centre
- post_notification
-
-
-
-
-
-
-
-
-
-
-
- Send All Standby Command
- all_standby
-
-
diff --git a/Server Plugin/Devices.xml b/Server Plugin/Devices.xml
deleted file mode 100644
index 97e5f01..0000000
--- a/Server Plugin/Devices.xml
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-
- B&O Gateway (MLGW, BLGW)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- String
- Audio Source Changed
- Current Audio Source is
-
-
- String
- Audio SourceName Changed
- Current Audio SourceName is
-
-
- String
- Now Playing
- Now Playing
-
-
- Integer
- Count of Active Audio Renderers
- Count of Active Audio Renderers
-
-
- String
- Names of Active Audio Renderers
- Names of Active Audio Renderers
-
-
- Integer
- Count of Active Video Renderers
- Count of Active Video Renderers
-
-
- String
- Names of Active Video Renderers
- Names of Active Video Renderers
-
-
-
-
-
- AV renderer (Beovision, Beosound)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Player Status Changed
- Player Status is
- Current Player Status
- Player Status is
-
-
- Boolean
- Mute
- Mute
-
-
- Integer
- Current Volume
- Current Volume
-
-
- Separator
-
-
-
- Integer
- Channel/Track
- Channel/Track
-
-
- String
- Now Playing
- Now Playing
-
-
- Separator
-
-
-
-
-
-
-
-
- Source Changed
- Source is
- Current Source
- Source is
-
-
- playState
-
-
diff --git a/Server Plugin/Events.xml b/Server Plugin/Events.xml
deleted file mode 100644
index 66473d6..0000000
--- a/Server Plugin/Events.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
- http://
-
- All Standby
-
-
-
- Light Command Received
-
-
-
-
-
-
-
-
-
-
-
-
-
- Control Command Received
-
-
-
-
-
-
-
-
-
-
-
-
-
- BeoRemote Command Received
-
-
-
-
-
-
-
-
-
-
-
-
-
- Virtual Button Pressed
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Server Plugin/MenuItems.xml b/Server Plugin/MenuItems.xml
deleted file mode 100644
index d8b9ee9..0000000
--- a/Server Plugin/MenuItems.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Server Plugin/PluginConfig.xml b/Server Plugin/PluginConfig.xml
deleted file mode 100644
index 93cb36a..0000000
--- a/Server Plugin/PluginConfig.xml
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
- http://
-
-
-
-
-
-
-
-
- set_gateway
-
-
-
- set_gateway
-
-
-
- set_gateway
-
-
-
-
-
-
-
- set_login
-
-
-
- set_login
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- set_default_audio
-
-
-
- set_music_control
- play and control Apple Music
-
-
-
-
-
-
-
-
-
-
-
- set_music_control
-
-
-
- set_trackmode
- prints track info to the Indigo log
-
-
-
-
-
-
-
- set_verbose
- prints device telegrams to the Indigo log
-
-
-
- set_notifymode
- posts information to the Notification Centre
-
-
-
- set_debug
- prints debug info to the Indigo log
-
-
diff --git a/Server Plugin/Resources/ASBridge.py b/Server Plugin/Resources/ASBridge.py
deleted file mode 100644
index 31a6fd4..0000000
--- a/Server Plugin/Resources/ASBridge.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/usr/bin/python
-import indigo
-import logging
-import os
-import unicodedata
-import threading
-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,
- running external appleScripts for more complex control, and
- 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 = self.app.currentTrack().name()
- album = self.app.currentTrack().album()
- artist = self.app.currentTrack().artist()
- number = self.app.currentTrack().trackNumber()
- if name:
- # Deal with tracks with non-ascii characters such as accents
- name = unicodedata.normalize('NFD', name).encode('ascii', 'ignore')
- album = unicodedata.normalize('NFD', album).encode('ascii', 'ignore')
- artist = unicodedata.normalize('NFD', artist).encode('ascii', 'ignore')
- return [name, album, artist, number]
-
- 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':
- self. app.setValue_forKey_('true', 'shuffleEnabled')
- 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):
- # self.app.wind()
-
- # Native wind function can be a bit annoying
- # I provide an alternative below that skips a set number of seconds forwards
- self.set_current_track_position(time)
-
- def rewind(self, time):
- # self.app.rewind()
-
- # Native rewind function can be a bit annoying
- # I provide an alternative below that skips a set number of seconds back
- self.set_current_track_position(time)
-
- # ########################################################################################
- # More complex playback control functions
- def shuffle(self):
- if self.app.shuffleEnabled():
- self.app.setValue_forKey_('false', 'shuffleEnabled')
- else:
- self.app.setValue_forKey_('true', 'shuffleEnabled')
-
- 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 - threaded due to execution time
- @staticmethod
- def run_script(script, debug):
- script = 'run script ("' + script + '" as POSIX file)'
- if debug:
- indigo.server.log(script, level=logging.DEBUG)
-
- def applet(_script):
- # Run an external applescript file
- s = NSAppleScript.alloc().initWithSource_(_script)
- s.executeAndReturnError_(None)
-
- threading.Thread(target=applet, args=(script,)).start()
-
- @staticmethod
- def notify(message, subtitle):
- def applet(body, title):
- # Post message in notification center
- path = os.path.dirname(os.path.abspath(__file__))
- script = 'tell application "' + path + '/Notify.app" to notify("BeoGateway", "' + \
- body + '", "' + title + '")'
- s = NSAppleScript.alloc().initWithSource_(script)
- s.executeAndReturnError_(None)
-
- threading.Thread(target=applet, args=(message, subtitle,)).start()
diff --git a/Server Plugin/Resources/ASBridge.pyc b/Server Plugin/Resources/ASBridge.pyc
deleted file mode 100644
index 0fa8ae4..0000000
Binary files a/Server Plugin/Resources/ASBridge.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/BLHIP_CLIENT.py b/Server Plugin/Resources/BLHIP_CLIENT.py
deleted file mode 100644
index 0ec9dbb..0000000
--- a/Server Plugin/Resources/BLHIP_CLIENT.py
+++ /dev/null
@@ -1,282 +0,0 @@
-import indigo
-import asynchat
-import socket
-import time
-import urllib
-import logging
-from collections import OrderedDict
-
-import Resources.CONSTANTS as CONST
-
-
-class BLHIPClient(asynchat.async_chat):
- """Client to interact with a Beolink Gateway via the Home Integration Protocol
- https://manualzz.com/download/14415327
- Full documentation of states, commands and events can be found in the driver development guide
- https://vdocument.in//blgw-driver-development-guide-blgw-driver-development-guide-7-2016-10-10"""
- def __init__(self, host_address='blgw.local', port=9100, user='admin', pwd='admin', name='BLGW_HIP',
- debug=False, cb=None):
- asynchat.async_chat.__init__(self)
-
- self.debug = debug
-
- self._host = host_address
- self._port = int(port)
- self._user = user
- self._pwd = pwd
- self.name = name
- self.is_connected = False
-
- self._received_data = ''
- self.last_sent = ''
- self.last_sent_at = time.time()
- self.last_received = ''
- self.last_received_at = time.time()
- self.last_message = {}
-
- # Optional callback function
- if cb:
- self.messageCallBack = cb
- else:
- self.messageCallBack = None
-
- # ########################################################################################
- # ##### Open Socket and connect to B&O Gateway
- self.client_connect()
-
- # ########################################################################################
- # ##### Client functions
- def collect_incoming_data(self, data):
- self.is_connected = True
- self._received_data += data
-
- def found_terminator(self):
- # indigo.server.log("Raw Data: " + self._received_data)
- self.last_received = self._received_data
- self.last_received_at = time.time()
-
- if self._received_data == 'error':
- self.handle_close()
-
- if self._received_data == 'e OK f%20%2A/%2A/%2A/%2A':
- indigo.server.log('\tAuthentication Successful!', level=logging.DEBUG)
- self.query(dev_type="AV renderer")
-
- self._received_data = urllib.unquote(self._received_data)
- telegram = self._received_data.replace("%201", "")
- telegram = telegram.split('/')
- header = telegram[0:4]
-
- self._decode(header, telegram)
-
- def _decode(self, header, telegram):
- e_string = str(header[0])
- if e_string[0] == 'e':
- if e_string[2:4] == 'OK' and self.debug:
- indigo.server.log('Command Successfully Processed: ' + str(urllib.unquote(self._received_data)),
- level=logging.DEBUG)
-
- elif e_string[2:5] == 'CMD':
- indigo.server.log('Wrong or Unrecognised Command: ' + str(urllib.unquote(self._received_data)),
- level=logging.WARNING)
-
- elif e_string[2:5] == 'SYN':
- indigo.server.log('Bad Syntax, or Wrong Character Encoding: ' +
- str(urllib.unquote(self._received_data)), level=logging.WARNING)
-
- elif e_string[2:5] == 'ACC':
- indigo.server.log('Zone Access Violation: ' + str(urllib.unquote(self._received_data)),
- level=logging.WARNING)
-
- elif e_string[2:5] == 'LEN':
- indigo.server.log('Received Message Too Long: ' + str(urllib.unquote(self._received_data)),
- level=logging.WARNING)
-
- self._received_data = ""
- return
- else:
- self._received_data = ""
-
- if len(telegram) > 4:
- state = telegram[4].replace('?', '&')
- state = state.split('&')[1:]
-
- message = OrderedDict()
- message['Zone'] = telegram[0][2:].upper()
- message['Room'] = telegram[1].upper()
- message['Type'] = telegram[2].upper()
- message['Device'] = telegram[3]
- message['State_Update'] = OrderedDict()
-
- for s in state:
- if s.split('=')[0] == "nowPlayingDetails":
- play_details = s.split('=')
- if len(play_details[1]) > 0:
- play_details = play_details[1].split('; ')
- message['State_Update']["nowPlayingDetails"] = OrderedDict()
- for p in play_details:
- if p.split(': ')[0] in ['track number', 'channel number']:
- message['State_Update']["nowPlayingDetails"]['channel_track'] = p.split(': ')[1]
- else:
- message['State_Update']["nowPlayingDetails"][p.split(': ')[0]] = p.split(': ')[1]
-
- elif s.split('=')[0] == "sourceUniqueId":
- src = s.split('=')[1].split(':')[0].upper()
- message['State_Update']['source'] = self._srcdictsanitize(CONST.blgw_srcdict, src)
- message['State_Update'][s.split('=')[0]] = s.split('=')[1]
- else:
- message['State_Update'][s.split('=')[0]] = s.split('=')[1]
-
- # call function to find channel details if type = Legacy
- try:
- if 'nowPlayingDetails' in message['State_Update'] \
- and message['State_Update']['nowPlayingDetails']['type'] == 'Legacy':
- self._get_channel_track(message)
- except KeyError:
- pass
-
- if message.get('Type') == 'BUTTON':
- if message['State_Update'].get('STATE') == '0':
- message['State_Update']['Status'] = 'Off'
- else:
- message['State_Update']['Status'] = 'On'
-
- if message.get('Type') == 'DIMMER':
- if message['State_Update'].get('LEVEL') == '0':
- message['State_Update']['Status'] = 'Off'
- else:
- message['State_Update']['Status'] = 'On'
-
- self._report(header, state, message)
-
- def _report(self, header, payload, message):
- self.last_message = message
- if self.messageCallBack:
- self.messageCallBack(self.name, str(list(header)), str(list(payload)), message)
-
- def client_connect(self):
- indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING)
- self.set_terminator(b'\r\n')
- # Create the socket
- try:
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- except socket.error, e:
- indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR)
- self.handle_close()
- # Now connect
- try:
- self.connect((self._host, self._port))
- except socket.gaierror, e:
- indigo.server.log("\tError with address: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.is_connected = True
- indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG)
-
- def handle_connect(self):
- indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING)
- self.send_cmd(self._user)
- self.send_cmd(self._pwd)
- self.statefilter()
-
- def handle_close(self):
- indigo.server.log(self.name + ": Closing socket", level=logging.ERROR)
- self.is_connected = False
- self.close()
-
- def send_cmd(self, telegram):
- try:
- self.push(telegram.encode("ascii") + "\r\n")
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.last_sent = telegram
- self.last_sent_at = time.time()
- if telegram == 'q Main/global/SYSTEM/BeoLink':
- if self.debug:
- indigo.server.log(self.name + " >>-SENT--> : " + telegram, level=logging.DEBUG)
- else:
- indigo.server.log(self.name + " >>-SENT--> : " + telegram, level=logging.INFO)
- time.sleep(0.2)
-
- def query(self, zone='*', room='*', dev_type='*', device='*'):
- query = "q " + zone + "/" + room + "/" + dev_type + '/' + device
-
- # Convert to human readable string
- if zone == '*':
- zone = ' in all zones.'
- else:
- zone = ' in zone ' + zone + '.'
- if room == '*':
- room = ' in all rooms'
- else:
- room = ' in room ' + room
- if dev_type == '*':
- dev_type = ' of all types'
- else:
- dev_type = ' of type ' + dev_type
- if device == '*':
- device = ' all devices'
- else:
- device = ' devices called ' + device
-
- if self.debug:
- indigo.server.log(self.name + ": sending state update request for" + device + dev_type + room + zone,
- level=logging.DEBUG)
- self.send_cmd(query)
-
- def statefilter(self, zone='*', room='*', dev_type='*', device='*'):
- s_filter = "f " + zone + "/" + room + "/" + dev_type + '/' + device
- self.send_cmd(s_filter)
-
- def locationevent(self, event):
- if event in ['leave', 'arrive']:
- event = 'l ' + event
- self.send_cmd(event)
-
- def ping(self):
- self.query('Main', 'global', 'SYSTEM', 'BeoLink')
-
- # Utility Functions
- @staticmethod
- def _srcdictsanitize(d, s):
- result = d.get(s)
- if result is None:
- result = s
- return str(result)
-
- def _get_channel_track(self, message):
- try:
- node = indigo.devices[message['Device']]
- # Get properties
- node_props = node.pluginProps
- source_name = message["State_Update"]["sourceName"].strip().replace(" ", "_")
- if self.debug:
- indigo.server.log('searching device ' + node.name + ' channel list for source ' + source_name,
- level=logging.DEBUG)
- if 'channels' in node_props['sources'][source_name]:
- for channel in node_props['sources'][source_name]['channels']:
- if self.debug:
- indigo.server.log(source_name + " Channel " + channel[0][1:] + " = " + channel[1],
- level=logging.DEBUG)
- if int(channel[0][1:]) == int(
- message["State_Update"]['nowPlayingDetails']["channel_track"]):
- message["State_Update"]["nowPlaying"] = channel[1]
- if self.debug:
- indigo.server.log("Current Channel: " + channel[1], level=logging.DEBUG)
- return
-
- # If source list exhausted then return Unknown
- message["State_Update"]["nowPlaying"] = 'Unknown'
- except KeyError:
- message["State_Update"]["nowPlaying"] = 'Unknown'
diff --git a/Server Plugin/Resources/BLHIP_CLIENT.pyc b/Server Plugin/Resources/BLHIP_CLIENT.pyc
deleted file mode 100644
index d7ec4e8..0000000
Binary files a/Server Plugin/Resources/BLHIP_CLIENT.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/CONSTANTS.py b/Server Plugin/Resources/CONSTANTS.py
deleted file mode 100644
index 7ea7e41..0000000
--- a/Server Plugin/Resources/CONSTANTS.py
+++ /dev/null
@@ -1,683 +0,0 @@
-from collections import OrderedDict
-
-
-# Constants for B&O telegram protocols
-# ########################################################################################
-# Config data (set on initialisation)
-rooms = []
-available_sources = []
-
-standby_state = [
- {'key': 'onOffState', 'value': False},
- {'key': 'playState', 'value': 'Standby'},
- {'key': 'source', 'value': 'Standby'},
- {'key': 'nowPlaying', 'value': 'Unknown'},
- {'key': 'channelTrack', 'value': 0},
- {'key': 'mute', 'value': True},
- {'key': 'volume', 'value': 0},
-]
-
-gw_all_stb = [
- {'key': 'AudioRenderers', 'value': ''},
- {'key': 'VideoRenderers', 'value': ''},
- {'key': 'nAudioRenderers', 'value': 0},
- {'key': 'nVideoRenderers', 'value': 0},
- {'key': 'currentAudioSource', 'value': 'Unknown'},
- {'key': 'currentAudioSourceName', 'value': 'Unknown'},
- {'key': 'nowPlaying', 'value': 'Unknown'},
-]
-
-# ########################################################################################
-# Source Types
-
-source_type_dict = dict(
- [
- ("Video Sources", ("TV", "V.AUX/DTV2", "MEDIA", "V.TAPE/V.MEM/DVD2", "DVD", "DVD2", "CAMERA",
- "SAT/DTV", "PC", "WEB", "DOORCAM", "PHOTO", "USB2", "WEBMEDIA", "AV.IN",
- "HOMEMEDIA", "DVB_RADIO", "DNLA", "RECORDINGS", "CAMERA", "USB", "DNLA-DMR", "YOUTUBE",
- "HOME.APP", "HDMI_1", "HDMI_2", "HDMI_3", "HDMI_4", "HDMI_5", "HDMI_6",
- "HDMI_7", "HDMI_8", "MATRIX_1", "MATRIX_2", "MATRIX_3", "MATRIX_4", "MATRIX_5",
- "MATRIX_6", "MATRIX_7", "MATRIX_8", "MATRIX_9", "MATRIX_10", "MATRIX_11",
- "MATRIX_12", "MATRIX_13", "MATRIX_14", "MATRIX_15", "MATRIX_16", "PERSONAL_1",
- "PERSONAL_2", "PERSONAL_3", "PERSONAL_4", "PERSONAL_5", "PERSONAL_6", "PERSONAL_7",
- "PERSONAL_8")),
- ("Audio Sources", ("RADIO", "A.AUX", "A.TAPE/A.MEM", "CD", "PHONO/N.RADIO", "A.TAPE2/N.MUSIC",
- "SERVER", "SPOTIFY", "CD2/JOIN", "TUNEIN", "DVB_RADIO", "LINE.IN", "BLUETOOTH",
- "MUSIC", "AIRPLAY", "SPOTIFY", "DEEZER", "QPLAY"))
- ]
-)
-
-# ########################################################################################
-# Beo4 Commands
-beo4_srcdict = OrderedDict(
- [
- # Source selection:
- (0x0C, "Standby"),
- (0x47, "Sleep"),
- (0x80, "TV"),
- (0x81, "Radio"),
- (0x82, "V.Aux/DTV2"),
- (0x83, "A.Aux"),
- (0x84, "Media"),
- (0x85, "V.Tape/V.Mem"),
- (0x86, "DVD"),
- (0x87, "Camera"),
- (0x88, "Text"),
- (0x8A, "Sat/DTV"),
- (0x8B, "PC"),
- (0x8C, "Web"),
- (0x8D, "Doorcam"),
- (0x8E, "Photo"),
- (0x90, "USB2"),
- (0x91, "A.Tape/A.Mem"),
- (0x92, "CD"),
- (0x93, "Phono/N.Radio"),
- (0x94, "A.Tape2/N.Music"),
- (0x95, "Server"),
- (0x96, "Spotify"),
- (0x97, "CD2/Join"),
- (0xBF, "AV"),
- ]
-)
-
-beo4_commanddict = OrderedDict(
- [
- # Source selection:
- (0x0C, "Standby"),
- (0x47, "Sleep"),
- (0x80, "TV"),
- (0x81, "Radio"),
- (0x82, "V.Aux/DTV2"),
- (0x83, "A.Aux"),
- (0x84, "Media"),
- (0x85, "V.Tape/V.Mem"),
- (0x86, "DVD"),
- (0x87, "Camera"),
- (0x88, "Text"),
- (0x8A, "Sat/DTV"),
- (0x8B, "PC"),
- (0x8C, "Web"),
- (0x8D, "Doorcam"),
- (0x8E, "Photo"),
- (0x90, "USB2"),
- (0x91, "A.Tape/A.Mem"),
- (0x92, "CD"),
- (0x93, "Phono/N.Radio"),
- (0x94, "A.Tape2/N.Music"),
- (0x95, "Server"),
- (0x96, "Spotify"),
- (0x97, "CD2/Join"),
- (0xBF, "AV"),
- (0xFA, "P-IN-P"),
- # Digits:
- (0x00, "Digit-0"),
- (0x01, "Digit-1"),
- (0x02, "Digit-2"),
- (0x03, "Digit-3"),
- (0x04, "Digit-4"),
- (0x05, "Digit-5"),
- (0x06, "Digit-6"),
- (0x07, "Digit-7"),
- (0x08, "Digit-8"),
- (0x09, "Digit-9"),
- # Source control:
- (0x1E, "Step Up"),
- (0x1F, "Step Down"),
- (0x32, "Rewind"),
- (0x33, "Return"),
- (0x34, "Wind"),
- (0x35, "Go/Play"),
- (0x36, "Stop"),
- (0xD4, "Yellow"),
- (0xD5, "Green"),
- (0xD8, "Blue"),
- (0xD9, "Red"),
- # Sound and picture control
- (0x0D, "Mute"),
- (0x1C, "P.Mute"),
- (0x2A, "Format"),
- (0x44, "Sound/Speaker"),
- (0x5C, "Menu"),
- (0x60, "Volume Up"),
- (0x64, "Volume Down"),
- (0xDA, "Cinema_On"),
- (0xDB, "Cinema_Off"),
- # Other controls:
- (0xF7, "Stand"),
- (0x0A, "Clear"),
- (0x0B, "Store"),
- (0x0E, "Reset"),
- (0x14, "Back"),
- (0x15, "MOTS"),
- (0x20, "Goto"),
- (0x28, "Show Clock"),
- (0x2D, "Eject"),
- (0x37, "Record"),
- (0x3F, "Select"),
- (0x46, "Sound"),
- (0x7F, "Exit"),
- (0xC0, "Shift-0/Edit"),
- (0xC1, "Shift-1/Random"),
- (0xC2, "Shift-2"),
- (0xC3, "Shift-3/Repeat"),
- (0xC4, "Shift-4/Select"),
- (0xC5, "Shift-5"),
- (0xC6, "Shift-6"),
- (0xC7, "Shift-7"),
- (0xC8, "Shift-8"),
- (0xC9, "Shift-9"),
- # Continue functionality:
- (0x70, "Rewind Repeat"),
- (0x71, "Wind Repeat"),
- (0x72, "Step_UP Repeat"),
- (0x73, "Step_DW Repeat"),
- (0x75, "Go Repeat"),
- (0x76, "Green Repeat"),
- (0x77, "Yellow Repeat"),
- (0x78, "Blue Repeat"),
- (0x79, "Red Repeat"),
- (0x7E, "Key Release"),
- # Functions:
- (0x40, "Guide"),
- (0x43, "Info"),
- (0x0F, "Function_1"),
- (0x10, "Function_2"),
- (0x11, "Function_3"),
- (0x12, "Function_4"),
- (0x19, "Function_5"),
- (0x1A, "Function_6"),
- (0x21, "Function_7"),
- (0x22, "Function_8"),
- (0x23, "Function_9"),
- (0x24, "Function_10"),
- (0x25, "Function_11"),
- (0x26, "Function_12"),
- (0x27, "Function_13"),
- (0x39, "Function_14"),
- (0x3A, "Function_15"),
- (0x3B, "Function_16"),
- (0x3C, "Function_17"),
- (0x3D, "Function_18"),
- (0x3E, "Function_19"),
- (0x4B, "Function_20"),
- (0x4C, "Function_21"),
- (0x50, "Function_22"),
- (0x51, "Function_23"),
- (0x7D, "Function_24"),
- (0xA5, "Function_25"),
- (0xA6, "Function_26"),
- (0xA9, "Function_27"),
- (0xAA, "Function_28"),
- (0xDD, "Function_29"),
- (0xDE, "Function_30"),
- (0xE0, "Function_31"),
- (0xE1, "Function_32"),
- (0xE2, "Function_33"),
- (0xE6, "Function_34"),
- (0xE7, "Function_35"),
- (0xF2, "Function_36"),
- (0xF3, "Function_37"),
- (0xF4, "Function_38"),
- (0xF5, "Function_39"),
- (0xF6, "Function_40"),
- # Cursor functions:
- (0x13, "Select"),
- (0xCA, "Cursor_Up"),
- (0xCB, "Cursor_Down"),
- (0xCC, "Cursor_Left"),
- (0xCD, "Cursor_Right"),
- # Light/Control commands
- (0x9B, "Light"),
- (0x9C, "Command"),
- (0x58, "Light Timeout"),
- # Dummy for 'Listen for all commands'
- (0xFF, ""),
- ]
-)
-BEO4_CMDS = {v.upper(): k for k, v in beo4_commanddict.items()}
-
-# BeoRemote One Commands
-beoremoteone_commanddict = OrderedDict(
- [
- # Source, (Cmd, Unit)
- ("Standby", (0x0C, 0)),
- ("TV", (0x80, 0)),
- ("RADIO", (0x81, 0)),
- ("TUNEIN", (0x81, 1)),
- ("DVB_RADIO", (0x81, 2)),
- ("AV.IN", (0x82, 0)),
- ("LINE.IN", (0x83, 0)),
- ("A.AUX", (0x83, 1)),
- ("BLUETOOTH", (0x83, 2)),
- ("HOMEMEDIA", (0x84, 0)),
- ("DNLA", (0x84, 1)),
- ("RECORDINGS", (0x85, 0)),
- ("CAMERA", (0x87, 0)),
- ("FUTURE.USE", (0x89, 0)),
- ("USB", (0x90, 0)),
- ("A.MEM", (0x91, 0)),
- ("CD", (0x92, 0)),
- ("N.RADIO", (0x93, 0)),
- ("A.TAPE2/N.MUSIC", (0x94, 0)),
- ("MUSIC", (0x94, 0)),
- ("DNLA-DMR", (0x94, 1)),
- ("AIRPLAY", (0x94, 2)),
- ("SPOTIFY", (0x96, 0)),
- ("DEEZER", (0x96, 1)),
- ("QPLAY", (0x96, 2)),
- ("JOIN", (0x97, 0)),
- ("WEBMEDIA", (0x8C, 0)),
- ("YOUTUBE", (0x8C, 1)),
- ("HOME.APP", (0x8C, 2)),
- ("HDMI_1", (0xCE, 0)),
- ("HDMI_2", (0xCE, 1)),
- ("HDMI_3", (0xCE, 2)),
- ("HDMI_4", (0xCE, 3)),
- ("HDMI_5", (0xCE, 4)),
- ("HDMI_6", (0xCE, 5)),
- ("HDMI_7", (0xCE, 6)),
- ("HDMI_8", (0xCE, 7)),
- ("MATRIX_1", (0xCF, 0)),
- ("MATRIX_2", (0xCF, 1)),
- ("MATRIX_3", (0xCF, 2)),
- ("MATRIX_4", (0xCF, 3)),
- ("MATRIX_5", (0xCF, 4)),
- ("MATRIX_6", (0xCF, 5)),
- ("MATRIX_7", (0xCF, 6)),
- ("MATRIX_8", (0xCF, 7)),
- ("MATRIX_9", (0xD0, 0)),
- ("MATRIX_10", (0xD0, 1)),
- ("MATRIX_11", (0xD0, 2)),
- ("MATRIX_12", (0xD0, 3)),
- ("MATRIX_13", (0xD0, 4)),
- ("MATRIX_14", (0xD0, 5)),
- ("MATRIX_15", (0xD0, 6)),
- ("MATRIX_16", (0xD0, 7)),
- ("PERSONAL_1", (0xD1, 0)),
- ("PERSONAL_2", (0xD1, 1)),
- ("PERSONAL_3", (0xD1, 2)),
- ("PERSONAL_4", (0xD1, 3)),
- ("PERSONAL_5", (0xD1, 4)),
- ("PERSONAL_6", (0xD1, 5)),
- ("PERSONAL_7", (0xD1, 6)),
- ("PERSONAL_8", (0xD1, 7)),
- ("TV.ON", (0xD2, 0)),
- ("MUSIC.ON", (0xD3, 0)),
- ("PATTERNPLAY", (0xD3, 1)),
- ]
-)
-
-beoremoteone_keydict = OrderedDict(
- [
- (0x0C, "Standby"),
- # Digits:
- (0x00, "Digit-0"),
- (0x01, "Digit-1"),
- (0x02, "Digit-2"),
- (0x03, "Digit-3"),
- (0x04, "Digit-4"),
- (0x05, "Digit-5"),
- (0x06, "Digit-6"),
- (0x07, "Digit-7"),
- (0x08, "Digit-8"),
- (0x09, "Digit-9"),
- # Source control:
- (0x1E, "Step Up"),
- (0x1F, "Step Down"),
- (0x32, "Rewind"),
- (0x33, "Return"),
- (0x34, "Wind"),
- (0x35, "Go/Play"),
- (0x36, "Stop"),
- (0xD4, "Yellow"),
- (0xD5, "Green"),
- (0xD8, "Blue"),
- (0xD9, "Red"),
- # Sound and picture control
- (0x0D, "Mute"),
- (0x1C, "P.Mute"),
- (0x2A, "Format"),
- (0x44, "Sound/Speaker"),
- (0x5C, "Menu"),
- (0x60, "Volume Up"),
- (0x64, "Volume Down"),
- (0xDA, "Cinema_On"),
- (0xDB, "Cinema_Off"),
- # Other controls:
- (0xF7, "Stand"),
- (0x0A, "Clear"),
- (0x0B, "Store"),
- (0x0E, "Reset"),
- (0x14, "Back"),
- (0x15, "MOTS"),
- (0x20, "Goto"),
- (0x28, "Show Clock"),
- (0x2D, "Eject"),
- (0x37, "Record"),
- (0x3F, "Select"),
- (0x46, "Sound"),
- (0x7F, "Exit"),
- (0xC0, "Shift-0/Edit"),
- (0xC1, "Shift-1/Random"),
- (0xC2, "Shift-2"),
- (0xC3, "Shift-3/Repeat"),
- (0xC4, "Shift-4/Select"),
- (0xC5, "Shift-5"),
- (0xC6, "Shift-6"),
- (0xC7, "Shift-7"),
- (0xC8, "Shift-8"),
- (0xC9, "Shift-9"),
- # Continue functionality:
- (0x70, "Rewind Repeat"),
- (0x71, "Wind Repeat"),
- (0x72, "Step_UP Repeat"),
- (0x73, "Step_DW Repeat"),
- (0x75, "Go Repeat"),
- (0x76, "Green Repeat"),
- (0x77, "Yellow Repeat"),
- (0x78, "Blue Repeat"),
- (0x79, "Red Repeat"),
- (0x7E, "Key Release"),
- # Functions:
- (0x40, "Guide"),
- (0x43, "Info"),
- # Cursor functions:
- (0x13, "Select"),
- (0xCA, "Cursor_Up"),
- (0xCB, "Cursor_Down"),
- (0xCC, "Cursor_Left"),
- (0xCD, "Cursor_Right"),
- # Light/Control commands
- (0x9B, "Light"),
- (0x9C, "Command"),
- (0x58, "Light Timeout")
- ]
-)
-
-# ########################################################################################
-# Source Activity
-sourceactivitydict = OrderedDict(
- [
- (0x00, "Unknown"),
- (0x01, "Stop"),
- (0x02, "Play"),
- (0x03, "Wind"),
- (0x04, "Rewind"),
- (0x05, "Record Lock"),
- (0x06, "Standby"),
- (0x07, "Load/No Media"),
- (0x08, "Still Picture"),
- (0x14, "Scan Forward"),
- (0x15, "Scan Reverse"),
- (0xFF, "None"),
- ]
-)
-
-# ########################################################################################
-# ##### MasterLink (not MLGW) Protocol packet constants
-ml_telegram_type_dict = dict(
- [
- (0x0A, "COMMAND"),
- (0x0B, "REQUEST"),
- (0x14, "RESPONSE"),
- (0x2C, "INFO"),
- (0x5E, "CONFIG"),
- ]
-)
-
-ml_command_type_dict = dict(
- [
- (0x04, "MASTER_PRESENT"),
- # REQUEST_DISTRIBUTED_SOURCE: seen when a device asks what source is being distributed
- # subtypes seen 01:request 04:no source 06:has source (byte 13 is source)
- (0x08, "REQUEST_DISTRIBUTED_SOURCE"),
- (0x0D, "BEO4_KEY"),
- (0x10, "STANDBY"),
- (0x11, "RELEASE"), # when a device turns off
- (0x20, "MLGW_REMOTE_BEO4"),
- # REQUEST_LOCAL_SOURCE: Seen when a device asks what source is playing locally to a device
- # subtypes seen 02:request 04:no source 05:secondary source 06:primary source (byte 11 is source)
- # byte 10 is bitmask for distribution: 0x01: coaxial cable - 0x02: MasterLink ML_BUS -
- # 0x08: local screen
- (0x30, "REQUEST_LOCAL_SOURCE"),
- (0x3C, "TIMER"),
- (0x40, "CLOCK"),
- (0x44, "TRACK_INFO"),
- (0x45, "GOTO_SOURCE"),
- # LOCKMANAGER_COMMAND: Lock to Determine what device issues source commands
- # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
- (0x5C, "LOCK_MANAGER_COMMAND"),
- (0x6C, "DISTRIBUTION_REQUEST"),
- (0x82, "TRACK_INFO_LONG"),
- # Source Status
- # byte 10:source - byte 13: 80 when DTV is turned off. 00 when it's on
- # byte 18H 17L: source medium - byte 19: channel/track - byte 21:activity
- # byte 22: 01: audio source 02: video source ff:undefined - byte 23: picture identifier
- (0x87, "STATUS_INFO"),
- (0x94, "VIDEO_TRACK_INFO"),
- #
- # -----------------------------------------------------------------------
- # More packets that we see on the bus, with a guess of the type
- # DISPLAY_SOURCE: Message sent with a payload showing the displayed source name.
- # subtype 3 has the printable source name starting at byte 10 of the payload
- (0x06, "DISPLAY_SOURCE"),
- # START_VIDEO_DISTRIBUTION: Sent when a locally playing source starts being distributed on coaxial cable
- (0x07, "START_VIDEO_DISTRIBUTION"),
- # EXTENDED_SOURCE_INFORMATION: message with 6 subtypes showing information about the source.
- # Printable info at byte 14 of the payload
- # For Radio: 1: "" 2: Genre 3: Country 4: RDS info 5: Associated beo4 button 6: "Unknown"
- # For A.Mem: 1: Genre 2: Album 3: Artist 4: Track name 5: Associated beo4 button 6: "Unknown"
- (0x0B, "EXTENDED_SOURCE_INFORMATION"),
- (0x96, "PC_PRESENT"),
- # PICTURE AND SOUND STATUS
- # byte 0: bit 0-1: sound status - bit 2-3: stereo mode (can be 0 in a 5.1 setup)
- # byte 1: speaker mode (see below)
- # byte 2: audio volume
- # byte 3: picture format identifier (see below)
- # byte 4: bit 0: screen1 mute - bit 1: screen2 mute - bit 2: screen1 active -
- # bit 3: screen2 active - bit 4: cinema mode
- (0x98, "PICTURE_AND_SOUND_STATUS"),
- # Unknown commands - seen on power up and initialisation
- #########################################################
- # On power up all devices send out a request key telegram. If
- # no lock manager is allocated the devices send out a key_lost telegram. The Video Master (or Power
- # Master in older implementations) then asserts a NEW_LOCKmANAGER telegram and assumes responsibility
- # for LOCKMANAGER_COMMAND telegrams until a key transfer occurs.
- # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
- (0x12, "KEY_LOST"), # ?
- # Unknown command with payload of length 1.
- # bit 0: unknown
- # bit 1: unknown
- (0xA0, "NEW_LOCKMANAGER"), # ?
- # Unknown command with payload of length 2
- # bit 0: unknown
- # bit 1: unknown
- # bit 2: unknown
- ]
-)
-
-ml_command_type_request_key_subtype_dict = dict(
- [
- (0x01, "Request Key"),
- (0x02, "Transfer Key"),
- (0x03, "Transfer Impossible"),
- (0x04, "Key Received"),
- (0x05, "Timeout"),
- (0xFF, "Undefined"),
- ]
-)
-
-ml_activity_dict = dict(
- [
- (0x01, "Request Source"),
- (0x02, "Request Source"),
- (0x04, "No Source"),
- (0x06, "Source Active"),
- ]
-)
-
-ml_device_dict = dict(
- [
- (0xC0, "VIDEO MASTER"),
- (0xC1, "AUDIO MASTER"),
- (0xC2, "SOURCE CENTER/SLAVE DEVICE"),
- (0x81, "ALL AUDIO LINK DEVICES"),
- (0x82, "ALL VIDEO LINK DEVICES"),
- (0x83, "ALL LINK DEVICES"),
- (0x80, "ALL"),
- (0xF0, "MLGW"),
- (0x29, "SYSTEM CONTROLLER/TIMER"),
- # Power Master exists in older (pre 1996?) ML implementations. Later revisions enforced the Video Master
- # as lock key manager for the system and the concept was phased out. If your system is older than 2000
- # you may see this device type on the network.
- # reference: https://tidsskrift.dk/daimipb/article/download/7043/6004/0
- (0xFF, "POWER MASTER"), # ?
- ]
-)
-
-ml_pictureformatdict = dict(
- [
- (0x00, "Not known"),
- (0x01, "Known by decoder"),
- (0x02, "4:3"),
- (0x03, "16:9"),
- (0x04, "4:3 Letterbox middle"),
- (0x05, "4:3 Letterbox top"),
- (0x06, "4:3 Letterbox bottom"),
- (0xFF, "Blank picture"),
- ]
-)
-
-ml_selectedsourcedict = dict(
- [
- (0x00, "NONE"),
- (0x0B, "TV"),
- (0x15, "V.TAPE/V.MEM"),
- (0x16, "DVD2"),
- (0x1F, "SAT/DTV"),
- (0x29, "DVD"),
- (0x33, "V.AUX/DTV2"),
- (0x3E, "DOORCAM"),
- (0x47, "PC"),
- (0x6F, "RADIO"),
- (0x79, "A.TAPE/A.MEM"),
- (0x7A, "A.TAPE2/N.MUSIC"),
- (0x8D, "CD"),
- (0x97, "A.AUX"),
- (0xA1, "PHONO/N.RADIO"),
- # Dummy for 'Listen for all sources'
- (0xFE, "ALL"), # have also seen 0xFF as "all"
- (0xFF, "ALL"),
- ]
-)
-
-ml_trackinfo_subtype_dict = dict([(0x05, "Current Source"), (0x07, "Change Source"), ])
-
-ml_sourcekind_dict = dict([(0x01, "audio source"), (0x02, "video source"), (0xFF, "undefined")])
-
-ml_selectedsource_type_dict = dict(
- [
- ("VIDEO", (0x0B, 0x1F)),
- ("VIDEO_PAUSABLE", (0x15, 0x16, 0x29, 0x33)),
- ("AUDIO", (0x6F, 0x97)),
- ("AUDIO_PAUSABLE", (0x8D, 0x79, 0x7A, 0xA1, 0x8D)),
- ("ALL", (0xFE, 0xFF)),
- ("OTHER", (0x47, 0x3E)),
- ]
-)
-
-# ########################################################################################
-# ##### MLGW Protocol packet constants
-mlgw_payloadtypedict = dict(
- [
- (0x01, "Beo4 Command"),
- (0x02, "Source Status"),
- (0x03, "Picture and Sound Status"),
- (0x04, "Light and Control command"),
- (0x05, "All standby notification"),
- (0x06, "BeoRemote One control command"),
- (0x07, "BeoRemote One source selection"),
- (0x20, "MLGW virtual button event"),
- (0x30, "Login request"),
- (0x31, "Login status"),
- (0x32, "Change password request"),
- (0x33, "Change password response"),
- (0x34, "Secure login request"),
- (0x36, "Ping"),
- (0x37, "Pong"),
- (0x38, "Configuration change notification"),
- (0x39, "Request Serial Number"),
- (0x3A, "Serial Number"),
- (0x40, "Location based event"),
- ]
-)
-MLGW_PL = {v.upper(): k for k, v in mlgw_payloadtypedict.items()}
-
-destselectordict = OrderedDict(
- [
- (0x00, "Video Source"),
- (0x01, "Audio Source"),
- (0x05, "Peripheral Video Source (V.TAPE/V.MEM/DVD)"),
- (0x06, "Secondary Peripheral Video Source (V.TAPE2/V.MEM2/DVD2)"),
- (0x0F, "All Products"),
- (0x1B, "MLGW"),
- ]
-)
-CMDS_DEST = {v.upper(): k for k, v in destselectordict.items()}
-
-mlgw_secsourcedict = dict([(0x00, "V.TAPE/V.MEM"), (0x01, "V.TAPE2/DVD2/V.MEM2")])
-mlgw_linkdict = dict([(0x00, "Local/Default Source"), (0x01, "Remote Source/Option 4 Product")])
-
-mlgw_virtualactiondict = dict([(0x01, "PRESS"), (0x02, "HOLD"), (0x03, "RELEASE")])
-
-# for '0x03: Picture and Sound Status'
-mlgw_soundstatusdict = dict([(0x00, "Not muted"), (0x01, "Muted")])
-
-mlgw_speakermodedict = dict(
- [
- (0x01, "Center channel"),
- (0x02, "2 channel stereo"),
- (0x03, "Front surround"),
- (0x04, "4 channel stereo"),
- (0x05, "Full surround"),
- (0xFD, ""), # Dummy for 'Listen for all modes'
- ]
-)
-
-mlgw_screenmutedict = dict([(0x00, "not muted"), (0x01, "muted")])
-mlgw_screenactivedict = dict([(0x00, "not active"), (0x01, "active")])
-mlgw_cinemamodedict = dict([(0x00, "Cinema mode off"), (0x01, "Cinema mode on")])
-mlgw_stereoindicatordict = dict([(0x00, "Mono"), (0x01, "Stereo")])
-
-# for '0x04: Light and Control command'
-mlgw_lctypedict = dict([(0x01, "LIGHT"), (0x02, "CONTROL")])
-
-# for '0x31: Login Status
-mlgw_loginstatusdict = dict([(0x00, "OK"), (0x01, "FAIL")])
-
-# ########################################################################################
-# ##### BeoLink Gateway Protocol packet constants
-blgw_srcdict = dict(
- [
- ("TV", "TV"),
- ("DVD", "DVD"),
- ("RADIO", "RADIO"),
- ("TP1", "A.TAPE/A.MEM"),
- ("TP2", "A.TAPE2/N.MUSIC"),
- ("CD", "CD"),
- ("PH", "PHONO/N.RADIO"),
- ]
-)
-
-blgw_devtypes = OrderedDict(
- [
- ("*", "All"),
- ("SYSTEM", "System"),
- ("AV renderer", "AV Renderer"),
- ("BUTTON", "Button"),
- ("Dimmer", "Dimmer"),
- ("GPIO", "GPIO"),
- ("Thermostat 1 setpoint", "Thermostat 1 setpoint"),
- ("Thermostat 2 setpoints", "Thermostat 2 setpoints")
- ]
-)
diff --git a/Server Plugin/Resources/CONSTANTS.pyc b/Server Plugin/Resources/CONSTANTS.pyc
deleted file mode 100644
index 3583052..0000000
Binary files a/Server Plugin/Resources/CONSTANTS.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/MLCLI_CLIENT.py b/Server Plugin/Resources/MLCLI_CLIENT.py
deleted file mode 100644
index 3ceef3a..0000000
--- a/Server Plugin/Resources/MLCLI_CLIENT.py
+++ /dev/null
@@ -1,462 +0,0 @@
-import indigo
-import asynchat
-import socket
-import time
-import logging
-from collections import OrderedDict
-
-import Resources.CONSTANTS as CONST
-
-
-class MLCLIClient(asynchat.async_chat):
- """Client to monitor raw packet traffic on the Masterlink network via the undocumented command line interface
- of the Bang & Olufsen Gateway."""
- def __init__(self, host_address='blgw.local', port=23, user='admin', pwd='admin', name='ML_CLI',
- debug=False, cb=None):
- asynchat.async_chat.__init__(self)
-
- self.debug = debug
-
- self._host = host_address
- self._port = int(port)
- self._user = user
- self._pwd = pwd
- self.name = name
- self.is_connected = False
-
- self._i = 0
- self._header_lines = 6
- self._received_data = ""
- self.last_sent = ''
- self.last_sent_at = time.time()
- self.last_received = ''
- self.last_received_at = time.time()
- self.last_message = {}
-
- self.isBLGW = False
-
- # Optional callback function
- if cb:
- self.messageCallBack = cb
- else:
- self.messageCallBack = None
-
- # ########################################################################################
- # ##### Open Socket and connect to B&O Gateway
- self.client_connect()
-
- # ########################################################################################
- # ##### Client functions
- def collect_incoming_data(self, data):
- self._received_data += data
-
- def found_terminator(self):
- self.last_received = self._received_data
- self.last_received_at = time.time()
-
- telegram = self._received_data
- self._received_data = ""
-
- # Clear login process lines before processing telegrams
- if self._i <= self._header_lines:
- self._i += 1
- if self._i == self._header_lines - 1:
- indigo.server.log("\tAuthenticated! Gateway type is " + telegram[0:4] + "\n", level=logging.DEBUG)
- if telegram[0:4] != "MLGW":
- self.isBLGW = True
-
- # Process telegrams and return json data in human readable format
- if self._i > self._header_lines:
- if "---- Logging" in telegram:
- # Pong telegram
- header = telegram
- payload = []
- message = OrderedDict([('payload_type', 'Pong'), ('State_Update', dict([('CONNECTION', 'Online')]))])
- self.is_connected = True
- if self.messageCallBack:
- self.messageCallBack(self.name, header, str(list(payload)), message)
- else:
- # ML protocol message detected
- items = telegram.split()[1:]
- if len(items):
- telegram = bytearray()
- for item in items:
- try:
- telegram.append(int(item[:-1], base=16))
- except (ValueError, TypeError):
- # abort if invalid character found
- if self.debug:
- indigo.server.log('Invalid character ' + str(item) + ' found in telegram: ' +
- ''.join(items) + '\nAborting!', level=logging.ERROR)
- break
-
- # Decode any telegram with a valid 9 byte header, excluding typy 0x14 (regular clock sync pings)
- if len(telegram) >= 9:
- # Header: To_Device/From_Device/1/Type/To_Source/From_Source/0/Payload_Type/Length
- header = telegram[:9]
- payload = telegram[9:]
- message = self._decode(telegram)
- self._report(header, payload, message)
-
- def client_connect(self):
- indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING)
- self.set_terminator(b'\r\n')
- # Create the socket
- try:
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- except socket.error, e:
- indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR)
- self.handle_close()
- # Now connect
- try:
- self.connect((self._host, self._port))
- except socket.gaierror, e:
- indigo.server.log("\tError with address: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.is_connected = True
- indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG)
-
- def handle_connect(self):
- indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING)
- self.send_cmd(self._pwd)
- self.send_cmd("_MLLOG ONLINE")
-
- def handle_close(self):
- indigo.server.log(self.name + ": Closing socket", level=logging.ERROR)
- self.is_connected = False
- self.close()
-
- def send_cmd(self, telegram):
- try:
- self.push(telegram + "\r\n")
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.last_sent = telegram
- self.last_sent_at = time.time()
- indigo.server.log(self.name + " >>-SENT--> : " + telegram, level=logging.INFO)
- time.sleep(0.2)
-
- def _report(self, header, payload, message):
- self.last_message = message
- if self.messageCallBack:
- self.messageCallBack(self.name, str(list(header)), str(list(payload)), message)
-
- def ping(self):
- if self.debug:
- indigo.server.log(self.name + " >>-SENT--> : Ping", level=logging.DEBUG)
- self.push('\n')
-
- # ########################################################################################
- # ##### Utility functions
- @staticmethod
- def _hexbyte(byte):
- resultstr = hex(byte)
- if byte < 16:
- resultstr = resultstr[:2] + "0" + resultstr[2]
- return resultstr
-
- def _hexword(self, byte1, byte2):
- resultstr = self._hexbyte(byte2)
- resultstr = self._hexbyte(byte1) + resultstr[2:]
- return resultstr
-
- def _dictsanitize(self, d, s):
- result = d.get(s)
- if result is None:
- result = self._hexbyte(s)
- return str(result)
-
- @staticmethod
- def _get_type(d, s):
- rev_dict = {value: key for key, value in d.items()}
- for i in range(len(list(rev_dict))):
- if s in list(rev_dict)[i]:
- return rev_dict.get(list(rev_dict)[i])
-
- # ########################################################################################
- # ##### Decode Masterlink Protocol packet to a serializable dict
- def _decode(self, telegram):
- # Decode header
- message = OrderedDict()
- self._get_device_info(message, telegram)
- if 'Device' not in message:
- # If ML telegram has been matched to a Masterlink node in the devices list then the 'from_device'
- # key is redundant - it will always be identical to the 'Device' key
- message["from_device"] = self._dictsanitize(CONST.ml_device_dict, telegram[1])
- message["from_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[5])
- message["to_device"] = self._get_device_name(self._dictsanitize(CONST.ml_device_dict, telegram[0]))
- message["to_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[4])
- message["type"] = self._dictsanitize(CONST.ml_telegram_type_dict, telegram[3])
- message["payload_type"] = self._dictsanitize(CONST.ml_command_type_dict, telegram[7])
- message["payload_len"] = telegram[8] + 1
- message["State_Update"] = OrderedDict()
-
- # RELEASE command signifies product standby
- if message.get("payload_type") in ["RELEASE", "STANDBY"]:
- message["State_Update"]["state"] = 'Standby'
-
- # source status info
- # TTFF__TYDSOS__PTLLPS SR____LS______SLSHTR__ACSTPI________________________TRTR______
- if message.get("payload_type") == "STATUS_INFO":
- message["State_Update"]["nowPlaying"] = 'Unknown'
-
- if telegram[8] < 27:
- c_trk = telegram[19]
- else:
- c_trk = telegram[36] * 256 + telegram[37]
-
- message["State_Update"]["nowPlayingDetails"] = OrderedDict(
- [
- ("local_source", telegram[13]),
- ("type", self._dictsanitize(CONST.ml_sourcekind_dict, telegram[22])),
- ("channel_track", c_trk),
- ("source_medium_position", self._hexword(telegram[18], telegram[17])),
- ("picture_format", self._dictsanitize(CONST.ml_pictureformatdict, telegram[23]))
- ]
- )
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[10])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[10]
- self._get_channel_track(message)
- message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[21])
-
- # display source information
- if message.get("payload_type") == "DISPLAY_SOURCE":
- _s = ""
- for i in range(0, telegram[8] - 5):
- _s = _s + chr(telegram[i + 15])
- message["State_Update"]["display_source"] = _s.rstrip()
-
- # extended source information
- if message.get("payload_type") == "EXTENDED_SOURCE_INFORMATION":
- message["State_Update"]["info_type"] = telegram[10]
- _s = ""
- for i in range(0, telegram[8] - 14):
- _s = _s + chr(telegram[i + 24])
- message["State_Update"]["info_value"] = _s
-
- # beo4 command
- if message.get("payload_type") == "BEO4_KEY":
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[10])
- self._get_source_name(source, message)
- message["State_Update"] = OrderedDict(
- [
- ("source", source),
- ("sourceID", telegram[10]),
- ("source_type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[10])),
- ("command", self._dictsanitize(CONST.beo4_commanddict, telegram[11]))
- ]
- )
-
- # audio track info long
- if message.get("payload_type") == "TRACK_INFO_LONG":
- message["State_Update"]["nowPlaying"] = 'Unknown'
- message["State_Update"]["nowPlayingDetails"] = OrderedDict(
- [
- ("type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])),
- ("channel_track", telegram[12]),
- ]
- )
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[11]
- self._get_channel_track(message)
- message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[13])
-
- # video track info
- if message.get("payload_type") == "VIDEO_TRACK_INFO":
- message["State_Update"]["nowPlaying"] = 'Unknown'
- message["State_Update"]["nowPlayingDetails"] = OrderedDict(
- [
- ("source_type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[13])),
- ("channel_track", telegram[11] * 256 + telegram[12])
- ]
- )
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[13])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[13]
- self._get_channel_track(message)
- message["State_Update"]["state"] = self._dictsanitize(CONST.sourceactivitydict, telegram[14])
-
- # track change info
- if message.get("payload_type") == "TRACK_INFO":
- message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_trackinfo_subtype_dict, telegram[9])
-
- # Change source
- if message["State_Update"].get("subtype") == "Change Source":
- message["State_Update"]["prev_source"] = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
- message["State_Update"]["prev_sourceID"] = telegram[11]
- message["State_Update"]["prev_source_type"] = self._get_type(
- CONST.ml_selectedsource_type_dict, telegram[11])
- if len(telegram) > 18:
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[22])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[22]
-
- # Current Source
- if message["State_Update"].get("subtype") == "Current Source":
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[11]
- message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
- message["State_Update"]["state"] = 'Unknown'
- else:
- message["State_Update"]["subtype"] = "Undefined: " + self._hexbyte(telegram[9])
-
- # goto source
- if message.get("payload_type") == "GOTO_SOURCE":
- message["State_Update"]["nowPlaying"] = 'Unknown'
- message["State_Update"]["nowPlayingDetails"] = OrderedDict(
- [
- ("source_type", self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])),
- ("channel_track", telegram[12])
- ]
- )
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[11]
- if telegram[12] not in [0, 255]:
- self._get_channel_track(message)
- # Device sending goto source command is playing
- message["State_Update"]["state"] = 'Play'
-
- # remote request
- if message.get("payload_type") == "MLGW_REMOTE_BEO4":
- message["State_Update"]["command"] = self._dictsanitize(CONST.beo4_commanddict, telegram[14])
- message["State_Update"]["destination"] = self._dictsanitize(CONST.destselectordict, telegram[11])
-
- # request_key
- if message.get("payload_type") == "LOCK_MANAGER_COMMAND":
- message["State_Update"]["subtype"] = self._dictsanitize(
- CONST.ml_command_type_request_key_subtype_dict, telegram[9])
-
- # request distributed audio source
- if message.get("payload_type") == "REQUEST_DISTRIBUTED_SOURCE":
- message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_activity_dict, telegram[9])
- if message["State_Update"].get('subtype') == "Source Active":
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[13])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[13]
- message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[13])
-
- # request local audio source
- if message.get("payload_type") == "REQUEST_LOCAL_SOURCE":
- message["State_Update"]["subtype"] = self._dictsanitize(CONST.ml_activity_dict, telegram[9])
- if message["State_Update"].get('subtype') == "Source Active":
- source = self._dictsanitize(CONST.ml_selectedsourcedict, telegram[11])
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- message["State_Update"]["sourceID"] = telegram[11]
- message["State_Update"]["source_type"] = self._get_type(CONST.ml_selectedsource_type_dict, telegram[11])
-
- # request local audio source
- if message.get("payload_type") == "PICTURE_AND_SOUND_STATUS":
- message["State_Update"]["sound_status"] = OrderedDict(
- [
- ("mute_status", self._dictsanitize(CONST.mlgw_soundstatusdict, telegram[10])),
- ("speaker_mode", self._dictsanitize(CONST.mlgw_speakermodedict, telegram[11])),
- # ("stereo_mode", self._dictsanitize(CONST.mlgw_stereoindicatordict, telegram[9]))
- ]
- )
- # message["State_Update"]["picture_status"] = OrderedDict()
-
- message['State_Update']['source'] = 'Unknown'
- message['State_Update']['sourceName'] = 'Unknown'
- message["State_Update"]["state"] = 'Unknown'
- message["volume"] = int(telegram[12])
-
- return message
-
- @staticmethod
- def _get_device_info(message, telegram):
- # Loop over the device list
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- # Get properties
- node_props = node.pluginProps
-
- # Skip netlink devices with no ml_id
- if node_props['mlid'] == 'NA':
- continue
-
- # identify if the mlid is a number or a text string
- try:
- ml_id = int(node_props['mlid'], base=16)
- except ValueError:
- # If it is a text mlid then loop over the dictionary and get the numeric key
- for item in CONST.ml_device_dict.items():
- if item[1] == node_props['mlid']:
- ml_id = int(item[0])
-
- if ml_id == int(telegram[1]): # Match ML_ID
- try:
- message["Zone"] = node_props['zone'].upper()
- except KeyError:
- pass
- message["Room"] = node_props['room'].upper()
- message["Type"] = "AV RENDERER"
- message["Device"] = node.name
- break
-
- def _get_channel_track(self, message):
- try:
- node = indigo.devices[message['Device']]
- # Get properties
- node_props = node.pluginProps
- source_name = message["State_Update"]["sourceName"].strip().replace(" ", "_")
- if self.debug:
- indigo.server.log('searching device ' + node.name + ' channel list for source ' + source_name,
- level=logging.DEBUG)
- if 'channels' in node_props['sources'][source_name]:
- for channel in node_props['sources'][source_name]['channels']:
- if self.debug:
- indigo.server.log(source_name + " Channel " + channel[0][1:] + " = " + channel[1],
- level=logging.DEBUG)
- if int(channel[0][1:]) == int(
- message["State_Update"]['nowPlayingDetails']["channel_track"]):
- message["State_Update"]["nowPlaying"] = channel[1]
- if self.debug:
- indigo.server.log("Current Channel: " + channel[1], level=logging.DEBUG)
- return
-
- # If source list exhausted then return Unknown
- message["State_Update"]["nowPlaying"] = 'Unknown'
- except KeyError:
- message["State_Update"]["nowPlaying"] = 'Unknown'
-
- @staticmethod
- def _get_device_name(dev):
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- # Get properties
- node_props = node.pluginProps
- if node_props['mlid'] == dev:
- return node.name
- return dev
-
- @staticmethod
- def _get_source_name(source, message):
- if CONST.available_sources:
- for src in CONST.available_sources:
- if str(src[0]) == str(source):
- message["State_Update"]["sourceName"] = src[1]
- return
- # If source list exhausted then return Unknown
- message["State_Update"]["sourceName"] = 'Unknown'
diff --git a/Server Plugin/Resources/MLCLI_CLIENT.pyc b/Server Plugin/Resources/MLCLI_CLIENT.pyc
deleted file mode 100644
index b9b8d8d..0000000
Binary files a/Server Plugin/Resources/MLCLI_CLIENT.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/MLCONFIG.py b/Server Plugin/Resources/MLCONFIG.py
deleted file mode 100644
index c1fc0bc..0000000
--- a/Server Plugin/Resources/MLCONFIG.py
+++ /dev/null
@@ -1,298 +0,0 @@
-import indigo
-import asyncore
-import json
-import requests
-import logging
-from requests.auth import HTTPDigestAuth, HTTPBasicAuth
-from collections import OrderedDict
-
-import Resources.CONSTANTS as CONST
-
-
-class MLConfig:
-
- def __init__(self, host_address='blgw.local', user='admin', pwd='admin', debug=False):
- self.debug =debug
-
- self._host = host_address
- self._user = user
- self._pwd = pwd
-
- self._download_data()
-
- def _download_data(self):
- try:
- indigo.server.log('Downloading configuration data from Gateway...', level=logging.WARNING)
- url = 'http://' + self._host + '/mlgwpservices.json'
- # try Basic Auth next (this is needed for the BLGW)
- response = requests.get(url, auth=HTTPBasicAuth(self._user, self._pwd))
-
- if response.status_code == 401:
- # try Digest Auth first (this is needed for the MLGW)
- response = requests.get(url, auth=HTTPDigestAuth(self._user, self._pwd))
-
- if response.status_code == 401:
- return
- else:
- # Once logged in successfully download and process the configuration data
- configurationdata = json.loads(response.text)
- self.configure_mlgw(configurationdata)
- except ValueError:
- pass
-
- def configure_mlgw(self, data):
- if "BeoGateway" not in indigo.devices.folders:
- indigo.devices.folder.create("BeoGateway")
- folder_id = indigo.devices.folders.getId("BeoGateway")
-
- # Check to see if any devices already exist to avoid duplication
- _nodes = []
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- _nodes.append(int(node.address))
-
- indigo.server.log('Processing Gateway configuration data...\n', level=logging.WARNING)
- # Check to see if gateway device exists and create one if not
- try:
- gw = indigo.device.create(
- protocol=indigo.kProtocol.Plugin,
- name="Bang and Olufsen Gateway",
- description="Automatically generated device for BeoGateway plugin:\n"
- " - Please do not delete or rename!\n"
- " - Editing device properties for advanced users only!",
- deviceTypeId="BOGateway",
- pluginId='uk.co.lukes_plugins.BeoGateway.plugin',
- folder=folder_id,
- address=1
- )
- except ValueError:
- gw = indigo.devices['Bang and Olufsen Gateway']
-
- try:
- gateway_type = 'blgw'
- gw.replacePluginPropsOnServer(
- {
- 'serial_no': data['sn'],
- 'project': data['project'],
- 'installer': str(data['installer']['name']),
- 'contact': str(data['installer']['contact']),
- 'isBLGW': 'BLGW'
- }
- )
- except KeyError:
- gateway_type = 'mlgw'
- gw.replacePluginPropsOnServer(
- {
- 'serial_no': data['sn'],
- 'project': data['project'],
- 'isBLGW': 'MLGW'
- }
- )
-
- # Replace States
- gw.updateStatesOnServer(CONST.gw_all_stb)
-
- # Loop over the config data to find the rooms, devices and sources in the installation
- for zone in data["zones"]:
- # Get rooms
- if int(zone['number']) == 240:
- continue
- room = OrderedDict()
- room['Room_Number'] = zone['number']
- if gateway_type == 'blgw':
- # BLGW arranges rooms within zones
- room['Zone'] = str(zone['name']).split('/')[0]
- room['Room_Name'] = str(zone['name']).split('/')[1]
- elif gateway_type == 'mlgw':
- # MLGW has no zoning concept - devices are arranged in rooms only
- room['Room_Name'] = str(zone['name'])
-
- # Get products
- for product in zone["products"]:
- device = OrderedDict()
-
- # Device identification
- device['Device'] = str(product["name"])
- device['MLN'] = product["MLN"]
- device['ML_ID'] = ''
- try:
- device['Serial_num'] = str(product["sn"])
- except KeyError:
- device['Serial_num'] = 'NA'
-
- # Physical location
- if gateway_type == 'blgw':
- # BLGW arranges rooms within zones
- device['Zone'] = str(zone['name']).split('/')[0]
- device['Room'] = str(zone['name']).split('/')[1]
- elif gateway_type == 'mlgw':
- # MLGW has no zoning concept - devices are arranged in rooms only
- device['Room'] = str(zone['name'])
- device['Room_Number'] = str(zone["number"])
-
- # Source information
- device['Sources'] = dict()
-
- for source in product["sources"]:
- src_name = str(source["name"]).strip().replace(' ', '_')
- device['Sources'][src_name] = dict()
- for selectCmd in source["selectCmds"]:
- if gateway_type == 'blgw':
- # get source information from the BLGW config file
- if str(source['sourceId']) == '':
- source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper()
- else:
- source_id = str(source['sourceId'].split(':')[0])
- source_id = self._srcdictsanitize(CONST.blgw_srcdict, source_id).upper()
- device['Sources'][src_name]['source'] = source_id
- device['Sources'][src_name]['uniqueID'] = str(source['sourceId'])
- else:
- # MLGW config file is structured differently
- source_id = self._srcdictsanitize(CONST.beo4_commanddict, source['selectID']).upper()
- device['Sources'][src_name]['source'] = source_id
- source_tuple = (str(source_id), str(source["name"]))
- device['Sources'][src_name]['BR1_cmd'] = \
- dict([('command', int(selectCmd["cmd"])), ('unit', int(selectCmd["unit"]))])
-
- # Establish the channel list for sources with favourites lists
- if 'channels' in source:
- device['Sources'][src_name]['channels'] = []
- for channel in source['channels']:
- c_num = 'c'
- if gateway_type == 'blgw':
- num = channel['selectSEQ'][::2]
- else:
- num = channel['selectSEQ'][:-1]
- for n in num:
- c_num += str(n)
- c = (c_num, str(channel['name']))
- device['Sources'][src_name]['channels'].append(c)
-
- if source_tuple not in CONST.available_sources:
- CONST.available_sources.append(source_tuple)
-
- # Create indigo devices to represent the B&O AV renderers in the installation
- if int(device['MLN']) not in _nodes:
- if self.debug:
- indigo.server.log("New Device! Creating Indigo Device " + device['Device'],
- level=logging.DEBUG)
-
- node = indigo.device.create(
- protocol=indigo.kProtocol.Plugin,
- name=device['Device'],
- description="Automatically generated device for BeoGateway plugin:\n"
- "- Device data sourced from gateway config:\n"
- "- Please do not delete or rename!\n"
- "- Editing device properties for advanced users only!",
- deviceTypeId="AVrenderer",
- pluginId='uk.co.lukes_plugins.BeoGateway.plugin',
- folder=folder_id,
- address=device['MLN'],
- props={
- 'serial_no': device['Serial_num'],
- 'mlid': 'NA',
- 'zone': room['Zone'],
- 'room': device['Room'],
- 'roomnum': device['Room_Number'],
- 'sources': device['Sources']
- }
- )
-
- # Update the device states
- node.updateStatesOnServer(CONST.standby_state)
- node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff)
- else:
- # if node already exists, update the properties in case they have been updated
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- if int(node.address) == int(device['MLN']):
- if self.debug:
- indigo.server.log("Old Device! Updating Properties for " + device['Device'] + "\n",
- level=logging.DEBUG)
- # Update the name of the device
- node.name = device['Device']
- node.description = "Automatically generated device for BeoGateway plugin:\n" \
- " - Device data sourced from gateway config:\n" \
- " - Please do not delete or rename!\n" \
- " - Editing device properties for advanced users only!"
- node.replaceOnServer()
- # Update the properties of the device
- node_props = node.pluginProps
- node_props.update(
- {
- 'serial_no': device['Serial_num'],
- 'zone': room['Zone'],
- 'room': device['Room'],
- 'roomnum': device['Room_Number'],
- 'sources': device['Sources']
- }
- )
- node.replacePluginPropsOnServer(node_props)
-
- # Update the device states
- node.stateListOrDisplayStateIdChanged()
- node.updateStatesOnServer(CONST.standby_state)
- node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff)
- indigo.device.moveToFolder(node.id, value=folder_id)
- break
-
- # Keep track of the room data
- CONST.rooms.append(room)
-
- # Report details of the configuration
- n_devices = indigo.devices.len(filter="uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer") - 1
- indigo.server.log('Found ' + str(n_devices) + ' AV Renderers!', level=logging.DEBUG)
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- indigo.server.log('\tMLN ' + str(node.address) + ': ' + str(node.name), level=logging.INFO)
- indigo.server.log('\tFound ' + str(len(CONST.available_sources)) + ' Available Sources [Type, Name]:',
- level=logging.DEBUG)
- for i in range(len(CONST.available_sources)):
- indigo.server.log('\t\t' + str(list(CONST.available_sources[i])), level=logging.INFO)
- indigo.server.log('\tDone!\n', level=logging.DEBUG)
-
- @staticmethod
- def get_masterlink_id(mlgw, mlcli):
- # Identify the MasterLink ID of products
- indigo.server.log("Finding MasterLink ID of products:", level=logging.WARNING)
- if mlgw.is_connected and mlcli.is_connected:
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- indigo.server.log("Finding MasterLink ID of product " + node.name, level=logging.WARNING)
- # Ping the device with a light timeout to elicit a ML telegram containing its ML_ID
- mlgw.send_beo4_cmd(int(node.address),
- CONST.CMDS_DEST.get("AUDIO SOURCE"),
- CONST.BEO4_CMDS.get("LIGHT TIMEOUT"))
- node_props = node.pluginProps
- if node_props['serial_no'] in [None, 'NA', '']:
- # If this is a MasterLink product it has no serial number...
- # loop to until expected response received from ML Command Line Interface
- test = True
- while test:
- try:
- if mlcli.last_message['from_device'] == "MLGW" and \
- mlcli.last_message['payload_type'] == "MLGW_REMOTE_BEO4" and \
- mlcli.last_message['State_Update']['command'] == "Light Timeout":
-
- if node_props['mlid'] == 'NA':
- node_props['mlid'] = mlcli.last_message.get('to_device')
- node_props['serial_no'] = 'NA'
- node.replacePluginPropsOnServer(node_props)
-
- indigo.server.log("\tMasterLink ID of product " +
- node.name + " is " + node_props['mlid'] + ".\n",
- level=logging.DEBUG)
- test = False
- except KeyError:
- asyncore.loop(count=1, timeout=0.2)
-
- else:
- # If this is a NetLink product then it has a serial number and no ML_ID
- indigo.server.log("\tNetworkLink ID of product " + node.name + " is " +
- node_props['serial_no'] + ". No MasterLink ID assigned.\n",
- level=logging.DEBUG)
-
- # ########################################################################################
- # Utility Functions
- @staticmethod
- def _srcdictsanitize(d, s):
- result = d.get(s)
- if result is None:
- result = s
- return str(result)
diff --git a/Server Plugin/Resources/MLCONFIG.pyc b/Server Plugin/Resources/MLCONFIG.pyc
deleted file mode 100644
index d8c3b25..0000000
Binary files a/Server Plugin/Resources/MLCONFIG.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/MLGW_CLIENT.py b/Server Plugin/Resources/MLGW_CLIENT.py
deleted file mode 100644
index e10f46c..0000000
--- a/Server Plugin/Resources/MLGW_CLIENT.py
+++ /dev/null
@@ -1,431 +0,0 @@
-import indigo
-import asynchat
-import socket
-import time
-import logging
-from collections import OrderedDict
-
-import Resources.CONSTANTS as CONST
-
-
-class MLGWClient(asynchat.async_chat):
- """Client to interact with a B&O Gateway via the MasterLink Gateway Protocol
- http://mlgw.bang-olufsen.dk/source/documents/mlgw_2.24b/MlgwProto0240.pdf"""
-
- def __init__(self, host_address='blgw.local', port=9000, user='admin', pwd='admin', name='MLGW_Protocol',
- debug=False, cb=None):
- asynchat.async_chat.__init__(self)
-
- self.debug = debug
-
- self._host = host_address
- self._port = int(port)
- self._user = user
- self._pwd = pwd
- self.name = name
- self.is_connected = False
-
- self._received_data = bytearray()
- self.last_sent = ''
- self.last_sent_at = time.time()
- self.last_received = ''
- self.last_received_at = time.time()
- self.last_message = {}
-
- # Optional callback function
- if cb:
- self.messageCallBack = cb
- else:
- self.messageCallBack = None
-
- # Expose dictionaries via API
- self.BEO4_CMDS = CONST.BEO4_CMDS
- self.BEORMT1_CMDS = CONST.beoremoteone_commanddict
- self.CMDS_DEST = CONST.CMDS_DEST
- self.MLGW_PL = CONST.MLGW_PL
-
- # ########################################################################################
- # ##### Open Socket and connect to B&O Gateway
- self.client_connect()
-
- # ########################################################################################
- # ##### Client functions
- def collect_incoming_data(self, data):
- self.is_connected = True
- self._received_data = bytearray(data)
-
- bit1 = int(self._received_data[0]) # Start of Header == 1
- bit2 = int(self._received_data[1]) # Message Type
- bit3 = int(self._received_data[2]) # Payload length
- bit4 = int(self._received_data[3]) # Spare Bit/End of Header == 0
-
- payload = bytearray()
- for item in self._received_data[4:bit3 + 4]:
- payload.append(item)
-
- if bit1 == 1 and len(self._received_data) == bit3 + 4 and bit4 == 0:
- self.found_terminator(bit2, payload)
- else:
- if self.debug:
- indigo.server.log("Incomplete Telegram Received: " + str(list(self._received_data)) + " - Ignoring!\n",
- level=logging.ERROR)
- self._received_data = ""
-
- def found_terminator(self, msg_type, payload):
- self.last_received = str(list(self._received_data))
- self.last_received_at = time.time()
-
- header = self._received_data[0:4]
- self._received_data = ""
- self._decode(msg_type, header, payload)
-
- def _decode(self, msg_type, header, payload):
- message = OrderedDict()
- payload_type = self._dictsanitize(CONST.mlgw_payloadtypedict, msg_type)
-
- if payload_type == "MLGW virtual button event":
- virtual_btn = payload[0]
- if len(payload) < 1:
- virtual_action = self._getvirtualactionstr(0x01)
- else:
- virtual_action = self._getvirtualactionstr(payload[1])
-
- message["payload_type"] = payload_type
- message["button"] = virtual_btn
- message["action"] = virtual_action
-
- elif payload_type == "Login status":
- if payload == 0:
- indigo.server.log("\tAuthentication Failed: Incorrect Password", level=logging.ERROR)
- self.handle_close()
- message['Connected'] = "False"
- return
- else:
- indigo.server.log("\tLogin successful to " + self._host, level=logging.DEBUG)
- self.is_connected = True
- message["payload_type"] = payload_type
- message['Connected'] = "True"
- self.get_serial()
-
- elif payload_type == "Pong":
- self.is_connected = True
- message = OrderedDict([('payload_type', 'Pong'), ('State_Update', dict([('CONNECTION', 'Online')]))])
-
- elif payload_type == "Serial Number":
- sn = ''
- for c in payload:
- sn += chr(c)
- message["payload_type"] = payload_type
- message['serial_Num'] = sn
-
- elif payload_type == "Source Status":
- self._get_device_info(message, payload)
- message["payload_type"] = payload_type
- message["MLN"] = payload[0]
- message["State_Update"] = OrderedDict()
- message["State_Update"]["nowPlaying"] = 'Unknown'
- message["State_Update"]["nowPlayingDetails"] = OrderedDict(
- [
- ("channel_track", self._hexword(payload[4], payload[5])),
- ("source_medium_position", self._hexword(payload[2], payload[3])),
- ("picture_format", self._getdictstr(CONST.ml_pictureformatdict, payload[7])),
- ]
- )
- source = self._getselectedsourcestr(payload[1]).upper()
- self._get_source_name(source, message)
- message["State_Update"]["source"] = source
- self._get_channel_track(message)
- message["State_Update"]["state"] = self._getdictstr(CONST.sourceactivitydict, payload[6])
-
- elif payload_type == "Picture and Sound Status":
- self._get_device_info(message, payload)
- message["payload_type"] = payload_type
- message["MLN"] = payload[0]
- message["State_Update"] = OrderedDict()
- message["State_Update"]["sound_status"] = OrderedDict(
- [
- ("mute_status", self._getdictstr(CONST.mlgw_soundstatusdict, payload[1])),
- ("speaker_mode", self._getdictstr(CONST.mlgw_speakermodedict, payload[2])),
- ("stereo_mode", self._getdictstr(CONST.mlgw_stereoindicatordict, payload[9])),
- ]
- )
- message["State_Update"]["picture_status"] = OrderedDict(
- [
- ("screen1_mute", self._getdictstr(CONST.mlgw_screenmutedict, payload[4])),
- ("screen1_active", self._getdictstr(CONST.mlgw_screenactivedict, payload[5])),
- ("screen2_mute", self._getdictstr(CONST.mlgw_screenmutedict, payload[6])),
- ("screen2_active", self._getdictstr(CONST.mlgw_screenactivedict, payload[7])),
- ("cinema_mode", self._getdictstr(CONST.mlgw_cinemamodedict, payload[8])),
- ]
- )
- message["State_Update"]["state"] = 'Unknown'
- message["volume"] = int(payload[3])
-
- elif payload_type == "All standby notification":
- message["payload_type"] = payload_type
- message["command"] = "All Standby"
-
- elif payload_type == "Light and Control command":
- if CONST.rooms:
- for room in CONST.rooms:
- if room['Room_Number'] == payload[0]:
- try:
- message["Zone"] = room['Zone'].upper()
- except KeyError:
- pass
- message["Room"] = room['Room_Name'].upper()
- message["Type"] = self._getdictstr(CONST.mlgw_lctypedict, payload[1]).upper() + " COMMAND"
- message["Device"] = 'Beo4/BeoRemote One'
- message["payload_type"] = payload_type
- message["room_number"] = str(payload[0])
- message["command"] = self._getbeo4commandstr(payload[2])
-
- if message != '':
- self._report(header, payload, message)
-
- def client_connect(self):
- indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING)
- self.set_terminator(b'\r\n')
- # Create the socket
- try:
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- except socket.error, e:
- indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR)
- self.handle_close()
- # Now connect
- try:
- self.connect((self._host, self._port))
- except socket.gaierror, e:
- indigo.server.log("\tError with address: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.is_connected = True
- indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG)
-
- def handle_connect(self):
- login = []
- for c in self._user:
- login.append(c)
- login.append(0x00)
- for c in self._pwd:
- login.append(c)
-
- indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING)
- self._send_cmd(CONST.MLGW_PL.get("LOGIN REQUEST"), login)
-
- def handle_close(self):
- indigo.server.log(self.name + ": Closing socket", level=logging.ERROR)
- self.is_connected = False
- self.close()
-
- def _report(self, header, payload, message):
- self.last_message = message
- if self.messageCallBack:
- self.messageCallBack(self.name, str(list(header)), str(list(payload)), message)
-
- # ########################################################################################
- # ##### mlgw send functions
-
- # send_cmd command to mlgw
- def _send_cmd(self, msg_type, payload):
- # Construct header
- telegram = [1, msg_type, len(payload), 0]
- # append payload
- for p in payload:
- telegram.append(p)
-
- try:
- self.push(str(bytearray(telegram)))
- except socket.timeout, e:
- indigo.server.log("\tSocket connection to timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.last_sent = str(bytearray(telegram))
- self.last_sent_at = time.time()
- if msg_type != CONST.MLGW_PL.get("PING"):
- indigo.server.log(
- self.name + " >>-SENT--> "
- + self._getpayloadtypestr(msg_type)
- + ": "
- + str(list(telegram)),
- level=logging.INFO)
- else:
- if self.debug:
- indigo.server.log(
- self.name + " >>-SENT--> "
- + self._getpayloadtypestr(msg_type)
- + ": "
- + str(list(telegram)),
- level=logging.DEBUG
- )
-
- # Sleep to allow msg to arrive
- time.sleep(0.2)
-
- # Ping the gateway
- def ping(self):
- self._send_cmd(CONST.MLGW_PL.get("PING"), "")
-
- # Get serial number of mlgw
- def get_serial(self):
- if self.is_connected:
- # Request serial number
- self._send_cmd(CONST.MLGW_PL.get("REQUEST SERIAL NUMBER"), "")
-
- # send_cmd Beo4 command to mlgw
- def send_beo4_cmd(self, mln, dest, cmd, sec_source=0x00, link=0x00):
- payload = [
- mln, # byte[0] MLN
- dest, # byte[1] Dest-Sel (0x00, 0x01, 0x05, 0x0f)
- cmd, # byte[2] Beo4 Command
- sec_source, # byte[3] Sec-Source
- link] # byte[4] Link
- self._send_cmd(CONST.MLGW_PL.get("BEO4 COMMAND"), payload)
-
- # send_cmd BeoRemote One command to mlgw
- def send_beoremoteone_cmd(self, mln, cmd, network_bit=0x00):
- payload = [
- mln, # byte[0] MLN
- cmd, # byte[1] Beo4 Command
- 0x00, # byte[2] AV (needs to be 0)
- network_bit] # byte[3] Network_bit (0 = local source, 1 = network source)
- self._send_cmd(CONST.MLGW_PL.get("BEOREMOTE ONE CONTROL COMMAND"), payload)
-
- # send_cmd BeoRemote One Source Select to mlgw
- def send_beoremoteone_select_source(self, mln, cmd, unit, network_bit=0x00):
- payload = [
- mln, # byte[0] MLN
- cmd, # byte[1] Beoremote One Command
- unit, # byte[2] Unit
- 0x00, # byte[3] AV (needs to be 0)
- network_bit] # byte[4] Network_bit (0 = local source, 1 = network source)
- self._send_cmd(CONST.MLGW_PL.get("BEOREMOTE ONE SOURCE SELECTION"), payload)
-
- def send_virtualbutton(self, button, action):
- payload = [
- button, # byte[0] Button number
- action] # byte[1] Action ID
- self._send_cmd(CONST.MLGW_PL.get("MLGW VIRTUAL BUTTON EVENT"), payload)
-
- # ########################################################################################
- # ##### Utility functions
-
- @staticmethod
- def _hexbyte(byte):
- resultstr = hex(byte)
- if byte < 16:
- resultstr = resultstr[:2] + "0" + resultstr[2]
- return resultstr
-
- def _hexword(self, byte1, byte2):
- resultstr = self._hexbyte(byte2)
- resultstr = self._hexbyte(byte1) + resultstr[2:]
- return resultstr
-
- def _dictsanitize(self, d, s):
- result = d.get(s)
- if result is None:
- result = "UNKNOWN (type=" + self._hexbyte(s) + ")"
- return str(result)
-
- # ########################################################################################
- # ##### Decode MLGW Protocol packet to readable string
-
- # Get message string for mlgw packet's payload type
- def _getpayloadtypestr(self, payloadtype):
- result = CONST.mlgw_payloadtypedict.get(payloadtype)
- if result is None:
- result = "UNKNOWN (type = " + self._hexbyte(payloadtype) + ")"
- return str(result)
-
- def _getbeo4commandstr(self, command):
- result = CONST.beo4_commanddict.get(command)
- if result is None:
- result = "CMD = " + self._hexbyte(command)
- return result
-
- def _getvirtualactionstr(self, action):
- result = CONST.mlgw_virtualactiondict.get(action)
- if result is None:
- result = "Action = " + self._hexbyte(action)
- return result
-
- def _getselectedsourcestr(self, source):
- result = CONST.ml_selectedsourcedict.get(source)
- if result is None:
- result = "SRC = " + self._hexbyte(source)
- return result
-
- def _getspeakermodestr(self, source):
- result = CONST.mlgw_speakermodedict.get(source)
- if result is None:
- result = "mode = " + self._hexbyte(source)
- return result
-
- def _getdictstr(self, mydict, mykey):
- result = mydict.get(mykey)
- if result is None:
- result = self._hexbyte(mykey)
- return result
-
- @staticmethod
- def _get_source_name(source, message):
- if CONST.available_sources:
- for src in CONST.available_sources:
- if src[1] == source:
- message["State_Update"]["sourceName"] = src[0]
- return
- # If source list exhausted then return Unknown
- message["State_Update"]["sourceName"] = 'Unknown'
-
- @staticmethod
- def _get_device_info(message, payload):
- # Loop over the device list
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- # Get properties
- node_props = node.pluginProps
-
- if int(node.address) == int(payload[0]): # Match MLN
- try:
- message["Zone"] = node_props['zone'].upper()
- except KeyError:
- pass
- message["Room"] = node_props['room'].upper()
- message["Type"] = "AV RENDERER"
- message["Device"] = node.name
- break
-
- def _get_channel_track(self, message):
- try:
- node = indigo.devices[message['Device']]
- # Get properties
- node_props = node.pluginProps
- source_name = message["State_Update"]["sourceName"].strip().replace(" ", "_")
- if self.debug:
- indigo.server.log('searching device ' + node.name + ' channel list for source ' + source_name,
- level=logging.DEBUG)
- if 'channels' in node_props['sources'][source_name]:
- for channel in node_props['sources'][source_name]['channels']:
- if self.debug:
- indigo.server.log(source_name + " Channel " + channel[0][1:] + " = " + channel[1],
- level=logging.DEBUG)
- if int(channel[0][1:]) == int(
- message["State_Update"]['nowPlayingDetails']["channel_track"]):
- message["State_Update"]["nowPlaying"] = channel[1]
- if self.debug:
- indigo.server.log("Current Channel: " + channel[1], level=logging.DEBUG)
- return
-
- # If source list exhausted then return Unknown
- message["State_Update"]["nowPlaying"] = 'Unknown'
- except KeyError:
- message["State_Update"]["nowPlaying"] = 'Unknown'
diff --git a/Server Plugin/Resources/MLGW_CLIENT.pyc b/Server Plugin/Resources/MLGW_CLIENT.pyc
deleted file mode 100644
index d6c86c8..0000000
Binary files a/Server Plugin/Resources/MLGW_CLIENT.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/MLtn_CLIENT.py b/Server Plugin/Resources/MLtn_CLIENT.py
deleted file mode 100644
index de1361d..0000000
--- a/Server Plugin/Resources/MLtn_CLIENT.py
+++ /dev/null
@@ -1,370 +0,0 @@
-import indigo
-import asynchat
-import socket
-import time
-import logging
-from collections import OrderedDict
-
-import Resources.CONSTANTS as CONST
-
-
-class MLtnClient(asynchat.async_chat):
- """Client to monitor network activity on a Masterlink Gateway via the telnet monitor"""
- def __init__(self, host_address='mlgw.local', port=23, user='admin', pwd='admin', name='MLGW_HIP',
- debug=False, cb=None):
- asynchat.async_chat.__init__(self)
-
- self.debug = debug
-
- self._host = host_address
- self._port = int(port)
- self._user = user
- self._pwd = pwd
- self.name = name
- self.is_connected = False
-
- self._i = 0
- self._header_lines = 4
- self._received_data = ''
- self.last_sent = ''
- self.last_sent_at = time.time()
- self.last_received = ''
- self.last_received_at = time.time()
- self.last_message = {}
-
- self.isBLGW = False
-
- # Optional callback function
- if cb:
- self.messageCallBack = cb
- else:
- self.messageCallBack = None
-
- # ########################################################################################
- # ##### Open Socket and connect to B&O Gateway
- self.client_connect()
-
- # ########################################################################################
- # ##### Client functions
- def collect_incoming_data(self, data):
- self._received_data += data
-
- def found_terminator(self):
- self.last_received = self._received_data
- self.last_received_at = time.time()
-
- items = self._received_data.split(' ')
-
- if self._i <= self._header_lines:
- self._i += 1
- if self._received_data[0:4] != "MLGW":
- self.isBLGW = True
- if self._i == self._header_lines - 1:
- if self.debug:
- indigo.server.log("\t" + self._received_data, level=logging.DEBUG)
- if self._received_data == 'incorrect password':
- self.handle_close()
-
- else:
- try:
- self._decode(items)
- except IndexError:
- pass
-
- self._received_data = ""
-
- def _decode(self, items):
- header = items[3][:-1]
- telegram_starts = len(''.join(items[:4])) + 4
- 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)
-
- if header == 'resource_found':
- message['State_Update'] = telegram[0]
-
- if header == 'action_executed':
- message = self._decode_action(telegram, message)
-
- if header == 'command_executed':
- message = self._decode_command(telegram, message)
-
- if header == 'macro_fired':
- message['Zone'] = telegram[0].upper()
- message['Room'] = telegram[1].upper()
- message['Macro_Name'] = telegram[3]
-
- if header == 'trigger_fired':
- message = self._decode_trigger(telegram, message)
-
- self._report(header, telegram, message)
-
- def _decode_ip(self, telegram, message):
- if ''.join(telegram).split(':')[0] == 'Integration Protocol login':
- chars = ''.join(telegram).split(':')[1][2:].split(' ')
- message['Payload'] = ''
- for c in chars:
- if c == '0x0':
- message['Payload'] += '0'
- else:
- message['Payload'] += chr(int(c, base=16))
-
- if ''.join(telegram).split(':')[0] == 'Integration Protocol':
- if ''.join(telegram).split(':')[1] == ' processed serial number request':
- message['Payload'] = 'processed serial number request'
- else:
- s = ''.join(telegram).split(' ')
- message['Type'] = 'Send Beo4 Command'
- message[s[5]] = s[6]
- message['Payload'] = OrderedDict()
- for k in range(10, len(s)):
- if k == 10:
- message['Payload']['to_MLN'] = int(s[k], base=16)
- if k == 11:
- message['Payload']['Destination'] = self._dictsanitize(
- CONST.destselectordict, int(s[k], base=16))
- if k == 12:
- message['Payload']['Command'] = self._dictsanitize(
- CONST.beo4_commanddict, int(s[k], base=16)).upper()
- if k == 13:
- message['Payload']['Sec-Source'] = self._dictsanitize(
- CONST.mlgw_secsourcedict, int(s[k], base=16))
- if k == 14:
- message['Payload']['Link'] = self._dictsanitize(
- CONST.mlgw_linkdict, int(s[k], base=16))
- if k > 14:
- message['Payload']['cmd' + str(k - 9)] = self._dictsanitize(
- CONST.beo4_commanddict, int(s[k], base=16))
- return message
-
- @staticmethod
- def _decode_action(telegram, message):
- message['Zone'] = telegram[0].upper()
- message['Room'] = telegram[1].upper()
- message['Type'] = telegram[2].upper()
- message['Device'] = telegram[3]
- message['State_Update'] = OrderedDict()
- if message.get('Type') == 'BUTTON':
- if telegram[4].split('=')[0] == '_SET STATE?STATE':
- message['State_Update']['STATE'] = telegram[4].split('=')[1]
- if message['State_Update'].get('STATE') == '0':
- message['State_Update']['Status'] = "Off"
- else:
- message['State_Update']['Status'] = "On"
- else:
- message['State_Update']['STATE'] = telegram[4]
-
- if message.get('Type') == 'DIMMER': # e.g. DownstairsHallwayDIMMERWall LightSTATE_UPDATE?LEVEL=5
- if telegram[4].split('=')[0] == '_SET STATE?LEVEL':
- message['State_Update']['LEVEL'] = telegram[4].split('=')[1]
- if message['State_Update'].get('LEVEL') == '0':
- message['State_Update']['Status'] = "Off"
- else:
- message['State_Update']['Status'] = "On"
- else:
- message['State_Update']['STATE'] = telegram[4]
- return message
-
- @staticmethod
- def _decode_command(telegram, message):
- message['Zone'] = telegram[0].upper()
- message['Room'] = telegram[1].upper()
- message['Type'] = telegram[2].upper()
- message['Device'] = telegram[3]
- message['State_Update'] = OrderedDict()
- if message.get('Type') == 'BUTTON':
- if telegram[4].split('=')[0] == '_SET STATE?STATE':
- message['State_Update']['STATE'] = telegram[4].split('=')[1]
- if message['State_Update'].get('STATE') == '0':
- message['State_Update']['Status'] = "Off"
- else:
- message['State_Update']['Status'] = "On"
- else:
- message['State_Update']['STATE'] = telegram[4]
-
- if message.get('Type') == 'DIMMER':
- if telegram[4].split('=')[0] == '_SET STATE?LEVEL':
- message['State_Update']['LEVEL'] = telegram[4].split('=')[1]
- if message['State_Update'].get('LEVEL') == '0':
- message['State_Update']['Status'] = "Off"
- else:
- message['State_Update']['Status'] = "On"
- else:
- message['State_Update']['STATE'] = telegram[4]
- return message
-
- def _decode_trigger(self, telegram, message):
- message['Zone'] = telegram[0].upper()
- message['Room'] = telegram[1].upper()
- message['Type'] = telegram[2].upper()
- message['Device'] = telegram[3]
- message['State_Update'] = OrderedDict()
-
- if message.get('Type') == 'BUTTON':
- if telegram[4].split('=')[0] == 'STATE_UPDATE?STATE':
- message['State_Update']['STATE'] = telegram[4].split('=')[1]
- if message['State_Update'].get('STATE') == '0':
- message['State_Update']['Status'] = "Off"
- else:
- message['State_Update']['Status'] = "On"
- else:
- message['State_Update']['STATE'] = telegram[4]
-
- if message.get('Type') == 'DIMMER':
- if telegram[4].split('=')[0] == 'STATE_UPDATE?LEVEL':
- message['State_Update']['LEVEL'] = telegram[4].split('=')[1]
- if message['State_Update'].get('LEVEL') == '0':
- message['State_Update']['Status'] = "Off"
- else:
- message['State_Update']['Status'] = "On"
- else:
- message['State_Update']['STATE'] = telegram[4]
-
- if message.get('Type') == 'AV RENDERER':
- if telegram[4][:5] == 'Light':
- state = telegram[4][6:].split('&')
- message['State_Update']['type'] = 'Light Command'
- for s in state:
- message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1].title()
- if message['State_Update'].get('command') == ' Cmd':
- message['State_Update']['command'] = self._dictsanitize(CONST.beo4_commanddict,
- int(s[13:].strip())).title()
- elif telegram[4][:7] == 'Control':
- state = telegram[4][6:].split('&')
- message['State_Update']['type'] = 'Control Command'
- for s in state:
- message['State_Update'][s.split('=')[0].lower()] = s.split('=')[1]
- if message['State_Update'].get('command') == ' cmd':
- message['State_Update']['command'] = self._dictsanitize(CONST.beo4_commanddict,
- int(s[13:].strip())).title()
- elif telegram[4] == 'All standby':
- message['State_Update']['command'] = telegram[4]
-
- else:
- state = telegram[4][13:].split('&')
- for s in state:
- if s.split('=')[0] == 'sourceUniqueId':
- src = s.split('=')[1].split(':')[0].upper()
- message['State_Update']['source'] = self._srcdictsanitize(CONST.blgw_srcdict, src)
- message['State_Update'][s.split('=')[0]] = s.split('=')[1]
- elif s.split('=')[0] == 'nowPlayingDetails':
- message['State_Update']['nowPlayingDetails'] = OrderedDict()
- details = s.split('=')[1].split(';')
- if len(details) > 1:
- for d in details:
- if d.split(':')[0].strip() in ['track number', 'channel number']:
- message['State_Update']['nowPlayingDetails']['channel_track'] \
- = d.split(':')[1].strip()
- else:
- message['State_Update']['nowPlayingDetails'][d.split(':')[0].strip()] \
- = d.split(':')[1].strip()
- else:
- message['State_Update'][s.split('=')[0]] = s.split('=')[1]
- return message
-
- def _report(self, header, telegram, message):
- self.last_message = message
- if self.messageCallBack:
- self.messageCallBack(self.name, ''.join(header).upper(), ''.join(telegram), message)
-
- def client_connect(self):
- indigo.server.log('Connecting to host at ' + self._host + ', port ' + str(self._port), level=logging.WARNING)
- self.set_terminator(b'\r\n')
- # Create the socket
- try:
- self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
- except socket.error, e:
- indigo.server.log("Error creating socket: " + str(e), level=logging.ERROR)
- self.handle_close()
- # Now connect
- try:
- self.connect((self._host, self._port))
- except socket.gaierror, e:
- indigo.server.log("\tError with address: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("\tError opening connection: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.is_connected = True
- indigo.server.log("\tConnected to B&O Gateway", level=logging.DEBUG)
-
- def handle_connect(self):
- indigo.server.log("\tAttempting to Authenticate...", level=logging.WARNING)
- self._send_cmd(self._pwd)
- self._send_cmd("MONITOR")
-
- def handle_close(self):
- indigo.server.log(self.name + ": Closing socket", level=logging.ERROR)
- self.is_connected = False
- self.close()
-
- def _send_cmd(self, telegram):
- try:
- self.push(telegram + "\r\n")
- except socket.timeout, e:
- indigo.server.log("\tSocket connection timed out: " + str(e), level=logging.ERROR)
- self.handle_close()
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
- else:
- self.last_sent = telegram
- self.last_sent_at = time.time()
- indigo.server.log(self.name + " >>-SENT--> : " + telegram, level=logging.INFO)
- time.sleep(0.2)
-
- def toggle_events(self):
- try:
- self.push('e')
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
-
- def toggle_macros(self):
- try:
- self.push('m')
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
-
- def toggle_commands(self):
- try:
- self.push('c')
- except socket.error, e:
- indigo.server.log("Error sending data: " + str(e), level=logging.ERROR)
- self.handle_close()
-
- def ping(self):
- self._send_cmd('')
-
- # ########################################################################################
- # ##### Utility functions
- @staticmethod
- def _hexbyte(byte):
- resultstr = hex(byte)
- if byte < 16:
- resultstr = resultstr[:2] + "0" + resultstr[2]
- return resultstr
-
- def _dictsanitize(self, d, s):
- result = d.get(s)
- if result is None:
- result = "UNKNOWN (type=" + self._hexbyte(s) + ")"
- return str(result)
-
- @staticmethod
- def _srcdictsanitize(d, s):
- result = d.get(s)
- if result is None:
- result = s
- return str(result)
diff --git a/Server Plugin/Resources/MLtn_CLIENT.pyc b/Server Plugin/Resources/MLtn_CLIENT.pyc
deleted file mode 100644
index 7be73e3..0000000
Binary files a/Server Plugin/Resources/MLtn_CLIENT.pyc and /dev/null differ
diff --git a/Server Plugin/Resources/Notify.app/Contents/Info.plist b/Server Plugin/Resources/Notify.app/Contents/Info.plist
deleted file mode 100644
index 429fb28..0000000
--- a/Server Plugin/Resources/Notify.app/Contents/Info.plist
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
- CFBundleAllowMixedLocalizations
-
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- applet
- CFBundleIconFile
- applet
- CFBundleIdentifier
- com.apple.ScriptEditor.id.Notify
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- Notify
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- aplt
- LSMinimumSystemVersionByArchitecture
-
- x86_64
- 10.6
-
- LSRequiresCarbon
-
- NSAppleEventsUsageDescription
- This script needs to control other applications to run.
- NSAppleMusicUsageDescription
- This script needs access to your music to run.
- NSCalendarsUsageDescription
- This script needs access to your calendars to run.
- NSCameraUsageDescription
- This script needs access to your camera to run.
- NSContactsUsageDescription
- This script needs access to your contacts to run.
- NSHomeKitUsageDescription
- This script needs access to your HomeKit Home to run.
- NSMicrophoneUsageDescription
- This script needs access to your microphone to run.
- NSPhotoLibraryUsageDescription
- This script needs access to your photos to run.
- NSRemindersUsageDescription
- This script needs access to your reminders to run.
- NSSiriUsageDescription
- This script needs access to Siri to run.
- NSSystemAdministrationUsageDescription
- This script needs access to administer this system to run.
- OSAAppletShowStartupScreen
-
- OSAAppletStayOpen
-
- WindowState
-
- bundleDividerCollapsed
-
- bundlePositionOfDivider
- 0.0
- dividerCollapsed
-
- eventLogLevel
- 2
- name
- ScriptWindowState
- positionOfDivider
- 419
- savedFrame
- 523 274 700 678 0 0 1680 1025
- selectedTab
- description
-
-
-
diff --git a/Server Plugin/Resources/Notify.app/Contents/MacOS/applet b/Server Plugin/Resources/Notify.app/Contents/MacOS/applet
deleted file mode 100644
index 7927912..0000000
Binary files a/Server Plugin/Resources/Notify.app/Contents/MacOS/applet and /dev/null differ
diff --git a/Server Plugin/Resources/Notify.app/Contents/PkgInfo b/Server Plugin/Resources/Notify.app/Contents/PkgInfo
deleted file mode 100644
index 3253614..0000000
--- a/Server Plugin/Resources/Notify.app/Contents/PkgInfo
+++ /dev/null
@@ -1 +0,0 @@
-APPLaplt
\ No newline at end of file
diff --git a/Server Plugin/Resources/Notify.app/Contents/Resources/Scripts/main.scpt b/Server Plugin/Resources/Notify.app/Contents/Resources/Scripts/main.scpt
deleted file mode 100644
index c478d35..0000000
Binary files a/Server Plugin/Resources/Notify.app/Contents/Resources/Scripts/main.scpt and /dev/null differ
diff --git a/Server Plugin/Resources/Notify.app/Contents/Resources/applet.icns b/Server Plugin/Resources/Notify.app/Contents/Resources/applet.icns
deleted file mode 100644
index 33f6b86..0000000
Binary files a/Server Plugin/Resources/Notify.app/Contents/Resources/applet.icns and /dev/null differ
diff --git a/Server Plugin/Resources/Notify.app/Contents/Resources/applet.rsrc b/Server Plugin/Resources/Notify.app/Contents/Resources/applet.rsrc
deleted file mode 100644
index 9471572..0000000
Binary files a/Server Plugin/Resources/Notify.app/Contents/Resources/applet.rsrc and /dev/null differ
diff --git a/Server Plugin/Resources/Notify.app/Contents/Resources/description.rtfd/TXT.rtf b/Server Plugin/Resources/Notify.app/Contents/Resources/description.rtfd/TXT.rtf
deleted file mode 100644
index 4d315a3..0000000
--- a/Server Plugin/Resources/Notify.app/Contents/Resources/description.rtfd/TXT.rtf
+++ /dev/null
@@ -1,5 +0,0 @@
-{\rtf1\ansi\ansicpg1252\cocoartf2636
-\cocoatextscaling0\cocoaplatform0{\fonttbl}
-{\colortbl;\red255\green255\blue255;}
-{\*\expandedcolortbl;;}
-}
\ No newline at end of file
diff --git a/Server Plugin/Resources/Scripts/blue.scpt b/Server Plugin/Resources/Scripts/blue.scpt
deleted file mode 100644
index c68227c..0000000
Binary files a/Server Plugin/Resources/Scripts/blue.scpt and /dev/null differ
diff --git a/Server Plugin/Resources/Scripts/green.scpt b/Server Plugin/Resources/Scripts/green.scpt
deleted file mode 100644
index fb4aa4a..0000000
Binary files a/Server Plugin/Resources/Scripts/green.scpt and /dev/null differ
diff --git a/Server Plugin/Resources/Scripts/red.scpt b/Server Plugin/Resources/Scripts/red.scpt
deleted file mode 100644
index 8fd8a2e..0000000
Binary files a/Server Plugin/Resources/Scripts/red.scpt and /dev/null differ
diff --git a/Server Plugin/Resources/Scripts/yellow.scpt b/Server Plugin/Resources/Scripts/yellow.scpt
deleted file mode 100644
index 2f082f5..0000000
Binary files a/Server Plugin/Resources/Scripts/yellow.scpt and /dev/null differ
diff --git a/Server Plugin/Resources/__init__.py b/Server Plugin/Resources/__init__.py
deleted file mode 100644
index a6131c1..0000000
--- a/Server Plugin/Resources/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# init
diff --git a/Server Plugin/Resources/__init__.pyc b/Server Plugin/Resources/__init__.pyc
deleted file mode 100644
index c3063b0..0000000
Binary files a/Server Plugin/Resources/__init__.pyc and /dev/null differ
diff --git a/Server Plugin/plugin.py b/Server Plugin/plugin.py
deleted file mode 100644
index 184cfcf..0000000
--- a/Server Plugin/plugin.py
+++ /dev/null
@@ -1,1277 +0,0 @@
-import indigo
-import asyncore
-import json
-import time
-import logging
-from datetime import datetime
-
-import Resources.CONSTANTS as CONST
-import Resources.MLCONFIG as MLCONFIG
-import Resources.MLGW_CLIENT as MLGW
-import Resources.MLCLI_CLIENT as MLCLI
-import Resources.BLHIP_CLIENT as BLHIP
-import Resources.MLtn_CLIENT as MLtn
-import Resources.ASBridge as ASBridge
-
-
-class Plugin(indigo.PluginBase):
-
- def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs):
- indigo.PluginBase.__init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs)
-
- self.pollinterval = 595 # ping sent out at 9:55, response evaluated at 10:00
- self.trackmode = self.pluginPrefs.get("trackMode")
- self.verbose = self.pluginPrefs.get("verboseMode")
- self.notifymode = self.pluginPrefs.get("notifyMode")
- self.debug = self.pluginPrefs.get("debugMode")
- self.default_audio_source = self.pluginPrefs.get("defaultAudio")
- self.itunes_control = self.pluginPrefs.get("iTunesControl")
- self.itunes_source = self.pluginPrefs.get("iTunesSource")
- self.goto_flag = datetime(1982, 04, 01, 13, 30, 00, 342380)
-
- self.triggers = []
-
- self.host = str(self.pluginPrefs.get('address')).encode('ascii')
- self.port = [int(self.pluginPrefs.get('mlgw_port')),
- int(self.pluginPrefs.get('hip_port')),
- 23] # Telnet port - 23
- self.user = str(self.pluginPrefs.get('userID')).encode('ascii')
- self.pwd = str(self.pluginPrefs.get('password')).encode('ascii')
-
- # Instantiate an AppleScriptBridge MusicController for N.MUSIC control of apple Music
- self.iTunes = ASBridge.MusicController()
-
- def triggerStartProcessing(self, trigger):
- self.triggers.append(trigger)
-
- def getDeviceStateList(self, dev):
- stateList = indigo.PluginBase.getDeviceStateList(self, dev)
-
- if stateList is not None:
- if dev.deviceTypeId in self.devicesTypeDict and dev.deviceTypeId == u"AVrenderer":
- # Add dynamic states onto stateList for devices of type AV renderer
- try:
- sources = dev.pluginProps['sources']
- for source_name in sources:
- stateList.append(
- {
- "Disabled": False,
- "Key": "source." + source_name,
- "StateKey": sources[source_name]['source'],
- "StateLabel": "Source is " + source_name,
- "TriggerLabel": "Source is " + source_name,
- "Type": 50
- }
- )
- except KeyError:
- indigo.server.log("Device " + dev.name + " does not have state key 'sources'\n",
- level=logging.WARNING)
- pass
- return stateList
-
- def deviceStartComm(self, dev):
- dev.stateListOrDisplayStateIdChanged()
-
- def __del__(self):
- indigo.PluginBase.__del__(self)
-
- # ########################################################################################
- # ##### Indigo UI menu constructors
- @staticmethod
- def zonelistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = [("*", "All"), ("Main", "Main")]
- for room in CONST.rooms:
- if (room['Zone'], room['Zone']) not in myarray:
- myarray.append((room['Zone'], room['Zone']))
- return myarray
-
- @staticmethod
- def roomlistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = [("99", "Any")]
- for room in CONST.rooms:
- myarray.append((room['Room_Number'], room['Room_Name']))
- return myarray
-
- @staticmethod
- def roomlistgenerator2(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = [("*", "All"), ("global", "global")]
- for room in CONST.rooms:
- myarray.append((room['Room_Name'], room['Room_Name']))
- return myarray
-
- @staticmethod
- def keylistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = []
- for item in CONST.beo4_commanddict.items():
- myarray.append(item)
- return myarray
-
- @staticmethod
- def keylistgenerator2(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = []
- for item in CONST.beoremoteone_keydict.items():
- myarray.append(item)
- return myarray
-
- @staticmethod
- def beo4sourcelistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = []
- for item in CONST.beo4_srcdict.items():
- myarray.append(item)
- return myarray
-
- @staticmethod
- def beo4sourcelistgenerator2(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = [('Any Source', 'Any Source'), ('Any Audio', 'Any Audio'), ('Any Video', 'Any Video')]
- for item in CONST.beo4_srcdict.items():
- myarray.append(item)
- return myarray
-
- @staticmethod
- def br1sourcelistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = []
- for item in CONST.available_sources:
- myarray.append(item)
- return myarray
-
- @staticmethod
- def destinationlistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = []
- for item in CONST.destselectordict.items():
- myarray.append(item)
- return myarray
-
- @staticmethod
- def srcactivitylistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = [(0x00, "Unknown")]
- for item in CONST.sourceactivitydict.items():
- if item not in myarray:
- myarray.append(item)
- return myarray
-
- @staticmethod
- def hiptypelistgenerator(filter="", valuesDict=None, typeId="", targetId=0):
- myarray = []
- for item in CONST.blgw_devtypes.items():
- myarray.append(item)
- return myarray
-
- # ########################################################################################
- # ##### Indigo UI Prefs
- def set_login(self, ui):
- # If LogIn data is updated in config, update the values in the plugin
- self.user = str(ui.get('userID')).encode('ascii')
- self.pwd = str(ui.get('password')).encode('ascii')
- indigo.server.log("BeoGateway device login details updated!", level=logging.DEBUG)
-
- def set_gateway(self, ui):
- # If gateway network address data is updated in config, update the values in the plugin
- self.host = str(ui.get('address')).encode('ascii')
- self.port = [int(ui.get('mlgw_port')),
- int(ui.get('hip_port')),
- 23] # Telnet port - 23
- indigo.server.log("BeoGateway device network address details updated!", level=logging.DEBUG)
-
- def set_trackmode(self, ui):
- # If Track Mode setting is updated in config, update the value in the plugin
- self.trackmode = ui.get("trackMode")
- indigo.server.log("Track reporting set to " + str(self.trackmode), level=logging.DEBUG)
-
- def set_verbose(self, ui):
- # If Verbose Mode setting is updated in config, update the value in the plugin
- self.verbose = ui.get("verboseMode")
- indigo.server.log("Verbose Mode set to " + str(self.verbose), level=logging.DEBUG)
-
- def set_notifymode(self, ui):
- # If Notify Mode setting is updated in config, update the value in the plugin
- self.notifymode = ui.get("notifyMode")
- indigo.server.log("Verbose Mode set to " + str(self.notifymode), level=logging.DEBUG)
-
- def set_debug(self, ui):
- # If Debug Mode setting is updated in config, update the value in the plugin
- self.debug = ui.get("debugMode")
-
- # Set the debug flag for the clients
- self.mlcli.debug = self.debug
- self.mlgw.debug = self.debug
- if self.mlcli.isBLGW:
- self.blgw.debug = self.debug
- else:
- self.mltn.debug = self.debug
-
- # Report the debug flag change
- indigo.server.log("Debug Mode set to " + str(self.debug), level=logging.DEBUG)
-
- def set_music_control(self, ui):
- # If Apple Music Control setting is updated in config, update the value in the plugin
- self.itunes_control = ui.get("iTunesControl")
- self.itunes_source = ui.get("iTunesSource")
-
- if self.itunes_control:
- indigo.server.log("Apple Music Control enabled on source: " + str(self.itunes_source), level=logging.DEBUG)
- else:
- indigo.server.log("Apple Music Control set to " + str(self.itunes_control), level=logging.DEBUG)
-
- def set_default_audio(self, ui):
- # Define default audio source for AVrenderers
- self.default_audio_source = ui.get("defaultAudio")
- indigo.server.log("Default Audio Source set to " + str(self.default_audio_source), level=logging.DEBUG)
-
- # ########################################################################################
- # ##### Indigo UI Actions
- def send_beo_4key(self, action, device):
- device_id = int(device.address)
- key_code = int(action.props.get("keyCode", 0))
- destination = int(action.props.get("destination", 0))
- link = int(action.props.get("linkcmd", 0))
- if destination == 0x06:
- self.mlgw.send_beo4_cmd(device_id, destination, key_code, 0x01, link)
- else:
- self.mlgw.send_beo4_cmd(device_id, destination, key_code, 0x00, link)
-
- def send_beo4_src(self, action, device):
- device_id = int(device.address)
- key_code = int(action.props.get("keyCode", 0))
- destination = int(action.props.get("destination", 0))
- link = int(action.props.get("linkcmd", 0))
- if destination == 0x06:
- self.mlgw.send_beo4_cmd(device_id, destination, key_code, 0x01, link)
- else:
- self.mlgw.send_beo4_cmd(device_id, destination, key_code, 0x00, link)
-
- def send_br1_key(self, action, device):
- device_id = int(device.address)
- key_code = int(action.props.get("keyCode", 0))
- network_bit = int(action.props.get("netBit", 0))
- self.mlgw.send_beoremoteone_cmd(device_id, key_code, network_bit)
-
- def send_br1_src(self, action, device):
- device_id = int(device.address)
- key_code = str(action.props.get("keyCode", 0))
- network_bit = int(action.props.get("netBit", 0))
- try:
- key_code = CONST.beoremoteone_commanddict.get(key_code)
- self.mlgw.send_beoremoteone_select_source(device_id, key_code[0], key_code[1], network_bit)
- except KeyError:
- pass
-
- def send_hip_cmd(self, action):
- zone = str(action.props.get("zone", 0))
- room = str(action.props.get("room", 0))
- device_type = str(action.props.get("devType", 0))
- device_id = str(action.props.get("deviceID", 0))
- hip_command = str(action.props.get("hip_cmd", 0))
- telegram = "c " + zone + "/" + room + "/" + device_type + "/" + device_id + "/" + hip_command
- if self.mlcli.isBLGW:
- self.blgw.send_cmd(telegram)
-
- def send_hip_cmd2(self, action):
- hip_command = str(action.props.get("hip_cmd", 0))
- if self.mlcli.isBLGW:
- self.blgw.send_cmd(hip_command)
-
- def send_hip_query(self, action):
- zone = str(action.props.get("zone", 0))
- room = str(action.props.get("room", 0))
- device_type = str(action.props.get("devType", 0))
- device_id = str(action.props.get("deviceID", 0))
- if self.mlcli.isBLGW:
- self.blgw.query(zone, room, device_type, device_id)
-
- def request_state_update(self, action, device):
- action_id = str(action.props.get("id", 0))
- zone = str(device.pluginProps['zone'])
- room = str(device.pluginProps['room'])
- device_type = "AV renderer"
- device_id = str(device.name)
- if self.mlcli.isBLGW:
- self.blgw.query(zone, room, device_type, device_id)
-
- def send_virtual_button(self, action):
- button_id = int(action.props.get("buttonID", 0))
- button_action = int(action.props.get("action", 0))
- self.mlgw.send_virtualbutton(button_id, button_action)
-
- def post_notification(self, action):
- title = str(action.props.get("title", 0))
- body = str(action.props.get("body", 0))
- self.iTunes.notify(body, title)
-
- def all_standby(self, action):
- self.mlgw.send_beo4_cmd(1, CONST.CMDS_DEST.get("ALL PRODUCTS"), CONST.BEO4_CMDS.get("STANDBY"))
-
- def request_serial_number(self):
- self.mlgw.get_serial()
-
- def request_device_update(self):
- if self.mlcli.isBLGW:
- self.blgw.query(dev_type="AV renderer")
-
- def reset_clients(self):
- self.check_connection(self.mlgw)
- self.check_connection(self.mlcli)
-
- if self.mlcli.isBLGW:
- self.check_connection(self.blgw)
- else:
- self.check_connection(self.mltn)
-
- # ########################################################################################
- # ##### Indigo UI Events
- def light_key(self, message):
- room = message['room_number']
- key_code = CONST.BEO4_CMDS.get(message['command'].upper())
-
- for trigger in self.triggers:
- props = trigger.globalProps["uk.co.lukes_plugins.BeoGateway.plugin"]
- if trigger.pluginTypeId == "lightKey" and \
- (props["room"] == str(99) or props["room"] == str(room)) and \
- props["keyCode"] == str(key_code):
- indigo.trigger.execute(trigger)
- break
-
- def control_key(self, message):
- room = message['room_number']
- key_code = CONST.BEO4_CMDS.get(message['command'].upper())
-
- for trigger in self.triggers:
- props = trigger.globalProps["uk.co.lukes_plugins.BeoGateway.plugin"]
- if trigger.pluginTypeId == "controlkey" and \
- (props["room"] == str(99) or props["room"] == str(room)) and \
- props["keyCode"] == str(key_code):
- indigo.trigger.execute(trigger)
- break
-
- def beo4_key(self, message):
- source = message['State_Update']['source']
- source_type = message['State_Update']['source']
- key_code = CONST.BEO4_CMDS.get(message['State_Update']['command'].upper())
-
- for trigger in self.triggers:
- props = trigger.globalProps["uk.co.lukes_plugins.BeoGateway.plugin"]
- if trigger.pluginTypeId == "beo4Key" and props["keyCode"] == str(key_code):
- if props["sourceType"] == "Any Source":
- indigo.trigger.execute(trigger)
- break
- elif props["sourceType"] == "Any Audio" and "AUDIO" in source_type:
- indigo.trigger.execute(trigger)
- break
- elif props["sourceType"] == "Any Audio" and "AUDIO" in source_type:
- indigo.trigger.execute(trigger)
- break
- elif props["sourceType"] == source:
- indigo.trigger.execute(trigger)
- break
-
- def virtual_button(self, message):
- button_id = message['button']
- action = message['action']
- for trigger in self.triggers:
- props = trigger.globalProps["uk.co.lukes_pugins.mlgw.plugin"]
- if trigger.pluginTypeId == "virtualButton" and \
- props["buttonID"] == str(button_id) and \
- props["action"] == str(action):
- indigo.trigger.execute(trigger)
- break
-
- # ########################################################################################
- # ##### Indigo UI Device Controls
- def actionControlDevice(self, action, node):
- """ Callback Method to Control a Relay Device. """
- if action.deviceAction == indigo.kDeviceAction.TurnOn:
- self._dev_on(node)
- elif action.deviceAction == indigo.kDeviceAction.TurnOff:
- self._dev_off(node)
- elif action.deviceAction == indigo.kDeviceAction.Toggle:
- if node.states["onOffState"]:
- self._dev_off(node)
- else:
- self._dev_on(node)
- elif action.deviceAction == indigo.kDeviceAction.RequestStatus:
- self._status_request(node)
-
- def _dev_on(self, node):
- indigo.server.log(node.name + " turned On")
-
- # Get a local copy of the gateway states from server
- active_renderers = self.gateway.states['AudioRenderers']
- active_source = self.gateway.states['currentAudioSource']
- active_sourceName = self.gateway.states['currentAudioSourceName']
-
- if self.debug:
- indigo.server.log('Active renderers: ' + active_renderers, level=logging.DEBUG)
- indigo.server.log('Active Audio Source: ' + active_source, level=logging.DEBUG)
-
- # Join if music already playing
- if active_renderers != '' and active_source != 'Unknown':
- # Send Beo4 command
- source = active_source
- self.mlgw.send_beo4_cmd(
- int(node.address),
- int(CONST.CMDS_DEST.get("AUDIO SOURCE")),
- int(CONST.BEO4_CMDS.get(source))
- )
-
- # Update device states
- sourceName = active_sourceName
- key_value_list = [
- {'key': 'onOffState', 'value': True},
- {'key': 'playState', 'value': 'Play'},
- {'key': 'source', 'value': sourceName},
- {'key': 'mute', 'value': False},
- ]
-
- if self.debug:
- indigo.server.log(
- node.name + " joining current audio experience " + sourceName + " (" + source +
- "). Joining active renderer(s): " + active_renderers,
- level=logging.DEBUG
- )
-
- # Otherwise start default music source
- else:
- # Send Beo4 command
- source = self.default_audio_source
- self.mlgw.send_beo4_cmd(
- int(node.address),
- int(CONST.CMDS_DEST.get("AUDIO SOURCE")),
- int(CONST.BEO4_CMDS.get(source))
- )
-
- # Update device states
- sourceName = dict(CONST.available_sources).get(self.default_audio_source)
- key_value_list = [
- {'key': 'onOffState', 'value': True},
- {'key': 'playState', 'value': 'Play'},
- {'key': 'source', 'value': sourceName},
- {'key': 'mute', 'value': False},
- ]
-
- if self.debug:
- indigo.server.log(
- node.name + " starting audio experience " + sourceName + " (" + source + ").",
- level=logging.DEBUG
- )
-
- # Update states on server
- node.updateStatesOnServer(key_value_list)
- node.updateStateImageOnServer(indigo.kStateImageSel.AvPlaying)
-
- # Add device to active renderers lists and update gateway
- self.add_to_renderers_list(node.name, 'Audio')
- key_value_list = [
- {'key': 'currentAudioSource', 'value': source},
- {'key': 'currentAudioSourceName', 'value': sourceName},
- ]
- self.gateway.updateStatesOnServer(key_value_list)
-
- def _dev_off(self, node):
- indigo.server.log(node.name + " turned Off")
-
- # Send Beo4 command
- self.mlgw.send_beo4_cmd(
- int(node.address),
- int(CONST.CMDS_DEST.get("AUDIO SOURCE")),
- int(CONST.BEO4_CMDS.get('STANDBY'))
- )
-
- # Update states to standby values
- node.updateStatesOnServer(CONST.standby_state)
- node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff)
-
- # Remove device from active renderers lists
- self.remove_from_renderers_list(node.name, 'All')
-
- def _status_request(self, node):
- if node.pluginProps['serial_no'] == 'NA': # Check if this is a netlink device
- indigo.server.log(node.name + " does not support status requests")
- else: # If netlink, request a status update
- self.blgw.query(dev_type="AV renderer", device=node.name)
-
- # ########################################################################################
- # Define callback function for message return from B&O Gateway
- def cb(self, name, header, payload, message):
- # ########################################################################################
- # Message handler
- # Handle Beo4 Command Events
- try:
- if message['payload_type'] == "BEO4_KEY":
- self.beo4_key(message)
- except KeyError:
- pass
-
- # Handle Light and Command Events
- try:
- if message['Type'] == "LIGHT COMMAND":
- self.light_key(message)
- elif message['Type'] == "CONTROL COMMAND":
- self.control_key(message)
- except KeyError:
- pass
-
- # Handle Virtual Button Events
- try:
- if message['payload_type'] == "MLGW virtual button event":
- self.virtual_button(message)
- except KeyError:
- pass
-
- # Handle all standby events
- try:
- if message["command"] == "All Standby":
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- node.updateStatesOnServer(CONST.standby_state)
- node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff)
-
- indigo.devices['Bang and Olufsen Gateway'].updateStatesOnServer(CONST.gw_all_stb)
- except KeyError:
- pass
-
- # Handle AV Events
- try:
- # Use regular incoming messages to sync nowPlaying data
- if self.gateway.states['AudioRenderers'] != '' and \
- self.gateway.states['currentAudioSource'] == str(self.itunes_source) and \
- self.itunes_control:
- self._get_itunes_track_info(message)
-
- # For messages of type AV RENDERER, scan keys to update device states
- if message["Type"] == "AV RENDERER":
- # Tidy up messages
- self.av_sanitise(message)
- # Filter messages that don't constitute meaningful state updates
- actionable = self.filter_messages(message)
- if self.debug:
- indigo.server.log("Process message: " + str(actionable), level=logging.WARNING)
- if actionable:
- # Keep track of what sources are playing on the network
- self.src_tracking(message)
- # Update individual devices based on updates
- self.dev_update(message)
- except KeyError:
- pass
-
- # Report message content to log
- if self.verbose:
- if 'State_Update' in message and 'CONNECTION' in message['State_Update']:
- # Don't print pong responses from regular client ping to check sockets are open -
- # approx every 600 seconds
- pass
- elif 'Device' in message and message['Device'] == 'Clock':
- # Don't print the Clock sync telegrams from the HIP -
- # approx every 60 seconds
- pass
- elif 'payload_type' in message and message['payload_type'] == '0x14':
- # Don't print the Clock sync telegrams from the ML -
- # approx every 6 seconds
- pass
- elif 'payload_type' in message and message['payload_type'] == 'CLOCK' and not self.debug:
- # Don't print the Clock message telegrams from the ML
- pass
- else:
- self.message_log(name, header, payload, message)
-
- # ########################################################################################
- # AV Handler Functions
-
- # #### Message Conditioning
- def av_sanitise(self, message):
- # Sanitise AV messages
- try: # Check for missing source information
- if message['State_Update']['source'] in [None, 'None', '']:
- message['State_Update']['source'] = 'Unknown'
- message['State_Update']['sourceName'] = 'Unknown'
-
- # Update for standby condition
- message['State_Update']['state'] = "Standby"
- except KeyError:
- pass
-
- try: # Check for unknown state
- if message['State_Update']['state'] in [None, 'None', '']:
- message['State_Update']['state'] = 'Unknown'
- except KeyError:
- pass
-
- try: # Sanitise unknown Channel/Tracks
- if message['State_Update']['nowPlayingDetails']['channel_track'] in [0, 255, '0', '255']:
- del message['State_Update']['nowPlayingDetails']['channel_track']
- except KeyError:
- pass
-
- try: # Add sourceName if not in message block
- if 'sourceName' not in message['State_Update']:
- # Find the sourceName from the source list for this device
- if 'Device' in message: # If device known use local source data
- self.find_source_name(message['State_Update']['source'],
- indigo.devices[message['Device']].pluginProps['sources'])
- else: # If device not known use global source data
- message['State_Update']['sourceName'] = \
- dict(CONST.available_sources).get(message['State_Update']['source'])
- except KeyError:
- pass
-
- try: # Catch GOTO_SOURCE commands and set the goto_flag
- if message['payload_type'] == 'GOTO_SOURCE':
- self.goto_flag = datetime.now()
- if self.debug:
- indigo.server.log("GOTO_SOURCE command received - goto_flag set", level=logging.WARNING)
- except KeyError:
- pass
-
- def filter_messages(self, message):
- # Filter state updates that are:
- # 1. Standby states that are received between source changes:
- # If device is changing source the state changes as follows [Old Source -> Standby -> New Source].
- # If the standby condition is processed, the New Source state will be filtered by the condition below
- #
- # 2. Play states that received <1.5 seconds after standby state set:
- # Some messages come in on the ML and HIP protocols relating to previous state etc.
- # These can be ignored to avoid false states for the indigo devices
- try:
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- if node.name == message['Device']:
-
- # Get time since last state update for this device
- time_delta1 = datetime.now() - node.lastChanged
- time_delta1 = time_delta1.total_seconds()
- # Get time since last GOTO_SOURCE command
- time_delta2 = datetime.now() - self.goto_flag
- time_delta2 = time_delta2.total_seconds()
-
- # If standby command received <2.0 seconds after GOTO_SOURCE command , ignore
- if 'state' in message['State_Update'] and message['State_Update']['state'] == "Standby" \
- and time_delta2 < 2.0: # Condition 1
- if self.debug:
- indigo.server.log(message['Device'] + " ignoring Standby: " + str(round(time_delta2, 2)) +
- " seconds elapsed since GOTO_STATE command - ignoring message!",
- level=logging.DEBUG)
- return False
-
- # If message received <1.5 seconds after standby state, ignore
- elif node.states['playState'] == "Standby" and time_delta1 < 1.5: # Condition 2
- if self.debug:
- indigo.server.log(message['Device'] + " in Standby: " + str(round(time_delta1, 2)) +
- " seconds elapsed since last state update - ignoring message!",
- level=logging.DEBUG)
- return False
- else:
- return True
- except KeyError:
- return False
-
- # #### State tracking
- def src_tracking(self, message):
- # Track active renderers via gateway device
- try:
- # If new source is an audio source then update the gateway accordingly
- if message['State_Update']['source'] in CONST.source_type_dict.get('Audio Sources'):
- try:
- # Keep track of which devices are playing audio sources
- if message['Device'] not in self.gateway.states['AudioRenderers'] and \
- message['State_Update']['state'] not in ['Standby', 'Unknown', 'None']:
-
- # Log current audio source (MasterLink allows a single audio source for distribution)
- source = message['State_Update']['source']
- sourceName = dict(CONST.available_sources).get(source)
-
- self.gateway.updateStateOnServer('currentAudioSource', value=source)
- self.gateway.updateStateOnServer('currentAudioSourceName', value=sourceName)
-
- try:
- self.gateway.updateStateOnServer('nowPlaying', value=message['State_Update']['nowPlaying'])
- except KeyError:
- self.gateway.updateStateOnServer('nowPlaying', value='Unknown')
-
- self.add_to_renderers_list(message['Device'], 'Audio')
-
- # Remove device from Video Renderers list if it is on there
- if message['Device'] in self.gateway.states['VideoRenderers']:
- self.remove_from_renderers_list(message['Device'], 'Video')
-
- except KeyError:
- pass
-
- # If source is N.Music then control accordingly
- if message['State_Update']['source'] == str(self.itunes_source) and self.itunes_control:
- self.iTunes_transport_control(message)
-
- # If new source is an video source then update the gateway accordingly
- elif message['State_Update']['source'] in CONST.source_type_dict.get('Video Sources'):
- try:
- # Keep track of which devices are playing video sources
- if message['Device'] not in self.gateway.states['VideoRenderers'] and \
- message['State_Update']['state'] not in ['Standby', 'Unknown', 'None']:
- self.add_to_renderers_list(message['Device'], 'Video')
-
- # Remove device from Audio Renderers list if it is on there
- if message['Device'] in self.gateway.states['AudioRenderers']:
- self.remove_from_renderers_list(message['Device'], 'Audio')
-
- except KeyError:
- pass
- except KeyError:
- pass
-
- def dev_update(self, message):
- # Update device states
- try:
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- if node.name == message['Device']:
- # Handle Standby state
- if message['State_Update']['state'] == "Standby":
- # Update states to standby values
- node.updateStatesOnServer(CONST.standby_state)
- node.updateStateImageOnServer(indigo.kStateImageSel.PowerOff)
-
- # Remove the device from active renderers list
- self.remove_from_renderers_list(message['Device'], 'All')
-
- # Post state update in Apple Notification Centre
- if self.notifymode:
- self.iTunes.notify(node.name + " now in Standby", "Device State Update")
- return
-
- # If device not in standby then update its state information
- # get current states as list # Index
- last_state = [
- node.states['playState'], # 0
- node.states['source'], # 1
- node.states['nowPlaying'], # 2
- node.states['channelTrack'], # 3
- node.states['volume'], # 4
- node.states['mute'], # 5
- node.states['onOffState'] # 6
- ]
-
- # Initialise new state list - the device is not in standby so it must be on
- new_state = last_state[:]
- new_state[5] = False
- new_state[6] = True
-
- # Update device states with values from message
- if 'state' in message['State_Update']:
- if message['State_Update']['state'] not in ['None', 'Standby', '', None]:
- if last_state[0] == 'Standby' and message['State_Update']['state'] == 'Unknown':
- new_state[0] = 'Play'
- elif last_state[0] != 'Standby' and message['State_Update']['state'] == 'Unknown':
- pass
- else:
- new_state[0] = message['State_Update']['state']
-
- if 'sourceName' in message['State_Update'] and message['State_Update']['sourceName'] != 'Unknown':
- # Sanitise source name to avoid indigo key errors (remove whitespace)
- source_name = message['State_Update']['sourceName'].strip().replace(" ", "_")
- new_state[1] = source_name
-
- if 'nowPlaying' in message['State_Update']:
- # Update now playing information unless the state value is empty or unknown
- if message['State_Update']['nowPlaying'] not in ['', 'Unknown']:
- new_state[2] = message['State_Update']['nowPlaying']
- # If the state value is empty/unknown and the source has not changed then no update required
- elif new_state[1] != last_state[1]:
- # If the state has changed and the value is unknown, then set as "Unknown"
- new_state[2] = 'Unknown'
-
- if 'nowPlayingDetails' in message['State_Update'] and \
- 'channel_track' in message['State_Update']['nowPlayingDetails']:
- new_state[3] = message['State_Update']['nowPlayingDetails']['channel_track']
- elif new_state[1] != last_state[1]:
- # If the state has changed and the value is unknown, then set as "Unknown"
- new_state[2] = 0
-
- if 'volume' in message['State_Update']:
- new_state[4] = message['State_Update']['volume']
-
- if new_state != last_state:
- # Update states on server
- key_value_list = [
- {'key': 'playState', 'value': new_state[0]},
- {'key': 'source', 'value': new_state[1]},
- {'key': 'nowPlaying', 'value': new_state[2]},
- {'key': 'channelTrack', 'value': new_state[3]},
- {'key': 'volume', 'value': new_state[4]},
- {'key': 'mute', 'value': new_state[5]},
- {'key': 'onOffState', 'value': new_state[6]},
- ]
- node.updateStatesOnServer(key_value_list)
-
- # Post notifications Notifications
- if self.notifymode:
- self.notifications(node.name, last_state, new_state)
-
- # Update state image on server
- if new_state[0] == "Stopped":
- node.updateStateImageOnServer(indigo.kStateImageSel.AvPaused)
- elif new_state[0] not in ['None', 'Unknown', 'Standby', '', None]:
- node.updateStateImageOnServer(indigo.kStateImageSel.AvPlaying)
-
- # If audio source active, update any other active audio renderers accordingly
- try:
- if new_state[0] not in ['None', 'Unknown', 'Standby', '', None] and \
- message['State_Update']['source'] in CONST.source_type_dict.get('Audio Sources'):
- self.all_audio_nodes_update(new_state, node.name, message['State_Update']['source'])
- except KeyError:
- pass
-
- break
- except KeyError:
- pass
-
- def all_audio_nodes_update(self, new_state, dev, source):
- # Loop over all active audio renderers to update them with the latest audio state
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- if node.name in self.gateway.states['AudioRenderers'] and node.name != dev:
- # Get current state of this node
- last_state = [
- node.states['playState'],
- node.states['source'],
- node.states['nowPlaying'],
- node.states['channelTrack'],
- node.states['volume'],
- node.states['mute'],
- ]
-
- if last_state[:4] != new_state[:4]:
- # Update the play state for active Audio renderers if new values are different from current ones
- key_value_list = [
- {'key': 'onOffState', 'value': True},
- {'key': 'playState', 'value': new_state[0]},
- {'key': 'source', 'value': new_state[1]},
- {'key': 'nowPlaying', 'value': new_state[2]},
- {'key': 'channelTrack', 'value': new_state[3]},
- {'key': 'mute', 'value': False},
- ]
- node.updateStatesOnServer(key_value_list)
- node.updateStateImageOnServer(indigo.kStateImageSel.AvPlaying)
-
- # Update the gateway
- if self.gateway.states['currentAudioSourceName'] != new_state[1]:
- # If the source has changed, update both source and nowPlaying
- sourceName = new_state[1]
-
- key_value_list = [
- {'key': 'currentAudioSource', 'value': source},
- {'key': 'currentAudioSourceName', 'value': sourceName},
- {'key': 'nowPlaying', 'value': new_state[2]},
- ]
- self.gateway.updateStatesOnServer(key_value_list)
-
- elif self.gateway.states['nowPlaying'] != new_state[2] and new_state[2] not in ['', 'Unknown']:
- # If the source has not changed, and nowPlaying is not Unknown, update nowPlaying
- self.gateway.updateStateOnServer('nowPlaying', value=new_state[2])
-
- # #### Active renderer list maintenance
- def add_to_renderers_list(self, dev, av):
- if av == "Audio":
- renderer_list = 'AudioRenderers'
- renderer_count = 'nAudioRenderers'
- else:
- renderer_list = 'VideoRenderers'
- renderer_count = 'nVideoRenderers'
-
- # Retrieve the renderers and convert from string to list
- renderers = self.gateway.states[renderer_list].split(', ')
-
- # Sanitise the list for stray blanks
- if '' in renderers:
- renderers.remove('')
-
- # Add device to list if not already on there
- if dev not in renderers:
- renderers.append(dev)
- self.gateway.updateStateOnServer(renderer_list, value=', '.join(renderers))
- self.gateway.updateStateOnServer(renderer_count, value=len(renderers))
-
- def remove_from_renderers_list(self, dev, av):
- # Remove devices from renderers lists when the enter standby mode
- if av in ['Audio', 'All']:
- if dev in self.gateway.states['AudioRenderers']:
- renderers = self.gateway.states['AudioRenderers'].split(', ')
- renderers.remove(dev)
-
- self.gateway.updateStateOnServer('AudioRenderers', value=', '.join(renderers))
- self.gateway.updateStateOnServer('nAudioRenderers', value=len(renderers))
-
- if av in ['Video', 'All']:
- if dev in self.gateway.states['VideoRenderers']:
- renderers = self.gateway.states['VideoRenderers'].split(', ')
- renderers.remove(dev)
-
- self.gateway.updateStateOnServer('VideoRenderers', value=', '.join(renderers))
- self.gateway.updateStateOnServer('nVideoRenderers', value=len(renderers))
-
- # If no audio sources are playing then update the gateway states
- if self.gateway.states['AudioRenderers'] == '':
- key_value_list = [
- {'key': 'AudioRenderers', 'value': ''},
- {'key': 'nAudioRenderers', 'value': 0},
- {'key': 'currentAudioSource', 'value': 'Unknown'},
- {'key': 'currentAudioSourceName', 'value': 'Unknown'},
- {'key': 'nowPlaying', 'value': 'Unknown'},
- ]
- self.gateway.updateStatesOnServer(key_value_list)
-
- # If no AV renderers are playing N.Music, stop iTunes playback
- if self.itunes_control:
- self.iTunes.stop()
-
- # #### Helper functions
- @staticmethod
- def find_source_name(source, sources):
- # Get the sourceName for source
- for source_name in sources:
- if sources[source_name]['source'] == str(source):
- return str(sources[source_name]).split()[0]
-
- # if source list exhausted and no valid name found return Unknown
- return 'Unknown'
-
- @staticmethod
- # Get the source ID for sourceName
- def get_source(sourceName, sources):
- for source_name in sources:
- if source_name == sourceName:
- return str(sources[source_name]['source'])
-
- # if source list exhausted and no valid name found return Unknown
- return 'Unknown'
-
- # ########################################################################################
- # Apple Music Control and feedback
- def iTunes_transport_control(self, message):
- # Transport controls for iTunes
- try: # If N.MUSIC command, trigger appropriate self.iTunes control
- if message['State_Update']['state'] not in ["", "Standby"]:
- self.iTunes.play()
- except KeyError:
- pass
-
- try: # If N.MUSIC selected and Beo4 command received then run appropriate transport commands
- if message['State_Update']['command'] == "Go/Play":
- self.iTunes.play()
-
- elif message['State_Update']['command'] == "Stop":
- self.iTunes.pause()
-
- elif message['State_Update']['command'] == "Exit":
- self.iTunes.stop()
-
- elif message['State_Update']['command'] == "Step Up":
- self.iTunes.next_track()
-
- elif message['State_Update']['command'] == "Step Down":
- self.iTunes.previous_track()
-
- elif message['State_Update']['command'] == "Wind":
- self.iTunes.wind(15)
-
- elif message['State_Update']['command'] == "Rewind":
- self.iTunes.rewind(-15)
-
- elif message['State_Update']['command'] == "Shift-1/Random":
- self.iTunes.shuffle()
-
- # If 'Info' pressed - update track info
- elif message['State_Update']['command'] == "Info":
- track_info = self.iTunes.get_current_track_info()
- if track_info[0] not in [None, 'None']:
- indigo.server.log(
- "\n\t----------------------------------------------------------------------------"
- "\n\tiTUNES CURRENT TRACK INFO:"
- "\n\t============================================================================"
- "\n\tNow playing: '" + track_info[0] + "'"
- "\n\t by " + track_info[2] +
- "\n\t from the album '" + track_info[1] + "'"
- "\n\t----------------------------------------------------------------------------"
- "\n\tACTIVE AUDIO RENDERERS: " + str(self.gateway.states['AudioRenderers']) + "\n\n",
- level=logging.DEBUG
- )
-
- self.iTunes.notify(
- "Now playing: '" + track_info[0] +
- "' by " + track_info[2] +
- "from the album '" + track_info[1] + "'",
- "Apple Music Track Info:"
- )
-
- # If 'Guide' pressed - print instructions to indigo log
- elif message['State_Update']['command'] == "Guide":
- indigo.server.log(
- "\n\t----------------------------------------------------------------------------"
- "\n\tBeo4/BeoRemote One Control of Apple Music"
- "\n\tKey mapping guide: [Key : Action]"
- "\n\t============================================================================"
- "\n\n\t** BASIC TRANSPORT CONTROLS **"
- "\n\tGO/PLAY : Play"
- "\n\tSTOP/Pause : Pause"
- "\n\tEXIT : Stop"
- "\n\tStep Up/P+ : Next Track"
- "\n\tStep Down/P- : Previous Track"
- "\n\tWind : Scan Forwards 15 Seconds"
- "\n\tRewind : Scan Backwards 15 Seconds"
- "\n\n\t** FUNCTIONS **"
- "\n\tShift-1/Random : Toggle Shuffle"
- "\n\tINFO : Display Track Info for Current Track"
- "\n\tGUIDE : This Guide"
- "\n\n\t** ADVANCED CONTROLS **"
- "\n\tGreen : Shuffle Playlist 'Recently Played'"
- "\n\tYellow : Play Digital Radio Stations from Playlist Radio"
- "\n\tRed : More of the Same"
- "\n\tBlue : Play the Album that the Current Track Resides On\n\n",
- level=logging.DEBUG
- )
-
- # If colour key pressed, execute the appropriate applescript
- elif message['State_Update']['command'] == "Green":
- # Play a specific playlist - defaults to Recently Played
- script = ASBridge.__file__[:-12] + '/Scripts/green.scpt'
- self.iTunes.run_script(script, self.debug)
-
- elif message['State_Update']['command'] == "Yellow":
- # Play a specific playlist - defaults to URL Radio stations
- script = ASBridge.__file__[:-12] + '/Scripts/yellow.scpt'
- self.iTunes.run_script(script, self.debug)
-
- elif message['State_Update']['command'] == "Blue":
- # Play the current album
- script = ASBridge.__file__[:-12] + '/Scripts/blue.scpt'
- self.iTunes.run_script(script, self.debug)
-
- elif message['State_Update']['command'] in ["0xf2", "Red", "MOTS"]:
- # More of the same (start a playlist with just current track and let autoplay find similar tunes)
- script = ASBridge.__file__[:-12] + '/Scripts/red.scpt'
- self.iTunes.run_script(script, self.debug)
- except KeyError:
- pass
-
- def _get_itunes_track_info(self, message):
- track_info = self.iTunes.get_current_track_info()
- if track_info[0] not in [None, 'None']:
- # Construct track info string
- track_info_ = "'" + track_info[0] + "' by " + track_info[2] + " from the album '" + track_info[1] + "'"
-
- # Add now playing info to the message block
- if 'Type' in message and message['Type'] == "AV RENDERER" and 'source' in message['State_Update'] \
- and message['State_Update']['source'] == str(self.itunes_source) and \
- 'nowPlaying' in message['State_Update']:
- message['State_Update']['nowPlaying'] = track_info_
- message['State_Update']['nowPlayingDetails']['channel_track'] = int(track_info[3])
-
- # Print track info to log if trackmode is set to true (via config UI)
- src = dict(CONST.available_sources).get(self.itunes_source)
- if self.gateway.states['currentAudioSource'] == str(self.itunes_source) and \
- track_info_ != self.gateway.states['nowPlaying'] and self.trackmode:
- indigo.server.log("\n\t----------------------------------------------------------------------------"
- "\n\tiTUNES CURRENT TRACK INFO:"
- "\n\t============================================================================"
- "\n\tNow playing: '" + track_info[0] + "'"
- "\n\t by " + track_info[2] +
- "\n\t from the album '" + track_info[1] + "'"
- "\n\t----------------------------------------------------------------------------"
- "\n\tACTIVE AUDIO RENDERERS: " + str(self.gateway.states['AudioRenderers']) + "\n\n")
-
- if self.notifymode:
- # Post track information to Apple Notification Centre
- self.iTunes.notify(track_info_ + " from source " + src,
- "Now Playing:")
-
- # Update nowPlaying on the gateway device
- if track_info_ != self.gateway.states['nowPlaying'] and \
- self.gateway.states['currentAudioSource'] == str(self.itunes_source):
- self.gateway.updateStateOnServer('nowPlaying', value=track_info_)
-
- # Update info on active Audio Renderers
- for node in indigo.devices.iter('uk.co.lukes_plugins.BeoGateway.plugin.AVrenderer'):
- if node.name in self.gateway.states['AudioRenderers']:
- key_value_list = [
- {'key': 'onOffState', 'value': True},
- {'key': 'playState', 'value': 'Play'},
- {'key': 'source', 'value': src},
- {'key': 'nowPlaying', 'value': track_info_},
- {'key': 'channelTrack', 'value': int(track_info[3])},
- ]
- node.updateStatesOnServer(key_value_list)
- node.updateStateImageOnServer(indigo.kStateImageSel.AvPlaying)
-
- # ########################################################################################
- # Message Reporting
- @staticmethod
- def message_log(name, header, payload, message):
- # Set reporting level for message logging
- try: # CLOCK messages are filtered except in debug mode
- if message['payload_type'] == 'CLOCK':
- debug_level = logging.DEBUG
- else: # Everything else is for INFO
- debug_level = logging.INFO
- except KeyError:
- debug_level = logging.INFO
-
- # Pretty formatting - convert to JSON format then remove braces
- message = json.dumps(message, indent=4)
- for r in (('"', ''), (',', ''), ('{', ''), ('}', '')):
- message = str(message).replace(*r)
-
- # Print message data
- if len(payload) + 9 < 73:
- indigo.server.log("\n\t----------------------------------------------------------------------------" +
- "\n\t" + name + ": <--DATA-RECEIVED!-<< " +
- datetime.now().strftime("on %d/%m/%y at %H:%M:%S") +
- "\n\t============================================================================" +
- "\n\tHeader: " + header +
- "\n\tPayload: " + payload +
- "\n\t----------------------------------------------------------------------------" +
- message, level=debug_level)
- elif 73 < len(payload) + 9 < 137:
- indigo.server.log("\n\t----------------------------------------------------------------------------" +
- "\n\t" + name + ": <--DATA-RECEIVED!-<< " +
- datetime.now().strftime("on %d/%m/%y at %H:%M:%S") +
- "\n\t============================================================================" +
- "\n\tHeader: " + header +
- "\n\tPayload: " + payload[:66] + "\n\t\t" + payload[66:137] +
- "\n\t----------------------------------------------------------------------------" +
- message, level=debug_level)
- else:
- indigo.server.log("\n\t----------------------------------------------------------------------------" +
- "\n\t" + name + ": <--DATA-RECEIVED!-<< " +
- datetime.now().strftime("on %d/%m/%y at %H:%M:%S") +
- "\n\t============================================================================" +
- "\n\tHeader: " + header +
- "\n\tPayload: " + payload[:66] + "\n\t\t" + payload[66:137] + "\n\t\t" + payload[137:] +
- "\n\t----------------------------------------------------------------------------" +
- message, level=debug_level)
-
- def notifications(self, name, last_state, new_state):
- # Post state information to the Apple Notification Centre
- # Information index:
- # node.states['playState'], # 0
- # node.states['source'], # 1
- # node.states['nowPlaying'], # 2
- # node.states['channelTrack'], # 3
- # node.states['volume'], # 4
- # node.states['mute'], # 5
- # node.states['onOffState'] # 6
-
- # Don't post notification if nothing has changed
- if last_state == new_state:
- return
-
- # Source status information
- if last_state[0] != new_state[0] and new_state[0] == "Standby": # Power off
- self.iTunes.notify(
- name + " now in Standby",
- "Device State Update"
- )
- return
- elif last_state[1] != new_state[1] and new_state[0] != "Standby": # Source Update
- self.iTunes.notify(
- name + " now playing from source " + new_state[1],
- "Device State Update"
- )
- return
- elif last_state[0] != new_state[0] and new_state[0] == "Play": # Power on
- self.iTunes.notify(
- name + " Active",
- "Device State Update"
- )
- return
-
- # Channel/Track information
- if new_state[2] not in [None, 'None', '', 0, '0', 'Unknown']: # Now Playing Update
- self.iTunes.notify(
- new_state[2] + " from source " + new_state[1],
- name + " Now Playing:"
- )
- elif last_state[3] != new_state[3] and new_state[3] not in [0, 255, '0', '255']: # Channel/Track Update
- self.iTunes.notify(
- name + " now playing channel/track " + new_state[3] + " from source " + new_state[1],
- "Device Channel/Track Information"
- )
-
- # ########################################################################################
- # Indigo Server Methods
- def startup(self):
- indigo.server.log(u"Startup called")
-
- # Download the config file from the gateway and initialise the devices
- config = MLCONFIG.MLConfig(self.host, self.user, self.pwd, self.debug)
- self.gateway = indigo.devices['Bang and Olufsen Gateway']
-
- # Create MLGW Protocol and ML_CLI Protocol clients (basic command listening)
- indigo.server.log('Creating MLGW Protocol Client...', level=logging.WARNING)
- self.mlgw = MLGW.MLGWClient(self.host, self.port[0], self.user, self.pwd, 'MLGW protocol', self.debug, self.cb)
- asyncore.loop(count=10, timeout=0.2)
-
- indigo.server.log('Creating ML Command Line Protocol Client...', level=logging.WARNING)
- self.mlcli = MLCLI.MLCLIClient(self.host, self.port[2], self.user, self.pwd,
- 'ML command line interface', self.debug, self.cb)
- # Log onto the MLCLI client and ascertain the gateway model
- asyncore.loop(count=10, timeout=0.2)
-
- # Now MLGW and MasterLink Command Line Client are set up, retrieve MasterLink IDs of products
- config.get_masterlink_id(self.mlgw, self.mlcli)
-
- # If the gateway is a BLGW use the BLHIP protocol, else use the legacy MLHIP protocol
- if self.mlcli.isBLGW:
- indigo.server.log('Creating BLGW Home Integration Protocol Client...', level=logging.WARNING)
- self.blgw = BLHIP.BLHIPClient(self.host, self.port[1], self.user, self.pwd,
- 'BLGW Home Integration Protocol', self.debug, self.cb)
- self.mltn = None
- else:
- indigo.server.log('Creating MLGW Home Integration Protocol Client...', level=logging.WARNING)
- self.mltn = MLtn.MLtnClient(self.host, self.port[2], self.user, self.pwd, 'ML telnet client',
- self.debug, self.cb)
- self.blgw = None
-
- # Connection polling
- def check_connection(self, client):
- last = round(time.time() - client.last_received_at, 2)
- # Reconnect if socket has disconnected, or if no response received to last ping
- if not client.is_connected or last > 60:
- indigo.server.log("\t" + client.name + ": Reconnecting!", level=logging.WARNING)
- client.handle_close()
- self.sleep(0.5)
- client.client_connect()
-
- # Indigo main program loop
- def runConcurrentThread(self):
- try:
- while True:
- # Ping all connections every 10 minutes to prompt messages on the network
- asyncore.loop(count=self.pollinterval, timeout=1)
- if self.mlgw.is_connected:
- self.mlgw.ping()
- if self.mlcli.is_connected:
- self.mlcli.ping()
- if self.mlcli.isBLGW:
- if self.blgw.is_connected:
- self.blgw.ping()
- else:
- if self.mltn.is_connected:
- self.mltn.ping()
-
- # Check the connections approximately every 10 minutes to keep sockets open
- asyncore.loop(count=5, timeout=1)
- self.check_connection(self.mlgw)
- self.check_connection(self.mlcli)
- if self.mlcli.isBLGW:
- self.check_connection(self.blgw)
- else:
- self.check_connection(self.mltn)
-
- self.sleep(0.5)
-
- except self.StopThread:
- raise asyncore.ExitNow('Server is quitting!')
-
- # Tidy up on shutdown
- def shutdown(self):
- indigo.server.log("Shutdown plugin")
- del self.mlgw
- del self.mlcli
- if self.mlcli.isBLGW:
- del self.blgw
- else:
- del self.mltn
- del self.iTunes
- raise asyncore.ExitNow('Server is quitting!')