base script
This commit is contained in:
283
yamaha_cli.py
Normal file
283
yamaha_cli.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import socket
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Impostazioni di connessione predefinite
|
||||
DEFAULT_HOST = "192.168.1.62"
|
||||
DEFAULT_PORT = 49280
|
||||
|
||||
# Configurazioni specifiche per Yamaha TF5
|
||||
TF5_INPUT_CHANNELS = 40 # 32 locali + 8 altro
|
||||
TF5_MIX_BUSSES = 20 # 20 Aux/Mix
|
||||
|
||||
def create_connection(host, port):
|
||||
"""Crea e restituisce un socket connesso."""
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(5)
|
||||
s.connect((host, port))
|
||||
return s
|
||||
except socket.error as e:
|
||||
print(f"Errore critico di connessione a {host}:{port} -> {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def send_command(host, port, command):
|
||||
"""Invia un comando singolo e stampa la risposta (modalità one-shot)."""
|
||||
s = create_connection(host, port)
|
||||
try:
|
||||
s.sendall((command + '\n').encode('utf-8'))
|
||||
print(f"Comando inviato: '{command}'")
|
||||
response = s.recv(4096)
|
||||
print(f"Risposta: {response.decode('utf-8', errors='ignore').strip()}")
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def get_param(sock, command):
|
||||
"""Funzione helper per ottenere un singolo parametro mantenendo il socket aperto."""
|
||||
try:
|
||||
sock.sendall((command + '\n').encode('utf-8'))
|
||||
# Riceviamo dati finché non troviamo un newline, per essere sicuri di avere la risposta intera
|
||||
response = b""
|
||||
while b'\n' not in response:
|
||||
chunk = sock.recv(1024)
|
||||
if not chunk: break
|
||||
response += chunk
|
||||
return response.decode('utf-8', errors='ignore').strip()
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def parse_val(response):
|
||||
"""Estrae l'ultimo valore numerico da una risposta standard 'OK ... value'."""
|
||||
parts = response.split()
|
||||
if len(parts) > 0 and parts[0] == "OK":
|
||||
return parts[-1]
|
||||
return "N/A"
|
||||
|
||||
def parse_name(response):
|
||||
"""Estrae il nome tra virgolette dalla risposta."""
|
||||
# Esempio risposta: OK MIXER:Current/InCh/Label/Name 0 0 "Kick Drum"
|
||||
try:
|
||||
start = response.find('"') + 1
|
||||
end = response.rfind('"')
|
||||
if start > 0 and end > start:
|
||||
return response[start:end]
|
||||
return "Unknown"
|
||||
except:
|
||||
return "Error"
|
||||
|
||||
def format_db(val_str):
|
||||
"""Converte il valore interno (es. 1000) in stringa dB (es. +10.0 dB)."""
|
||||
try:
|
||||
val = int(val_str)
|
||||
if val <= -32768:
|
||||
return "-inf dB"
|
||||
return f"{val / 100.0:+.1f} dB"
|
||||
except:
|
||||
return val_str
|
||||
|
||||
def format_pan(val_str):
|
||||
"""Converte il valore pan (-63 a 63) in formato leggibile (L63, C, R63)."""
|
||||
try:
|
||||
val = int(val_str)
|
||||
if val == 0: return "C"
|
||||
if val < 0: return f"L{abs(val)}"
|
||||
return f"R{val}"
|
||||
except:
|
||||
return val_str
|
||||
|
||||
def get_full_config(host, port):
|
||||
"""Scarica e stampa la configurazione di tutti i canali e mix."""
|
||||
print(f"Connessione a {host}... Inizio download configurazione.")
|
||||
print("Attendere prego, l'operazione potrebbe richiedere alcuni secondi...\n")
|
||||
|
||||
s = create_connection(host, port)
|
||||
# Impostiamo un timeout breve per le singole richieste per velocizzare eventuali errori
|
||||
s.settimeout(2)
|
||||
|
||||
try:
|
||||
# --- INPUT CHANNELS ---
|
||||
print("-" * 65)
|
||||
print(f"{'CH':<4} {'NAME':<16} {'STATUS':<8} {'FADER':<12} {'PAN':<8}")
|
||||
print("-" * 65)
|
||||
|
||||
for i in range(TF5_INPUT_CHANNELS):
|
||||
# Recupera Nome
|
||||
resp_name = get_param(s, f"get MIXER:Current/InCh/Label/Name {i} 0")
|
||||
name = parse_name(resp_name)
|
||||
|
||||
# Recupera ON/OFF
|
||||
resp_on = get_param(s, f"get MIXER:Current/InCh/Fader/On {i} 0")
|
||||
is_on = parse_val(resp_on)
|
||||
status = " [ON] " if is_on == "1" else " OFF "
|
||||
|
||||
# Recupera Livello Fader
|
||||
resp_lvl = get_param(s, f"get MIXER:Current/InCh/Fader/Level {i} 0")
|
||||
level_db = format_db(parse_val(resp_lvl))
|
||||
|
||||
# Recupera Pan
|
||||
resp_pan = get_param(s, f"get MIXER:Current/InCh/ToSt/Pan {i} 0")
|
||||
pan_str = format_pan(parse_val(resp_pan))
|
||||
|
||||
print(f"CH{i+1:<2} {name[:15]:<16} {status:<8} {level_db:<12} {pan_str:<8}")
|
||||
# Piccolo sleep per non saturare il buffer del mixer se necessario
|
||||
# time.sleep(0.01)
|
||||
|
||||
print("\n")
|
||||
# --- MIX (AUX) BUSSES ---
|
||||
print("-" * 50)
|
||||
print(f"{'MIX':<4} {'NAME':<16} {'STATUS':<8} {'FADER':<12}")
|
||||
print("-" * 50)
|
||||
|
||||
for i in range(TF5_MIX_BUSSES):
|
||||
# Recupera Nome Mix
|
||||
resp_name = get_param(s, f"get MIXER:Current/Mix/Label/Name {i} 0")
|
||||
name = parse_name(resp_name)
|
||||
|
||||
# Recupera ON/OFF Mix
|
||||
resp_on = get_param(s, f"get MIXER:Current/Mix/Fader/On {i} 0")
|
||||
is_on = parse_val(resp_on)
|
||||
status = " [ON] " if is_on == "1" else " OFF "
|
||||
|
||||
# Recupera Livello Mix
|
||||
resp_lvl = get_param(s, f"get MIXER:Current/Mix/Fader/Level {i} 0")
|
||||
level_db = format_db(parse_val(resp_lvl))
|
||||
|
||||
print(f"MX{i+1:<2} {name[:15]:<16} {status:<8} {level_db:<12}")
|
||||
|
||||
print("-" * 50)
|
||||
print("\nConfigurazione completata.")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperazione interrotta dall'utente.")
|
||||
except Exception as e:
|
||||
print(f"\nErrore durante il recupero della configurazione: {e}")
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def get_full_config_dict(host, port):
|
||||
"""
|
||||
Scarica la configurazione di tutti i canali e mix e la restituisce come dizionario.
|
||||
"""
|
||||
print(f"Connessione a {host}... Inizio download configurazione.")
|
||||
print("Attendere prego, l'operazione potrebbe richiedere alcuni secondi...\n")
|
||||
|
||||
s = create_connection(host, port)
|
||||
# Impostiamo un timeout breve per le singole richieste per velocizzare eventuali errori
|
||||
s.settimeout(2)
|
||||
|
||||
config = {
|
||||
"input_channels": [],
|
||||
"mix_busses": []
|
||||
}
|
||||
|
||||
try:
|
||||
# --- INPUT CHANNELS ---
|
||||
for i in range(TF5_INPUT_CHANNELS):
|
||||
channel_data = {}
|
||||
|
||||
# Recupera Nome
|
||||
resp_name = get_param(s, f"get MIXER:Current/InCh/Label/Name {i} 0")
|
||||
channel_data['name'] = parse_name(resp_name)
|
||||
|
||||
# Recupera ON/OFF
|
||||
resp_on = get_param(s, f"get MIXER:Current/InCh/Fader/On {i} 0")
|
||||
channel_data['status'] = 'ON' if parse_val(resp_on) == "1" else 'OFF'
|
||||
|
||||
# Recupera Livello Fader
|
||||
resp_lvl = get_param(s, f"get MIXER:Current/InCh/Fader/Level {i} 0")
|
||||
channel_data['fader_db'] = format_db(parse_val(resp_lvl))
|
||||
|
||||
# Recupera Pan
|
||||
resp_pan = get_param(s, f"get MIXER:Current/InCh/ToSt/Pan {i} 0")
|
||||
channel_data['pan'] = format_pan(parse_val(resp_pan))
|
||||
|
||||
config["input_channels"].append(channel_data)
|
||||
|
||||
# --- MIX (AUX) BUSSES ---
|
||||
for i in range(TF5_MIX_BUSSES):
|
||||
mix_data = {}
|
||||
|
||||
# Recupera Nome Mix
|
||||
resp_name = get_param(s, f"get MIXER:Current/Mix/Label/Name {i} 0")
|
||||
mix_data['name'] = parse_name(resp_name)
|
||||
|
||||
# Recupera ON/OFF Mix
|
||||
resp_on = get_param(s, f"get MIXER:Current/Mix/Fader/On {i} 0")
|
||||
mix_data['status'] = 'ON' if parse_val(resp_on) == "1" else 'OFF'
|
||||
|
||||
# Recupera Livello Mix
|
||||
resp_lvl = get_param(s, f"get MIXER:Current/Mix/Fader/Level {i} 0")
|
||||
mix_data['fader_db'] = format_db(parse_val(resp_lvl))
|
||||
|
||||
config["mix_busses"].append(mix_data)
|
||||
|
||||
print("Configurazione scaricata con successo.")
|
||||
return config
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperazione interrotta dall'utente.")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"\nErrore durante il recupero della configurazione: {e}")
|
||||
return None
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='CLI per controllare un mixer Yamaha TF5.',
|
||||
formatter_class=argparse.RawTextHelpFormatter
|
||||
)
|
||||
parser.add_argument('--host', default=DEFAULT_HOST, help=f'IP del mixer (default: {DEFAULT_HOST})')
|
||||
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help=f'Porta (default: {DEFAULT_PORT})')
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='Comandi disponibili')
|
||||
|
||||
# Comandi esistenti
|
||||
p_recall = subparsers.add_parser('recall', help='Richiama scena (es: recall a 0)')
|
||||
p_recall.add_argument('banco', choices=['a', 'b'])
|
||||
p_recall.add_argument('numero', type=int)
|
||||
|
||||
p_level = subparsers.add_parser('level', help='Imposta fader (es: level 0 1000)')
|
||||
p_level.add_argument('canale', type=int)
|
||||
p_level.add_argument('valore', type=int)
|
||||
|
||||
p_onoff = subparsers.add_parser('onoff', help='Imposta on/off (es: onoff 0 on)')
|
||||
p_onoff.add_argument('canale', type=int)
|
||||
p_onoff.add_argument('stato', choices=['on', 'off'])
|
||||
|
||||
p_pan = subparsers.add_parser('pan', help='Imposta pan (es: pan 0 -63)')
|
||||
p_pan.add_argument('canale', type=int)
|
||||
p_pan.add_argument('valore', type=int)
|
||||
|
||||
p_raw = subparsers.add_parser('raw', help='Invia comando grezzo')
|
||||
p_raw.add_argument('raw_cmd', nargs='+')
|
||||
|
||||
# --- NUOVO COMANDO ---
|
||||
subparsers.add_parser('config', help='Legge e stampa la configurazione completa del mixer')
|
||||
# ---------------------
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == 'config':
|
||||
get_full_config(args.host, args.port)
|
||||
elif args.command == 'recall':
|
||||
send_command(args.host, args.port, f"ssrecall_ex scene_{args.banco} {args.numero}")
|
||||
elif args.command == 'level':
|
||||
send_command(args.host, args.port, f"set MIXER:Current/InCh/Fader/Level {args.canale} 0 {args.valore}")
|
||||
elif args.command == 'onoff':
|
||||
val = 1 if args.stato == 'on' else 0
|
||||
send_command(args.host, args.port, f"set MIXER:Current/InCh/Fader/On {args.canale} 0 {val}")
|
||||
elif args.command == 'pan':
|
||||
send_command(args.host, args.port, f"set MIXER:Current/InCh/ToSt/Pan {args.canale} 0 {args.valore}")
|
||||
elif args.command == 'raw':
|
||||
send_command(args.host, args.port, ' '.join(args.raw_cmd))
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user