feat(sat): the station side — elements, plan, passes and tuning
What internal/sat could not know: where the antenna is, which birds the operator cares about, and where the files live. Startup reads the cached elements and the frequency plan from disk and nothing else — one file and a few hundred parses, so the tab is full the moment it is opened, on a shack PC with no internet as much as on one with. Fetching is the slow, optional half and never blocks a launch; it happens on its own only when the set is stale and the operator asked for it. Elements pasted in by hand go in their own file. The feed cache is replaced wholesale on every refresh, so a freshly launched satellite — whose elements circulate on a mailing list days before any feed carries it, which is exactly the week everybody wants to hear it — would otherwise be wiped by the first automatic update. The list joins both halves and shows what is missing on either side. A bird with elements and no plan is one the operator can still track; a bird with a plan and no elements is the visible symptom of an element set that is too old. Dropping either turns a fixable configuration problem into a satellite that "does not exist". GetSatelliteTuning is the working answer, and everything that will later drive a radio is built on top of it rather than beside it, so the display and the rig can never disagree. It keeps the operator's frequency nominal and applies Doppler only on the way out: on a linear pass the station being answered stays put on the dial while both radios chase the shift. A geostationary bird is corrected by nothing at all.
This commit is contained in:
Vendored
+23
@@ -19,6 +19,7 @@ import {pskrtgt} from '../models';
|
||||
import {pskr} from '../models';
|
||||
import {psu} from '../models';
|
||||
import {spe} from '../models';
|
||||
import {sat} from '../models';
|
||||
import {solar} from '../models';
|
||||
import {tunergenius} from '../models';
|
||||
import {webpub} from '../models';
|
||||
@@ -52,6 +53,8 @@ export function ActiveRadioMyRig():Promise<string>;
|
||||
|
||||
export function AddQSO(arg1:qso.QSO):Promise<number>;
|
||||
|
||||
export function AddSatelliteElements(arg1:string):Promise<number>;
|
||||
|
||||
export function AmpFanMode(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function AmpOperate(arg1:string,arg2:boolean):Promise<void>;
|
||||
@@ -588,6 +591,22 @@ export function GetRowColors():Promise<main.RowColorSettings>;
|
||||
|
||||
export function GetSPEStatus():Promise<spe.Status>;
|
||||
|
||||
export function GetSatSettings():Promise<main.SatSettings>;
|
||||
|
||||
export function GetSatelliteBirds():Promise<Array<main.SatBird>>;
|
||||
|
||||
export function GetSatelliteGroundTrack(arg1:string,arg2:number):Promise<Array<sat.Position>>;
|
||||
|
||||
export function GetSatelliteObserver():Promise<Record<string, any>>;
|
||||
|
||||
export function GetSatellitePasses(arg1:Array<string>,arg2:number):Promise<Array<sat.Pass>>;
|
||||
|
||||
export function GetSatellitePositions(arg1:Array<string>):Promise<Array<sat.Position>>;
|
||||
|
||||
export function GetSatelliteTLEInfo():Promise<main.SatTLEInfo>;
|
||||
|
||||
export function GetSatelliteTuning(arg1:string,arg2:number,arg3:number):Promise<main.SatTuning>;
|
||||
|
||||
export function GetScpStatus():Promise<main.ScpStatus>;
|
||||
|
||||
export function GetSecretStatus():Promise<main.SecretStatus>;
|
||||
@@ -974,6 +993,8 @@ export function RefreshDXpeditions():Promise<void>;
|
||||
|
||||
export function RefreshKenwood():Promise<void>;
|
||||
|
||||
export function RefreshSatelliteTLE():Promise<main.SatTLEInfo>;
|
||||
|
||||
export function RefreshSolar():Promise<void>;
|
||||
|
||||
export function RefreshYaesuPanel():Promise<void>;
|
||||
@@ -1116,6 +1137,8 @@ export function SaveRotorPresets(arg1:Array<main.RotorPreset>):Promise<void>;
|
||||
|
||||
export function SaveRowColors(arg1:main.RowColorSettings):Promise<void>;
|
||||
|
||||
export function SaveSatSettings(arg1:main.SatSettings):Promise<void>;
|
||||
|
||||
export function SaveSelfSpotSettings(arg1:main.SelfSpotSettings):Promise<void>;
|
||||
|
||||
export function SaveSpotColors(arg1:main.SpotColors):Promise<void>;
|
||||
|
||||
@@ -38,6 +38,10 @@ export function AddQSO(arg1) {
|
||||
return window['go']['main']['App']['AddQSO'](arg1);
|
||||
}
|
||||
|
||||
export function AddSatelliteElements(arg1) {
|
||||
return window['go']['main']['App']['AddSatelliteElements'](arg1);
|
||||
}
|
||||
|
||||
export function AmpFanMode(arg1, arg2) {
|
||||
return window['go']['main']['App']['AmpFanMode'](arg1, arg2);
|
||||
}
|
||||
@@ -1110,6 +1114,38 @@ export function GetSPEStatus() {
|
||||
return window['go']['main']['App']['GetSPEStatus']();
|
||||
}
|
||||
|
||||
export function GetSatSettings() {
|
||||
return window['go']['main']['App']['GetSatSettings']();
|
||||
}
|
||||
|
||||
export function GetSatelliteBirds() {
|
||||
return window['go']['main']['App']['GetSatelliteBirds']();
|
||||
}
|
||||
|
||||
export function GetSatelliteGroundTrack(arg1, arg2) {
|
||||
return window['go']['main']['App']['GetSatelliteGroundTrack'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function GetSatelliteObserver() {
|
||||
return window['go']['main']['App']['GetSatelliteObserver']();
|
||||
}
|
||||
|
||||
export function GetSatellitePasses(arg1, arg2) {
|
||||
return window['go']['main']['App']['GetSatellitePasses'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function GetSatellitePositions(arg1) {
|
||||
return window['go']['main']['App']['GetSatellitePositions'](arg1);
|
||||
}
|
||||
|
||||
export function GetSatelliteTLEInfo() {
|
||||
return window['go']['main']['App']['GetSatelliteTLEInfo']();
|
||||
}
|
||||
|
||||
export function GetSatelliteTuning(arg1, arg2, arg3) {
|
||||
return window['go']['main']['App']['GetSatelliteTuning'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function GetScpStatus() {
|
||||
return window['go']['main']['App']['GetScpStatus']();
|
||||
}
|
||||
@@ -1882,6 +1918,10 @@ export function RefreshKenwood() {
|
||||
return window['go']['main']['App']['RefreshKenwood']();
|
||||
}
|
||||
|
||||
export function RefreshSatelliteTLE() {
|
||||
return window['go']['main']['App']['RefreshSatelliteTLE']();
|
||||
}
|
||||
|
||||
export function RefreshSolar() {
|
||||
return window['go']['main']['App']['RefreshSolar']();
|
||||
}
|
||||
@@ -2166,6 +2206,10 @@ export function SaveRowColors(arg1) {
|
||||
return window['go']['main']['App']['SaveRowColors'](arg1);
|
||||
}
|
||||
|
||||
export function SaveSatSettings(arg1) {
|
||||
return window['go']['main']['App']['SaveSatSettings'](arg1);
|
||||
}
|
||||
|
||||
export function SaveSelfSpotSettings(arg1) {
|
||||
return window['go']['main']['App']['SaveSelfSpotSettings'](arg1);
|
||||
}
|
||||
|
||||
@@ -4003,6 +4003,199 @@ export namespace main {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class SatTransponder {
|
||||
label: string;
|
||||
mode: string;
|
||||
down_lo: number;
|
||||
down_hi: number;
|
||||
up_lo: number;
|
||||
up_hi: number;
|
||||
inverting: boolean;
|
||||
ctcss: number;
|
||||
linear: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SatTransponder(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.label = source["label"];
|
||||
this.mode = source["mode"];
|
||||
this.down_lo = source["down_lo"];
|
||||
this.down_hi = source["down_hi"];
|
||||
this.up_lo = source["up_lo"];
|
||||
this.up_hi = source["up_hi"];
|
||||
this.inverting = source["inverting"];
|
||||
this.ctcss = source["ctcss"];
|
||||
this.linear = source["linear"];
|
||||
}
|
||||
}
|
||||
export class SatBird {
|
||||
name: string;
|
||||
norad: number;
|
||||
geostationary: boolean;
|
||||
favorite: boolean;
|
||||
has_elements: boolean;
|
||||
element_name: string;
|
||||
epoch_age_h: number;
|
||||
transponders: SatTransponder[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SatBird(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.norad = source["norad"];
|
||||
this.geostationary = source["geostationary"];
|
||||
this.favorite = source["favorite"];
|
||||
this.has_elements = source["has_elements"];
|
||||
this.element_name = source["element_name"];
|
||||
this.epoch_age_h = source["epoch_age_h"];
|
||||
this.transponders = this.convertValues(source["transponders"], SatTransponder);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class SatSettings {
|
||||
favorites: string[];
|
||||
min_el: number;
|
||||
window_h: number;
|
||||
auto_tle: boolean;
|
||||
grid: string;
|
||||
alt_m: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SatSettings(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.favorites = source["favorites"];
|
||||
this.min_el = source["min_el"];
|
||||
this.window_h = source["window_h"];
|
||||
this.auto_tle = source["auto_tle"];
|
||||
this.grid = source["grid"];
|
||||
this.alt_m = source["alt_m"];
|
||||
}
|
||||
}
|
||||
export class SatTLEInfo {
|
||||
count: number;
|
||||
// Go type: time
|
||||
fetched_at: any;
|
||||
age_h: number;
|
||||
stale: boolean;
|
||||
custom: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SatTLEInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.count = source["count"];
|
||||
this.fetched_at = this.convertValues(source["fetched_at"], null);
|
||||
this.age_h = source["age_h"];
|
||||
this.stale = source["stale"];
|
||||
this.custom = source["custom"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
export class SatTuning {
|
||||
name: string;
|
||||
transponder: string;
|
||||
mode: string;
|
||||
nominal_down: number;
|
||||
nominal_up: number;
|
||||
down_hz: number;
|
||||
up_hz: number;
|
||||
ctcss: number;
|
||||
inverting: boolean;
|
||||
az: number;
|
||||
el: number;
|
||||
range_km: number;
|
||||
range_rate: number;
|
||||
visible: boolean;
|
||||
// Go type: time
|
||||
at: any;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SatTuning(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.transponder = source["transponder"];
|
||||
this.mode = source["mode"];
|
||||
this.nominal_down = source["nominal_down"];
|
||||
this.nominal_up = source["nominal_up"];
|
||||
this.down_hz = source["down_hz"];
|
||||
this.up_hz = source["up_hz"];
|
||||
this.ctcss = source["ctcss"];
|
||||
this.inverting = source["inverting"];
|
||||
this.az = source["az"];
|
||||
this.el = source["el"];
|
||||
this.range_km = source["range_km"];
|
||||
this.range_rate = source["range_rate"];
|
||||
this.visible = source["visible"];
|
||||
this.at = this.convertValues(source["at"], null);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class ScpStatus {
|
||||
enabled: boolean;
|
||||
count: number;
|
||||
@@ -6073,6 +6266,109 @@ export namespace qso {
|
||||
|
||||
}
|
||||
|
||||
export namespace sat {
|
||||
|
||||
export class Pass {
|
||||
name: string;
|
||||
// Go type: time
|
||||
aos: any;
|
||||
// Go type: time
|
||||
los: any;
|
||||
aos_az: number;
|
||||
los_az: number;
|
||||
max_el: number;
|
||||
max_el_az: number;
|
||||
// Go type: time
|
||||
max_el_at: any;
|
||||
duration_s: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Pass(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.aos = this.convertValues(source["aos"], null);
|
||||
this.los = this.convertValues(source["los"], null);
|
||||
this.aos_az = source["aos_az"];
|
||||
this.los_az = source["los_az"];
|
||||
this.max_el = source["max_el"];
|
||||
this.max_el_az = source["max_el_az"];
|
||||
this.max_el_at = this.convertValues(source["max_el_at"], null);
|
||||
this.duration_s = source["duration_s"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class Position {
|
||||
name: string;
|
||||
// Go type: time
|
||||
at: any;
|
||||
lat: number;
|
||||
lon: number;
|
||||
alt_km: number;
|
||||
footprint_km: number;
|
||||
az: number;
|
||||
el: number;
|
||||
range_km: number;
|
||||
range_rate: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Position(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.at = this.convertValues(source["at"], null);
|
||||
this.lat = source["lat"];
|
||||
this.lon = source["lon"];
|
||||
this.alt_km = source["alt_km"];
|
||||
this.footprint_km = source["footprint_km"];
|
||||
this.az = source["az"];
|
||||
this.el = source["el"];
|
||||
this.range_km = source["range_km"];
|
||||
this.range_rate = source["range_rate"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace scp {
|
||||
|
||||
export class Result {
|
||||
|
||||
Reference in New Issue
Block a user