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 cpsDownloadBtn = document.getElementById("cps-download-btn");
|
||||||
var cpsCopyBtn = document.getElementById("cps-copy-btn");
|
var cpsCopyBtn = document.getElementById("cps-copy-btn");
|
||||||
var cpsChannelCount = document.getElementById("cps-channel-count");
|
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 cpsRepeaters = [];
|
||||||
var cpsActiveRepeaters = {};
|
var cpsActiveRepeaters = {};
|
||||||
|
|
@ -367,7 +373,8 @@
|
||||||
renderRepeaterTags();
|
renderRepeaterTags();
|
||||||
renderTgTable();
|
renderTgTable();
|
||||||
updateChannelCount();
|
updateChannelCount();
|
||||||
updateCpsButtons();
|
cpsType.value = "";
|
||||||
|
onCpsTypeChange();
|
||||||
cpsLanguage.value = (getLocale() === "de") ? "de" : "en";
|
cpsLanguage.value = (getLocale() === "de") ? "de" : "en";
|
||||||
translateDOM();
|
translateDOM();
|
||||||
}
|
}
|
||||||
|
|
@ -400,9 +407,21 @@
|
||||||
function updateCpsButtons() {
|
function updateCpsButtons() {
|
||||||
var hasData = getActiveRepeaters().length > 0 && cpsTalkgroups.length > 0;
|
var hasData = getActiveRepeaters().length > 0 && cpsTalkgroups.length > 0;
|
||||||
var hasTgs = cpsTalkgroups.length > 0;
|
var hasTgs = cpsTalkgroups.length > 0;
|
||||||
|
// Motorola
|
||||||
cpsContactsBtn.disabled = !hasTgs;
|
cpsContactsBtn.disabled = !hasTgs;
|
||||||
cpsDownloadBtn.disabled = !hasData;
|
cpsDownloadBtn.disabled = !hasData;
|
||||||
cpsCopyBtn.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() {
|
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) {
|
function showCount(count) {
|
||||||
countEl.textContent = t("repeater_count", { count: count });
|
countEl.textContent = t("repeater_count", { count: count });
|
||||||
}
|
}
|
||||||
|
|
@ -1393,9 +1495,12 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cpsAliasFormat.addEventListener("change", reformatAllAliases);
|
cpsAliasFormat.addEventListener("change", reformatAllAliases);
|
||||||
|
cpsType.addEventListener("change", onCpsTypeChange);
|
||||||
cpsContactsBtn.addEventListener("click", downloadContactsCsv);
|
cpsContactsBtn.addEventListener("click", downloadContactsCsv);
|
||||||
cpsDownloadBtn.addEventListener("click", downloadCpsXml);
|
cpsDownloadBtn.addEventListener("click", downloadCpsXml);
|
||||||
cpsCopyBtn.addEventListener("click", copyCpsXml);
|
cpsCopyBtn.addEventListener("click", copyCpsXml);
|
||||||
|
cpsAtTgBtn.addEventListener("click", downloadAnytoneContactsCsv);
|
||||||
|
cpsAtChBtn.addEventListener("click", downloadAnytoneChannelsCsv);
|
||||||
|
|
||||||
// TG autocomplete
|
// TG autocomplete
|
||||||
var cpsAcIdx = -1;
|
var cpsAcIdx = -1;
|
||||||
|
|
|
||||||
|
|
@ -94,14 +94,16 @@
|
||||||
<option value="tg-ts">TG262-TS2</option>
|
<option value="tg-ts">TG262-TS2</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="cps-alias-format">
|
<div id="cps-motorola-settings">
|
||||||
<label data-i18n="cps_language">CPS language</label>
|
<div class="cps-alias-format">
|
||||||
<select id="cps-language">
|
<label data-i18n="cps_language">CPS language</label>
|
||||||
<option value="de">Deutsch</option>
|
<select id="cps-language">
|
||||||
<option value="en">English</option>
|
<option value="de">Deutsch</option>
|
||||||
</select>
|
<option value="en">English</option>
|
||||||
|
</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>
|
</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>
|
|
||||||
<table class="cps-tg-table">
|
<table class="cps-tg-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -121,17 +123,41 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cps-modal-footer">
|
<div class="cps-modal-footer">
|
||||||
<div class="cps-export-step">
|
<div class="cps-type-row">
|
||||||
<span class="cps-step-label" data-i18n="cps_step1_label">Step 1: Import contacts</span>
|
<label data-i18n="cps_type_label">CPS type</label>
|
||||||
<div class="cps-step-buttons">
|
<select id="cps-type">
|
||||||
<button type="button" id="cps-contacts-btn" class="cps-btn-primary" disabled data-i18n="cps_download_contacts">Download Contacts CSV</button>
|
<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">
|
||||||
|
<button type="button" id="cps-contacts-btn" class="cps-btn-primary" disabled data-i18n="cps_download_contacts">Download Contacts CSV</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cps-export-step">
|
||||||
|
<span class="cps-step-label" data-i18n="cps_step2_label">Step 2: Paste zone</span>
|
||||||
|
<div class="cps-step-buttons">
|
||||||
|
<button type="button" id="cps-download-btn" class="cps-btn-primary" disabled data-i18n="cps_download_xml">Download Zone XML</button>
|
||||||
|
<button type="button" id="cps-copy-btn" class="cps-btn-secondary" disabled data-i18n="cps_copy_xml">Copy to Clipboard</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="cps-export-step">
|
<div id="cps-export-anytone" style="display:none">
|
||||||
<span class="cps-step-label" data-i18n="cps_step2_label">Step 2: Paste zone</span>
|
<div class="cps-export-step">
|
||||||
<div class="cps-step-buttons">
|
<span class="cps-step-label" data-i18n="cps_at_step1_label">Step 1: Import talk groups</span>
|
||||||
<button type="button" id="cps-download-btn" class="cps-btn-primary" disabled data-i18n="cps_download_xml">Download Zone XML</button>
|
<div class="cps-step-buttons">
|
||||||
<button type="button" id="cps-copy-btn" class="cps-btn-secondary" disabled data-i18n="cps_copy_xml">Copy to Clipboard</button>
|
<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>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -56,5 +56,11 @@
|
||||||
"cps_copy_xml": "In Zwischenablage",
|
"cps_copy_xml": "In Zwischenablage",
|
||||||
"cps_copied": "Kopiert!",
|
"cps_copied": "Kopiert!",
|
||||||
"cps_unknown_tg": "Unbekannt",
|
"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_copy_xml": "Copy to Clipboard",
|
||||||
"cps_copied": "Copied!",
|
"cps_copied": "Copied!",
|
||||||
"cps_unknown_tg": "Unknown",
|
"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_copy_xml": "Copiar al portapapeles",
|
||||||
"cps_copied": "¡Copiado!",
|
"cps_copied": "¡Copiado!",
|
||||||
"cps_unknown_tg": "Desconocido",
|
"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_copy_xml": "Copier dans le presse-papiers",
|
||||||
"cps_copied": "Copié !",
|
"cps_copied": "Copié !",
|
||||||
"cps_unknown_tg": "Inconnu",
|
"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_copy_xml": "Copia negli appunti",
|
||||||
"cps_copied": "Copiato!",
|
"cps_copied": "Copiato!",
|
||||||
"cps_unknown_tg": "Sconosciuto",
|
"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_copy_xml": "Kopiuj do schowka",
|
||||||
"cps_copied": "Skopiowano!",
|
"cps_copied": "Skopiowano!",
|
||||||
"cps_unknown_tg": "Nieznany",
|
"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);
|
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 {
|
.cps-modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue