Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/checkPHP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

jobs:
php-lint:
name: PHP Lint — PHP 8.4
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/checkPHPCompat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: PHP Compatibility Check

on:
pull_request:
branches:
- beta

jobs:
php-compat:
name: PHPStan — PHP ${{ matrix.php-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-version: ['7.4', '8.2', '8.4']

steps:
- uses: actions/checkout@v6

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer
coverage: none

- name: Install PHPStan
run: composer global require phpstan/phpstan --no-interaction --quiet

- name: Checkout Jeedom Core (sparse)
run: |
git clone --depth 1 --filter=blob:none --sparse \
https://github.com/jeedom/core.git /tmp/jeedom-core
git -C /tmp/jeedom-core sparse-checkout set core/class core/php core/repo

- name: PHP ${{ matrix.php-version }} Compatibility Check
run: |
PHPSTAN_VERSION=$(echo "${{ matrix.php-version }}" | awk -F. '{printf "%d%02d00", $1, $2}')
printf 'includes:\n - phpstan.neon.dist\nparameters:\n phpVersion: %s\n scanDirectories:\n - /tmp/jeedom-core/core/class\n - /tmp/jeedom-core/core/php\n - /tmp/jeedom-core/core/repo\n' "$PHPSTAN_VERSION" > phpstan.neon
phpstan analyse --level 0 --no-progress core/ desktop/ plugin_info/
25 changes: 25 additions & 0 deletions .github/workflows/checkPython.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Python Check

on:
pull_request:
branches:
- beta

jobs:
python-check:
name: Ruff Lint
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Install ruff
run: pip install ruff

- name: Ruff Check
run: ruff check resources/tvremoted/
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"orangetv",
"owtv",
"paircode",
"phpstan",
"phpunit",
"plex",
"primevideo",
Expand All @@ -77,12 +78,14 @@
"scanupdtvremote",
"screencap",
"sendpaircode",
"shivammathur",
"sockethost",
"spotify",
"stype",
"tunein",
"tvhosts",
"TVHOSTS",
"tvloglevel",
"tvremote",
"tvremoted",
"updatelasttime",
Expand Down
1 change: 1 addition & 0 deletions core/class/tvremote.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ public static function deamon_start() {
$path = realpath(__DIR__ . '/../../resources/tvremoted');
$cmd = self::PYTHON3_PATH . " {$path}/tvremoted.py";
$cmd .= ' --loglevel ' . log::convertLogLevel(log::getLogLevel(__CLASS__));
$cmd .= ' --tvloglevel ' . config::byKey('tvLogLevel', __CLASS__, 'daemon');
$cmd .= ' --pluginversion ' . config::byKey('pluginVersion', __CLASS__, '0.0.0');
$cmd .= ' --socketport ' . config::byKey('socketport', __CLASS__, '55112');
$cmd .= ' --cyclefactor ' . config::byKey('cyclefactor', __CLASS__, '1.0');
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
parameters:
reportUnmatchedIgnoredErrors: true
16 changes: 16 additions & 0 deletions plugin_info/configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@
</select>
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">{{Niveau de log des lib TV}}
<sup><i class="fas fa-exclamation-triangle tooltips" style="color:var(--al-warning-color)!important;" title="{{Le démon devra être redémarré après la modification de ce paramètre}}"></i></sup>
<sup><i class="fas fa-question-circle tooltips" title="{{Niveau de log des librairies TV du démon. Par défaut, hérite du niveau de log global.}}"></i></sup>
</label>
<div class="col-lg-2">
<select class="configKey form-control" data-l1key="tvLogLevel">
<option value="daemon" selected>{{Identique au démon (défaut)}}</option>
<option value="debug">{{Debug}}</option>
<option value="info">{{Info}}</option>
<option value="warning">{{Warning}}</option>
<option value="error">{{Error}}</option>
<option value="critical">{{Critical}}</option>
</select>
</div>
</div>
</div>
</fieldset>
<fieldset>
Expand Down
2 changes: 1 addition & 1 deletion plugin_info/info.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "tvremote",
"name": "TV Remote",
"pluginVersion": "1.3.18",
"pluginVersion": "1.3.19",
"description": {
"fr_FR": "Plugin pour contrôler les équipements Android de type Télévision (Sony, Nvidia Shield, Freebox TV, etc...) via le service de télécommande Google (TVRemote) ou via ADB. Il permet de contrôler les différentes fonctions de sa télévision (power, volume, entrées HDMI, lancement d'applications, chaînes TV, navigation dans l'interface, etc...)",
"en_US": "Plugin to control Android TV devices (Sony, Nvidia Shield, Freebox TV, etc...) via Google remote control service (TVRemote) or via ADB. It allows you to control various functions of your TV (power, volume, HDMI inputs, launching applications, TV channels, navigating the interface, etc...)",
Expand Down
6 changes: 6 additions & 0 deletions plugin_info/install.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ function tvremote_install() {
if (config::byKey('cyclefactor', 'tvremote') == '') {
config::save('cyclefactor', '1.0', 'tvremote');
}
if (config::byKey('tvLogLevel', 'tvremote') == '') {
config::save('tvLogLevel', 'daemon', 'tvremote');
}
if (config::byKey('debugInstallUpdates', 'tvremote') == '') {
config::save('debugInstallUpdates', '0', 'tvremote');
}
Expand Down Expand Up @@ -90,6 +93,9 @@ function tvremote_update() {
if (config::byKey('cyclefactor', 'tvremote') == '') {
config::save('cyclefactor', '1.0', 'tvremote');
}
if (config::byKey('tvLogLevel', 'tvremote') == '') {
config::save('tvLogLevel', 'daemon', 'tvremote');
}
if (config::byKey('debugInstallUpdates', 'tvremote') == '') {
config::save('debugInstallUpdates', '0', 'tvremote');
}
Expand Down
2 changes: 1 addition & 1 deletion resources/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
zeroconf==0.148.0
zeroconf==0.149.16
aiohttp==3.13.5
androidtvremote2==0.3.1
adb-shell[async]==0.4.4
Expand Down
4 changes: 4 additions & 0 deletions resources/tvremoted/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ def socket_port(self):
def log_level(self):
return self._kwargs.get('loglevel', 'error')

@property
def tv_log_level(self):
return self._kwargs.get('tvloglevel', 'daemon')

@property
def api_key(self):
return self._kwargs.get('apikey', '')
Expand Down
31 changes: 19 additions & 12 deletions resources/tvremoted/tvremoted.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def main(self) -> None:
You should start the asyncio task with this function like this: `asyncio.createtask(myEQRemote.main())`
"""
try:
self._logger.debug("[EQRemote][MAIN][%s] Starting Main for Host :: %s", self._macAddr, self._host)
self._logger.info("[EQRemote][MAIN][%s] Starting Main for Host :: %s", self._macAddr, self._host)

self._remote = AndroidTVRemote(self._config.client_name, self._config.cert_file, self._config.key_file, self._host, enable_ime=self._enable_ime)

Expand Down Expand Up @@ -421,7 +421,7 @@ async def main(self) -> None:
You should start the asyncio task with this function like this: `asyncio.create_task(myEQRemoteADB.main())`
"""
try:
self._logger.debug("[EQRemoteADB][MAIN][%s] Starting Main for Host :: %s", self._macAddr, self._host)
self._logger.info("[EQRemoteADB][MAIN][%s] Starting Main for Host :: %s", self._macAddr, self._host)

# Small initial delay to let the network and device stabilize after daemon start
await asyncio.sleep(2)
Expand Down Expand Up @@ -952,28 +952,28 @@ async def _on_socket_message(self, message: dict) -> None:
else:
self._logger.warning('[DAEMON][SOCKET] Unknown Action :: %s', message['cmd_action'])
elif message['cmd'] == "scanOn":
self._logger.debug('[DAEMON][SOCKET] ScanState = scanOn')
self._logger.info('[DAEMON][SOCKET] ScanState = scanOn')
self._config.scanmode = True
self._config.scanmode_start = int(time.time())
if self._jeedom_publisher is not None:
await self._jeedom_publisher.send_to_jeedom({'scanState': 'scanOn'})
elif message['cmd'] == "scanOff":
self._logger.debug('[DAEMON][SOCKET] ScanState = scanOff')
self._logger.info('[DAEMON][SOCKET] ScanState = scanOff')
self._config.scanmode = False
if self._jeedom_publisher is not None:
await self._jeedom_publisher.send_to_jeedom({'scanState': 'scanOff'})
elif message['cmd'] == "sendBeginPairing":
self._logger.debug('[DAEMON][SOCKET] Begin Pairing for (Mac :: %s) :: %s:%s / %s', message['mac'], message['host'], message['port'])
self._logger.info('[DAEMON][SOCKET] Begin Pairing for (Mac :: %s) :: %s:%s', message['mac'], message['host'], message['port'])
await self._pairing(message['mac'], message['host'], message['port'])
elif message['cmd'] == "sendBeginPairingAdb":
self._logger.debug('[DAEMON][SOCKET] Begin ADB Pairing for (Mac :: %s) :: %s', message['mac'], message['host'])
self._logger.info('[DAEMON][SOCKET] Begin ADB Pairing for (Mac :: %s) :: %s', message['mac'], message['host'])
await self._pairing_adb(message['mac'], message['host'])
elif message['cmd'] == "sendPairCode":
self._logger.debug('[DAEMON][SOCKET] Received Pairing Code (Mac :: %s) :: %s', message['mac'], message['paircode'])
self._config.pairing_code = message['paircode']
elif message['cmd'] == "addtvremote":
if all(keys in message for keys in ('mac', 'host', 'port', 'friendly_name')):
self._logger.debug('[DAEMON][SOCKET] Add TVRemote Device (Mac :: %s) :: %s:%s', message['mac'], message['host'], message['port'])
self._logger.info('[DAEMON][SOCKET] Add TVRemote Device (Mac :: %s) :: %s:%s', message['mac'], message['host'], message['port'])
if message['host'] not in self._config.known_hosts:
self._config.known_hosts.append(message['host'])
self._logger.debug('[DAEMON][SOCKET] Add TVRemote (AndroidTVRemote2) to KNOWN Devices :: %s', str(self._config.known_hosts))
Expand All @@ -996,7 +996,7 @@ async def _on_socket_message(self, message: dict) -> None:
self._logger.debug('[DAEMON][SOCKET] TVRemote device %s already exists', message['mac'])
elif message['cmd'] == "removetvremote":
if all(keys in message for keys in ('mac', 'host', 'port', 'friendly_name')):
self._logger.debug('[DAEMON][SOCKET] Remove TVRemote (Mac :: %s) :: %s:%s', message['mac'], message['host'], message['port'])
self._logger.info('[DAEMON][SOCKET] Remove TVRemote (Mac :: %s) :: %s:%s', message['mac'], message['host'], message['port'])
if message['host'] in self._config.known_hosts:
self._config.known_hosts.remove(message['host'])
self._logger.debug('[DAEMON][SOCKET] Remove TVRemote (AndroidTVRemote2) from KNOWN Devices :: %s', str(self._config.known_hosts))
Expand All @@ -1020,7 +1020,7 @@ async def _on_socket_message(self, message: dict) -> None:
del self._config.remote_devices[message['mac']]
elif message['cmd'] == "addtvremote_adb":
if all(keys in message for keys in ('mac', 'host', 'friendly_name')):
self._logger.debug('[DAEMON][SOCKET] Add ADB Device (Mac :: %s) :: %s', message['mac'], message['host'])
self._logger.info('[DAEMON][SOCKET] Add ADB Device (Mac :: %s) :: %s', message['mac'], message['host'])
# Ensure ADB keys exist before adding device
await self.ensure_adb_keys(notify_jeedom=False)
if message['host'] not in self._config.known_hosts_adb:
Expand Down Expand Up @@ -1063,7 +1063,7 @@ async def _on_socket_message(self, message: dict) -> None:
self._logger.debug('[DAEMON][SOCKET] Device %s already exists, updated paired=%s, persistent=%s', message['mac'], device._adb_paired, device._persistent_connection)
elif message['cmd'] == "removetvremote_adb":
if all(keys in message for keys in ('mac', 'host', 'friendly_name')):
self._logger.debug('[DAEMON][SOCKET] Remove ADB Device (Mac :: %s) :: %s', message['mac'], message['host'])
self._logger.info('[DAEMON][SOCKET] Remove ADB Device (Mac :: %s) :: %s', message['mac'], message['host'])
if message['host'] in self._config.known_hosts_adb:
self._config.known_hosts_adb.remove(message['host'])
self._logger.debug('[DAEMON][SOCKET] Remove ADB from KNOWN Devices :: %s', str(self._config.known_hosts_adb))
Expand Down Expand Up @@ -1118,7 +1118,7 @@ async def _pairing(self, _mac=None, _host=None, _port=None) -> None:

try:
try:
self._logger.debug("[PAIRING][START][%s] Start Pairing...", _mac)
self._logger.info("[PAIRING][START][%s] Start Pairing...", _mac)
await remote.async_start_pairing()
except CannotConnect as e:
self._logger.warning("[PAIRING][START][%s] Cannot connect (device may be offline) :: %s", _mac, e)
Expand Down Expand Up @@ -1517,7 +1517,7 @@ def close(self) -> None:
This function can be called from outside to stop the daemon if needed`
You need to close your remote connexions and cancel background tasks if any here.
"""
self._logger.debug('[CLOSE] Cancelling all tasks')
self._logger.info('[CLOSE] Cancelling all tasks')

# Collect all tasks to cancel
tasks_to_cancel = []
Expand Down Expand Up @@ -1557,6 +1557,7 @@ def close(self) -> None:
def get_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description='TVRemote Daemon for Jeedom plugin')
parser.add_argument("--loglevel", help="Log Level for the daemon", type=str)
parser.add_argument("--tvloglevel", help="Log Level for TV libraries (zeroconf, androidtvremote2, adb_shell)", type=str, default='daemon')
parser.add_argument("--pluginversion", help="Plugin Version", type=str)
parser.add_argument("--socketport", help="Port for TVRemote server", type=str)
parser.add_argument("--cyclefactor", help="Cycle Factor", type=str)
Expand Down Expand Up @@ -1591,12 +1592,18 @@ def shutdown() -> None:
Utils.init_logger(config.log_level)
_LOGGER = logging.getLogger(__name__)
logging.getLogger('asyncio').setLevel(logging.WARNING)
if config.tv_log_level != 'daemon':
_tv_level = Utils.convert_log_level(config.tv_log_level)
logging.getLogger('zeroconf').setLevel(_tv_level)
logging.getLogger('androidtvremote2').setLevel(_tv_level)
logging.getLogger('adb_shell').setLevel(_tv_level)

try:
_LOGGER.info('[DAEMON] Starting Daemon')
_LOGGER.info('[DAEMON] Plugin Version: %s', config.plugin_version)
_LOGGER.info('[DAEMON] Pairing Name: %s', config.client_name)
_LOGGER.info('[DAEMON] Log Level: %s', config.log_level)
_LOGGER.info('[DAEMON] TV Log Level: %s', config.tv_log_level)
_LOGGER.info('[DAEMON] Socket Port: %s', config.socket_port)
_LOGGER.info('[DAEMON] Socket Host: %s', config.socket_host)
_LOGGER.info('[DAEMON] Cycle Factor: %s', config.cycle_factor)
Expand Down