UNPKG

@springfield/radio-module-baofeng

Version:

Radio module for Baofeng UV-5R series ham radios

99 lines (75 loc) 3.94 kB
import { type CTCSS, type DCS, Frequency, type RadioChannel, type RadioMemory, type RadioProgram, type RadioProgrammedChannel, type RadioTone, RadioToneType } from "@springfield/ham-radio-api"; import type { BaofengConfig } from "./baofeng-codec.js"; import type { ILogLayer } from "loglayer"; import { dcsValues } from "./baofeng-dcs-tones.js"; export class BaofengDecoder { private config: BaofengConfig; private logger: ILogLayer; constructor(config: BaofengConfig, logger: ILogLayer) { this.config = config; this.logger = logger; } public decode(memory: RadioMemory): RadioProgram { const radioProgram: RadioProgram = { channels: [], settings: {}, }; for (let channelNumber = 0; channelNumber < this.config.numberChannels; channelNumber += 1) { this.logger.debug(`Decoding channel: ${channelNumber}`); const channelAddress = this.getChannelAddress(channelNumber); if (memory.contents[channelAddress] !== 0xFF) { const channel = this.decodeChannel(memory, channelAddress, channelNumber); radioProgram.channels.push(channel); } } return radioProgram; } private getChannelAddress(channelNumber: number): number { return channelNumber * this.config.channelSize; } private decodeChannel(memory: RadioMemory, channelAddress: number, channelNumber: number): RadioProgrammedChannel { const transmitPower = this.decodePower(memory.contents[channelAddress + this.config.powerOffset]); const name = this.decodeChannelName(channelNumber, memory); const receiveFrequency = this.decodeFrequency(memory.contents, channelAddress, this.config.receiveFrequencyOffset); const transmitFrequency = this.decodeFrequency(memory.contents, channelAddress, this.config.transmitFrequencyOffset); const receiveTone = this.decodeTone(memory.contents, channelAddress, this.config.receiveToneOffset); const transmitTone = this.decodeTone(memory.contents, channelAddress, this.config.transmitToneOffset); const radioChannel: RadioChannel = { name, receiveFrequency, receiveTone, transmitFrequency, transmitTone }; const radioSpecificChannelSettings = { transmitPower }; return { channelNumber, radioChannel, settings: radioSpecificChannelSettings }; } private decodePower(power: number): number { return power === 0x0 ? 5 : 1; } private decodeChannelName(channelNumber: number, memory: RadioMemory): string { // Channel names start at 0x1000 and each name is 16 bytes apart (0x10 offset) const channelNameAddress = 0x1000 + (channelNumber * 0x10); let channelName = ""; // Read 7 characters (8th byte should be 0xFF terminator) for (let index = 0; index < 7; index += 1) { const value = memory.contents[channelNameAddress + index]; if (value !== 0xFF && value !== 0x00) { channelName += String.fromCodePoint(value); } } return channelName.trim(); } private decodeFrequency(memoryData: Uint8Array, channelOffset: number, valueOffset: number): Frequency { let value = 0; for (let index = 0; index < 4; index += 1) { value |= memoryData[channelOffset + valueOffset + index] << (8 * index); } return Frequency(Number.parseInt(value.toString(16), 10) * 10); } private decodeTone(memoryData: Uint8Array, channelOffset: number, valueOffset: number): RadioTone { // DCS: 1 byte index, CTCSS: 2 bytes value const dcsIndex = memoryData[channelOffset + valueOffset]; const ctcssValue = (memoryData[channelOffset + valueOffset] & 0xFF) | (memoryData[channelOffset + valueOffset + 1] << 8); // Heuristic: if the second byte is 0, treat as DCS (index), else CTCSS if (memoryData[channelOffset + valueOffset + 1] === 0) { const dcs = dcsValues[dcsIndex] ?? 0; return { tone: dcs as DCS, type: RadioToneType.DCS }; } return { tone: ctcssValue as CTCSS, type: RadioToneType.CTCSS }; } }