claude: move to typescript

This commit is contained in:
2026-03-20 13:17:47 +01:00
parent 009b6f1b08
commit d88322fe46
14 changed files with 440 additions and 341 deletions

130
src/RotClient.ts Normal file
View File

@@ -0,0 +1,130 @@
import type { ClientConfig, DynamicData, InitData } from './types.js';
// Numeric parsing strategy for rotator HTTP endpoints
const NumType = {
String: 0,
Int: 1,
Float: 2,
} as const;
type NumType = (typeof NumType)[keyof typeof NumType];
interface EndpointDef {
key: string;
url: string;
numType: NumType;
}
/**
* Client for the simple_rotator_interface_v protocol.
* @see https://remoteqth.com/w/doku.php?id=simple_rotator_interface_v
*/
export default class RotClient {
readonly label: string;
private readonly ip: string;
private readonly bands: number[];
private cachedData: InitData | null = null;
private dynamicData: DynamicData | null = null;
private readonly dynamicHandlers: Array<(data: DynamicData) => void> = [];
constructor(label: string, { ip, bands }: ClientConfig) {
this.label = label;
this.ip = ip;
this.bands = bands;
setInterval(() => void this.fetchDynamicData(), 1000);
}
async readInitData(): Promise<InitData> {
if (this.cachedData) return this.cachedData;
const endpoints: EndpointDef[] = [
{ key: 'azShift', url: 'readStart', numType: NumType.Int },
{ key: 'azRange', url: 'readMax', numType: NumType.Int },
{ key: 'antRadiationAngle', url: 'readAnt', numType: NumType.Int },
{ key: 'antName', url: 'readAntName', numType: NumType.String },
{ key: 'mapUrl', url: 'readMapUrl', numType: NumType.String },
{ key: 'mac', url: 'readMAC', numType: NumType.String },
{ key: 'elevation', url: 'readElevation', numType: NumType.Int },
];
this.cachedData = await this.readEndpoints(endpoints) as unknown as InitData;
console.log(`${this.label} Set the initial offset to ${this.cachedData.azShift}.`);
return this.cachedData;
}
private async readEndpoints(endpoints: EndpointDef[]): Promise<Record<string, string | number | null>> {
const data: Record<string, string | number | null> = {};
for (const { key, url, numType } of endpoints) {
data[key] = await this.readKey(url, numType);
}
return data;
}
private async fetchDynamicData(): Promise<void> {
const endpoints: EndpointDef[] = [
{ key: 'adc', url: 'readADC', numType: NumType.Float },
{ key: 'azimuth', url: 'readAZ', numType: NumType.Int },
{ key: 'status', url: 'readStat', numType: NumType.Int },
];
const data = await this.readEndpoints(endpoints) as unknown as DynamicData;
if (this.dynamicData !== null) {
const oldData = this.dynamicData;
const hasChanged = (Object.keys(oldData) as Array<keyof DynamicData>).some(
key => oldData[key] !== data[key]
);
if (hasChanged) {
this.dynamicHandlers.forEach(handler => handler(data));
}
}
this.dynamicData = data;
console.log(`${this.label}: `, this.dynamicData);
}
onDynamicDataUpdate(handler: (data: DynamicData) => void): void {
this.dynamicHandlers.push(handler);
}
hasBand(band: number): boolean {
return this.bands.includes(band);
}
turn(az: number): void {
fetch(`http://${this.ip}:88/`, {
body: `ROT=${az}`,
method: 'POST',
}).catch(() => {});
console.log(`Turning ${this.label} to ${az}°...`);
}
private async readKey(endpoint: string, numType: NumType): Promise<string | number | null> {
try {
const resp = await fetch(`http://${this.ip}:88/${endpoint}`);
const text = await resp.text();
if (numType === NumType.String) return text;
if (numType === NumType.Int) return parseInt(text, 10);
return parseFloat(text);
} catch (ex) {
console.error(`${this.label}: Failed to read ${endpoint}:`, ex);
return null;
}
}
getDynamicData(): DynamicData | null {
return this.dynamicData;
}
getAzimuth(): number | null {
return this.dynamicData?.azimuth ?? null;
}
getAdc(): number | null {
return this.dynamicData?.adc ?? null;
}
getStatus(): number | null {
return this.dynamicData?.status ?? null;
}
}