From b61670b0cc972f39c57aba54c5770316c0b23bf6 Mon Sep 17 00:00:00 2001 From: TB-1993 Date: Mon, 23 Sep 2024 17:33:09 +0100 Subject: [PATCH 1/6] Upgrade #92: Creation of HDMICEC module --- framework/core/deviceManager.py | 4 + framework/core/hdmiCECController.py | 170 ++++++++++++++ framework/core/hdmicecModules/__init__.py | 30 +++ .../hdmicecModules/abstractCECController.py | 98 ++++++++ framework/core/hdmicecModules/cecClient.py | 212 ++++++++++++++++++ 5 files changed, 514 insertions(+) create mode 100644 framework/core/hdmiCECController.py create mode 100644 framework/core/hdmicecModules/__init__.py create mode 100644 framework/core/hdmicecModules/abstractCECController.py create mode 100644 framework/core/hdmicecModules/cecClient.py diff --git a/framework/core/deviceManager.py b/framework/core/deviceManager.py index 1d79026..51820e7 100644 --- a/framework/core/deviceManager.py +++ b/framework/core/deviceManager.py @@ -40,6 +40,7 @@ from framework.core.powerControl import powerControlClass from framework.core.outboundClient import outboundClientClass from framework.core.commonRemote import commonRemoteClass +from framework.core.hdmiCECController import HDMICECController from framework.core.utilities import utilities dir_path = os.path.dirname(os.path.realpath(__file__)) @@ -174,6 +175,9 @@ def __init__(self, log:logModule, logPath:str, devices:dict): config = device.get("remoteController") if config != None: self.remoteController = commonRemoteClass(log, config) + config = device.get("hdmiCECController") + if config != None: + self.hdmiCECController = HDMICECController(log, config) self.session = self.getConsoleSession() def getField(self, fieldName:str, itemsList:dict = None): diff --git a/framework/core/hdmiCECController.py b/framework/core/hdmiCECController.py new file mode 100644 index 0000000..81027f1 --- /dev/null +++ b/framework/core/hdmiCECController.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 +#** ***************************************************************************** +# * +# * If not stated otherwise in this file or this component's LICENSE file the +# * following copyright and licenses apply: +# * +# * Copyright 2023 RDK Management +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * +# http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# * +#* ****************************************************************************** +#* +#* ** Project : RAFT +#* ** @addtogroup : core +#* ** @date : 22/11/2021 +#* ** +#* ** @brief : HDMICECController class to differentiate into the whichever +#* ** cec controller type is specified. +#* ** +#* ****************************************************************************** + +from os import path + +import sys +MY_PATH = path.realpath(__file__) +MY_DIR = path.dirname(MY_PATH) +sys.path.append(path.join(MY_DIR,'../../')) +from framework.core.logModule import logModule +from hdmicecModules import CECClientController, MonitoringType + +class HDMICECController(): + """ + This class provides a high-level interface for controlling and monitoring + Consumer Electronics Control (CEC) devices. + """ + + def __init__(self, log: logModule, config: dict): + """ + Initializes the HDMICECController instance. + + Args: + log (logModule): An instance of a logging module for recording messages. + config (dict): A dictionary containing configuration options + """ + self._log = log + self.controllerType = config.get('type') + self.cecAdaptor = config.get('adaptor') + if self.controllerType.lower() == 'cec-client': + self.controller = CECClientController(self.cecAdaptor, self._log) + self._read_line = 0 + self._monitoringLog = path.join(self._log.logPath, 'cecMonitor.log') + + def send_message(self, message: str) -> bool: + """ + Sends a CEC message to connected devices using the configured controller. + + Args: + message (str): The CEC message to be sent. + + Returns: + bool: True if the message was sent successfully, False otherwise. + """ + self._log.debug('Sending CEC message: [%s]' % message) + return self.controller.sendMessage(message) + + def startMonitoring(self, deviceType: MonitoringType = MonitoringType.RECORDER) -> None: + """ + Starts monitoring CEC messages from the adaptor as the specified device type. + + Args: + deviceType (MonitoringType, optional): The type of device to monitor (default: MonitoringType.RECORDER). + + Raises: + RuntimeError: If monitoring is already running. + """ + if self.controller.monitoring is False: + self._log.debug('Starting monitoring on adaptor: [%s]' % self.cecAdaptor) + self._log.debug('Monitoring as device type [%s]' % deviceType.name) + return self.controller.startMonitoring(self._monitoringLog, deviceType) + else: + self._log.warn('CEC monitoring is already running') + + def stopMonitoring(self): + """ + Stops the CEC monitoring process. + + Delegates the stop task to the underlying `CECClientController`. + """ + return self.controller.stopMonitoring() + + def readUntil(self, message: str, retries: int = 5) -> bool: + """ + Reads the monitoring log until the specified message is found. + + Opens the monitoring log file and checks for the message within a specified retry limit. + + Args: + message (str): The message to search for in the monitoring log. + retries (int, optional): The maximum number of retries before giving up (default: 5). + + Returns: + bool: True if the message was found, False otherwise. + """ + self._log.debug('Starting readUntil for message as [%s] with [%s] retries' % (message,retries)) + result = False + with open(self._monitoringLog, 'r') as logFile: + retry = 0 + max_retries = retries + while retry != max_retries and not result: + logLines = logFile.readlines() + read_line = self._read_line + write_line = len(logLines) + while read_line != write_line: + if message in logLines[read_line]: + result = True + break + read_line+=1 + retry += 1 + self._read_line = read_line + return result + + def listDevices(self) -> list: + """ + Retrieves a list of discovered CEC devices with their OSD names (if available). + + Returns: + list: A list of dictionaries representing discovered devices. + """ + self._log.debug('Listing devices on CEC network') + return self.controller.listDevices() + + +if __name__ == "__main__": + import time + import json + LOG = logModule('CECTEST', logModule.DEBUG) + CONFIGS = [ + { + 'type': 'cec-client', + 'adaptor': '/dev/ttyACM0' + }, + ] + for config in CONFIGS: + LOG.setFilename('./logs/','CECTEST%s.log' % config.get('type')) + LOG.stepStart('Testing with %s' % json.dumps(config)) + CEC = HDMICECController(LOG, config) + DEVICES = CEC.listDevices() + LOG.info(json.dumps(DEVICES)) + # The user will need to check all the devices expected from their + # cec network are shown in this output. + CEC.startMonitoring() + # It's is expected that a user will send a standby command on their cec + # network during this 2 minutes. + time.sleep(120) + result = CEC.readUntil('standby') + CEC.stopMonitoring() + LOG.stepResult(result, 'The readUntil result is: [%s]' % result) + # The user should check here the monitoring log for thier type contains + # the expected information. diff --git a/framework/core/hdmicecModules/__init__.py b/framework/core/hdmicecModules/__init__.py new file mode 100644 index 0000000..df8f28f --- /dev/null +++ b/framework/core/hdmicecModules/__init__.py @@ -0,0 +1,30 @@ +#** ***************************************************************************** +# * +# * If not stated otherwise in this file or this component's LICENSE file the +# * following copyright and licenses apply: +# * +# * Copyright 2023 RDK Management +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * +# http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# * +#* ****************************************************************************** +#* +#* ** Project : RAFT +#* ** @addtogroup : core +#* ** @date : 22/11/2021 +#* ** +#* ****************************************************************************** + +from .cecClient import CECClientController +from .cecTypes import MonitoringType diff --git a/framework/core/hdmicecModules/abstractCECController.py b/framework/core/hdmicecModules/abstractCECController.py new file mode 100644 index 0000000..5a00a14 --- /dev/null +++ b/framework/core/hdmicecModules/abstractCECController.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +#** ***************************************************************************** +# * +# * If not stated otherwise in this file or this component's LICENSE file the +# * following copyright and licenses apply: +# * +# * Copyright 2023 RDK Management +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * +# http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# * +#* ****************************************************************************** +#* +#* ** Project : RAFT +#* ** @addtogroup : core +#* ** @date : 02/10/2024 +#* ** +#* ** @brief : Abstract class for CEC controller types. +#* ** +#* ****************************************************************************** + +from abc import ABCMeta, abstractmethod + +from framework.core.logModule import logModule +from .cecTypes import MonitoringType + +class CECInterface(metaclass=ABCMeta): + + def __init__(self, adaptor_path:str, logger:logModule): + self.adaptor = adaptor_path + self._log = logger + self._monitoring = False + + @property + def monitoring(self) -> bool: + return self._monitoring + + @abstractmethod + def sendMessage(cls, message:str) -> bool: + """ + Send a CEC message to the CEC network. + + Args: + message (str): The CEC message to be sent. + + Returns: + bool: True if the message was sent successfully, False otherwise. + """ + pass + + @abstractmethod + def listDevices(cls) -> list: + """ + List CEC devices on CEC network. + + The list returned contains dicts in the following format: + { + 'name': 'TV' + 'address': '0.0.0.0', + 'active source': True, + 'vendor': 'Unknown', + 'osd string': 'TV', + 'CEC version': '1.3a', + 'power status': 'on', + 'language': 'eng', + } + Returns: + list: A list of dictionaries representing discovered devices. + """ + pass + + @abstractmethod + def startMonitoring(cls, monitoringLog: str, deviceType: MonitoringType=MonitoringType.RECORDER) -> None: + """ + Starts monitoring CEC messages with a specified device type. + + Args: + deviceType (MonitoringType, optional): The type of device to monitor (default: MonitoringType.RECORDER). + monitoringLog (str) : Path to write the monitoring log out + """ + pass + + @abstractmethod + def stopMonitoring(cls) -> None: + """ + Stops the CEC monitoring process. + """ + pass diff --git a/framework/core/hdmicecModules/cecClient.py b/framework/core/hdmicecModules/cecClient.py new file mode 100644 index 0000000..2110872 --- /dev/null +++ b/framework/core/hdmicecModules/cecClient.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +#** ***************************************************************************** +# * +# * If not stated otherwise in this file or this component's LICENSE file the +# * following copyright and licenses apply: +# * +# * Copyright 2023 RDK Management +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * +# http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# * +#* ****************************************************************************** +#* +#* ** Project : RAFT +#* ** @addtogroup : core +#* ** @date : 02/10/2024 +#* ** +#* ** @brief : cecClient controller. Class for running cec-client commands on +#* ** the host PC +#* ** +#* ****************************************************************************** + +from io import IOBase +import os +import re +import subprocess +from threading import Thread + +import sys +sys.path.append('/mnt/1TB/home/toby/Documents/Scripts/python/python_raft/') + +from framework.core.logModule import logModule +from .abstractCECController import CECInterface +from .cecTypes import MonitoringType + + +class CECClientController(CECInterface): + """ + This class provides an interface for controlling Consumer Electronics Control (CEC) + devices through the `cec-client` command-line tool. + """ + + def __init__(self, adaptor_path:str, logger:logModule): + """ + Initializes the CECClientController instance. + + Args: + adaptor_path (str): Path to the CEC adaptor device. + logger (logModule): An instance of a logging module for recording messages. + + Raises: + AttributeError: If the specified CEC adaptor is not found. + """ + + self._log = logger + self.adaptor = adaptor_path + self._log.debug('Initialising CECClientController for [%s]' % self.adaptor) + if self.adaptor not in map(lambda x: x.get('com port'),self._getAdaptors()): + raise AttributeError('CEC Adaptor specified not found') + self._monitoring = False + self._m_proc = None + self._m_stdout_thread = None + self._m_log = None + + def sendMessage(self,message: str) -> bool: + exit_code, stdout = self._sendMessage(message, 0) + self._log.debug('Output of message sent: [%s]' % stdout) + if exit_code != 0: + return False + return True + + def _sendMessage(self, message: str, debug: int = 1) -> tuple: + """ + Internal method for sending a CEC message using `subprocess`. + + Args: + message (str): The CEC message to be sent. + debug (int, optional): Debug level for `cec-client` (default: 1). + + Returns: + tuple: A tuple containing the exit code of the subprocess call and the standard output. + """ + result = subprocess.run(f'echo "{message}" | cec-client {self.adaptor} -s -d {debug}', + shell=True, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout = result.stdout.decode('utf-8') + stderr = result.stderr.decode('utf-8') + exit_code = result.returncode + return exit_code, stdout + + def _getAdaptors(self) -> list: + """ + Retrieves a list of available CEC adaptors using `cec-client`. + + Returns: + list: A list of dictionaries representing available adaptors with details like COM port. + """ + result = subprocess.run(f'cec-client -l', + shell=True, + text=True, + capture_output=True, + check=True) + stdout = result.stdout + adaptor_count = re.search(r'Found devices: ([0-9]+)',stdout, re.M).group(1) + adaptors = self._splitDeviceSectionsToDicts(stdout) + return adaptors + + def _scanCECNetwork(self) -> list: + """ + Scans the CEC network for connected devices using `cec-client`. + + Sends a "scan" message and parses the response to identify connected devices. + + Returns: + list: A list of dictionaries representing discovered devices with details. + """ + _, result = self._sendMessage('scan') + self._log.debug('Output of scan on CEC Network: [%s]' % result) + devicesOnNetwork = self._splitDeviceSectionsToDicts(result) + return devicesOnNetwork + + def listDevices(self) -> list: + devices = self._scanCECNetwork() + for device_dict in devices: + device_dict['name'] = device_dict.get('osd string') + if device_dict.get('active source') == 'yes': + device_dict['active source'] = True + else: + device_dict['active source'] = False + return devices + + def _splitDeviceSectionsToDicts(self,command_output:str) -> list: + """ + Splits the output of a `cec-client` command into individual device sections and parses them into dictionaries. + + Args: + command_output (str): The output string from the `cec-client` command. + + Returns: + list: A list of dictionaries, each representing a single CEC device with its attributes. + """ + devices = [] + device_sections = re.findall(r'^device[ #0-9]{0,}:[\s\S]+?(?:type|language): +[\S ]+$', + command_output, + re.M) + if device_sections: + for section in device_sections: + device_dict = {} + for line in section.split('\n'): + line_split = re.search(r'^([\w #]+): +?(\S[\S ]{0,})$',line) + if line_split: + device_dict[line_split.group(1)] = line_split.group(2) + devices.append(device_dict) + return devices + + def startMonitoring(self, monitoringLog: str, device_type: MonitoringType = MonitoringType.RECORDER) -> None: + self._monitoring = True + try: + self._m_proc = subprocess.Popen(f'cec-client {self.adaptor} -m -d 0 -t {device_type.value}'.split(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True) + self._m_log = open(monitoringLog, 'w+', encoding='utf-8') + self._m_stdout_thread = Thread(target=self._write_monitoring_log, + args=[self._m_proc.stdout, self._m_log], + daemon=True) + self._m_stdout_thread.start() + except Exception as e: + self.stopMonitoring() + raise + + def _write_monitoring_log(self,stream_in: IOBase, stream_out: IOBase) -> None: + """ + Writes the output of the monitoring process to a log file. + + Args: + stream_in (IOBase): The input stream from the monitoring process. + stream_out (IOBase): The output stream to the log file. + """ + while True: + chunk = stream_in.readline() + if chunk == '': + break + stream_out.write(chunk) + + def stopMonitoring(self) -> None: + self._log.debug('Stopping monitoring of adaptor [%s]' % self.adaptor) + if self.monitoring is False: + return + self._m_proc.terminate() + exit_code = self._m_proc.wait() + self._m_stdout_thread.join() + self._m_log.close() + self._monitoring = False + + def __del__(self): + """ + Destructor for the class, ensures monitoring is stopped. + """ + self.stopMonitoring() From 14437d9e77efc35b5be0d8d3cb92dd8945463f9e Mon Sep 17 00:00:00 2001 From: TB-1993 <109213741+TB-1993@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:18:50 +0100 Subject: [PATCH 2/6] Fix #92: Updated documentation and fixed bug in monitoring --- README.md | 6 +++++- examples/configs/example_rack_config.yml | 3 +++ framework/core/hdmiCECController.py | 12 ++++++------ framework/core/hdmicecModules/cecClient.py | 21 ++++++++------------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 821710b..0492df5 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,14 @@ It understands how to: - Python (=3.11.8) - Pyenv can be used to manage multiple versions of Python (please refer to the [documentation](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation)). - - All the packages listed in requirements.txt - Can be installed using `$ pip install -r requirements.txt` +#### Optional Packages + +- To use the cec-client type hdmiCECController the `cec-utils` package is required. + - `sudo apt install -y cec-utils` + ### User Installation Clone the repository and ensure that all requirements are met. diff --git a/examples/configs/example_rack_config.yml b/examples/configs/example_rack_config.yml index e011f06..fab3834 100644 --- a/examples/configs/example_rack_config.yml +++ b/examples/configs/example_rack_config.yml @@ -100,6 +100,9 @@ rackConfig: type: "HS100" ip: "192.168.1.7" port: 9999 + #hdmiCECController: Specific hdmiCECController for the slot + # supported types: + # [type: "cec-client", adaptor: "/dev/ttycec"] - pi2: ip: "192.168.99.1" description: "local pi4" diff --git a/framework/core/hdmiCECController.py b/framework/core/hdmiCECController.py index 81027f1..17f4787 100644 --- a/framework/core/hdmiCECController.py +++ b/framework/core/hdmiCECController.py @@ -114,10 +114,10 @@ def readUntil(self, message: str, retries: int = 5) -> bool: """ self._log.debug('Starting readUntil for message as [%s] with [%s] retries' % (message,retries)) result = False - with open(self._monitoringLog, 'r') as logFile: - retry = 0 - max_retries = retries - while retry != max_retries and not result: + retry = 0 + max_retries = retries + while retry != max_retries and not result: + with open(self._monitoringLog, 'r') as logFile: logLines = logFile.readlines() read_line = self._read_line write_line = len(logLines) @@ -126,8 +126,8 @@ def readUntil(self, message: str, retries: int = 5) -> bool: result = True break read_line+=1 - retry += 1 - self._read_line = read_line + retry += 1 + self._read_line = read_line return result def listDevices(self) -> list: diff --git a/framework/core/hdmicecModules/cecClient.py b/framework/core/hdmicecModules/cecClient.py index 2110872..0e2c758 100644 --- a/framework/core/hdmicecModules/cecClient.py +++ b/framework/core/hdmicecModules/cecClient.py @@ -26,19 +26,16 @@ #* ** @date : 02/10/2024 #* ** #* ** @brief : cecClient controller. Class for running cec-client commands on -#* ** the host PC +#* ** the host PC. This requires the cec-utils package to be installed +#* ** on the host. #* ** #* ****************************************************************************** from io import IOBase -import os import re import subprocess from threading import Thread -import sys -sys.path.append('/mnt/1TB/home/toby/Documents/Scripts/python/python_raft/') - from framework.core.logModule import logModule from .abstractCECController import CECInterface from .cecTypes import MonitoringType @@ -70,7 +67,6 @@ def __init__(self, adaptor_path:str, logger:logModule): self._monitoring = False self._m_proc = None self._m_stdout_thread = None - self._m_log = None def sendMessage(self,message: str) -> bool: exit_code, stdout = self._sendMessage(message, 0) @@ -172,28 +168,28 @@ def startMonitoring(self, monitoringLog: str, device_type: MonitoringType = Moni stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) - self._m_log = open(monitoringLog, 'w+', encoding='utf-8') self._m_stdout_thread = Thread(target=self._write_monitoring_log, - args=[self._m_proc.stdout, self._m_log], + args=[self._m_proc.stdout, monitoringLog], daemon=True) self._m_stdout_thread.start() except Exception as e: self.stopMonitoring() raise - def _write_monitoring_log(self,stream_in: IOBase, stream_out: IOBase) -> None: + def _write_monitoring_log(self,streamIn: IOBase, logFilePath: str) -> None: """ Writes the output of the monitoring process to a log file. Args: stream_in (IOBase): The input stream from the monitoring process. - stream_out (IOBase): The output stream to the log file. + logFilePath (str): File path to write the monitoring log out to. """ while True: - chunk = stream_in.readline() + chunk = streamIn.readline() if chunk == '': break - stream_out.write(chunk) + with open(logFilePath, 'a+',) as out: + out.write(chunk) def stopMonitoring(self) -> None: self._log.debug('Stopping monitoring of adaptor [%s]' % self.adaptor) @@ -202,7 +198,6 @@ def stopMonitoring(self) -> None: self._m_proc.terminate() exit_code = self._m_proc.wait() self._m_stdout_thread.join() - self._m_log.close() self._monitoring = False def __del__(self): From a09d30c732f9d6b1db4b57421e7818944a2e8d47 Mon Sep 17 00:00:00 2001 From: TB-1993 <109213741+TB-1993@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:51:31 +0100 Subject: [PATCH 3/6] Update #92: Updated example_rack_config.yml Updated example_rack_config.yml to better document the usage of optional modules. --- examples/configs/example_rack_config.yml | 63 +++++++++++------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/examples/configs/example_rack_config.yml b/examples/configs/example_rack_config.yml index fab3834..5cfeddf 100644 --- a/examples/configs/example_rack_config.yml +++ b/examples/configs/example_rack_config.yml @@ -22,27 +22,22 @@ # This config file creates your environment, defaults are setup internally unless otherwise overridden. # you will need at least 1 rack and 1 slot to operate. -# each module e.g. console options, will be off by default unless otherwise stated +# Each optional module will be off by default unless otherwise stated. +# Uncomment and fill out the sections for the modules you require. -# Depreciated +# Deprecated # slotx: address: IP of device while running locally, replaced with slotx: ip # slotx: deviceConsole: IP of device while running locally, replaced with slotx: devices # Data that is global to all tests. globalConfig: includes: - # [ includes: ] - # [ deviceConfig: "required.yml file" ] deviceConfig: "example_device_config.yml" - capture: - # [capture: optional] - # [ocrEnginePath: "/usr/bin/tesseract"] # "C:\\Program Files\\Tesseract-OCR\\tesseract.exe" (For Windows) - tesseract binary - # [resolution: "1080p"] - Capture resolution - # [input: 0] - which input is connected to the video path + # [capture: optional] - This section is required for use with the videoCapture module. + # [ocrEnginePath: "/usr/bin/tesseract"] # "C:\\Program Files\\Tesseract-OCR\\tesseract.exe" (For Windows) - tesseract binary + # [resolution: "1080p"] - Capture resolution + # [input: 0] - which input is connected to the video path # Note: Video capture will not be installed unless screenRegions: is defined in deviceConfig: - ocrEnginePath: "/usr/bin/tesseract" # "C:\\Program Files\\Tesseract-OCR\\tesseract.exe" (For Windows) - resolution: "1080p" - input: 0 local: log: # log for each slot directory: "./logs" @@ -57,15 +52,16 @@ rackConfig: # [ name: "required", description: "optional"] name: "slot1" devices: - # [ devices: ] - # [ type: "serial": port: "COM7" baudRate: "(default)115200" dataBits: "optional(8)" stopBits: "optional(1)" parity: "optional(None)" FlowControl: "optional(None)" ] - # [ type: "ssh": port: 22 username: "test" password: "test" ] - # [ type: "telnet": port: 23 username: "test" password: "test" ] - dut: ip: "127.0.0.1" # IP Address of the ADA Hub description: "local PC" platform: "linux PC" consoles: + # - [ name ] - consoles should be listed here with a name. Defined as one of the types below. + # supported types: + # [ type: "serial", port: "COM7", baudRate: "(default)115200", dataBits: "optional(8)", stopBits: "optional(1)", parity: "optional(None)", FlowControl: "optional(None)", ] + # [ type: "ssh", port: 22, username: "test", password: "test" ] + # [ type: "telnet", port: 23, username: "test", password: "test" ] - default: type: "serial" port: "/dev/ttyUSB0" @@ -73,21 +69,22 @@ rackConfig: port: 22 username: "root" ip: "192.168.99.1" - remoteController: - # [ remoteController: ] - # [ type: "olimex" ip: "192.168.0.17" port: 7 map: "llama_rc6", config: "remote_commander.yml" ] - # [ type: "skyProc" map: "skyq_map", config: "remote_commander.yml" ] + + # [ remoteController: optional ] - This section is required for use with the remoteController module. + # supported types: + # [ type: "olimex", ip: "192.168.0.17", port: 7, map: "llama_rc6", config: "remote_commander.yml" ] + # [ type: "skyProc", map: "skyq_map", config: "remote_commander.yml" ] # [ type: "None" ] - type: "none" - map: "skyq_map" - config: "example_remote_commander.yml" - outbound: - download_url: "tftp://tftp-server.com/rack1/slot1/" # download location for the CPE device - upload_url: "sftp://server-address/home/workspace/tftp/rack1/slot1/" # upload location - upload_url_base_dir: "sftp://server-address/home/workspace/tftp/rack1/slot1" # alternative upload location - httpProxy: 'socks5h://localhost:1234' # Local Proxy if required - workspaceDirectory: './logs/workspace' # Local working directory - powerSwitch: # Specific power switch for each slot + + # [ outbound: optional ] - This section is used to configure paths for downloads and uploads from your test + # supported usage: + # [download_url: "url" ] - download location for the CPE device + # [ upload_url: "url" ] - upload location + # [ upload_url_base_dir: "url" ] - alternative upload location + # [ httpProxy: "uri" ] - Local Proxy if required + # [ workspaceDirectory: "path to directory on host" ] - Local working directory + + # [ powerSwitch: optional ] - Power switch for the slot # supported types, if this section is undefined this is ok # [type: "orvbioS20", ip: "", mac: "", port:"optional", relay:"optional"] # [type: "kasa", ip: "", options:"--plug" ] # <- Plug @@ -97,10 +94,8 @@ rackConfig: # [type: "olimex", ip:"", port:"optional", relay:"" ] # [type: "SLP", ip:"", username: "", password: "", outlet_id:"", port:"optional"] # [type: "none" ] if section doesn't exist then type:none will be used - type: "HS100" - ip: "192.168.1.7" - port: 9999 - #hdmiCECController: Specific hdmiCECController for the slot + + # [ hdmiCECController: optional ] - Specific hdmiCECController for the slot # supported types: # [type: "cec-client", adaptor: "/dev/ttycec"] - pi2: From bcde2964139a2a42018ed26b3d57d27026735f33 Mon Sep 17 00:00:00 2001 From: TB-1993 <109213741+TB-1993@users.noreply.github.com> Date: Mon, 14 Oct 2024 10:07:24 +0100 Subject: [PATCH 4/6] Fix #92: Added missing cecTypes.py --- framework/core/hdmicecModules/cecTypes.py | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 framework/core/hdmicecModules/cecTypes.py diff --git a/framework/core/hdmicecModules/cecTypes.py b/framework/core/hdmicecModules/cecTypes.py new file mode 100644 index 0000000..62e81f4 --- /dev/null +++ b/framework/core/hdmicecModules/cecTypes.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +#** ***************************************************************************** +# * +# * If not stated otherwise in this file or this component's LICENSE file the +# * following copyright and licenses apply: +# * +# * Copyright 2023 RDK Management +# * +# * Licensed under the Apache License, Version 2.0 (the "License"); +# * you may not use this file except in compliance with the License. +# * You may obtain a copy of the License at +# * +# * +# http://www.apache.org/licenses/LICENSE-2.0 +# * +# * Unless required by applicable law or agreed to in writing, software +# * distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. +# * +#* ****************************************************************************** +#* +#* ** Project : RAFT +#* ** @addtogroup : core +#* ** @date : 02/10/2024 +#* ** +#* ** @brief : cecTypes defined to standardise cecController usage. +#* ** +#* ****************************************************************************** + +from enum import Enum + +class MonitoringType(Enum): + PLAYBACK = "p" + RECORDER = "r" + TUNER = "t" + AUDIO = "a" From 28e1f99d6662d5b1358dc8bd82a124bdca868ff8 Mon Sep 17 00:00:00 2001 From: TB-1993 <109213741+TB-1993@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:45:03 +0000 Subject: [PATCH 5/6] Update #92 - Updated installation to install cec-client --- README.md | 14 +-- installation/activate.sh | 3 - installation/install_requirements.sh | 168 ++++++++++++++++++++++++--- 3 files changed, 160 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 0492df5..0161670 100644 --- a/README.md +++ b/README.md @@ -58,19 +58,17 @@ It understands how to: ### Requirements -- Python (=3.11.8) +- Python (version 3.10+) - Pyenv can be used to manage multiple versions of Python (please refer to the [documentation](https://github.com/pyenv/pyenv?tab=readme-ov-file#installation)). -- All the packages listed in requirements.txt - - Can be installed using `$ pip install -r requirements.txt` -#### Optional Packages +### User Installation -- To use the cec-client type hdmiCECController the `cec-utils` package is required. - - `sudo apt install -y cec-utils` +Clone the repository and run the [`install_requirements.sh`](installation/install_requirements.sh) script. -### User Installation +Administrator rights are required to install the below packages. Without these some modules may not work: +- `cec-client` - Required to use the CECClient hdmiCECController module. -Clone the repository and ensure that all requirements are met. +*If you do not have administrator rights, please ask your administrator to install the above packages for you.* ## Getting Started diff --git a/installation/activate.sh b/installation/activate.sh index da95526..5666b6d 100755 --- a/installation/activate.sh +++ b/installation/activate.sh @@ -27,6 +27,3 @@ if [ $0 != "bash" ];then fi . ${PWD}/VENV/bin/activate -export PYTHONPATH=${PWD}/VENV/lib/python3.8/site-packages/:$PYTHONPATH - - diff --git a/installation/install_requirements.sh b/installation/install_requirements.sh index 40b1352..adaec2c 100755 --- a/installation/install_requirements.sh +++ b/installation/install_requirements.sh @@ -22,26 +22,166 @@ #* ****************************************************************************** MY_PATH="`dirname \"${BASH_SOURCE[0]}\"`" -python_venv=$MY_PATH/VENV +PYTHON_VENV=$MY_PATH/VENV +SUDO=0 -venv_check=$(dpkg --list | grep python3-venv) -if [ -z "$venv_check" ]; then - echo "Please install python3-venv with the following command" - echo "sudo apt install -y python3-venv" - exit +NO_COLOR="\e[0m" +RED="\e[0;31m" +CYAN="\e[0;36m" +YELLOW="\e[1;33m" +GREEN="\e[0;32m" +RED_BOLD="\e[1;31m" +BLUE_BOLD="\e[1;34m" +YELLOW_BOLD="\e[1;33m" + +DEBUG_FLAG=0 +function ECHO() +{ + echo -e "$*" +} + +function DEBUG() +{ + # if set -x is in use debug messages are useless as whole stript will be shown + if [[ "$-" =~ "x" ]]; then + return + fi + if [[ "${DEBUG_FLAG}" == "1" ]];then + ECHO "${BLUE_BOLD}DEBUG: ${CYAN}$*${NO_COLOR}" > /dev/stderr + fi +} + +function INFO() +{ + ECHO "${GREEN}$*${NO_COLOR}" +} + +function WARNING() +{ + ECHO "${YELLOW_BOLD}Warning: ${YELLOW}$*${NO_COLOR}" > /dev/stderr +} + +function ERROR() +{ + ECHO "${RED_BOLD}ERROR: ${RED}$*${NO_COLOR}" + exit 1 +} + +function check_package_installed() +{ +# Check if a given package is installed. +# +# Arguments: +# $1: package_name: The package to check. +# +# Returns: +# 0: If package is installed. +# 1: If package is not installed. +# + DEBUG "BEGIN: ${FUNCNAME} [$*]" + local package_name="$1" + DEBUG "command -v ${package_name}" + local package_check="$(command -v ${package_name})" + if [[ -n "${package_check}" ]]; then + # If package check isn't empty + return 0 + fi + return 1 +} + +function version_check() +{ +# Check if a version is correct or not +# Arguments: +# $1: Version to check +# $2: Required version +# +: as the last character can be used to signify any version over the given number. +# -: as the last character can be used to signify any version below the given number. +# + DEBUG "BEGIN: ${FUNCNAME} [$*]" + local check_version="$1" + local required_version="$2" + local check_version_split=(${check_version//\./" "}) + DEBUG "Version split: [${version_split[0]}]" + local req_version_split=(${required_version//\./" "}) + local stop=$(("${#req_version_split[@]}"-1)) + DEBUG "LOOP STOP: [$stop]" + for i in $(seq 0 ${stop}) + do + local req_version_section="${req_version_split[$i]}" + DEBUG "Req Version Sect: [${req_version_section}]" + local check_version_section="${check_version_split[$i]}" + DEBUG "Check Version Sect: [${check_version_section}]" + case "${req_version_section}" in + *"+") + # Remove the + from the end of the string + req_version_section="${req_version_section%+}" + if [[ "$check_version_section" -ge "${req_version_section}" ]];then + return 0 + fi + return 1 + ;; + *"-") + # Remove the - from end of the string + req_version_section="${req_version_section%-}" + if [[ "$check_version_section" -le "${req_version_section}" ]];then + return 0 + fi + return 1 + ;; + *) + if [[ "${check_version_section}" != "${req_version_section}" ]];then + return 1 + fi + ;; + esac + done + return 0 +} + +##### MAIN ##### + +# Check for sudo rights. +if [[ -n "$(groups | grep sudo)" ]]; then + # If sudo is in groups. + DEBUG "SUDO=1" + SUDO=1 fi -if [ -d "$python_venv" ] && [ -e "$python_venv"/bin/activate ];then - . "$python_venv"/bin/activate +# Check python3 is installed. +check_package_installed python3 +if [[ "$?" != "0" ]];then + ERROR "Python 3 not found.\nPlease install python 3.10+" +fi + +# Python Version check. +python_version="$(python3 --version)" +# Remove the only text before the space +version_check "${python_version##* }" "3.10+" +if [[ "$?" != "0" ]];then + ERROR "Python version installed is too old. Version 3.10+ required" +fi + +if [ -d "${PYTHON_VENV}" ] && [ -e "${PYTHON_VENV}"/bin/activate ];then + . "${PYTHON_VENV}/bin/activate" pip install -qr $MY_PATH/requirements.txt else - rm -rf "$python_venv" - mkdir -p "$python_venv" - python3 -m venv "$python_venv" + rm -rf "${PYTHON_VENV}" + mkdir -p "${PYTHON_VENV}" + python3 -m venv "${PYTHON_VENV}" if [ "$?" != "0" ];then - echo "The python virtual environment could not be created" + ERROR "The python virtual environment could not be created" fi - . "$python_venv"/bin/activate - pip install -qr $MY_PATH/requirements.txt + . "${PYTHON_VENV}/bin/activate" + pip install -qr "${MY_PATH}/requirements.txt" fi +check_package_installed "cec-client" +if [[ "$?" != "0" ]];then + if [[ "${SUDO}" == "1" ]];then + sudo apt update && sudo apt install -y cec-client + else + WARNING "cec-client is not installed" + WARNING "You will not be able to use the CECClient module" + fi +fi From 71e8e15935ed3ef255dc6c0dacc6724795cb92c3 Mon Sep 17 00:00:00 2001 From: TB-1993 <109213741+TB-1993@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:01:57 +0000 Subject: [PATCH 6/6] Fix #92: Removed deprecation comment from example_rack_config Removed the deprecated comment from the example_rack_config as the settings have not existed for this repository. --- examples/configs/example_rack_config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/configs/example_rack_config.yml b/examples/configs/example_rack_config.yml index 5cfeddf..1b89f0d 100644 --- a/examples/configs/example_rack_config.yml +++ b/examples/configs/example_rack_config.yml @@ -25,10 +25,6 @@ # Each optional module will be off by default unless otherwise stated. # Uncomment and fill out the sections for the modules you require. -# Deprecated -# slotx: address: IP of device while running locally, replaced with slotx: ip -# slotx: deviceConsole: IP of device while running locally, replaced with slotx: devices - # Data that is global to all tests. globalConfig: includes: