|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab |
| 3 | +######################################################################### |
| 4 | +# Copyright 2022 <Onkel Andy> <onkelandy@hotmail.com> |
| 5 | +######################################################################### |
| 6 | +# This file is part of SmartHomeNG |
| 7 | +# |
| 8 | +# Logitech Mediaserver/Squeezebox plugin for SmartDevicePlugin class |
| 9 | +# |
| 10 | +# SmartHomeNG is free software: you can redistribute it and/or modify |
| 11 | +# it under the terms of the GNU General Public License as published by |
| 12 | +# the Free Software Foundation, either version 3 of the License, or |
| 13 | +# (at your option) any later version. |
| 14 | +# |
| 15 | +# SmartHomeNG is distributed in the hope that it will be useful, |
| 16 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | +# GNU General Public License for more details. |
| 19 | +# |
| 20 | +# You should have received a copy of the GNU General Public License |
| 21 | +# along with SmartHomeNG If not, see <http://www.gnu.org/licenses/>. |
| 22 | +######################################################################### |
| 23 | + |
| 24 | +import builtins |
| 25 | +import os |
| 26 | +import sys |
| 27 | + |
| 28 | +if __name__ == '__main__': |
| 29 | + builtins.SDP_standalone = True |
| 30 | + |
| 31 | + class SmartPlugin(): |
| 32 | + pass |
| 33 | + |
| 34 | + class SmartPluginWebIf(): |
| 35 | + pass |
| 36 | + |
| 37 | + BASE = os.path.sep.join(os.path.realpath(__file__).split(os.path.sep)[:-3]) |
| 38 | + sys.path.insert(0, BASE) |
| 39 | + |
| 40 | +else: |
| 41 | + builtins.SDP_standalone = False |
| 42 | + |
| 43 | +from lib.model.sdp.globals import (CUSTOM_SEP, PLUGIN_ATTR_NET_HOST, PLUGIN_ATTR_RECURSIVE, PLUGIN_ATTR_CONN_TERMINATOR) |
| 44 | +from lib.model.smartdeviceplugin import SmartDevicePlugin, Standalone |
| 45 | + |
| 46 | +import urllib.parse |
| 47 | + |
| 48 | + |
| 49 | +class lms(SmartDevicePlugin): |
| 50 | + """ Device class for Logitech Mediaserver/Squeezebox function. """ |
| 51 | + |
| 52 | + PLUGIN_VERSION = '1.5.2' |
| 53 | + |
| 54 | + def _set_device_defaults(self): |
| 55 | + self.custom_commands = 1 |
| 56 | + self._token_pattern = '([0-9a-fA-F]{2}[-:]){5}[0-9a-fA-F]{2}' |
| 57 | + # for substitution in reply_pattern |
| 58 | + self._custom_patterns = {1: '(?:[0-9a-fA-F]{2}[-:]){5}[0-9a-fA-F]{2}', 2: '', 3: ''} |
| 59 | + self._use_callbacks = True |
| 60 | + self._parameters[PLUGIN_ATTR_RECURSIVE] = 1 |
| 61 | + self._parameters['web_port'] = self.get_parameter_value('web_port') |
| 62 | + if self.get_parameter_value('web_host') == '': |
| 63 | + host = self._parameters.get(PLUGIN_ATTR_NET_HOST) |
| 64 | + if host.startswith('http'): |
| 65 | + self._parameters['web_host'] = host |
| 66 | + else: |
| 67 | + self._parameters['web_host'] = f'http://{host}' |
| 68 | + else: |
| 69 | + host = self.get_parameter_value('web_host') |
| 70 | + if host.startswith('http'): |
| 71 | + self._parameters['web_host'] = host |
| 72 | + else: |
| 73 | + self._parameters['web_host'] = f'http://{host}' |
| 74 | + |
| 75 | + def on_connect(self, by=None): |
| 76 | + self.logger.debug("Activating listen mode after connection.") |
| 77 | + self.send_command('server.listenmode', True) |
| 78 | + |
| 79 | + def _transform_send_data(self, data=None, **kwargs): |
| 80 | + if isinstance(data, dict): |
| 81 | + data['limit_response'] = self._parameters[PLUGIN_ATTR_CONN_TERMINATOR] |
| 82 | + data['payload'] = f'{data.get("payload")}{data["limit_response"]}' |
| 83 | + return data |
| 84 | + |
| 85 | + def _transform_received_data(self, data): |
| 86 | + # fix weird representation of MAC address (%3A = :), etc. |
| 87 | + return urllib.parse.unquote_plus(data) |
| 88 | + |
| 89 | + def _process_additional_data(self, command, data, value, custom, by): |
| 90 | + |
| 91 | + def trigger_read(command): |
| 92 | + self.send_command(command + CUSTOM_SEP + custom) |
| 93 | + |
| 94 | + if not custom: |
| 95 | + return |
| 96 | + |
| 97 | + if command == 'player.info.playlists.names': |
| 98 | + self.logger.debug(f"Got command playlist names {command} data {data} value {value} custom {custom} by {by}") |
| 99 | + trigger_read('player.playlist.id') |
| 100 | + trigger_read('player.playlist.name') |
| 101 | + |
| 102 | + if command == 'playlist.rename': |
| 103 | + trigger_read('info.playlists.names') |
| 104 | + # set alarm |
| 105 | + if command == 'player.control.alarms': |
| 106 | + # This does not really work currently. The created string is somehow correct. |
| 107 | + # However, much more logic has to be included to add/update/delete alarms, etc. |
| 108 | + try: |
| 109 | + for i in value.keys(): |
| 110 | + d = value.get(i) |
| 111 | + alarm = f"id:{i} " |
| 112 | + for k, v in d.items(): |
| 113 | + alarm += f"{k}:{v} " |
| 114 | + alarm = f"alarm add {alarm.strip()}" |
| 115 | + self.logger.debug(f"Set alarm: {alarm}") |
| 116 | + self.send_command('player.control.set_alarm' + CUSTOM_SEP + custom, alarm) |
| 117 | + except Exception as e: |
| 118 | + self.logger.error(f"Error setting alarm: {e}") |
| 119 | + |
| 120 | + # set album art URL |
| 121 | + if command == 'player.info.album': |
| 122 | + self.logger.debug(f"Got command album {command} data {data} value {value} custom {custom} by {by}") |
| 123 | + host = self._parameters['web_host'] |
| 124 | + port = self._parameters['web_port'] |
| 125 | + if port == 0: |
| 126 | + url = f'{host}/music/current/cover.jpg?player={custom}' |
| 127 | + else: |
| 128 | + url = f'{host}:{port}/music/current/cover.jpg?player={custom}' |
| 129 | + self.logger.debug(f"Setting albumarturl to {url}") |
| 130 | + self._dispatch_callback('player.info.albumarturl' + CUSTOM_SEP + custom, url, by) |
| 131 | + |
| 132 | + # set playlist ID |
| 133 | + if command == 'player.playlist.load': |
| 134 | + self.logger.debug(f"Got command load {command} data {data} value {value} custom {custom} by {by}") |
| 135 | + trigger_read('player.playlist.id') |
| 136 | + trigger_read('player.control.playmode') |
| 137 | + |
| 138 | + if command == 'player.playlist.id': |
| 139 | + self.logger.debug(f"Got command id {command} data {data} value {value} custom {custom} by {by}") |
| 140 | + trigger_read('player.playlist.name') |
| 141 | + |
| 142 | + # update on new song |
| 143 | + if command == 'player.info.title': |
| 144 | + # trigger_read('player.control.playmode') |
| 145 | + # trigger_read('player.playlist.index') |
| 146 | + trigger_read('player.info.duration') |
| 147 | + trigger_read('player.info.album') |
| 148 | + trigger_read('player.info.artist') |
| 149 | + trigger_read('player.info.genre') |
| 150 | + trigger_read('player.info.path') |
| 151 | + |
| 152 | + # update on new song |
| 153 | + if command == 'player.control.playpause' and value: |
| 154 | + trigger_read('player.control.playmode') |
| 155 | + trigger_read('player.info.duration') |
| 156 | + trigger_read('player.info.album') |
| 157 | + trigger_read('player.info.artist') |
| 158 | + trigger_read('player.info.genre') |
| 159 | + trigger_read('player.info.path') |
| 160 | + |
| 161 | + # update on new song |
| 162 | + if command == 'player.playlist.index': |
| 163 | + self.logger.debug(f"Got command index {command} data {data} value {value} custom {custom} by {by}") |
| 164 | + trigger_read('player.control.playmode') |
| 165 | + trigger_read('player.info.duration') |
| 166 | + trigger_read('player.info.album') |
| 167 | + trigger_read('player.info.artist') |
| 168 | + trigger_read('player.info.genre') |
| 169 | + trigger_read('player.info.path') |
| 170 | + trigger_read('player.info.title') |
| 171 | + |
| 172 | + # update current time info |
| 173 | + if command in ['player.control.forward', 'player.control.rewind']: |
| 174 | + self.logger.debug(f"Got command forward/rewind {command} data {data} value {value} custom {custom} by {by}") |
| 175 | + trigger_read('player.control.time') |
| 176 | + |
| 177 | + # update play and stop items based on playmode |
| 178 | + if command == 'player.control.playmode': |
| 179 | + self.logger.debug(f"Got command playmode {command} data {data} value {value} custom {custom} by {by}") |
| 180 | + mode = data.split("mode")[-1].strip() |
| 181 | + mode = mode.split("playlist")[-1].strip() |
| 182 | + self._dispatch_callback('player.control.playpause' + CUSTOM_SEP + custom, |
| 183 | + True if mode in ["play", "pause 0"] else False, by) |
| 184 | + self._dispatch_callback('player.control.stop' + CUSTOM_SEP + custom, |
| 185 | + True if mode == "stop" else False, by) |
| 186 | + trigger_read('player.control.time') |
| 187 | + |
| 188 | + # update play and stop items based on playmode |
| 189 | + if command == 'player.control.stop' or (command == 'player.control.playpause' and not value): |
| 190 | + self.logger.debug(f"Got command stop or pause {command} data {data} value {value} custom {custom} by {by}") |
| 191 | + trigger_read('player.control.playmode') |
| 192 | + |
| 193 | + |
| 194 | +if __name__ == '__main__': |
| 195 | + s = Standalone(lms, sys.argv[0]) |
0 commit comments