rigs/civ_profiles.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""
civ_profiles.py — profile CI-V dla modeli ze wbudowanym scope (SCOPE_MODELS).
Kazdy profil zawiera parametry specyficzne dla danego modelu radia:
- default_addr : domyslny adres CI-V (hex int) — uzywany gdy uzytkownik
nie poda wlasnego w ustawieniach
- default_baud : domyslna predkosc portu szeregowego
- mode_map : mapowanie bajtu trybu (0x04/0x01) -> nazwa trybu
(wiekszosc Icomow uzywa tej samej tabeli, ale niektore
modele maja dodatkowe/inne tryby — np. D-STAR w IC-9100)
- scope_max : maksymalna wartosc amplitudy w danych scope (do
normalizacji 0-255 w UI)
- scope_header_len: dlugosc naglowka pierwszej ramki scope (przed danymi)
- notes : uwagi/zrodlo dla osoby aktualizujacej profil
UWAGA: Wartosci dla modeli innych niz IC-7300 (referencyjny, przetestowany)
NIE byly zweryfikowane na sprzecie. Jezeli cos nie dziala dla danego modelu —
sprawdz oficjalna instrukcje "CI-V Reference Guide" dla tego radia
(dostepna na stronie icomeurope.com/icomamerica.com) i zaktualizuj profil.
"""
# Wspolna baza trybow — wiekszosc Icomow CI-V uzywa tego mapowania
_BASE_MODE_MAP = {
0: "LSB", 1: "USB", 2: "AM", 3: "CW", 4: "RTTY",
5: "FM", 6: "WFM", 7: "CW-R", 8: "RTTY-R", 17: "DV",
}
# Standardowy zestaw capabilities dla radia CI-V z bezposrednim sterowaniem.
# Kazdy klucz odpowiada jednej funkcji widocznej w panelu (przyciski pod waterfallem).
# Wartosc = True/False oznacza czy radio TECHNICZNIE wspiera te funkcje
# (czy CivRig potrafi ja wykonac). To NIE jest whitelist dla uzytkownikow —
# to jest zrodlo prawdy "co radio umie", a admin decyduje co WLACZYC w config.json.
_BASE_CAPS_CIV = {
"freq_set": True, # zmiana czestotliwosci (0x05)
"mode_set": True, # zmiana trybu pracy (0x06)
"ptt": True, # PTT przez CI-V (0x1C 00)
"split": True, # split VFO A/B (0x0F)
"smeter": True, # odczyt S-metru (0x15 02)
"scope": True, # waterfall/scope (0x27 00)
"agc": True, # tryb AGC (16 12) — zaimplementowane, potwierdzone zywo 2026-10-18
# REMOVED (2026-10-18): "rit" capability flag. RIT/ΔTX byl krotko
# zaimplementowany w civ.py (CI-V 0x21), ale ustawianie Hz dawalo NG na
# tym radiu nawet po poprawce kodowania BCD - przyczyny nie znaleziono,
# user zdecydowal usunac funkcje calkowicie zamiast dalej szukac. Patrz
# webapp.py's removed /api/rig/rit comment po pelna liste usunietych miejsc.
"memory": False, # kanaly pamieci — niezaimplementowane
"vfo_ab": True, # Select VFO A/B (0x07 00/01)
"power": True, # Power ON/OFF radia (0x18 01/00)
"dstar": False, # D-STAR (tylko niektore modele)
# Dodane 2026-09-03: TUNER i AUTOTUNE faktycznie byly zaimplementowane
# w civ.py (set_tuner/start_tuner_autotune) od dawna, ale nigdy nie
# mialy wlasnego wpisu tutaj ani w rigs/features.py - ich widocznosc dla
# zwyklego usera byla przypadkowo podpieta pod mode_set/ptt (patrz
# webapp.py, elif t == "tuner"/"tuner_autotune"). Teraz wlasne klucze,
# zeby admin mogl je wlaczac/wylaczac niezaleznie w KONFIGURACJA ->
# FUNKCJE RADIA, tak jak split.
"tuner": True, # Tuner ON/OFF (CI-V 1C 01 00/01)
"autotune": True, # Autotune - cykl dopasowania, generuje TX (CI-V 1C 01 02)
}
# ── Set Level (CI-V cmd 0x14) — IC-7300 ──────────────────────────────────────
# Kazdy wpis: nazwa -> {sub: subcommand (0x14 XX), label, min/max (jednostki
# w UI), civ_max: max wartosc CI-V (0..255 lub 0..120 — zalezy od parametru)
# Wiekszosc poziomow IC-7300 uzywa zakresu CI-V 0000-0255 (BCD, 2 bajty).
# KEYSPD: 0000-0060 (6-48 WPM mapowane na 0-255 przez Hamlib, ale natywnie
# IC-7300 uzywa wlasnej skali 0000-0060 dla 6-48 WPM).
_IC7300_LEVELS = {
"AF": {"sub": 0x01, "min": 0, "max": 1.0, "civ_max": 255, "label": "AF"},
"RF": {"sub": 0x02, "min": 0, "max": 1.0, "civ_max": 255, "label": "RF GAIN"},
"SQL": {"sub": 0x03, "min": 0, "max": 1.0, "civ_max": 255, "label": "SQL"},
"NR": {"sub": 0x06, "min": 0, "max": 1.0, "civ_max": 255, "label": "NR"},
"PBT_IN": {"sub": 0x07, "min": 0, "max": 1.0, "civ_max": 255, "label": "PBT IN"},
"PBT_OUT": {"sub": 0x08, "min": 0, "max": 1.0, "civ_max": 255, "label": "PBT OUT"},
"CWPITCH": {"sub": 0x09, "min": 300, "max": 900, "civ_max": 255, "label": "CW PITCH"},
"RFPOWER": {"sub": 0x0A, "min": 0, "max": 1.0, "civ_max": 255, "label": "RF PWR"},
"MICGAIN": {"sub": 0x0B, "min": 0, "max": 1.0, "civ_max": 255, "label": "MIC GAIN"},
"KEYSPD": {"sub": 0x0C, "min": 6, "max": 48, "civ_max": 255, "label": "KEYSPD"},
"NOTCHF": {"sub": 0x0D, "min": 0, "max": 1.0, "civ_max": 255, "label": "NOTCH"},
"COMP": {"sub": 0x0E, "min": 0, "max": 1.0, "civ_max": 255, "label": "COMP"},
# POPRAWKA: Break-IN Delay to CI-V 14 0F (00 00=2.0d .. 02 55=13.0d),
# NIE 14 12 (to jest NB level, patrz NB_LEVEL ponizej). Wczesniejsza
# wersja mapowala BKINDL na 0x12 — kolidowalo to z NB_LEVEL i bylo
# niezgodne z dokumentacja CI-V (str.19-3).
"BKINDL": {"sub": 0x0F, "min": 0, "max": 1.0, "civ_max": 255, "label": "BK-IN DELAY"},
"NB_LEVEL": {"sub": 0x12, "min": 0, "max": 1.0, "civ_max": 255, "label": "NB LEVEL"},
# USUNIETO: AGC_TIME (poprzednio blednie 14 37 — taki subcommand nie
# istnieje w tabeli 0x14). Faktyczny AGC time constant to CI-V 1A 04
# (00=OFF, AM:01-13=0.3-8.0s, SSB/CW/RTTY:01-13=0.1-6.0s) — INNA
# struktura komendy (1A, nie 14), niezaimplementowana w set_level().
# Wymaga osobnej obslugi w civ.py (set_agc_time) przed przywroceniem
# tego slidera.
}
# ── Set Func (CI-V cmd 0x16) — IC-7300 ───────────────────────────────────────
# Kazdy wpis: nazwa -> subcommand (0x16 XX 00/01)
_IC7300_FUNCS = {
"NB": 0x22, # Noise Blanker
"NR": 0x40, # Noise Reduction
"ANF": 0x41, # Auto Notch Filter
"COMP": 0x44, # Compressor
"VOX": 0x46, # VOX
"TONE": 0x42, # Tone (repeater tone)
"TSQL": 0x43, # Tone Squelch
# POPRAWKA: 0x47 to "BK-IN function" (00=OFF,01=Semi,02=Full) wg
# dokumentacji CI-V — NIE "Twin PBT" jak bylo wczesniej oznaczone.
# Twin PBT to regulacja ciagla (sliders PBT_IN/PBT_OUT, cmd 14 07/08),
# nie prosty toggle 0x16. Przemianowano na BKIN zgodnie z faktyczna
# funkcja CI-V.
"BKIN": 0x47, # BK-IN function (semi/full break-in)
# POPRAWKA: 0x55 nie istnieje w tabeli 0x16 (Set Func). "Monitor
# function" to faktycznie 0x45.
"MON": 0x45, # Monitor (sidetone TX)
}
# Etykiety dla funkcji (zgodne z hamlib_caps.FUNC_LABELS gdzie mozliwe).
# Format "SKROT (opis)": frontend (radiofunctions.js) tnie po '(' -> na
# przycisku widac tylko uniwersalny skrot (SKROT), pelny string (z polskim
# opisem) trafia do title (tooltip po najechaniu) — tak przyciski dzialaja
# identycznie w PL/EN bez osobnego tlumaczenia backendu.
_FUNC_LABELS = {
"NB": "NB (Noise Blanker)",
"NR": "NR (Redukcja szumow)",
"ANF": "ANF (Auto Notch Filter)",
"COMP": "COMP (Kompresor)",
"VOX": "VOX",
"TONE": "TONE (Tone CTCSS TX)",
"TSQL": "TSQL (Tone Squelch)",
"BKIN": "BKIN (Break-In)",
"MON": "MON (Monitor sidetone TX)",
}
# ── Advanced ("admin-only") radio settings — IC-7300 ────────────────────────
# Deeper CI-V commands beyond the generic Set Level (0x14)/Set Func (0x16)
# tables above - decided 2026-10-15 (user request) to gate these behind the
# admin role (webapp.py checks this on every endpoint that reads/writes one
# of these, not per-rig-whitelist like the generic levels/funcs above):
# modulation input source/level, AGC behavior, VOX fine-tuning, noise
# blanker depth/width, and TX sequencing delay (relevant for amplifier/
# antenna-relay keying, see the GSK fork's Sosny PA-controller work).
# Source: official Icom "CI-V REFERENCE GUIDE HF/50MHz TRANSCEIVER 7300MK2"
# (command table, cmd 0x1A sub 0x05 "SET > ..." menu-item area + cmds 0x14/
# 0x16) - verified against the PDF directly, not guessed.
#
# Each entry's "cmd" is the FULL byte sequence up to (not including) the
# data bytes - civ.py's get_adv/set_adv send this as-is for a read, or with
# the encoded value appended for a write, and validate a read reply's
# echoed address bytes (cmd[1:]) before trusting the data that follows.
#
# kind "enum": 1 data byte, the raw value IS the UI value (0-based
# index into "labels"); "n" = how many valid values.
# kind "range": 1 data byte, raw clamped to "rawmax" (default 255,
# set explicitly below for every entry whose real menu
# range is smaller - the radio's own menu limit, not
# just a UI nicety: an out-of-range raw byte is a
# command the radio would reject anyway), UI value =
# raw + "offset" (default 0), scaled by "scale"
# (default 1) if given.
# kind "bcd2pct": 2-byte BCD 0000-0255, UI value = fraction 0.0-1.0.
# kind "bcd2range": 2-byte BCD 0000-0255, UI value = "min".."max" (linear).
_IC7300_ADV = {
# FIX (2026-10-18, live NG): the AGC time constant's DATA byte is
# 1-indexed on the wire (01=FAST, 02=MID, 03=SLOW - 00 is not a valid
# value for this specific sub-command), unlike most other enum-kind
# commands which start at 00. "wire_base" shifts the UI's 0-based
# dropdown index onto the radio's actual 1-based wire value - see
# civ.py's _decode_adv/_encode_adv.
# "feature": "agc" (2026-10-18, live request: "userzy nie widza agc...
# trzeba dac to gdzies w konfiguracji") - opts this ONE entry out of the
# blanket "settings"-only gate every other adv entry still uses. Gated
# instead by the per-rig FEATURES whitelist (rigs/features.py's "agc"
# entry, KONFIGURACJA -> FUNKCJE RADIA) - ONE switch turns it on for
# every regular operator at once (no per-user granularity needed for a
# small station - see webapp.py's /api/rig/adv handlers, which check
# _feature_allowed("agc", role) for this key instead of _has_perm(...,
# "settings")). An earlier per-user "perm" field approach was tried and
# reverted the same day per user preference for the simpler whitelist.
"AGC_MODE": {"cmd": [0x16, 0x12], "kind": "enum", "n": 3, "wire_base": 1,
"feature": "agc",
"labels": ["FAST", "MID", "SLOW"]},
# TX Delay per band - relevant for amp/antenna-relay PTT sequencing.
# 00~05 = OFF/10/15/20/25/30ms. Confirmed working live 2026-10-18.
"TX_DELAY_HF": {"cmd": [0x1A, 0x05, 0x00, 0x29], "kind": "enum", "n": 6,
"labels": ["OFF", "10ms", "15ms", "20ms", "25ms", "30ms"]},
"TX_DELAY_50M": {"cmd": [0x1A, 0x05, 0x00, 0x30], "kind": "enum", "n": 6,
"labels": ["OFF", "10ms", "15ms", "20ms", "25ms", "30ms"]},
"TX_DELAY_70M": {"cmd": [0x1A, 0x05, 0x00, 0x31], "kind": "enum", "n": 6,
"labels": ["OFF", "10ms", "15ms", "20ms", "25ms", "30ms"]},
# REMOVED (2026-10-18, live NG on all of them): VOX_GAIN (14 16),
# ANTI_VOX (14 17), VOX_DELAY (1A 05 02 67), NB_DEPTH (1A 05 02 65),
# NB_WIDTH (1A 05 02 66), MOD_USB/ACC/LAN (1A 05 00 81/82/83),
# DATA_OFF_MOD/DATA_MOD (1A 05 00 84/85) - part of the same
# 2026-10-15 "approved" batch as the entries above, but unlike
# AGC_MODE (fixed, see wire_base above) no concrete encoding bug was
# found for these - every one of them was rejected (NG) live. Possible
# causes: wrong addresses for this non-MK2 unit/firmware, sourced from
# material that doesn't apply here. Don't re-add without a confirmed
# (not guessed) address for this exact radio/firmware.
#
# Also REMOVED same day: TX_BASS_SSB/TX_TREBLE_SSB (1A 05 00 12/13),
# TBW_ACTIVE (16 58), TBW_WIDE/MID/NAR (1A 05 00 14/15/16) - added during
# a "czy HiFiSSB jest wykonalne programowo" investigation, sourced from
# the IC-7300MK2 CI-V Reference Guide on the (wrong, in hindsight)
# reasoning that since TBW/Tone Control predate MK2 as FRONT-PANEL
# features, their CI-V REMOTE exposure must predate MK2 too. Live-tested
# on the user's actual (non-MK2) IC-7300: ALL SIX gave "NG" (rejected) -
# so this specific 1A 05 00 1x address range is MK2-only after all,
# same failure class as the earlier APF mistake, just for a feature
# that (unlike APF) genuinely isn't MK2-exclusive on the front panel -
# only its CI-V remote control is. Don't re-add these addresses without
# a real (non-MK2) IC-7300 CI-V reference confirming them - TBW is still
# changeable manually on the radio's own FUNCTION button, just not
# remotely through HAMCTRL.
}
# ── Pasma per model radia ────────────────────────────────────────────────────
# Kazde radio obejmuje inny zakres pasm. IC-7300 to HF + 6m (bez VHF/UHF),
# IC-9700 to czysto VHF/UHF/SHF (bez HF). Zeby UI pokazywalo tylko pasma,
# ktore dane radio FAKTYCZNIE ma, wiazemy liste pasm z profilem modelu.
#
# Format: nazwa -> (min_hz, max_hz, default_hz). Transwertery (13cm/23cm na
# radiach bez natywnego SHF) obsluguje sie osobno przez offset — patrz uwaga
# przy pasmach SHF.
_BANDS_HF = {
"160m": (1810000, 2000000, 1850000),
"80m": (3500000, 3800000, 3650000),
"60m": (5351500, 5366500, 5357000),
"40m": (7000000, 7200000, 7100000),
"30m": (10100000, 10150000, 10125000),
"20m": (14000000, 14350000, 14200000),
"17m": (18068000, 18168000, 18100000),
"15m": (21000000, 21450000, 21200000),
"12m": (24890000, 24990000, 24930000),
"10m": (28000000, 29700000, 28400000),
}
_BAND_6M = {"6m": (50000000, 52000000, 50150000)}
_BAND_4M = {"4m": (70000000, 70500000, 70150000)}
_BAND_2M = {"2m": (144000000, 146000000, 144300000)}
_BAND_70CM = {"70cm": (430000000, 440000000, 432100000)}
_BAND_23CM = {"23cm": (1240000000, 1300000000, 1296200000)}
# 13cm: na Icomach zwykle przez transwerter (radio pokazuje IF, np. 144 lub
# 432 MHz, a nadaje na 2320/2400). Podajemy pasmo docelowe; obsluga offsetu
# transwertera to osobne ustawienie admina (transverter_offset w config).
_BAND_13CM = {"13cm": (2320000000, 2450000000, 2320200000)}
# Zlozone zestawy pasm per typ radia
_BANDS_HF_6M = {**_BANDS_HF, **_BAND_6M}
_BANDS_HF_6M_4M = {**_BANDS_HF, **_BAND_6M, **_BAND_4M}
_BANDS_HF_6M_2M = {**_BANDS_HF, **_BAND_6M, **_BAND_2M} # IC-746: HF+50+144MHz, BEZ 70cm
_BANDS_HF_VU = {**_BANDS_HF, **_BAND_6M, **_BAND_2M, **_BAND_70CM}
_BANDS_HF_VU_23 = {**_BANDS_HF, **_BAND_6M, **_BAND_2M, **_BAND_70CM, **_BAND_23CM}
_BANDS_VUSHF = {**_BAND_2M, **_BAND_70CM, **_BAND_23CM} # IC-9700
CIV_PROFILES = {
# ── IC-7300 — profil REFERENCYJNY, przetestowany ────────────────────────
"3073": {
"name": "IC-7300",
"default_addr": 0x94,
"default_baud": 115200,
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_6M_4M), # HF + 6m + 4m (70 MHz)
"scope_max": 160, # 0x00..0xA0
"scope_header_len": 15,
"capabilities": dict(_BASE_CAPS_CIV),
"levels": dict(_IC7300_LEVELS),
"funcs": dict(_IC7300_FUNCS),
"func_labels": dict(_FUNC_LABELS),
# Admin-only advanced settings (1A 05 menu tree + 14/16 extras) -
# NOT copied to IC-746 below: that's a much older radio generation
# (no scope, no DATA/USB-D mode per its own profile notes) and this
# modern per-menu-item CI-V addressing almost certainly doesn't
# exist on it at all.
"adv": dict(_IC7300_ADV),
"notes": "Profil referencyjny — przetestowany na sprzecie.",
},
# ── IC-746 — stary CI-V (BEZ scope, BEZ trybu DATA/USB-D) ────────────────
# Dodane 2026-09-02, zeby ominac rigctld/Hamlib (padal w trakcie sesji,
# patrz _respawn_rigctld w rigcat.py) - IC-746 jest CI-V, wiec civ.py
# moze mowic z nim bezposrednio, TRZEBA jednak innej ramki set_mode.
# Zrodlo: oficjalny CI-V reference (adres domyslny 0x56, 19200bd, pasma
# HF+50+144MHz bez 70cm - universal-radio.com/qsl.net specs) +
# Hamlib rigs/icom/icom.c (RIG_IS_IC746 jest jawnie na liscie radii ze
# "icmode_ext = -1" w icom_set_mode_without_data() - komenda 06 <mode>
# BEZ trzeciego bajtu filtra, w przeciwienstwie do IC-7300's
# 06 <mode> <fil>). Poziomy (0x14 xx) i funkcje (0x16 xx) to ten sam,
# stary, uniwersalny zestaw Icoma co IC-7300 (potwierdzone w
# icom_defs.h) - stad "levels"/"funcs" ponizej to bezposrednia kopia
# IC-7300, NIE zgadywanie.
"3023": {
"name": "IC-746",
"default_addr": 0x56,
"default_baud": 19200,
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_6M_2M), # HF + 6m + 2m, BEZ 70cm
"scope_max": 160, # nieuzywane (capabilities.scope=False), placeholder
"scope_header_len": 15, # jw.
# power=False: Hamlib's own IC-746 backend (rigs/icom/ic746.c) never
# assigns a set_powerstat function at all - CI-V remote power-ON
# needs the radio's standby circuit to listen on the bus while
# "off", a feature Icom only added starting with later designs.
# Leaving power=True here would show a PWR ON/OFF button that's
# unverified and likely does nothing on this radio.
"capabilities": {**_BASE_CAPS_CIV, "scope": False, "power": False},
"levels": dict(_IC7300_LEVELS),
"funcs": dict(_IC7300_FUNCS),
"func_labels": dict(_FUNC_LABELS),
# Radio-specific quirks czytane przez civ.py::set_mode() - patrz
# komentarz tam. Domyslnie (brak klucza) = True, jak IC-7300.
"mode_has_filter_byte": False, # 06 <mode> TYLKO, bez bajtu filtra
"data_mode_supported": False, # brak 1A 06 (USB-D/DATA) - starsze radio
# has_vfo_b_read=False: CI-V 25/26 (odczyt VFO-B/nieaktywnego VFO)
# to komenda NOWEJ generacji - Hamlib sam to wykrywa i loguje przy
# starcie: "icom_set_x25x26_ability: Hamlib thinks rig does not
# support x25/x26 command" (potwierdzone live na tym IC-746, log
# 2026-09-02). Bez tej flagi self.freq_b zostaje na starej/domyslnej
# wartosci NA STALE (0x25 zawsze NG/timeout) - realna luka
# bezpieczenstwa: zabezpieczenie cross-band-split (webapp.py
# _is_split_cross_band) porownywalo pasmo na podstawie tej
# zamrozonej, falszywej wartosci i nigdy nie wykrywalo prawdziwego
# cross-band. Flaga wylacza zarowno zbedny poll 0x25 (radio i tak
# go odrzuci) jak i każe cross-band-checkowi failować BEZPIECZNIE
# (blokować TX na SPLIT) zamiast ufać nieznanej wartosci.
"has_vfo_b_read": False,
"notes": ("NIEZWERYFIKOWANE na sprzecie (dopiero po tym "
"profilu) - ale komendy sprawdzone zrodlowo w "
"Hamlibie, nie zgadywane. Jesli mode/levels nie "
"dzialaja: sprawdz najpierw czy radio faktycznie "
"ma adres CI-V 0x56 (MENU > SET > CI-V), "
"19200bd."),
},
# ── IC-7610 ──────────────────────────────────────────────────────────────
"3078": {
"name": "IC-7610",
"default_addr": 0x98,
"default_baud": 115200,
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_6M_4M), # HF + 6m + 4m
"scope_max": 160,
"scope_header_len": 15,
"capabilities": dict(_BASE_CAPS_CIV),
"notes": ("NIEZWERYFIKOWANE na sprzecie. IC-7610 ma dual-RX "
"(Main/Sub) — naglowek scope zawiera dodatkowy bajt "
"wyboru scope'u (Main=0x00/Sub=0x01) na pozycji "
"zaraz po seq/total. Jezeli scope nie dziala "
"poprawnie, sprawdz CI-V Reference Guide IC-7610 "
"sekcja 'Scope waveform data' (cmd 27 00)."),
},
# ── IC-705 ───────────────────────────────────────────────────────────────
"3085": {
"name": "IC-705",
"default_addr": 0xA4,
"default_baud": 115200,
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_VU), # HF, 6m, 2m, 70cm
"scope_max": 160,
"scope_header_len": 15,
"capabilities": dict(_BASE_CAPS_CIV),
"notes": ("NIEZWERYFIKOWANE na sprzecie. IC-705 czesto "
"laczy sie przez Bluetooth/WLAN zamiast USB-serial "
"— jesli uzywasz USB, sprawdz baudrate w "
"MENU > SET > Connectors > CI-V (domyslnie 115200 "
"dla USB)."),
},
# ── IC-9100 ──────────────────────────────────────────────────────────────
"3068": {
"name": "IC-9100",
"default_addr": 0x7C,
"default_baud": 115200,
# IC-9100 dodaje tryb DV (D-STAR) — juz w _BASE_MODE_MAP (17)
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_VU_23), # HF, 6m, 2m, 70cm, 23cm (z opcja UX-9100)
"scope_max": 160,
"scope_header_len": 15,
"capabilities": {**_BASE_CAPS_CIV, "dstar": True},
"notes": ("NIEZWERYFIKOWANE na sprzecie. IC-9100 to radio "
"VHF/UHF/HF z dual-band — moze wymagac dodatkowego "
"parametru wyboru pasma (Main/Sub = HF czy V/UHF) "
"przed komendami scope. Sprawdz CI-V Reference "
"Guide IC-9100, sekcja 'Scope waveform data'."),
},
# ── IC-7100 ──────────────────────────────────────────────────────────────
"3070": {
"name": "IC-7100",
"default_addr": 0x88,
"default_baud": 115200,
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_VU), # HF, 6m, 2m, 70cm
"scope_max": 160,
"scope_header_len": 15,
"capabilities": {**_BASE_CAPS_CIV, "dstar": True},
"notes": ("NIEZWERYFIKOWANE na sprzecie. IC-7100 ma scope "
"tylko w trybie 'Center' (nie Fixed) wedlug "
"niektorych zrodel — jesli scope nie wysyla "
"danych, sprawdz ustawienie SCOPE > SPAN/MODE "
"na radiu."),
},
# ── IC-9700 — czysto VHF/UHF/SHF (2m/70cm/23cm, BEZ HF) ─────────────────
"3081": {
"name": "IC-9700",
"default_addr": 0xA2,
"default_baud": 115200,
"mode_map": dict(_BASE_MODE_MAP), # ma DV (D-STAR), juz w bazie
"bands": dict(_BANDS_VUSHF), # 2m, 70cm, 23cm — bez HF
"scope_max": 160,
"scope_header_len": 15,
"capabilities": {**_BASE_CAPS_CIV, "dstar": True},
"notes": ("NIEZWERYFIKOWANE na sprzecie. IC-9700 to radio "
"czysto VHF/UHF/SHF (2m/70cm/23cm) — NIE ma HF. "
"Ma satelitarny dual-watch (Main/Sub) — scope moze "
"wymagac wyboru pasma przed komenda 27 00, podobnie "
"jak IC-9100. 23cm (1296) natywne, bez transwertera. "
"Adres CI-V domyslny 0xA2. Sprawdz CI-V Reference "
"Guide IC-9700 sekcja 'Scope waveform data' jesli "
"scope nie dziala."),
},
}
# Profil domyslny — uzywany gdy model nie jest w CIV_PROFILES
# (nie powinno sie zdarzyc jesli SCOPE_MODELS i CIV_PROFILES sa zsynchronizowane,
# ale zabezpiecza przed KeyError przy literowce/nowym modelu)
DEFAULT_PROFILE = {
"name": "Nieznany model CI-V",
"default_addr": 0x94,
"default_baud": 115200,
"mode_map": dict(_BASE_MODE_MAP),
"bands": dict(_BANDS_HF_6M_4M), # fallback: zakres jak IC-7300
"scope_max": 160,
"scope_header_len": 15,
"capabilities": dict(_BASE_CAPS_CIV),
"levels": {},
"funcs": {},
"func_labels": {},
"notes": "Profil domyslny (fallback) — model nie ma dedykowanego profilu.",
}
def get_civ_profile(model_id: str) -> dict:
"""
Zwroc profil CI-V dla danego model_id (np. '3073').
Jesli model nie ma profilu, zwroc DEFAULT_PROFILE i wypisz ostrzezenie.
Brakujace klucze 'levels'/'funcs'/'func_labels' sa dopelniane pustymi
dict — pozwala to civ.py bezpiecznie wolac .get('levels', {}) bez
sprawdzania kazdego profilu osobno.
"""
model_id = str(model_id)
profile = CIV_PROFILES.get(model_id)
if profile is None:
print(f"[civ_profiles] UWAGA: brak profilu dla model={model_id}, "
f"uzywam DEFAULT_PROFILE")
return DEFAULT_PROFILE
# Dopelnij brakujace klucze (modele bez levels/funcs/bands zdefiniowanych jawnie)
for k in ("levels", "funcs", "func_labels"):
profile.setdefault(k, {})
# bands: gdyby jakis profil go nie mial, daj zakres jak IC-7300 (bezpieczny
# fallback HF+6m+4m) zamiast pustej listy, ktora ukrylaby wszystkie pasma
profile.setdefault("bands", dict(_BANDS_HF_6M_4M))
return profile