Fix AnyTone Channel export
This commit is contained in:
parent
e53b008679
commit
fade468ee0
2 changed files with 97 additions and 38 deletions
|
|
@ -14,6 +14,11 @@ const makeRepeater = (overrides: Partial<Repeater> = {}): Repeater => ({
|
|||
...overrides,
|
||||
});
|
||||
|
||||
/** Parse a quoted CSV row into unquoted values. */
|
||||
function parseCsvRow(row: string): string[] {
|
||||
return row.split(",").map(v => v.replace(/^"|"$/g, ""));
|
||||
}
|
||||
|
||||
describe("generateAnytoneContactsCsv", () => {
|
||||
const tgs: CpsTalkgroup[] = [
|
||||
{ id: 262, name: "TG262 Deutschlan", slot: "1" },
|
||||
|
|
@ -71,34 +76,77 @@ describe("generateAnytoneChannelsCsv", () => {
|
|||
expect(lines).toHaveLength(3); // header + 2 channels
|
||||
});
|
||||
|
||||
it("has 55 columns per row (matching header)", () => {
|
||||
it("has 56 columns per row (matching header)", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
const lines = csv.split("\r\n");
|
||||
const headerCols = lines[0].split(",").length;
|
||||
expect(headerCols).toBe(55);
|
||||
expect(headerCols).toBe(56);
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
expect(lines[i].split(",")).toHaveLength(headerCols);
|
||||
}
|
||||
});
|
||||
|
||||
it("includes correct frequencies", () => {
|
||||
it("all values are double-quoted", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
expect(csv).toContain("439.500000");
|
||||
expect(csv).toContain("431.900000");
|
||||
const lines = csv.split("\r\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const fields = lines[i].split(",");
|
||||
for (const field of fields) {
|
||||
expect(field).toMatch(/^".*"$/);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("includes correct frequencies with 5 decimal places", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
expect(csv).toContain("439.50000");
|
||||
expect(csv).toContain("431.90000");
|
||||
});
|
||||
|
||||
it("includes color code and timeslot", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
const lines = csv.split("\r\n");
|
||||
const cols = lines[1].split(",");
|
||||
// Color code at index 20, slot at index 21
|
||||
const cols = parseCsvRow(lines[1]);
|
||||
// RX Color Code at index 20, Slot at index 21
|
||||
expect(cols[20]).toBe("1");
|
||||
expect(cols[21]).toBe("1");
|
||||
});
|
||||
|
||||
it("has empty Radio ID field", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
const lines = csv.split("\r\n");
|
||||
const cols = parseCsvRow(lines[1]);
|
||||
expect(cols[12]).toBe("");
|
||||
});
|
||||
|
||||
it("has Through Mode set to On", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
const lines = csv.split("\r\n");
|
||||
const cols = parseCsvRow(lines[1]);
|
||||
expect(cols[36]).toBe("On");
|
||||
});
|
||||
|
||||
it("has TxCC as last column set to 1", () => {
|
||||
const csv = generateAnytoneChannelsCsv(repeaters, tgs);
|
||||
const lines = csv.split("\r\n");
|
||||
const cols = parseCsvRow(lines[1]);
|
||||
expect(cols[55]).toBe("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ANYTONE_CH_HEADER", () => {
|
||||
it("has 55 columns", () => {
|
||||
expect(ANYTONE_CH_HEADER.split(",")).toHaveLength(55);
|
||||
it("has 56 columns", () => {
|
||||
expect(ANYTONE_CH_HEADER.split(",")).toHaveLength(56);
|
||||
});
|
||||
|
||||
it("uses RX Color Code column name", () => {
|
||||
expect(ANYTONE_CH_HEADER).toContain('"RX Color Code"');
|
||||
});
|
||||
|
||||
it("has all values double-quoted", () => {
|
||||
const fields = ANYTONE_CH_HEADER.split(",");
|
||||
for (const field of fields) {
|
||||
expect(field).toMatch(/^".*"$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
import type { Repeater, CpsTalkgroup } from "./types";
|
||||
import { formatFreq } from "./format";
|
||||
|
||||
export const ANYTONE_CH_HEADER = "No.,Channel Name,Receive Frequency,Transmit Frequency," +
|
||||
"Channel Type,Transmit Power,Band Width,CTCSS/DCS Decode,CTCSS/DCS Encode," +
|
||||
"Contact,Contact Call Type,Contact TG/DMR ID,Radio ID," +
|
||||
"Busy Lock/TX Permit,Squelch Mode,Optional Signal,DTMF ID,2Tone ID,5Tone ID,PTT ID," +
|
||||
"Color Code,Slot,Scan List,Receive Group List,PTT Prohibit,Reverse," +
|
||||
"Simplex TDMA,Slot Suit,AES Digital Encryption,Digital Encryption," +
|
||||
"Call Confirmation,Talk Around(Simplex),Work Alone,Custom CTCSS," +
|
||||
"2TONE Decode,Ranging,Through Mode,APRS RX,Analog APRS PTT Mode," +
|
||||
"Digital APRS PTT Mode,APRS Report Type,Digital APRS Report Channel," +
|
||||
"Correct Frequency[Hz],SMS Confirmation,Exclude channel from roaming," +
|
||||
"DMR MODE,DataACK Disable,R5toneBot,R5ToneEot,Auto Scan," +
|
||||
"Ana APRS TX Path,APRS TX Tone,APRS Signal Path,Digi APRS TX CH,Alert Tone";
|
||||
function q(v: string | number): string {
|
||||
return '"' + v + '"';
|
||||
}
|
||||
|
||||
export const ANYTONE_CH_HEADER = [
|
||||
"No.", "Channel Name", "Receive Frequency", "Transmit Frequency",
|
||||
"Channel Type", "Transmit Power", "Band Width", "CTCSS/DCS Decode",
|
||||
"CTCSS/DCS Encode", "Contact", "Contact Call Type", "Contact TG/DMR ID",
|
||||
"Radio ID", "Busy Lock/TX Permit", "Squelch Mode", "Optional Signal",
|
||||
"DTMF ID", "2Tone ID", "5Tone ID", "PTT ID", "RX Color Code", "Slot",
|
||||
"Scan List", "Receive Group List", "PTT Prohibit", "Reverse",
|
||||
"Simplex TDMA", "Slot Suit", "AES Digital Encryption", "Digital Encryption",
|
||||
"Call Confirmation", "Talk Around(Simplex)", "Work Alone", "Custom CTCSS",
|
||||
"2TONE Decode", "Ranging", "Through Mode", "APRS RX",
|
||||
"Analog APRS PTT Mode", "Digital APRS PTT Mode", "APRS Report Type",
|
||||
"Digital APRS Report Channel", "Correct Frequency[Hz]", "SMS Confirmation",
|
||||
"Exclude channel from roaming", "DMR MODE", "DataACK Disable",
|
||||
"R5toneBot", "R5ToneEot", "Auto Scan", "Ana Aprs Mute",
|
||||
"Send Talker Alias", "AnaAprsTxPath", "ARC4", "ex_emg_kind", "TxCC",
|
||||
].map(q).join(",");
|
||||
|
||||
/** Generate an Anytone CPS talkgroups CSV. */
|
||||
export function generateAnytoneContactsCsv(talkgroups: CpsTalkgroup[]): string {
|
||||
|
|
@ -24,32 +31,36 @@ export function generateAnytoneContactsCsv(talkgroups: CpsTalkgroup[]): string {
|
|||
return rows.join("\r\n");
|
||||
}
|
||||
|
||||
/** Format frequency to 5 decimal places for Anytone CPS. */
|
||||
function formatFreq5(mhz: number | string): string {
|
||||
return parseFloat(String(mhz)).toFixed(5);
|
||||
}
|
||||
|
||||
/** Generate an Anytone CPS channels CSV. */
|
||||
export function generateAnytoneChannelsCsv(repeaters: Repeater[], talkgroups: CpsTalkgroup[]): string {
|
||||
const rows = [ANYTONE_CH_HEADER];
|
||||
let num = 0;
|
||||
repeaters.forEach(function (r) {
|
||||
const rxFreq = formatFreq(r.freq_tx);
|
||||
const txFreq = formatFreq(r.freq_rx);
|
||||
const rxFreq = formatFreq5(r.freq_tx);
|
||||
const txFreq = formatFreq5(r.freq_rx);
|
||||
const cc = r.color_code || 1;
|
||||
talkgroups.forEach(function (tg) {
|
||||
num++;
|
||||
const chName = (r.callsign + " " + tg.name).substring(0, 16);
|
||||
const contactName = (tg.name || "TG" + tg.id).trim().substring(0, 16);
|
||||
rows.push(
|
||||
num + "," + chName + "," + rxFreq + "," + txFreq + "," +
|
||||
"D-Digital,High,12.5K,Off,Off," +
|
||||
contactName + ",Group Call," + tg.id + ",1," +
|
||||
"Same Color Code,Carrier,Off,1,1,1,Off," +
|
||||
cc + "," + tg.slot + ",None,None,Off,Off," +
|
||||
"Off,Off,Normal Encryption,Off," +
|
||||
"Off,Off,Off,251.1," +
|
||||
"0,Off,Off,Off,Off," +
|
||||
"Off,Off,1," +
|
||||
"0,Off,0," +
|
||||
"0,0,0,0,Off," +
|
||||
"Off,Off,Off,1,Off"
|
||||
);
|
||||
rows.push([
|
||||
num, chName, rxFreq, txFreq,
|
||||
"D-Digital", "High", "12.5K", "Off", "Off",
|
||||
contactName, "Group Call", tg.id, "",
|
||||
"Same Color Code", "Carrier", "Off", 1, 1, 1, "Off",
|
||||
cc, tg.slot, "None", "None", "Off", "Off",
|
||||
"Off", "Off", "Normal Encryption", "Off",
|
||||
"Off", "Off", "Off", "251.1",
|
||||
0, "Off", "On", "Off", "Off",
|
||||
"Off", "Off", 1, 0, "Off",
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
].map(q).join(","));
|
||||
});
|
||||
});
|
||||
return rows.join("\r\n");
|
||||
|
|
|
|||
Loading…
Reference in a new issue