- fix the problem with the

This commit is contained in:
Dmytro Bogovych 2023-08-19 14:10:57 +03:00
parent fd57047197
commit 0c5652f98a
6 changed files with 102 additions and 75 deletions

View File

@ -48,6 +48,10 @@ audio:
bluetooth: yes bluetooth: yes
bluetooth_mac: "MAC_ADDRESS" bluetooth_mac: "MAC_ADDRESS"
# Relative pathes starts at agent_gsm root
backup_dir: backup
temp_dir: /dev/shm
log: log:
# Log file path (otherwise log will be sent to syslog) # Log file path (otherwise log will be sent to syslog)
path: path:
@ -57,9 +61,3 @@ log:
# Log ADB output # Log ADB output
adb: yes adb: yes
# Upload full audio recordings
audio: yes
# Where to keep audio
audio_dir: /dev/shm

View File

@ -19,7 +19,7 @@ KillSignal=SIGQUIT
# make sure log directory exists and owned by syslog # make sure log directory exists and owned by syslog
PermissionsStartOnly=true PermissionsStartOnly=true
ExecStartPre=/usr/bin/rm -f ABSOLUTE_INSTALL_DIR/qualtest.pid ExecStartPre=/usr/bin/rm -f /dev/shm/qualtest.pid
#ExecStartPre=/bin/chown syslog:adm /var/log/sleepservice #ExecStartPre=/bin/chown syslog:adm /var/log/sleepservice
#ExecStartPre=/bin/chmod 755 /var/log/sleepservice #ExecStartPre=/bin/chmod 755 /var/log/sleepservice

View File

@ -15,6 +15,8 @@ import utils_sevana
import utils_mcon import utils_mcon
import utils_logcat import utils_logcat
import utils import utils
import utils_cache
from bt_controller import Bluetoothctl from bt_controller import Bluetoothctl
import bt_call_controller import bt_call_controller
import bt_signal import bt_signal
@ -57,15 +59,14 @@ CALL_LIMIT = 0
# Find script's directory # Find script's directory
DIR_THIS = Path(__file__).resolve().parent DIR_THIS = Path(__file__).resolve().parent
DIR_PROJECT = DIR_THIS.parent
# Backup directory (to run without internet)
DIR_CACHE = None
CACHE = utils_cache.InfoCache(None)
# PID file name # PID file name
QUALTEST_PID = DIR_THIS / "qualtest.pid" QUALTEST_PID = "/dev/shm/qualtest.pid"
# Keep the recorded audio in the directory
LOG_AUDIO = False
# Recorded audio directory
LOG_AUDIO_DIR = DIR_THIS.parent / 'log_audio'
# Should the first task run immediately ? # Should the first task run immediately ?
FORCE_RUN = False FORCE_RUN = False
@ -74,6 +75,7 @@ FORCE_RUN = False
EXIT_OK = 0 EXIT_OK = 0
EXIT_ERROR = 1 EXIT_ERROR = 1
# Use silence eraser or not (speech detector is used in this case) # Use silence eraser or not (speech detector is used in this case)
USE_SILENCE_ERASER = True USE_SILENCE_ERASER = True
@ -87,7 +89,7 @@ def remove_oldest_log_audio():
def detect_degraded_signal(file_test: Path, file_reference: Path) -> SignalBoundaries: def detect_degraded_signal(file_test: Path, file_reference: Path) -> SignalBoundaries:
global USE_SILENCE_ERASER, LOG_AUDIO, LOG_AUDIO_DIR, BackendServer global USE_SILENCE_ERASER, LOG_AUDIO_DIR, BackendServer
is_caller : bool = 'caller' in BackendServer.phone.role is_caller : bool = 'caller' in BackendServer.phone.role
is_answerer : bool = 'answer' in BackendServer.phone.role is_answerer : bool = 'answer' in BackendServer.phone.role
@ -105,9 +107,7 @@ def detect_degraded_signal(file_test: Path, file_reference: Path) -> SignalBound
def detect_reference_signal(file_reference: Path) -> SignalBoundaries: def detect_reference_signal(file_reference: Path) -> SignalBoundaries:
global USE_SILENCE_ERASER, LOG_AUDIO, LOG_AUDIO_DIR
# Run silence eraser on reference file as well # Run silence eraser on reference file as well
result = bt_signal.find_reference_signal(file_reference) result = bt_signal.find_reference_signal(file_reference)
return result return result
@ -138,12 +138,7 @@ def run_analyze(file_test: str, file_reference: str, number: str) -> bool:
bounds_signal : SignalBoundaries = detect_degraded_signal(Path(file_test), Path(file_reference)) bounds_signal : SignalBoundaries = detect_degraded_signal(Path(file_test), Path(file_reference))
# bounds_signal.offset_start = 0 # bounds_signal.offset_start = 0
# bounds_signal.offset_finish = 0 # bounds_signal.offset_finish = 0
print(f'Found signal bounds: {bounds_signal}') print(f'Found signal bounds: {bounds_signal}')
# Check if there is a time to remove oldest files
if LOG_AUDIO:
remove_oldest_log_audio()
remove_oldest_log_audio()
# PVQA report # PVQA report
pvqa_mos, pvqa_report, pvqa_rfactor = utils_sevana.find_pvqa_mos(file_test, bounds_signal.offset_start, bounds_signal.offset_finish) pvqa_mos, pvqa_report, pvqa_rfactor = utils_sevana.find_pvqa_mos(file_test, bounds_signal.offset_start, bounds_signal.offset_finish)
@ -188,8 +183,8 @@ def run_analyze(file_test: str, file_reference: str, number: str) -> bool:
r['task_name'] = CURRENT_TASK r['task_name'] = CURRENT_TASK
# Upload report # Upload report
upload_id = BackendServer.upload_report(r, []) upload_id, success = BackendServer.upload_report(r)
if upload_id != None: if upload_id != None and success:
utils.log('Report is uploaded ok.') utils.log('Report is uploaded ok.')
# Upload recorded audio # Upload recorded audio
@ -316,10 +311,15 @@ def run_probe():
while True: while True:
# Get task list update # Get task list update
tasks = BackendServer.load_tasks() tasks = BackendServer.load_tasks()
if tasks is None:
# Check in cache
tasks = CACHE.get_tasks(BackendServer.phone.name)
# Did we fetch anything ? # Did we fetch anything ?
if tasks: if tasks:
# Merge with existing ones. Some tasks can be removed, some can be add. # Merge with existing ones. Some tasks can be removed, some can be add.
changed = TASK_LIST.merge_with(tasks) changed = TASK_LIST.merge_with(tasks)
CACHE.put_tasks(changed)
else: else:
utils.log_verbose(f"No task list assigned, exiting.") utils.log_verbose(f"No task list assigned, exiting.")
sys.exit(EXIT_ERROR) sys.exit(EXIT_ERROR)
@ -467,24 +467,16 @@ if config['log']['adb']:
utils.log('Enabled adb logcat output') utils.log('Enabled adb logcat output')
# Audio directories # Audio directories
if 'audio_dir' in config['log']: if 'cache_dir' in config:
if config['log']['audio_dir']: DIR_CACHE = Path(config['cache_dir'])
LOG_AUDIO_DIR = config['log']['audio_dir'] if not DIR_CACHE.is_absolute():
DIR_CACHE = DIR_CACHE / config['cache_dir']
# Ensure subdirectory log_audio exists
if not os.path.exists(LOG_AUDIO_DIR):
utils.log(f'Creating {LOG_AUDIO_DIR}')
os.mkdir(LOG_AUDIO_DIR)
if 'audio' in config['log']:
if config['log']['audio']:
LOG_AUDIO = True
CACHE = utils_cache.InfoCache(dir=DIR_CACHE)
# Update path to pvqa/aqua-wb # Update path to pvqa/aqua-wb
dir_script = os.path.dirname(os.path.realpath(__file__)) utils_sevana.find_binaries(DIR_PROJECT / 'bin')
utils_sevana.find_binaries(os.path.join(dir_script, "../bin"))
utils.log('Analyzer binaries are found') utils.log('Analyzer binaries are found')
# Load latest licenses & configs - this requires utils_sevana.find_binaries() to be called before # Load latest licenses & configs - this requires utils_sevana.find_binaries() to be called before
@ -518,7 +510,11 @@ with open(QUALTEST_PID, "w") as f:
try: try:
# Load information about phone # Load information about phone
utils.log(f'Loading information about the node {BackendServer.instance} from {BackendServer.address}') utils.log(f'Loading information about the node {BackendServer.instance} from {BackendServer.address}')
BackendServer.preload() BackendServer.preload(CACHE.dir)
if BackendServer.phone is None:
utils.log_error(f'Failed to obtain information about {BackendServer.instance}. Exiting.')
exit(EXIT_ERROR)
if 'answerer' in BackendServer.phone.role: if 'answerer' in BackendServer.phone.role:
# Check if task name is specified # Check if task name is specified
@ -533,9 +529,13 @@ try:
# Load reference audio # Load reference audio
utils.log('Loading reference audio...') utils.log('Loading reference audio...')
if not BackendServer.load_audio(BackendServer.phone.audio_id, REFERENCE_AUDIO): if BackendServer.load_audio(BackendServer.phone.audio_id, REFERENCE_AUDIO):
utils.log_error('Audio is not available, exiting.') CACHE.add_reference_audio(BackendServer.phone.audio_id, REFERENCE_AUDIO)
sys.exit(EXIT_ERROR) else:
utils.log_error('Audio is not available online.')
if not CACHE.get_reference_audio(BackendServer.phone.audio_id, REFERENCE_AUDIO):
utils.log_error('Reference audio is not cached, sorry. Exiting.')
sys.exit(EXIT_ERROR)
# Preparing reference audio # Preparing reference audio
utils.log('Running answering loop...') utils.log('Running answering loop...')

View File

@ -26,7 +26,7 @@ verbose_logging: bool = False
the_log = None the_log = None
# 1 minute network timeout # 1 minute network timeout
NETWORK_TIMEOUT = 60 NETWORK_TIMEOUT = 15
def open_log_file(path: str, mode: str): def open_log_file(path: str, mode: str):

View File

@ -17,6 +17,7 @@ import requests
from socket import timeout from socket import timeout
from crontab import CronTab from crontab import CronTab
from pathlib import Path from pathlib import Path
from utils_cache import InfoCache
start_system_time = time.time() start_system_time = time.time()
start_monotonic_time = time.monotonic() start_monotonic_time = time.monotonic()
@ -149,6 +150,32 @@ class Phone:
self.attributes = dict() self.attributes = dict()
self.audio_id = 0 self.audio_id = 0
def to_dict(self) -> dict:
return {
'id': self.identifier,
'name': self.name,
'role': self.role,
'attr': self.attributes,
'audio_id': self.audio_id
}
def make(d: dict):
r = Phone()
r.identifier = d['id']
r.name = d['name']
r.role = d['role']
if 'attr' in d:
r.attr = d['attr']
else:
r.attr = None
if 'audio_id' in d:
r.audio_id = d['audio_id']
else:
r.audio_id = None
return r
class QualtestBackend: class QualtestBackend:
address: str address: str
@ -165,34 +192,31 @@ class QualtestBackend:
return self.__phone return self.__phone
def preload(self): def preload(self, cache_dir: Path):
self.__phone = self.load_phone() self.__phone = self.load_phone(cache_dir)
def upload_report(self, report, files) -> str: def upload_report(self, report, cache: InfoCache) -> (str, bool):
# UUID string as result # UUID string as result
result = None result = (None, False)
# Log about upload attempt # Log about upload attempt
utils.log_verbose(f"Uploading to {self.address} files {files} and report: {json.dumps(report, indent=4)}") utils.log_verbose(f"Uploading to {self.address} report: {json.dumps(report, indent=4)}")
# POST will be sent to args.qualtest_server with args.qualtest_instance ID
json_content = json.dumps(report, indent=4).encode('utf8')
# Find URL for uploading
url = utils.join_host_and_path(self.address, "/probes/") url = utils.join_host_and_path(self.address, "/probes/")
try:
# Step 1 - upload result record
req = urllib.request.Request(url,
data=json_content,
headers={'content-type': 'application/json'})
response = urllib.request.urlopen(req, timeout=utils.NETWORK_TIMEOUT)
result = response.read().decode('utf8')
utils.log_verbose(f"Response (probe ID): {result}")
utils.log_verbose(f"Upload to {self.address} finished.")
try:
r = requests.post(url=url, json=report, timeout=utils.NETWORK_TIMEOUT)
utils.log_verbose(f"Upload report finished. Response (probe ID): {r.content}")
if r.status_code != 200:
raise RuntimeError(f'Server returned code {r.status_code} and content {r.content}')
result = (r.content.decode().strip(), True)
except Exception as e: except Exception as e:
utils.log_error(f"Upload to {self.address} finished with error.", err=e) utils.log_error(f"Upload report to {self.address} finished with error.", err=e)
# Backup probe result
probe_id = cache.add_report(report)
result = (probe_id, False)
return result return result
@ -216,8 +240,7 @@ class QualtestBackend:
if response.status_code != 200: if response.status_code != 200:
utils.log_error(f"Upload audio to {self.address} finished with error {response.status_code}", None) utils.log_error(f"Upload audio to {self.address} finished with error {response.status_code}", None)
else: else:
utils.log_verbose(f"Response (audio ID): {response.text}") utils.log_verbose(f"Upload audio finished. Response (audio ID): {response.text}")
utils.log_verbose(f"Upload audio to {self.address} finished.")
result = True result = True
except Exception as e: except Exception as e:
utils.log_error(f"Upload audio to {self.address} finished with error.", err=e) utils.log_error(f"Upload audio to {self.address} finished with error.", err=e)
@ -248,7 +271,8 @@ class QualtestBackend:
return None return None
def load_phone(self) -> dict: def load_phone(self, cache: InfoCache) -> dict:
result = None
try: try:
# Build query for both V1 & V2 API # Build query for both V1 & V2 API
instance = urllib.parse.urlencode({"phone_id": self.instance, "phone_name": self.instance}) instance = urllib.parse.urlencode({"phone_id": self.instance, "phone_name": self.instance})
@ -257,16 +281,21 @@ class QualtestBackend:
url = utils.join_host_and_path(self.address, "/phones/?") + instance url = utils.join_host_and_path(self.address, "/phones/?") + instance
# Get response from server # Get response from server
response = urllib.request.urlopen(url, timeout=utils.NETWORK_TIMEOUT) try:
if response.getcode() != 200: response = urllib.request.urlopen(url, timeout=utils.NETWORK_TIMEOUT)
utils.log_error("Failed to get task list. Error code: %s" % response.getcode()) if response.getcode() != 200:
return None utils.log_error("Failed to get task list. Error code: %s" % response.getcode())
return None
except Exception as e:
utils.log_error(f'Problem when loading the phone definition.')
return cache.get_phone(self.instance)
result: Phone = Phone() # Get possible list of phones
phones = json.loads(response.read().decode()) phones = json.loads(response.read().decode())
if len(phones) == 0: if len(phones) == 0:
return result return None
# But use first one
phone = phones[0] phone = phones[0]
attr_dict = dict() attr_dict = dict()
@ -290,6 +319,7 @@ class QualtestBackend:
if 'sip_useproxy' not in attr_dict: if 'sip_useproxy' not in attr_dict:
attr_dict['sip_useproxy'] = True attr_dict['sip_useproxy'] = True
result = Phone()
result.attributes = attr_dict result.attributes = attr_dict
result.identifier = phone['id'] result.identifier = phone['id']
result.name = phone['instance'] result.name = phone['instance']

View File

@ -13,6 +13,7 @@ import urllib
from pathlib import Path from pathlib import Path
from colorama import Fore, Style from colorama import Fore, Style
from utils_cache import InfoCache
PVQA_CMD = "{pvqa} --license {pvqa_lic} --config {pvqa_cfg} --mode analysis --channel 0 " \ PVQA_CMD = "{pvqa} --license {pvqa_lic} --config {pvqa_cfg} --mode analysis --channel 0 " \
"--report {output} --input {input} --cut-begin {cut_begin} --cut-end {cut_end}" "--report {output} --input {input} --cut-begin {cut_begin} --cut-end {cut_end}"
@ -77,7 +78,7 @@ def load_config_and_licenses(server: str):
load_file(utils.join_host_and_path(server, '/deploy/aqua-wb.lic'), AQUA_LIC_PATH) load_file(utils.join_host_and_path(server, '/deploy/aqua-wb.lic'), AQUA_LIC_PATH)
def find_binaries(directory: str, license_server: str = None): def find_binaries(bin_directory: Path, license_server: str = None):
# Update path to pvqa/aqua-wb # Update path to pvqa/aqua-wb
global PVQA_CFG_PATH, PVQA_LIC_PATH, AQUA_LIC_PATH, PVQA_PATH, AQUA_PATH, PVQA_CMD, AQUA_CMD, SILER_PATH, SPEECH_DETECTOR_PATH global PVQA_CFG_PATH, PVQA_LIC_PATH, AQUA_LIC_PATH, PVQA_PATH, AQUA_PATH, PVQA_CMD, AQUA_CMD, SILER_PATH, SPEECH_DETECTOR_PATH
@ -86,8 +87,6 @@ def find_binaries(directory: str, license_server: str = None):
if utils.is_raspberrypi(): if utils.is_raspberrypi():
platform_prefix = 'rpi' platform_prefix = 'rpi'
bin_directory = Path(directory)
PVQA_PATH = bin_directory / platform_prefix / PVQA_PATH PVQA_PATH = bin_directory / platform_prefix / PVQA_PATH
PVQA_LIC_PATH = bin_directory / PVQA_LIC_PATH PVQA_LIC_PATH = bin_directory / PVQA_LIC_PATH
PVQA_CFG_PATH = bin_directory / PVQA_CFG_PATH PVQA_CFG_PATH = bin_directory / PVQA_CFG_PATH