Implement CPS export for Anytone
This commit is contained in:
parent
c8c9267d6a
commit
14f76ab30e
9 changed files with 220 additions and 23 deletions
107
static/app.js
107
static/app.js
|
|
@ -73,6 +73,12 @@
|
|||
var cpsDownloadBtn = document.getElementById("cps-download-btn");
|
||||
var cpsCopyBtn = document.getElementById("cps-copy-btn");
|
||||
var cpsChannelCount = document.getElementById("cps-channel-count");
|
||||
var cpsType = document.getElementById("cps-type");
|
||||
var cpsExportMotorola = document.getElementById("cps-export-motorola");
|
||||
var cpsExportAnytone = document.getElementById("cps-export-anytone");
|
||||
var cpsMotorolaSettings = document.getElementById("cps-motorola-settings");
|
||||
var cpsAtTgBtn = document.getElementById("cps-at-tg-btn");
|
||||
var cpsAtChBtn = document.getElementById("cps-at-ch-btn");
|
||||
|
||||
var cpsRepeaters = [];
|
||||
var cpsActiveRepeaters = {};
|
||||
|
|
@ -367,7 +373,8 @@
|
|||
renderRepeaterTags();
|
||||
renderTgTable();
|
||||
updateChannelCount();
|
||||
updateCpsButtons();
|
||||
cpsType.value = "";
|
||||
onCpsTypeChange();
|
||||
cpsLanguage.value = (getLocale() === "de") ? "de" : "en";
|
||||
translateDOM();
|
||||
}
|
||||
|
|
@ -400,9 +407,21 @@
|
|||
function updateCpsButtons() {
|
||||
var hasData = getActiveRepeaters().length > 0 && cpsTalkgroups.length > 0;
|
||||
var hasTgs = cpsTalkgroups.length > 0;
|
||||
// Motorola
|
||||
cpsContactsBtn.disabled = !hasTgs;
|
||||
cpsDownloadBtn.disabled = !hasData;
|
||||
cpsCopyBtn.disabled = !hasData;
|
||||
// Anytone
|
||||
cpsAtTgBtn.disabled = !hasTgs;
|
||||
cpsAtChBtn.disabled = !hasData;
|
||||
}
|
||||
|
||||
function onCpsTypeChange() {
|
||||
var type = cpsType.value;
|
||||
cpsMotorolaSettings.style.display = (type === "motorola") ? "" : "none";
|
||||
cpsExportMotorola.style.display = (type === "motorola") ? "flex" : "none";
|
||||
cpsExportAnytone.style.display = (type === "anytone") ? "flex" : "none";
|
||||
updateCpsButtons();
|
||||
}
|
||||
|
||||
function closeCpsModal() {
|
||||
|
|
@ -631,6 +650,89 @@
|
|||
});
|
||||
}
|
||||
|
||||
// === Anytone CPS Export ===
|
||||
|
||||
function generateAnytoneContactsCsv(talkgroups) {
|
||||
var rows = ["No.,Radio ID,Name,Call Type,Call Alert"];
|
||||
talkgroups.forEach(function (tg, idx) {
|
||||
var name = (tg.name || "TG" + tg.id).trim().substring(0, 16);
|
||||
rows.push((idx + 1) + "," + tg.id + "," + name + ",Group Call,None");
|
||||
});
|
||||
return rows.join("\r\n");
|
||||
}
|
||||
|
||||
var 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 generateAnytoneChannelsCsv(repeaters, talkgroups) {
|
||||
var rows = [ANYTONE_CH_HEADER];
|
||||
var num = 0;
|
||||
repeaters.forEach(function (r) {
|
||||
var rxFreq = formatFreq(r.freq_tx);
|
||||
var txFreq = formatFreq(r.freq_rx);
|
||||
var cc = r.color_code || 1;
|
||||
talkgroups.forEach(function (tg) {
|
||||
num++;
|
||||
var chName = (r.callsign + " " + tg.name).substring(0, 16);
|
||||
var 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,Off"
|
||||
);
|
||||
});
|
||||
});
|
||||
return rows.join("\r\n");
|
||||
}
|
||||
|
||||
function downloadAnytoneContactsCsv() {
|
||||
if (!cpsTalkgroups.length) return;
|
||||
var csv = generateAnytoneContactsCsv(cpsTalkgroups);
|
||||
var blob = new Blob([csv], { type: "text/csv" });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "dmrmap-anytone-talkgroups.csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function downloadAnytoneChannelsCsv() {
|
||||
var active = getActiveRepeaters();
|
||||
if (!cpsTalkgroups.length || !active.length) return;
|
||||
var csv = generateAnytoneChannelsCsv(active, cpsTalkgroups);
|
||||
var blob = new Blob([csv], { type: "text/csv" });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "dmrmap-anytone-channels.csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function showCount(count) {
|
||||
countEl.textContent = t("repeater_count", { count: count });
|
||||
}
|
||||
|
|
@ -1393,9 +1495,12 @@
|
|||
}
|
||||
});
|
||||
cpsAliasFormat.addEventListener("change", reformatAllAliases);
|
||||
cpsType.addEventListener("change", onCpsTypeChange);
|
||||
cpsContactsBtn.addEventListener("click", downloadContactsCsv);
|
||||
cpsDownloadBtn.addEventListener("click", downloadCpsXml);
|
||||
cpsCopyBtn.addEventListener("click", copyCpsXml);
|
||||
cpsAtTgBtn.addEventListener("click", downloadAnytoneContactsCsv);
|
||||
cpsAtChBtn.addEventListener("click", downloadAnytoneChannelsCsv);
|
||||
|
||||
// TG autocomplete
|
||||
var cpsAcIdx = -1;
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@
|
|||
<option value="tg-ts">TG262-TS2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="cps-motorola-settings">
|
||||
<div class="cps-alias-format">
|
||||
<label data-i18n="cps_language">CPS language</label>
|
||||
<select id="cps-language">
|
||||
|
|
@ -102,6 +103,7 @@
|
|||
</select>
|
||||
</div>
|
||||
<p class="cps-exp-note" data-i18n="cps_language_note">The selected language must match your CPS installation language, otherwise the import will fail.</p>
|
||||
</div>
|
||||
<table class="cps-tg-table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -121,6 +123,15 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="cps-modal-footer">
|
||||
<div class="cps-type-row">
|
||||
<label data-i18n="cps_type_label">CPS type</label>
|
||||
<select id="cps-type">
|
||||
<option value="" selected data-i18n="cps_type_select">Select CPS type...</option>
|
||||
<option value="motorola">Motorola</option>
|
||||
<option value="anytone">Anytone</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="cps-export-motorola" style="display:none">
|
||||
<div class="cps-export-step">
|
||||
<span class="cps-step-label" data-i18n="cps_step1_label">Step 1: Import contacts</span>
|
||||
<div class="cps-step-buttons">
|
||||
|
|
@ -135,6 +146,21 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cps-export-anytone" style="display:none">
|
||||
<div class="cps-export-step">
|
||||
<span class="cps-step-label" data-i18n="cps_at_step1_label">Step 1: Import talk groups</span>
|
||||
<div class="cps-step-buttons">
|
||||
<button type="button" id="cps-at-tg-btn" class="cps-btn-primary" disabled data-i18n="cps_at_download_tg">Download Talkgroups CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cps-export-step">
|
||||
<span class="cps-step-label" data-i18n="cps_at_step2_label">Step 2: Import channels</span>
|
||||
<div class="cps-step-buttons">
|
||||
<button type="button" id="cps-at-ch-btn" class="cps-btn-primary" disabled data-i18n="cps_at_download_ch">Download Channels CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="lib/i18next.min.js"></script>
|
||||
|
|
|
|||
|
|
@ -56,5 +56,11 @@
|
|||
"cps_copy_xml": "In Zwischenablage",
|
||||
"cps_copied": "Kopiert!",
|
||||
"cps_unknown_tg": "Unbekannt",
|
||||
"cps_channel_count": "{{channels}} Kanäle ({{repeaters}} Relais × {{tgs}} Talkgroups)"
|
||||
"cps_channel_count": "{{channels}} Kanäle ({{repeaters}} Relais × {{tgs}} Talkgroups)",
|
||||
"cps_type_label": "CPS-Typ",
|
||||
"cps_type_select": "CPS-Typ auswählen...",
|
||||
"cps_at_step1_label": "Schritt 1: Talkgroups importieren",
|
||||
"cps_at_download_tg": "Talkgroups CSV herunterladen",
|
||||
"cps_at_step2_label": "Schritt 2: Kanäle importieren",
|
||||
"cps_at_download_ch": "Kanäle CSV herunterladen"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,5 +56,11 @@
|
|||
"cps_copy_xml": "Copy to Clipboard",
|
||||
"cps_copied": "Copied!",
|
||||
"cps_unknown_tg": "Unknown",
|
||||
"cps_channel_count": "{{channels}} channels ({{repeaters}} repeaters × {{tgs}} talk groups)"
|
||||
"cps_channel_count": "{{channels}} channels ({{repeaters}} repeaters × {{tgs}} talk groups)",
|
||||
"cps_type_label": "CPS type",
|
||||
"cps_type_select": "Select CPS type...",
|
||||
"cps_at_step1_label": "Step 1: Import talk groups",
|
||||
"cps_at_download_tg": "Download Talkgroups CSV",
|
||||
"cps_at_step2_label": "Step 2: Import channels",
|
||||
"cps_at_download_ch": "Download Channels CSV"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,5 +56,11 @@
|
|||
"cps_copy_xml": "Copiar al portapapeles",
|
||||
"cps_copied": "¡Copiado!",
|
||||
"cps_unknown_tg": "Desconocido",
|
||||
"cps_channel_count": "{{channels}} canales ({{repeaters}} repetidores × {{tgs}} grupos)"
|
||||
"cps_channel_count": "{{channels}} canales ({{repeaters}} repetidores × {{tgs}} grupos)",
|
||||
"cps_type_label": "Tipo de CPS",
|
||||
"cps_type_select": "Seleccionar tipo de CPS...",
|
||||
"cps_at_step1_label": "Paso 1: Importar grupos",
|
||||
"cps_at_download_tg": "Descargar grupos CSV",
|
||||
"cps_at_step2_label": "Paso 2: Importar canales",
|
||||
"cps_at_download_ch": "Descargar canales CSV"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,5 +56,11 @@
|
|||
"cps_copy_xml": "Copier dans le presse-papiers",
|
||||
"cps_copied": "Copié !",
|
||||
"cps_unknown_tg": "Inconnu",
|
||||
"cps_channel_count": "{{channels}} canaux ({{repeaters}} relais × {{tgs}} groupes)"
|
||||
"cps_channel_count": "{{channels}} canaux ({{repeaters}} relais × {{tgs}} groupes)",
|
||||
"cps_type_label": "Type CPS",
|
||||
"cps_type_select": "S\u00e9lectionner le type CPS...",
|
||||
"cps_at_step1_label": "\u00c9tape 1 : Importer les groupes",
|
||||
"cps_at_download_tg": "T\u00e9l\u00e9charger groupes CSV",
|
||||
"cps_at_step2_label": "\u00c9tape 2 : Importer les canaux",
|
||||
"cps_at_download_ch": "T\u00e9l\u00e9charger canaux CSV"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,5 +56,11 @@
|
|||
"cps_copy_xml": "Copia negli appunti",
|
||||
"cps_copied": "Copiato!",
|
||||
"cps_unknown_tg": "Sconosciuto",
|
||||
"cps_channel_count": "{{channels}} canali ({{repeaters}} ripetitori × {{tgs}} gruppi)"
|
||||
"cps_channel_count": "{{channels}} canali ({{repeaters}} ripetitori × {{tgs}} gruppi)",
|
||||
"cps_type_label": "Tipo CPS",
|
||||
"cps_type_select": "Seleziona tipo CPS...",
|
||||
"cps_at_step1_label": "Passo 1: Importa gruppi",
|
||||
"cps_at_download_tg": "Scarica gruppi CSV",
|
||||
"cps_at_step2_label": "Passo 2: Importa canali",
|
||||
"cps_at_download_ch": "Scarica canali CSV"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,5 +58,11 @@
|
|||
"cps_copy_xml": "Kopiuj do schowka",
|
||||
"cps_copied": "Skopiowano!",
|
||||
"cps_unknown_tg": "Nieznany",
|
||||
"cps_channel_count": "{{channels}} kanałów ({{repeaters}} przemienników × {{tgs}} grup)"
|
||||
"cps_channel_count": "{{channels}} kanałów ({{repeaters}} przemienników × {{tgs}} grup)",
|
||||
"cps_type_label": "Typ CPS",
|
||||
"cps_type_select": "Wybierz typ CPS...",
|
||||
"cps_at_step1_label": "Krok 1: Importuj grupy",
|
||||
"cps_at_download_tg": "Pobierz grupy CSV",
|
||||
"cps_at_step2_label": "Krok 2: Importuj kana\u0142y",
|
||||
"cps_at_download_ch": "Pobierz kana\u0142y CSV"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -946,6 +946,36 @@ html, body {
|
|||
color: var(--text-fainter);
|
||||
}
|
||||
|
||||
.cps-type-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cps-type-row label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cps-type-row select {
|
||||
flex: 1;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#cps-export-motorola,
|
||||
#cps-export-anytone {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.cps-modal-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
Loading…
Reference in a new issue