core

Конфигурация, типы и исключения. Пакет без ввода-вывода — за одним исключением: инстанцирование настроек создаёт директории и открывает лог-файлы, см. Архитектуру.

Настройки

Сборка всех разделов настроек в единственный синглтон settings.

Настройки инстанцируются на импорте модуля: читается .env и проверяются обязательные STATION__*. Диска импорт не трогает — рабочие директории и файловые обработчики логов поднимает configure_runtime(), который зовут из main().

class core.configs.Settings(_case_sensitive=None, _nested_model_default_partial_update=None, _env_prefix=None, _env_prefix_target=None, _env_file=PosixPath('.'), _env_file_encoding=None, _env_ignore_empty=None, _env_nested_delimiter=None, _env_nested_max_split=None, _env_parse_none_str=None, _env_parse_enums=None, _cli_prog_name=None, _cli_parse_args=None, _cli_settings_source=None, _cli_parse_none_str=None, _cli_hide_none_type=None, _cli_avoid_json=None, _cli_enforce_required=None, _cli_use_class_docs_for_groups=None, _cli_show_env_vars=None, _cli_exit_on_error=None, _cli_prefix=None, _cli_flag_prefix_char=None, _cli_implicit_flags=None, _cli_ignore_unknown_args=None, _cli_kebab_case=None, _cli_shortcuts=None, _secrets_dir=None, _build_sources=None, *, antenna=AntennaSettings(rotator=RotatorSettings(ENABLED=False, MODEL='ROT_MODEL_NETROTCTL', BAUD=9600, PORT='rotctld:4533', THRESHOLD=4.0, MODE=<RotatorMode.NORMAL: 'Normal'>, MIN_ELEVATION=0.0), rig=RigSettings(IP='rigctld', PORT=4532)), url=URLSettings(BASE='https://sonik.space/api/', JOBS_PATH='jobs/', OBSERVATIONS_PATH='observations/', BUNDLES_PATH='v2/bundles/', STATIONS_PATH='v2/stations/'), api=APISettings(TIMEOUT_IN_SECONDS_REQUEST_DATA=45, TIMEOUT_IN_SECONDS_SEND_DATA=120), flowgraph=FlowgraphSettings(RX_SAMP_RATE=None, RX_BANDWIDTH=None, DOPPLER_CORR_PER_SEC=None, LO_OFFSET=None, LO_TRANSVERTER=None, PPM_ERROR=0, GAIN_MODE='Overall', RF_GAIN=None, ANTENNA='RX', DEV_ARGS=None, STREAM_ARGS=None, TUNE_ARGS=None, OTHER_SETTINGS=None, DC_REMOVAL=None, BB_FREQ=None, ENABLE_IQ_DUMP=False, IQ_DUMP_FILENAME=None, UDP_DUMP_HOST='127.0.0.1', UDP_DUMP_PORT=57356, FLOWGRAPH_DISPATCHER='flowgraph_dispatcher', DEFAULT_MODE='FM'), health=HealthSettings(PORT=8080), log=LoggingSettings(FORMAT='%(asctime)s - %(name)s - %(levelname)s - %(message)s', LEVEL='INFO', SCRIPT_LEVEL='INFO', FLOWGRAPH_LEVEL='INFO', DIRECTORY='/var/lib/soniks-client/logs', FILE_NAME='client.log', MAX_BYTES=5242880, BACKUP_COUNT=5, ENCODING='utf-8'), mqtt=MqttSettings(ENABLED=True, URL='wss://sonik.space/mqtt'), observation=ObservationSettings(REMOVE_WATERFALL_RAW_FILES=True, REMOVE_OBSERVATION_DATA=True, SOAPY_RX_DEVICE='driver=airspy,biastee=true', TIME_FORMAT_IN_FILENAME='%Y-%m-%dT%H-%M-%S', FILENAME_TEMPLATE='{observation_dir}/{prefix}_{observation_id}_{timestamp}.{extension}', RIG_UPDATE_INTERVAL=0.1, ROTATOR_UPDATE_INTERVAL=3, PRE_OBSERVATION_SCRIPT='satnogs-pre', POST_OBSERVATION_SCRIPT='satnogs-post', RUN_PRE_OBSERVATION_SCRIPT=True, RUN_POST_OBSERVATION_SCRIPT=True, SCRIPT_TIMEOUT_IN_SECONDS=900, BATCH_DELAY=1, prefix=ObservationPrefix(PAYLOAD='payload', WATERFALL='waterfall', RAW_WATERFALL='raw_waterfall', DATA='data', METADATA='metadata'), extension=ObservationExtension(PAYLOAD='ogg', WATERFALL='png', DATA_APT='png', METADATA='json', RAW_WATERFALL='dat'), sending_key=ObservationSendingKey(PAYLOAD='payload', WATERFALL='waterfall', DEMODDATA='demoddata', CLIENT_METADATA='client_metadata'), upload_types=ObservationUploadTypes(PAYLOAD=True, WATERFALL=True)), paths=PathSettings(BASE='/tmp/.soniks', OUTPUT_DIR='output', COMPLETE_DIR='complete', INCOMPLETE_DIR='incomplete', STATE_FILE='/var/lib/soniks-client/desired-state.json'), station, jobs=JobSettings(PREFIX='job_'), waterfall=WaterfallSettings(AUTORANGE=False, THRESHOLD=-200, MIN_VALID_SAMPLES=10000, DEFAULT_MIN_VALUE=-110, DEFAULT_MAX_VALUE=-50, MIN_VALUE=-110, MAX_VALUE=-50, WIDTH=8.32, HEIGHT=16.03, HELP_LINES=False, GRIDSPEC={'nrows': 1, 'ncols': 2, 'width_ratios': [0.93, 0.03], 'left': 0.09, 'right': 0.91, 'top': 0.995, 'bottom': 0.03}, TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S.%fZ', TIME_FORMAT='%H:%M', INTERVAL_IN_MINUTES=1), scheduler=SchedulerSettings(MAX_WORKERS=30, COALESCE=True, MAX_INSTANCES=1, MISFIRE_GRACE_TIME_IN_SECOND=30, JOB_REQUEST_INTERVAL_IN_MINUTES=1, RESENDING_INTERVAL_IN_MINUTES=20, SAT_DATA_SYNC_INTERVAL_IN_HOURS=6, LOG_LEVEL='WARNING'), sdr=SdrSettings(SCAN_INTERVAL_IN_MINUTES=60, IDLE_WINDOW_IN_MINUTES=3, GAIN_STEP_DB=2, NOISE_LIFT_DB=10, CALIBRATION_SAMPLES=65536))[исходный код]

Базовые классы: BaseSettings

Корневая модель настроек. Разделы адресуются через __ в именах переменных окружения: STATION__TOKEN, ANTENNA__ROTATOR__MODE.

Параметры:
  • _case_sensitive (bool | None)

  • _nested_model_default_partial_update (bool | None)

  • _env_prefix (str | None)

  • _env_prefix_target (EnvPrefixTarget | None)

  • _env_file (DotenvType | None)

  • _env_file_encoding (str | None)

  • _env_ignore_empty (bool | None)

  • _env_nested_delimiter (str | None)

  • _env_nested_max_split (int | None)

  • _env_parse_none_str (str | None)

  • _env_parse_enums (bool | None)

  • _cli_prog_name (str | None)

  • _cli_parse_args (bool | list[str] | tuple[str, ...] | None)

  • _cli_settings_source (CliSettingsSource[Any] | None)

  • _cli_parse_none_str (str | None)

  • _cli_hide_none_type (bool | None)

  • _cli_avoid_json (bool | None)

  • _cli_enforce_required (bool | None)

  • _cli_use_class_docs_for_groups (bool | None)

  • _cli_show_env_vars (bool | None)

  • _cli_exit_on_error (bool | None)

  • _cli_prefix (str | None)

  • _cli_flag_prefix_char (str | None)

  • _cli_implicit_flags (bool | Literal['dual', 'toggle'] | None)

  • _cli_ignore_unknown_args (bool | None)

  • _cli_kebab_case (bool | Literal['all', 'no_enums'] | None)

  • _cli_shortcuts (Mapping[str, str | list[str]] | None)

  • _secrets_dir (PathType | None)

  • _build_sources (tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]] | None)

  • antenna (AntennaSettings)

  • url (URLSettings)

  • api (APISettings)

  • flowgraph (FlowgraphSettings)

  • health (HealthSettings)

  • log (LoggingSettings)

  • mqtt (MqttSettings)

  • observation (ObservationSettings)

  • paths (PathSettings)

  • station (StationSettings)

  • jobs (JobSettings)

  • waterfall (WaterfallSettings)

  • scheduler (SchedulerSettings)

  • sdr (SdrSettings)

model_config = {'arbitrary_types_allowed': True, 'case_sensitive': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_exit_on_error': True, 'cli_flag_prefix_char': '-', 'cli_hide_none_type': False, 'cli_ignore_unknown_args': False, 'cli_implicit_flags': False, 'cli_kebab_case': False, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_prefix': '', 'cli_prog_name': None, 'cli_shortcuts': None, 'cli_show_env_vars': False, 'cli_use_class_docs_for_groups': False, 'enable_decoding': True, 'env_file': '.env', 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': '__', 'env_nested_max_split': None, 'env_parse_enums': None, 'env_parse_none_str': None, 'env_prefix': '', 'env_prefix_target': 'variable', 'extra': 'forbid', 'json_file': None, 'json_file_encoding': None, 'nested_model_default_partial_update': False, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'secrets_dir': None, 'toml_file': None, 'validate_default': True, 'yaml_config_section': None, 'yaml_file': None, 'yaml_file_encoding': None}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

antenna: AntennaSettings
url: URLSettings
api: APISettings
flowgraph: FlowgraphSettings
health: HealthSettings
log: LoggingSettings
mqtt: MqttSettings
observation: ObservationSettings
paths: PathSettings
station: StationSettings
jobs: JobSettings
waterfall: WaterfallSettings
scheduler: SchedulerSettings
sdr: SdrSettings
core.configs.configure_runtime()[исходный код]

Поднять то, что трогает диск: обработчики логов и рабочие директории.

Вызывается один раз при старте приложения. Вынесено из импорта, чтобы import чего угодно из src/ не создавал каталоги и файл лога.

Тип результата:

None

Разделы настроек

Параметры станции — единственный обязательный раздел настроек.

class core.configs.station.StationSettings(*, TOKEN=None, ID=None, LATITUDE=None, LONGITUDE=None, ELEVATION=None)[исходный код]

Параметры станции.

Обязательны TOKEN и ID — без них клиент не стартует. Координаты и высота необязательны: если их нет в .env, они берутся с портала вместе с конфигурацией станции (remote_config). Заданные в .env имеют приоритет и, как раньше, уходят на портал в запросе расписания.

Параметры:
  • TOKEN (str | None)

  • ID (int | None)

  • LATITUDE (float | None)

  • LONGITUDE (float | None)

  • ELEVATION (int | None)

TOKEN: str | None
ID: int | None
LATITUDE: float | None
LONGITUDE: float | None
ELEVATION: int | None
check_required_fields()[исходный код]

Проверить, что заполнены STATION__TOKEN и STATION__ID.

Исключение:

ValueError – Со списком незаполненных параметров.

Тип результата:

Self

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройки поворотного устройства и приёмного тракта.

class core.configs.antenna.RotatorMode(value)[исходный код]

Режим слежения поворотного устройства.

NORMAL = 'Normal'
FLIP = 'Flip'
RANGE_250 = '250'
class core.configs.antenna.RotatorSettings(*, ENABLED=False, MODEL='ROT_MODEL_NETROTCTL', BAUD=9600, PORT='rotctld:4533', THRESHOLD=4.0, MODE=RotatorMode.NORMAL, MIN_ELEVATION=0.0)[исходный код]

Настройки поворотного устройства (ANTENNA__ROTATOR__*).

Параметры:
  • ENABLED (bool)

  • MODEL (str)

  • BAUD (int)

  • PORT (str)

  • THRESHOLD (float)

  • MODE (RotatorMode)

  • MIN_ELEVATION (float)

ENABLED: bool
MODEL: str
BAUD: int
PORT: str
THRESHOLD: float
MODE: RotatorMode
MIN_ELEVATION: float
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class core.configs.antenna.RigSettings(*, IP='rigctld', PORT=4532)[исходный код]

Адрес демона rigctld (ANTENNA__RIG__*).

Параметры:
  • IP (str)

  • PORT (int)

IP: str
PORT: int
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class core.configs.antenna.AntennaSettings(*, rotator=RotatorSettings(ENABLED=False, MODEL='ROT_MODEL_NETROTCTL', BAUD=9600, PORT='rotctld:4533', THRESHOLD=4.0, MODE=<RotatorMode.NORMAL: 'Normal'>, MIN_ELEVATION=0.0), rig=RigSettings(IP='rigctld', PORT=4532))[исходный код]

Настройки антенного тракта: поворотное и приёмное устройства.

Параметры:
rotator: RotatorSettings
rig: RigSettings
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройки наблюдения: префиксы, расширения и состав выгружаемых файлов.

class core.configs.observation.ObservationSendingKey(*, PAYLOAD='payload', WATERFALL='waterfall', DEMODDATA='demoddata', CLIENT_METADATA='client_metadata')[исходный код]

Имена полей формы при выгрузке на портал.

Параметры:
  • PAYLOAD (str)

  • WATERFALL (str)

  • DEMODDATA (str)

  • CLIENT_METADATA (str)

PAYLOAD: str
WATERFALL: str
DEMODDATA: str
CLIENT_METADATA: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class core.configs.observation.ObservationPrefix(*, PAYLOAD='payload', WATERFALL='waterfall', RAW_WATERFALL='raw_waterfall', DATA='data', METADATA='metadata')[исходный код]

Префиксы имён файлов наблюдения.

Параметры:
  • PAYLOAD (str)

  • WATERFALL (str)

  • RAW_WATERFALL (str)

  • DATA (str)

  • METADATA (str)

PAYLOAD: str
WATERFALL: str
RAW_WATERFALL: str
DATA: str
METADATA: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class core.configs.observation.ObservationExtension(*, PAYLOAD='ogg', WATERFALL='png', DATA_APT='png', METADATA='json', RAW_WATERFALL='dat')[исходный код]

Расширения файлов наблюдения.

Параметры:
  • PAYLOAD (str)

  • WATERFALL (str)

  • DATA_APT (str)

  • METADATA (str)

  • RAW_WATERFALL (str)

PAYLOAD: str
WATERFALL: str
DATA_APT: str
METADATA: str
RAW_WATERFALL: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class core.configs.observation.ObservationUploadTypes(*, PAYLOAD=True, WATERFALL=True)[исходный код]

Какие типы файлов выгружать на портал.

Параметры:
  • PAYLOAD (bool)

  • WATERFALL (bool)

PAYLOAD: bool
WATERFALL: bool
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class core.configs.observation.ObservationSettings(*, REMOVE_WATERFALL_RAW_FILES=True, REMOVE_OBSERVATION_DATA=True, SOAPY_RX_DEVICE='driver=airspy,biastee=true', TIME_FORMAT_IN_FILENAME='%Y-%m-%dT%H-%M-%S', FILENAME_TEMPLATE='{observation_dir}/{prefix}_{observation_id}_{timestamp}.{extension}', RIG_UPDATE_INTERVAL=0.1, ROTATOR_UPDATE_INTERVAL=3, PRE_OBSERVATION_SCRIPT='satnogs-pre', POST_OBSERVATION_SCRIPT='satnogs-post', RUN_PRE_OBSERVATION_SCRIPT=True, RUN_POST_OBSERVATION_SCRIPT=True, SCRIPT_TIMEOUT_IN_SECONDS=900, BATCH_DELAY=1, prefix=ObservationPrefix(PAYLOAD='payload', WATERFALL='waterfall', RAW_WATERFALL='raw_waterfall', DATA='data', METADATA='metadata'), extension=ObservationExtension(PAYLOAD='ogg', WATERFALL='png', DATA_APT='png', METADATA='json', RAW_WATERFALL='dat'), sending_key=ObservationSendingKey(PAYLOAD='payload', WATERFALL='waterfall', DEMODDATA='demoddata', CLIENT_METADATA='client_metadata'), upload_types=ObservationUploadTypes(PAYLOAD=True, WATERFALL=True))[исходный код]

Параметры проведения наблюдения и состав выгружаемых файлов.

Параметры:
  • REMOVE_WATERFALL_RAW_FILES (bool)

  • REMOVE_OBSERVATION_DATA (bool)

  • SOAPY_RX_DEVICE (str)

  • TIME_FORMAT_IN_FILENAME (str)

  • FILENAME_TEMPLATE (str)

  • RIG_UPDATE_INTERVAL (float)

  • ROTATOR_UPDATE_INTERVAL (float)

  • PRE_OBSERVATION_SCRIPT (str)

  • POST_OBSERVATION_SCRIPT (str)

  • RUN_PRE_OBSERVATION_SCRIPT (bool)

  • RUN_POST_OBSERVATION_SCRIPT (bool)

  • SCRIPT_TIMEOUT_IN_SECONDS (float)

  • BATCH_DELAY (float)

  • prefix (ObservationPrefix)

  • extension (ObservationExtension)

  • sending_key (ObservationSendingKey)

  • upload_types (ObservationUploadTypes)

REMOVE_WATERFALL_RAW_FILES: bool
REMOVE_OBSERVATION_DATA: bool
SOAPY_RX_DEVICE: str
TIME_FORMAT_IN_FILENAME: str
FILENAME_TEMPLATE: str
RIG_UPDATE_INTERVAL: float
ROTATOR_UPDATE_INTERVAL: float
PRE_OBSERVATION_SCRIPT: str
POST_OBSERVATION_SCRIPT: str
RUN_PRE_OBSERVATION_SCRIPT: bool
RUN_POST_OBSERVATION_SCRIPT: bool
SCRIPT_TIMEOUT_IN_SECONDS: float
BATCH_DELAY: float
prefix: ObservationPrefix
extension: ObservationExtension
sending_key: ObservationSendingKey
upload_types: ObservationUploadTypes
get_prefixes_of_files_available_for_upload()[исходный код]

Вернуть префиксы файлов, разрешённых к выгрузке.

Кадры и метаданные выгружаются всегда, аудио и водопад — по OBSERVATION__UPLOAD_TYPES__*. Метаданные первыми: порядок префиксов задаёт порядок отправки, а до появления очереди они уходили на портал раньше остальных файлов.

Тип результата:

list[str]

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройки потокового графа и таблицы соответствия режимов демодуляции.

Каждое поле FlowgraphSettings доезжает до командной строки flowgraph_dispatcher как аргумент; поля со значением None не передаются.

SCRIPTS и MODES — предметные данные, а не настройки: они описывают внешний контракт с gr-satnogs и не переопределяются из окружения. Полями модели они были по недосмотру, из-за чего FLOWGRAPH__MODES с JSON-значением молча перезаписывал таблицу целиком.

class core.configs.flowgraph.FlowgraphSettings(*, RX_SAMP_RATE=None, RX_BANDWIDTH=None, DOPPLER_CORR_PER_SEC=None, LO_OFFSET=None, LO_TRANSVERTER=None, PPM_ERROR=0, GAIN_MODE='Overall', RF_GAIN=None, ANTENNA='RX', DEV_ARGS=None, STREAM_ARGS=None, TUNE_ARGS=None, OTHER_SETTINGS=None, DC_REMOVAL=None, BB_FREQ=None, ENABLE_IQ_DUMP=False, IQ_DUMP_FILENAME=None, UDP_DUMP_HOST='127.0.0.1', UDP_DUMP_PORT=57356, FLOWGRAPH_DISPATCHER='flowgraph_dispatcher', DEFAULT_MODE='FM')[исходный код]

Параметры потокового графа.

Каждое поле становится аргументом --kebab-case=значение; поля со значением None в командную строку не попадают. Таблицы режимов живут рядом, в константах модуля: SCRIPTS и MODES.

Параметры:
  • RX_SAMP_RATE (str | None)

  • RX_BANDWIDTH (int | None)

  • DOPPLER_CORR_PER_SEC (int | None)

  • LO_OFFSET (int | None)

  • LO_TRANSVERTER (int | None)

  • PPM_ERROR (float)

  • GAIN_MODE (str)

  • RF_GAIN (float | None)

  • ANTENNA (str)

  • DEV_ARGS (str | None)

  • STREAM_ARGS (str | None)

  • TUNE_ARGS (str | None)

  • OTHER_SETTINGS (str | None)

  • DC_REMOVAL (bool | None)

  • BB_FREQ (int | None)

  • ENABLE_IQ_DUMP (bool)

  • IQ_DUMP_FILENAME (str | None)

  • UDP_DUMP_HOST (str)

  • UDP_DUMP_PORT (int)

  • FLOWGRAPH_DISPATCHER (str)

  • DEFAULT_MODE (str)

RX_SAMP_RATE: str | None
RX_BANDWIDTH: int | None
DOPPLER_CORR_PER_SEC: int | None
LO_OFFSET: int | None
LO_TRANSVERTER: int | None
PPM_ERROR: float
GAIN_MODE: str
RF_GAIN: float | None
ANTENNA: str
DEV_ARGS: str | None
STREAM_ARGS: str | None
TUNE_ARGS: str | None
OTHER_SETTINGS: str | None
DC_REMOVAL: bool | None
BB_FREQ: int | None
ENABLE_IQ_DUMP: bool
IQ_DUMP_FILENAME: str | None
UDP_DUMP_HOST: str
UDP_DUMP_PORT: int
FLOWGRAPH_DISPATCHER: str
DEFAULT_MODE: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройки построения и анализа водопада.

class core.configs.waterfall.WaterfallSettings(*, AUTORANGE=False, THRESHOLD=-200, MIN_VALID_SAMPLES=10000, DEFAULT_MIN_VALUE=-110, DEFAULT_MAX_VALUE=-50, MIN_VALUE=-110, MAX_VALUE=-50, WIDTH=8.32, HEIGHT=16.03, HELP_LINES=False, GRIDSPEC={'bottom': 0.03, 'left': 0.09, 'ncols': 2, 'nrows': 1, 'right': 0.91, 'top': 0.995, 'width_ratios': [0.93, 0.03]}, TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S.%fZ', TIME_FORMAT='%H:%M', INTERVAL_IN_MINUTES=1)[исходный код]

Параметры отрисовки водопада и границы динамического диапазона.

Параметры:
  • AUTORANGE (bool)

  • THRESHOLD (int)

  • MIN_VALID_SAMPLES (int)

  • DEFAULT_MIN_VALUE (int)

  • DEFAULT_MAX_VALUE (int)

  • MIN_VALUE (int)

  • MAX_VALUE (int)

  • WIDTH (float)

  • HEIGHT (float)

  • HELP_LINES (bool)

  • GRIDSPEC (dict[str, float | list[float]])

  • TIMESTAMP_FORMAT (str)

  • TIME_FORMAT (str)

  • INTERVAL_IN_MINUTES (int)

AUTORANGE: bool
THRESHOLD: int
MIN_VALID_SAMPLES: int
DEFAULT_MIN_VALUE: int
DEFAULT_MAX_VALUE: int
MIN_VALUE: int
MAX_VALUE: int
WIDTH: float
HEIGHT: float
HELP_LINES: bool
TIMESTAMP_FORMAT: str
TIME_FORMAT: str
INTERVAL_IN_MINUTES: int
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Адреса конечных точек API портала.

class core.configs.url.URLSettings(*, BASE='https://sonik.space/api/', JOBS_PATH='jobs/', OBSERVATIONS_PATH='observations/', BUNDLES_PATH='v2/bundles/', STATIONS_PATH='v2/stations/')[исходный код]

Адреса конечных точек API портала.

Параметры:
  • BASE (str)

  • JOBS_PATH (str)

  • OBSERVATIONS_PATH (str)

  • BUNDLES_PATH (str)

  • STATIONS_PATH (str)

BASE: str
JOBS_PATH: str
OBSERVATIONS_PATH: str
BUNDLES_PATH: str
STATIONS_PATH: str
property jobs_url: str
property stations_url: str
property bundles_url: str
property observations_url: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Таймауты HTTP-обмена с порталом.

class core.configs.api.APISettings(*, TIMEOUT_IN_SECONDS_REQUEST_DATA=45, TIMEOUT_IN_SECONDS_SEND_DATA=120)[исходный код]

Таймауты HTTP-запросов к порталу.

Параметры:
  • TIMEOUT_IN_SECONDS_REQUEST_DATA (int)

  • TIMEOUT_IN_SECONDS_SEND_DATA (int)

TIMEOUT_IN_SECONDS_REQUEST_DATA: int
TIMEOUT_IN_SECONDS_SEND_DATA: int
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройка логгеров приложения и вывода внешних скриптов.

class core.configs.log.LoggingSettings(*, FORMAT='%(asctime)s - %(name)s - %(levelname)s - %(message)s', LEVEL='INFO', SCRIPT_LEVEL='INFO', FLOWGRAPH_LEVEL='INFO', DIRECTORY='/var/lib/soniks-client/logs', FILE_NAME='client.log', MAX_BYTES=5242880, BACKUP_COUNT=5, ENCODING='utf-8')[исходный код]

Настройка логгеров: консоль плюс файл с ротацией.

Параметры:
  • FORMAT (str)

  • LEVEL (str)

  • SCRIPT_LEVEL (str)

  • FLOWGRAPH_LEVEL (str)

  • DIRECTORY (str)

  • FILE_NAME (str)

  • MAX_BYTES (int)

  • BACKUP_COUNT (int)

  • ENCODING (str)

FORMAT: str
LEVEL: str
SCRIPT_LEVEL: str
FLOWGRAPH_LEVEL: str
DIRECTORY: str
FILE_NAME: str
MAX_BYTES: int
BACKUP_COUNT: int
ENCODING: str
property script_log_level: int
property flowgraph_log_level: int
configure_logger(name='app')[исходный код]

Собрать основной логгер приложения.

Параметры:

name (str)

Тип результата:

Logger

configure_raw_logger(name='row_logs')[исходный код]

Собрать логгер сырого вывода внешних скриптов (без префиксов).

Параметры:

name (str)

Тип результата:

Logger

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Живой канал с порталом (MQTT__*).

class core.configs.mqtt.MqttSettings(*, ENABLED=True, URL='wss://sonik.space/mqtt')[исходный код]

Подключение к брокеру портала.

Один хост на всё: брокер стоит за тем же sonik.space и слушает WebSocket на 443 — закрытым сетям ничего дополнительно открывать не нужно. Отключённый или недоступный MQTT станцию не ломает: конфигурация всё равно забирается REST’ом на каждой сверке расписания, просто не мгновенно.

Параметры:
  • ENABLED (bool)

  • URL (str)

ENABLED: bool
URL: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройки планировщика заданий APScheduler.

class core.configs.scheduler.SchedulerSettings(*, MAX_WORKERS=30, COALESCE=True, MAX_INSTANCES=1, MISFIRE_GRACE_TIME_IN_SECOND=30, JOB_REQUEST_INTERVAL_IN_MINUTES=1, RESENDING_INTERVAL_IN_MINUTES=20, SAT_DATA_SYNC_INTERVAL_IN_HOURS=6, LOG_LEVEL='WARNING')[исходный код]

Параметры планировщика APScheduler и периодичность заданий.

Параметры:
  • MAX_WORKERS (int)

  • COALESCE (bool)

  • MAX_INSTANCES (int)

  • MISFIRE_GRACE_TIME_IN_SECOND (int)

  • JOB_REQUEST_INTERVAL_IN_MINUTES (int)

  • RESENDING_INTERVAL_IN_MINUTES (int)

  • SAT_DATA_SYNC_INTERVAL_IN_HOURS (int)

  • LOG_LEVEL (str)

MAX_WORKERS: int
COALESCE: bool
MAX_INSTANCES: int
MISFIRE_GRACE_TIME_IN_SECOND: int
JOB_REQUEST_INTERVAL_IN_MINUTES: int
RESENDING_INTERVAL_IN_MINUTES: int
SAT_DATA_SYNC_INTERVAL_IN_HOURS: int
LOG_LEVEL: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Рабочие директории клиента.

Пути считаются от BASE, на диск ничего не пишется до явного вызова create_directories() — его делает configure_runtime() при старте приложения. Раньше директории создавались валидатором, то есть побочным эффектом импорта.

class core.configs.path.PathSettings(*, BASE='/tmp/.soniks', OUTPUT_DIR='output', COMPLETE_DIR='complete', INCOMPLETE_DIR='incomplete', STATE_FILE='/var/lib/soniks-client/desired-state.json')[исходный код]

Рабочие директории клиента под PATHS__BASE.

Параметры:
  • BASE (str)

  • OUTPUT_DIR (str)

  • COMPLETE_DIR (str)

  • INCOMPLETE_DIR (str)

  • STATE_FILE (str)

BASE: str
OUTPUT_DIR: str
COMPLETE_DIR: str
INCOMPLETE_DIR: str
STATE_FILE: str
property base_path: Path
property output_path: Path
property complete_path: Path
property incomplete_path: Path
create_directories()[исходный код]

Создать рабочие директории на диске.

Тип результата:

None

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Настройки именования заданий наблюдения в планировщике.

class core.configs.job.JobSettings(*, PREFIX='job_')[исходный код]

Именование заданий наблюдения в планировщике.

Параметры:

PREFIX (str)

PREFIX: str
get_job_name(job_id)[исходный код]

Построить имя задания планировщика по идентификатору наблюдения.

Параметры:

job_id (str)

Тип результата:

str

is_observation_job(job_name)[исходный код]

Задание наблюдения ли это.

Именно префикс, а не вхождение подстроки: PREFIX in job.name отбирал по совпадению, а не по замыслу, и любое задание со словом job_ в имени попадало в сверку расписания.

Параметры:

job_name (str)

Тип результата:

bool

model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Эндпоинт состояния станции.

class core.configs.health.HealthSettings(*, PORT=8080)[исходный код]

Параметры /healthz.

Параметры:

PORT (int)

PORT: int
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Исключения

Сигнализируют, что делать с файлами наблюдения, см. Загрузку данных.

Исключения клиента.

Служат не для всплытия наверх, а для выбора дальнейшей судьбы файлов наблюдения: удалить, переместить в complete или отложить в incomplete до повторной отправки.

exception core.exceptions.ObservationNotFoundError[исходный код]

Базовые классы: Exception

Портал ответил 404: наблюдение удалено на его стороне.

Отправка прекращается. С первой попытки данные не удаляются — файлы откладываются в incomplete; удаляет директорию только повторная отправка, получившая 404 второй раз.

exception core.exceptions.FileNotUploadedError[исходный код]

Базовые классы: Exception

Файл выгрузить не удалось: сеть, таймаут или ошибка портала.

Файл переезжает в incomplete и будет отправлен повторно.

exception core.exceptions.FileRejectedError[исходный код]

Базовые классы: FileNotUploadedError

Портал ответил 400: файл отвергнут, те же байты получат тот же ответ.

Подкласс FileNotUploadedError, поэтому первый отказ ведёт себя как любая ошибка выгрузки — файл ложится в incomplete. Закрывает его, как и второй 404, только повторная отправка, получившая 400 ещё раз.

exception core.exceptions.NoSessionParametersError[исходный код]

Базовые классы: Exception

Сессия связи запущена без параметров пролёта.

exception core.exceptions.RotatorPositionError[исходный код]

Базовые классы: Exception

Не удалось получить или задать положение поворотного устройства.

exception core.exceptions.EquipmentConnectionError[исходный код]

Базовые классы: Exception

Hamlib не смог открыть соединение с приёмником или ротатором.

Hamlib возвращает коды ошибок, а не исключения: без явной проверки неудачный open() невидим, а все последующие команды становятся тихим no-op. Сессия связи ловит это исключение и не начинает проход.

exception core.exceptions.NoCompatibleRxDeviceError[исходный код]

Базовые классы: Exception

Ни один приёмник не подходит под частоту прохода.

Наблюдение не начинается; см. OBSERVATION__SOAPY_RX_DEVICE.

exception core.exceptions.EmptyWaterfallError[исходный код]

Базовые классы: Exception

Файл водопада прочитан, но не содержит отсчётов.

exception core.exceptions.WaterfallFileError[исходный код]

Базовые классы: Exception

Файл водопада не найден или не читается.

exception core.exceptions.TimestampError[исходный код]

Базовые классы: Exception

Метка времени в файле водопада не разобрана.

Типы

Типы-псевдонимы, общие для всего клиента.