// Copyright 2017, 2018, 2019, 2020, 2021, 2022 Max H. Parke KA1RBI
// Copyright 2020, 2021, 2022 Michael Rose
//
// This file is part of OP25
//
// OP25 is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// OP25 is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
//
// You should have received a copy of the GNU General Public License
// along with OP25; see the file COPYING. If not, write to the Free
// Software Foundation, Inc., 51 Franklin Street, Boston, MA
// 02110-1301, USA.
var lastUpdated = "09-Jan-2022";
var d_debug = 0;
var http_req = new XMLHttpRequest();
var counter1 = 0;
var error_val = null;
var current_tgid = null;
var active_tgid = null;
var active_nac = null;
var send_busy = 0;
var send_qfull = 0;
var send_queue = [];
var req_cb_count = 0;
var request_count = 0;
var nfinal_count = 0;
var n200_count = 0;
var r200_count = 0;
var SEND_QLIMIT = 5;
var summary_mode = true;
var enable_changed = false;
var enable_status = [];
var last_srcaddr = [];
var last_srctag = [];
var last_alg = [];
var last_algid = [];
var last_keyid = [];
var tgid_files = {};
var srcid_files = {};
var channel_id = {};
var freqTable = true;
var chsize = 1;
var evsize = 1;
var ersize = 1;
var event_source = null; // must be in global scope for Babysitter to work.
window.g_change_freq = [];
window.g_cc_event = [];
window.src = [];
var intvAlias = null;
var intvCss = null;
localStorage.AliasTableUpdated == true;
localStorage.ColorsTableUpdated == true;
var encsym = "∅";
const zeroPad = (num, places) => String(num).padStart(places, '0'); // leading zeros for single digit time values
function do_onload() {
$('#div_status').show(window.animateSpeed);
$('#babysitter').hide();
$('#b1').addClass('nav-button-active');
$('#uiupdated').html(lastUpdated);
document.documentElement.setAttribute('data-theme', 'dark');
window.siteAlias = null;
intvAlias = setInterval(getSiteAlias, 5000);
intvCss = setInterval(generateCSS, 4000);
resetFileReload = setInterval(rstFileReload, 12000)
connect();
generateCSS();
getSiteAlias();
beginJsonSettings();
accColorSel();
for (i = 1; i < 100; i ++) {
$('#unk_default').append(new Option(i, i));
}
window.animateSpeed = $('#ani_speed').val();
}
// vars set when saving things...
// localStorage.AliasTableUpdated == true;
// localStorage.ColorsTableUpdated == true;
$(document).ready(function() {
// // populate url into oplog url field
// var x = window.location.origin.split( ':' );
// var y = x[0] + ':' + x[1] + ':5000';
// $('#oplogUrl').val(y);
loadHelp();
});
function connect() {
event_source = new EventSource('/stream');
event_source.addEventListener('message', eventsource_listener);
event_source.onerror = function() {
event_source.close();
}
setReconnect();
}
function eventsource_listener(event) {
dispatch_commands(event.data);
}
// Babysitter - watches /stream, reacts when lost/restored
function setReconnect() {
// readyState values: 0 = connecting, 1 = open, 2 = closed
var reconnecting = false;
// do we need to setInterval with a var here since we call setReconnect again?
setInterval(() => {
if (event_source.readyState == 2) {
reconnecting = true;
$('#babysitter').show();
$('#estat').html(event_source.readyState);
connect();
} else if (reconnecting) {
reconnecting = false
$('#estat').html(event_source.readyState);
if (!event_source.readyState == 0)
$('#estat').html('OK');
$('#babysitter').hide();
}
}, 3000);
}
function rstFileReload() {
if (event_source == null || event_source.readyState != 1)
return;
// forces the alias and colors info to reload in case user
// makes changes to them from another browser.
localStorage.AliasTableUpdated == true;
localStorage.ColorsTableUpdated == true;
}
function navOplog() {
window.open($('#oplogUrl').val());
}
function phaseType(nac) {
// reset the UI to a Phase 1 display (untested)
window[nac + 'flavor'] = null;
}
function nav_update(command) {
var names = [
'b1',
'b2',
'b3',
'b4',
'b5',
'b7'
];
var bmap = {
'status': 'b1',
'settings': 'b2',
'rx': 'b3',
'help': 'b4',
'view': 'b5',
'about': 'b7'
};
var id = bmap[command];
for (var id1 in names) {
b = document.getElementById(names[id1]);
if (names[id1] == id) {
b.className = 'nav-button-active';
} else {
b.className = 'nav-button';
}
}
}
function f_select(command) {
var div_list = [
'status',
'settings',
'rx',
'help',
'about'
];
var orig_command = command;
if (command == 'rx') {
$('#div_logs').show(window.animateSpeed);
command = 'status';
summary_mode = false;
} else {
summary_mode = true;
$('#div_logs').hide(window.animateSpeed);
}
for (var i = 0; i < div_list.length; i++) {
(command == div_list[i]) ? $('#div_' + div_list[i]).show(window.animateSpeed) : $('#div_' + div_list[i]).hide(window.animateSpeed);
}
(command == 'status' && summary_mode == true) ? $('#div_images').show(window.animateSpeed) : $('#div_images').hide(window.animateSpeed);
(command == 'status') ? $('#controls').show() : $('#controls').hide();
nav_update(orig_command);
if (command == 'settings')
f_list();
}
function rx_update(d) {
if (d['files'].length > 0) {
for (var i = 0; i < d['files'].length; i++) {
var img = document.getElementById('img' + i);
if (img['src'] != d['files'][i]) {
img['src'] = d['files'][i];
img.style['display'] = '';
}
}
}
error_val = d['error'];
fine_tune = d['fine_tune'];
}
// frequency, system, and talkgroup display
function change_freq(d) { // d json_type = change_freq
t = 'TDMA';
var t = 'FDMA';
if ((d['tdma'] == 0) || (d['tdma'] == 1)) {
t = 'TDMA ' + d['tdma'] + '';
}
$('#stx').html(t);
var displayTgid = '—';
var displayTag = ' ';
var display_src = '—';
var display_alg = '—';
var display_keyid = '—';
var display_srctag = '—';
var e_class = 'value';
var trunc = $('#valTruncate').val();
last_srcaddr[d['nac']] = d['srcaddr'];
last_alg[d['nac']] = d['alg'];
last_algid[d['nac']] = d['algid'];
last_keyid[d['nac']] = d['keyid'];
last_srctag[d['nac']] = d['srcaddr.tag'];
if (d['tgid'] != null) {
displayTgid = d['tgid'];
displayTag = d['tag'].substring(0, trunc);
if (d['srcaddr'] != null && d['srcaddr'] > 0) {
display_src = d['srcaddr'];
display_srctag = d['srcaddr_tag'];
}
display_alg = d['alg'];
if (d['algid'] != 128) {
display_keyid = hex(d['keyid']);
e_class = 'red_value';
}
}
// main display - system, talkgroup, encryption, keyid, source addr display
var d_sys = 'system' in d ? d['system'].substring(0, trunc) : 'Undefined';
var html = '
';
return html; // end adjacent sites table
} // end adjacent_data()
function trunk_update(d) {
var html;
if (summary_mode) {
html = trunk_summary(d); // home screen
} else {
html = trunk_detail(d); // RX screen
}
$('#div_s1').html(html);
if (!summary_mode)
sortTable('adjacent-sites', 4);
// display hold indicator
if (d['data']['hold_mode']) {
$('#holdIndicator').show();
} else {
$('#holdIndicator').hide();
}
// display last command unless it was more than 10 seconds ago
x2 = d['data']['last_command'];
if (x2 && d['data']['last_command_time'] > -10) {
$('#lastCommand').html('Last Command ' + x2.toUpperCase() + ' ' + ' ' + d['data']['last_command_time'] * -1 + ' secs ago');
} else {
$('#lastCommand').html('');
}
update_data(d);
} // end trunk_update()
function update_data(d) { // d json type = trunk_update
if (active_nac == null || active_tgid == null)
return;
var display_src = '—';
var display_srctag = '—';
var display_alg = '—';
var display_keyid = '—';
var e_class = 'value';
if (last_srcaddr[active_nac] != null) {
display_src = last_srcaddr[active_nac];
var ele = document.getElementById('dSrc');
if (ele != null)
ele.innerHTML = display_src;
}
if (last_srctag[active_nac] != null) {
display_srctag = last_srctag[active_nac];
var ele = document.getElementById('dSrctag');
if (ele != null)
ele.innerHTML = display_srctag;
}
if (last_algid[active_nac] == null || last_alg[active_nac] == null || last_keyid[active_nac] == null)
return;
display_alg = last_alg[active_nac];
if (last_algid[active_nac] != 128) {
display_keyid = last_keyid[active_nac];
e_class = 'red_value';
}
ele = document.getElementById('dAlg');
if (ele != null) {
ele.innerHTML = display_alg;
ele.className = e_class;
}
ele = document.getElementById('dKey');
if (ele != null)
ele.innerHTML = display_keyid;
} // end update_data()
function error_tracking() {
return; // empty right now, handled in dispatch_commands switch block
} // end error_tracking
function dispatch_commands(txt) {
if (txt == '[]') {
return;
}
var dl = JSON.parse(txt);
var dispatch = {
'trunk_update': trunk_update,
'change_freq': change_freq,
'rx_update': rx_update,
'config_data': config_data,
'config_list': config_list,
'cc_event': cc_event,
'freq_error_tracking': error_tracking
};
for (var i = 0; i < dl.length; i++) {
var d = dl[i];
if (!('json_type' in d))
continue;
if (!(d['json_type'] in dispatch))
continue;
var j_type = d['json_type'];
var time = getTime(new Date());
if (d.time)
time = getTime(d.time * 1000);
switch (j_type) {
case 'freq_error_tracking':
var ele, bx, cx, dx, ex, fx, gx;
$('#error_tracking').show();
bx = d.device;
cx = d.name;
dx = d.error_band;
ex = d.freq_correction;
fx = d.freq_error;
gx = d.tuning_error;
appendErrorTable (time, bx, cx, dx, ex, fx, gx, 'errors');
break;
case 'change_freq':
cb = cbState('log_cf');
time = getTime(d['effective_time'] * 1000);
if (d.tgid && !d.tag) {
d.tag = 'Talkgroup ' + d.tgid + ''; // talkgroup tag isn't in the tsv
d.tag_color = $('#unk_default').val();
}
sysid = d.sysid ? hex(d.sysid).toUpperCase() : "—"
var freq = d['freq'] / 1000000;
var srctag = "—";
var color, srccolor, srcaddr;
if (d['tag_color']) {
color = d['tag_color'];
} else {
color = smartColor(d.tag);
d['tag_color'] = color;
}
if (d['srcaddr_color']) {
srccolor = d['srcaddr_color'];
} else {
srccolor = smartColor(d.tag);
}
var tag = '' + d.tag + '';
var tgid = '' + d.tgid + '';
// TODO - cb not defined keeps coming up a couple times in the console, right at start up.
if (cb && d.tgid) {
appendJsonTable(time, j_type, sysid, tag, tgid, 'OP25', freq, '--', 'history');
}
if (d.tgid) {
srctag = (window['srct' + d.srcaddr]) ? '' + window['srct' + d.srcaddr] : "—";
srcaddr = '' + d.srcaddr;
}
window.g_change_freq = d;
break;
case 'trunk_update':
cb = cbState('log_tu');
var tf, sf, sid, a, z;
var sysid, site, rfid, color;
var grpaddr, c_grpaddr, srcaddr, c_srcaddr, talkgroup, c_talkgroup, srctag, c_srctag;
for (nac in d) {
if (Number.isInteger(parseInt(nac))) {
sysid = d[nac]['sysid'];
sid = sysid;
sysid = hex(sysid).toUpperCase();
site = d[nac]['stid'];
rfid = d[nac]['rfid'];
grpaddr = d[nac]['grpaddr'];
srcaddr = d[nac]['srcaddr'];
tf = d[nac]['tgid_tags_file'];
tgid_files[sid] = tf;
sf = d[nac]['unit_id_tags_file'];
srcid_files[sid] = sf;
if (window['tgidt' + grpaddr])
talkgroup = window['tgidt' + grpaddr];
srctag = (window['srct' + srcaddr]) ? (window['srct' + srcaddr]) : " ";
color = smartColor(talkgroup);
srccolor = smartColor(talkgroup);
if (window['tgidc' + grpaddr])
color = window['tgidc' + grpaddr];
c_grpaddr = '' + grpaddr + '';
c_talkgroup = '' + talkgroup + '';
color = (window['srcc' + srcaddr]) ? window['srcc' + srcaddr] : "0";
c_srcaddr = '' + srcaddr + '';
c_srctag = '' + srctag + '';
var sr = "Site: " + site + " RFID: " + rfid;
var col_f = "OP25"; // empty for now
if (cb) {
if (grpaddr) { // do not append the table if no grpaddr is present - TU into Events table is pretty much useless anyway
appendJsonTable(time, j_type, sysid, sr, c_grpaddr, col_f, "Update", "--", "history");
}
} //end if cb
} // end if nac is number
} // end for nac in d
sources(d);
window.g_trunk_update = d;
break;
case 'rx_update':
cb = cbState('log_rx');
if (d['files'][0]) {
var ps = "Plots: True";
} else {
var ps = "Plots: False"
}
if (d['fine_tune']) {
var ft = d['fine_tune'];
} else {
var ft = "n/a";
}
if (cb) {
appendJsonTable(time, j_type, ps, 'Fine Tune: ' + ft, 'Tune Err: ' + d['error'], 'OP25', 'RX Update', '--', 'history');
} // this Events table entry doesn't add much value either.
sessionStorage.fineTune = d['fine_tune'] ? d['fine_tune'] : "—";
sessionStorage.errorVal = d['error'] ? d['error'] : "—";
break;
case 'cc_event':
time = getTime(d['time'] * 1000);
cb = cbState('log_cc');
var opcode, n_opcode, sysid, tag, target, source, srctag, src_c, color;
var logCall = 0;
var noLog = 0;
var xp = 0;
if (d.opcode !== null) {
opcode = d['opcode'].toString(16);
if (g_opcode[opcode]) {
n_opcode = g_opcode[opcode];
} else {
n_opcode = 'Opcode Not Found: ' + opcode;
}
}
tag = target = source = '—';
srctag = " "; // used
// [group] does not appear in every cc_event!
if (d.group) {
if (d.group && d.group.tag) {
tag = d.group.tag;
} else {
d.group.tag = 'Talkgroup ' + d.group.tg_id;
d.group.color = $('#unk_default').val();
}
} // end if d.group
if (d.reason) {
tag = getReason(hex(d.reason));
}
n_opcode = g_opcode[opcode] ? g_opcode[opcode] : opcode;
if (d['srcaddr']) {
source = (d.srcaddr.unit_id) ? d.srcaddr.unit_id : "— No ID"; // This condition is reached when there is traffic
srctag = (d.srcaddr.tag) ? d.srcaddr.tag : " "; // but no source unit id is present. on ebrcs, this
// happens when an old u/vhf system is patched onto ebrcs.
}
// handle manufacturer opcodes:
// 0x10 = Relm/BK
// 0x68 = Kenwood
// 144(10) 0x90 = Motorola
// 164(10) 0xA4 = Harris
// 0xD8 = Tait
// 0xF8 = Vertex Standard
// trunking.py sends the following opcodes:
// -1, 0x00, 01, 02, 03, 09, 20, 27, 28, 2a, 2b, 2c, 2d, 2f, 31, 33, 34, 3d
switch (opcode) {
case "-1": // end call (not a P25 standard)
tag = d.tgid.tag;
target = d.tgid.tg_id;
n_opcode = "End Call";
noLog = 1; // Events
// TODO - maybe nothing?
break;
case "0":
if (d.mfrid != null) {
switch (d.mfrid) {
case 0:
n_opcode = "Call"; // GRP_V_CH_GRANT
target = d['group']['tg_id'];
srctag = d['srcaddr']['tag'];
noLog = cbState('je_calls') ? 0 : 1; //events
logCall = 1; // callHistory
break;
case 144: // MOTOROLA
n_opcode = "XP Adds"; //mot_grg_add_cmd 0x00"
tag = d.sg.tag;
target = d.sg.tg_id;
noLog = 1; // chatty af
logCall = 0; // callHistory
break;
case "default":
n_opcode = "Call";
target = d['group']['tg_id'];
srctag = d['srcaddr']['tag'];
noLog = cbState('je_calls') ? 0 : 1;
logCall = 1; // callHistory
break;
} // end switch
} // end if
break;
case "1": // 0x01 - RESERVED
if (d.mfrid != null) {
switch (d.mfrid) {
case 0:
n_opcode = "Reserved 0x01";
break;
case 144: // MOTOROLA
n_opcode = "XP Drops";
tag = d.sg.tag;
target = d.sg.tg_id;
break;
} // end switch
} // end if
break;
case "2": // 0x02 - grp_v_ch_grant_updt
if (d.mfrid != null) {
switch (d.mfrid) {
case 0:
n_opcode = "grp_v_ch_grant_updt 0x02";
noLog = cbState('je_calls') ? 0 : 1;
break;
case 144: // MOTOROLA
d['group'] = d['sg'];
d['srcaddr'] = d['sa'];
srctag = d.sa.tag;
n_opcode = "XP Call"; // MOT XP Call mot_grg_cn_grant 0x02
tag = d.sg.tag;
target = d.sg.tg_id;
source = d.sa.unit_id;
noLog = cbState('je_calls') ? 0 : 1;
logCall = 1; // callHistory
xp = 1;
break;
} // end switch
} // end if
break;
case "3": // 0x03 - GRP_V_CH_GRANT_UPDT_EXP
if (d.mfrid != null) {
switch (d.mfrid) {
case 0:
n_opcode = "grp_v_ch_grant_updt_exp 0x03";
break;
case 144: // MOTOROLA
n_opcode = "mot_grg_cn_grant_updt 0x03";
tag = d.sg1.tag;
target = d.sg1.tg_id;
noLog = 1; // used for late entry, very chatty, probably should not log to JSON Events
break;
} // end switch
} // end if
break;
case "9": // MOT System Load ? Could be "Motorola Scan Marker"
if (d.mfrid != null) {
switch (d.mfrid) {
case 0:
n_opcode = "Opcode 0x03";
break;
case 144: // MOTOROLA
n_opcode = "System Load " + d.test1;
noLog = 1;
break;
} // end switch
} // end if
break;
case "20": // 0x20 - ACK_RSP_FNE
noLog = 1;
break;
case "24": // 0x24 - Extended Function Command (inhibit) TODO: get reason code, log it
var efclass = d.efclass;
var efoperand = d.efoperand;
var efargs = d.efargs;
var target = d.target;
n_opcode = "Ext Fnct Cmd: " + efoperand;
source = efargs;
noLog = 0;
break;
case "27": // 0x27 - DENY_RSP
source = d.target.unit_id;
srctag = d.target.tag;
noLog = cbState('je_deny') ? 0 : 1;
break;
case "28": // 0x28 - GRP_AFF_RSP
source = d.target.unit_id;
target = d.group.tg_id;
noLog = cbState('je_joins') ? 0 : 1;
break;
case "2a": // 0x2A - GRP_AFF_Q - Group Affiliate Query
noLog = 1;
break;
case "2b": // 0x2B - LOC_REG_RSP - Location Registration Response
if (d['rv'] != null) {
switch (d['rv']) {
case 0:
n_opcode = "Joins";
target = d.group.tg_id;
source = d.target.unit_id;
srctag = d.target.tag;
noLog = cbState('je_joins') ? 0 : 1;
break;
case 1:
n_opcode = "Reg Fail";
target = d.group.tg_id;
source = d.target.unit_id;
srctag = d.target.tag;
noLog = cbState('je_deny') ? 0 : 1;
break;
case 2:
n_opcode = "Reg Denied";
target = d.group.tg_id;
source = d.target.unit_id;
srctag = d.target.tag;
noLog = cbState('je_deny') ? 0 : 1;
break;
case 3:
n_opcode = "Reg Refused";
target = d.group.tg_id;
source = d.target.unit_id;
srctag = d.target.tag;
noLog = cbState('je_deny') ? 0 : 1;
break;
} // end rv switch
} // end if
break;
case "2c": // 0x2C - U_REG_RSP - Login TODO: source and target are the same from the json
source = d.source.unit_id;
srctag = d.source.tag;
// target = d.target.unit_id;
// tag = d.target.tag;
noLog = cbState('je_log') ? 0 : 1;
break;
case "2d": // 0x2D - U_REG_CMD - Force SU Registration
source = d.source.unit_id;
srctag = d.source.tag;
target = d.target.unit_id;
tag = d.target.tag;
noLog = cbState('je_log') ? 0 : 1;
break;
case "2f": // 0x2F - U_DE_REG_ACK - Logout
source = d.source.unit_id;
srctag = d.source.tag;
noLog = cbState('je_log') ? 0 : 1;
break;
case "31": // 0x31 - AUTH_DMD - Authentication Demand
source = d.target_id.unit_id;
srctag = d.target_id.tag;
target = d.target_address.unit_id;
tag = d.target_address.tag;
break;
case "33": // 0x33 - iden up tdma
var sysid = d.sysid;
var type = 'TDMA';
var iden = d.iden;
var freq = d.freq / 1000000;
var offset = d.offset / 1000000;
var step = d.step/100000;
var slots = d.slots;
channelId (sysid, iden, type, freq, offset, step, slots);
noLog = 1;
break;
case "34": // 0x34 - iden up vhf/uhf
// TODO - test this
var sysid = d.sysid;
var type = 'FDMA';
var iden = d.iden;
var freq = d.freq / 1000000;
var offset = d.offset / 1000000;
var step = d.step/100000;
var slots = 1;
channelId (sysid, iden, type, freq, offset, step, slots);
noLog = 1;
break;
case "3d": // 0x3D - iden up (7/800)
var sysid = d.sysid;
var type = 'FDMA';
var iden = d.iden;
var freq = d.freq / 1000000;
var offset = d.offset / 1000000;
var step = d.step/100000;
var slots = 1;
channelId (sysid, iden, type, freq, offset, step, slots);
noLog = 1;
break;
} // end switch - end handle manf opcodes
if (d.options) {
n_opcode = ( (d.options >> 6) & 1 ) ? n_opcode = n_opcode + " ∅" : n_opcode; //encrypted
}
c = smartColor(tag);
src_c = 0;
if (d['group'])
c = (d['group']['color']) ? d['group']['color'] : c;
if (d['sg'])
c = (d['sg']['color']) ? d['sg']['color'] : c;
tag = "" + tag;
target = "" + target;
if (d['group']) {
src_c = d['group']['color'];
}
if (d['source']) {
src_c = d['source']['color'];
}
if (d['srcaddr']) { // present for voice calls, not present for other cc_events
src_c = (d['srcaddr']['color']) ? d['srcaddr']['color'] : src_c;
}
source = "" + source;
srctag = "" + srctag;
if (cb && noLog == 0) {
appendJsonTable(time, j_type, n_opcode, tag, target, source, srctag, opcode, 'history');
}
if (target != "—" && logCall) {
target = "" + target;
tag = "" + tag;
source = "" + source;
srctag = "" + srctag;
var tdma_slot = d.tdma_slot; // s/b null if not running tdma
appendCallHistory(time, "--", target, tag, source, srctag, 'callHistory', d.options, xp, d.sysid, d.nac, tdma_slot);
}
window.g_cc_event = d;
break;
} // end switch
dispatch[d['json_type']](d); // correct function is called based on json type in the dataset
} // end for
f_debug(d);
} // end dispatch_commands()
function cc_event(d) {
// does nothing right now
return;
} // end cc_event()
function do_update() {
f_debug();
} // do_update()
function f_scan_button(command) {
if (current_tgid == null)
send_command(command, -1);
else
send_command(command, current_tgid);
}
function f_goto_button(command) {
var _tgid = 0;
if (command == 'goto') {
command = 'hold';
if (current_tgid != null)
_tgid = current_tgid;
_tgid = parseInt(prompt('Enter TGID to hold.', _tgid));
if (isNaN(_tgid) || _tgid < 0 || _tgid > 65535) {
return; // Cancel was pressed or invalid entry
}
// it's necessary to release current hold before trying to hold on a new tg
if ($('#holdIndicator').is(':visible') ){
send_command('skip', -1);
setTimeout(function() {
send_command(command, _tgid);
}, 500);
return;
}
send_command(command, _tgid);
}
}
function f_debug(d) {
if (!d_debug)
return;
var html = "
Debug: ";
// html += 'window.g_nac = ' + window.g_nac;
html += ' ';
html += 'json type: ' + d['json_type'];
html += ' ';
html += "busy " + send_busy;
html += " qfull " + send_qfull;
html += " sendq size " + send_queue.length;
html += " requests " + request_count;
html += " callbacks: ";
html += " total=" + req_cb_count;
html += " incomplete=" + nfinal_count;
html += " error=" + n200_count;
html += " OK=" + r200_count;
html += "
";
$('#div_debug').html(html);
}
function popOut() {
var myWindow = window.open(window.location.href, '', 'width=760,height=400');
}
function toggleCSS() {
$(document.documentElement).attr('data-theme') == 'light' ?
$(document.documentElement).attr('data-theme', 'dark') :
$(document.documentElement).attr('data-theme', 'light');
sdmode();
}
function comma(x) {
// add comma formatting to whatever you give it (xx,xxxx,xxxx)
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
document.onkeydown = function(evt) {
console.log(evt.altKey);
// keyboard shortcuts
evt = evt || window.event;
var x = document.activeElement.tagName;
if (x == "INPUT" || evt.altKey == true || evt.ctrlKey == true)
return; // don't do anything if user is typing in an input field or if alt, ctrl key is bing used
switch (evt.keyCode) {
case 70:
// 'f' key - show/hide frequency table
$('#lastCommand').html('F - Freq Tbl
').show();
$('#show_adj').trigger('click');
break;
case 66:
// 'b' key - bold
$('#valFontStyle').val('bold');
break;
case 78:
// 'n' key - normal
$('#valFontStyle').val('normal');
break;
} // end switch
}; // end onkeydown
function minify(div) {
$('#' + div).toggle(window.animateSpeed);
}
function appendJsonTable(a, b, c, d, e, f, srctag, opcode, target) {
var numRows = document.getElementById(target).rows.length;
var size = parseInt($('#log_len').val());
if (!Number.isInteger(size)) {
// entry in Config / Display Options must be a number
size = 1500;
}
// shorter friendly view
var fv = {
'cc_event': "CC",
'rx_update': 'RX',
'change_freq': 'CF',
'trunk_update': 'TU'
};
if (numRows > size)
document.getElementById(target).deleteRow(-1);
var table = document.getElementById(target);
var lastRowIndex = table.rows.length - 1;
$('#eh-count').html(' ');
if (d_debug == 1)
$('#eh-count').html('   ' + comma(table.rows.length));
var skip = 0;
var prevTime = nohtml(table.rows[1].cells[0].innerHTML); // time
// do not duplicate history enteries - uses window object to store previous enteries and compares them with current data
// seems to work...
if (target == 'history' && b == 'trunk_update' && window.g_trunk_update_c && window.g_trunk_update_d) {
if (c == window.g_trunk_update_c && d == window.g_trunk_update_d && a == prevTime) {
skip = 1;
}
}
if (target == 'history' && b.includes('cc_event') && window.g_cc_event_c && window.g_cc_event_e) {
if (c == window.g_cc_event_c && (e == window.g_cc_event_e || f == window.g_cc_event_f) && a == prevTime) {
skip = 1;
}
}
if (target == 'history' && b.includes('change_freq') && window.g_change_freq_c && window.g_change_freq_e) {
if (c == window.g_change_freq_c && e == window.g_change_freq_e && a == prevTime) {
skip = 1;
}
}
if (!skip) {
var row = table.insertRow(1); // 2nd row insert
var cell0 = row.insertCell(0);
var cell1 = row.insertCell(1);
var cell2 = row.insertCell(2);
var cell3 = row.insertCell(3);
var cell4 = row.insertCell(4);
var cell5 = row.insertCell(5);
var cell6 = row.insertCell(6);
var cell7 = row.insertCell(7);
opcode = opcode.toString();
opcode = opcode.length == 1 ? "0" + opcode : opcode;
cell0.innerHTML = a; //time
cell1.innerHTML = (target == 'history') ? '
' + fv[b] + '
' : '
' + b + '
'; // type
cell2.innerHTML = f; // source id
cell3.innerHTML = srctag;
cell4.innerHTML = '
' + opcode.toUpperCase() + '
'; // opcode
cell5.innerHTML = c; // n_coode (event)
cell6.innerHTML = e; // target
cell7.innerHTML = d; // tag
// only update the window globals if we haven't skipped, so do this inside if !skip
if (target == 'history' && b == 'trunk_update') {
window.g_trunk_update_c = c;
window.g_trunk_update_d = d;
}
if (target == 'history' && b.includes('cc_event')) {
window.g_cc_event_c = c;
window.g_cc_event_e = e;
window.g_cc_event_f = f;
}
if (target == 'history' && b.includes('change_freq')) {
window.g_change_freq_c = c;
window.g_change_freq_e = e;
}
} // end if !skip
} // end appendJsonTable
function appendCallHistory(a, b, c, d, e, f, target, options, xp, sysid, nac, tdma_slot) {
var numRows = document.getElementById(target).rows.length;
// var size = document.getElementById('log_len').value;
var size = parseInt($('#log_len').val());
if (!Number.isInteger(size)) {
// entry in Config / Display Options must be a number
size = 1500;
}
if (numRows > size)
$('#' + target + ' tr:last').remove();
var table = document.getElementById(target);
$('#ch-count').html(' ');
if (d_debug == 1)
$('#ch-count').html('   ' + comma((table.rows.length)));
var lastRowIndex = table.rows.length - 1;
var skip = 0;
var pri, enc, xpatch, x, y;
b = (options) ? options : " ";
enc = ((b >> 6) & 1 ) ? "∅ " : "";
pri = ((b >> 2) & 1).toString() + ((b >> 1) & 1).toString() + ((b >> 0) & 1).toString();
pri = parseInt(pri, 2);
pri = (xp) ? "XP " : pri;
var prevTime, prevTg, prevSrc;
// avoid duplicate channel grant enteries into Call History table.
// Search previous 9 rows, compares current data with existing tgid, srcaddr, and time
// if tgid and srcaddr are equal and time is within 2 seconds, skip the entry,
search: {
for (x = 1; x < 8; x++) {
if (!table.rows[x]) {
break search;
}
prevTime = nohtml(table.rows[x].cells[0].innerHTML); // time
prevTg = nohtml(table.rows[x].cells[3].innerHTML); // tgid
prevSrc = nohtml(table.rows[x].cells[5].innerHTML); // source addr
var psec = prevTime.slice(-1);
var asec = a.slice(-1);
var diff = Math.abs(psec - asec);
if (nohtml(c) == prevTg && nohtml(e) == prevSrc && (a == prevTime || diff <= 2)) {
skip = 1;
} // end if
} // end for
} // end search
if (((b >> 6) & 1) && cbState('hide_enc')) { // hide encrypted calls if selected in Config
skip = 1;
}
// do not append the Call History table if not enabled on Home tab
if (enable_status[nac] == false) {
skip = 1;
}
var tslot = '';
if (cbState('showSlot') == true && tdma_slot != null) {
tslot = 'S' + ( tdma_slot +1 ) + ' ';
}
if (!skip) {
var row = table.insertRow(1); // 2nd row insert
var cell0 = row.insertCell(0);
var cell1 = row.insertCell(1);
var cell2 = row.insertCell(2);
var cell3 = row.insertCell(3);
var cell4 = row.insertCell(4);
var cell5 = row.insertCell(5);
var cell6 = row.insertCell(6);
cell0.innerHTML = a;
cell1.innerHTML = '
' + hex(sysid).toUpperCase() + '
';
cell2.innerHTML = '
' + tslot + enc + pri + '
';
cell3.innerHTML = c;
cell4.innerHTML = d;
cell5.innerHTML = e;
cell6.innerHTML = f;
} // end if !skip
} // end appendCallHistory
function appendErrorTable(ax, bx, cx, dx, ex, fx, gx, target) {
var numRows = document.getElementById(target).rows.length;
var size = parseInt($('#log_len').val());
if (!Number.isInteger(size)) {
// entry in Config / Display Options must be a number
size = 1500;
}
if (numRows > size)
document.getElementById(target).deleteRow(-1);
var table = document.getElementById(target);
var lastRowIndex = table.rows.length - 1;
var skip = 0;
if (!skip) {
var row = table.insertRow(1); // 2nd row insert
var cell0 = row.insertCell(0);
var cell1 = row.insertCell(1);
var cell2 = row.insertCell(2);
var cell3 = row.insertCell(3);
var cell4 = row.insertCell(4);
var cell5 = row.insertCell(5);
var cell6 = row.insertCell(6);
cell0.innerHTML = ax; //time
cell1.innerHTML = bx;
cell2.innerHTML = cx;
cell3.innerHTML = dx;
cell4.innerHTML = ex;
cell5.innerHTML = fx;
cell6.innerHTML = gx;
} // end if !skip
} // end appendErrorTable
function update_freq(d) {
return; // not currently used
}
function channelId (sysid, iden, type, freq, offset, step, slots) {
!(sysid in channel_id) && (channel_id[sysid] = {});
!(iden in channel_id[sysid]) && (channel_id[sysid][iden] = {});
channel_id[sysid][iden] = {
'iden': iden, 'type': type, 'freq': freq, 'offset': offset, 'step': step, 'slots': slots
};
}
function smartColor(t) {
// searches string t for items in sc1 and sc2, returns a color if found.
var z = 4; // number of smart colors - TOTO add the UI elements in index.html
if (!t || !cbState('smartcolors'))
return 0; // do nothing if there is no talkgroup name passed, or if the box is not checked (cb default is false!)
var tag = t.toString().toUpperCase(); // throws an error if t is an int
var color = 0;
var ele, x, i, sc, alen;
for (i = 1; i < (z+1); i++) {
ele = document.getElementById('sc'+i).value;
sc = ele.split(" ");
for (var x = 0; x < sc.length; x++) {
if (tag.includes(sc[x].toUpperCase())) {
color = i;
return color;
}
}
}
return color;
} // end smartColor()
function divExpand(div) {
console.log(div);
var h = $('#' + div).height();
console.log(typeof(h));
switch (true) {
case (h < 2):
console.log('case 1:' + h);
$('#' + div).height(301);
$('#' + div).show();
break;
case (h <= 301):
console.log('case 2:' + h);
$('#' + div).height(702);
break;
case (h < 9999):
console.log('case 3:' + h);
$('#' + div).height(1);
$('#' + div).hide();
break;
default:
console.log(h < 2);
console.log(h < 301);
console.log(h < 9999);
}
}
function openTable(div, ref) {
// popout window for review/search of log tables
var divText = $('#' + div).prop('outerHTML');
divText = divText.replace('table id="history"', 'table id="searchTable"');
divText = divText.replace('table id="callHistory"', 'table id="searchTable"');
divText = divText.replace('table id="errors"', 'table id="searchTable"');
var myWindow = window.open('', '', 'width=900,height=600');
var view = document.documentElement.getAttribute('data-theme');
var doc = myWindow.document;
doc.open();
doc.write('');
doc.write('');
doc.write('');
doc.write('');
doc.write('');
doc.write('');
// search icon 🔎
doc.write('' + ref.id + '
');
doc.write('');
doc.write('');
if (view == 'dark')
doc.documentElement.setAttribute('data-theme', 'dark');
doc.write(divText);
doc.close();
}
function searchTable() {
var input, filter, table, tr, i, x, s, cols;
var td = [];
cols = document.getElementById('searchTable').rows[1].cells.length;
input = document.getElementById('searchInput');
filter = input.value.toUpperCase();
table = document.getElementById('searchTable');
tr = table.getElementsByTagName('tr');
for (i = 1; i < tr.length; i++) {
var s = undefined;
for (x = 0; x < cols; x++) {
td[x] = tr[i].getElementsByTagName('td')[x];
s = s + ' ' + nohtml(td[x].innerHTML).toUpperCase();
}
if ( s.includes(filter)) {
tr[i].style.display = '';
} else {
tr[i].style.display = 'none'
}
} // end for
} // end function
function searchTsvTable() {
var input, filter, table, tr, i, x, s, y, cols;
var td = [];
table = document.getElementById('talkgroups');
cols = table.rows[1].cells.length;
input = document.getElementById('searchInput');
filter = input.value.toUpperCase();
tr = table.getElementsByTagName('tr');
for (i = 1; i < tr.length; i++) {
var s = undefined;
for (x = 1; x < cols; x++) { // don't search the first column [0]
td[x] = tr[i].getElementsByTagName('td')[x];
s += ' ' + td[x].firstChild.value.toUpperCase();
}
if ( s.includes(filter)) {
tr[i].style.display = '';
} else {
tr[i].style.display = 'none'
}
} // end for
} // end function
function hex(dec) {
if (!dec) return;
return dec.toString(16);
}
function dec(hex) {
if (!hex) return;
return parseInt(hex, 16);
}
function nohtml(str) {
if (typeof str != 'string') return str;
var x = str.replace(/(<([^>]+)>)/gi, "");
return x;
}
function freqDisplay(f) {
var freq;
if (!f) return;
if (cbState('trailing_zeros')) {
freq = f.toFixed(5);
} else {
if (Number.isInteger(f)) {
freq = f + ".0";
} else {
freq = f;
}
}
return freq;
}
function displayMode() { // just returns the display mode
var x,y;
x = document.documentElement.getAttribute('data-theme');
y = (x == "dark") ? "dark" : "light";
return y;
}
function sdmode() {
$('#selDispMode').val(displayMode());
}
function sdmodeChange() {
var y = document.getElementById('selDispMode');
document.documentElement.setAttribute('data-theme', y.value);
}
function is_digit(s) {
return ((s >= '0' && s <= '9'));
}
function getTime(x) {
//expects Unix timestamp, returns 24 hour time, hrs:min:sec
date = new Date(parseInt(x));
var time = zeroPad(date.getHours(), 2) + ':' + zeroPad(date.getMinutes(), 2) + ':' + zeroPad(date.getSeconds(), 2);
return time;
}
function cbState(x) {
// returns the state (true / false) of whatever checkbox you ask it to
return $('#' + x).is(':checked');
}
function csvTable(table_id, separator = ',') { // Quick and simple export target #table_id into a csv
// Select rows from table_id
var rows = document.querySelectorAll('table#' + table_id + ' tr');
// Construct csv
var csv = [];
for (var i = 0; i < rows.length; i++) {
var row = [],
cols = rows[i].querySelectorAll('td, th');
for (var j = 0; j < cols.length; j++) {
// Clean innertext to remove multiple spaces and jumpline (break csv)
var data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' ');
// Escape double-quote with double-double-quote
data = data.replace(/"/g, '""');
// Push escaped string
row.push('"' + data + '"');
}
csv.push(row.join(separator));
}
var csv_string = csv.join('\n');
// Download it
var filename = 'export_' + table_id + '_' + new Date().toLocaleDateString() + '.csv';
var link = document.createElement('a');
link.style.display = 'none';
link.setAttribute('target', '_blank');
link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string));
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function exportToCsv(filename, rows) { // another csv export tool, but this one accepts an array.
var processRow = function (row) {
var finalVal = '';
for (var j = 0; j < row.length; j++) {
var innerValue = row[j] === null ? '' : row[j].toString();
if (row[j] instanceof Date) {
innerValue = row[j].toLocaleString();
};
var result = innerValue.replace(/"/g, '""');
if (result.search(/("|,|\n)/g) >= 0)
result = '"' + result + '"';
if (j > 0)
finalVal += ',';
finalVal += result;
}
return finalVal + '\n';
};
var csvFile = '';
for (var i = 0; i < rows.length; i++) {
csvFile += processRow(rows[i]);
}
var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) { // IE 10+
navigator.msSaveBlob(blob, filename);
} else {
var link = document.createElement("a");
if (link.download !== undefined) { // feature detection
// Browsers that support HTML5 download attribute
var url = URL.createObjectURL(blob);
link.setAttribute("href", url);
link.setAttribute("download", filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
}
function sortTable(t, c) { // table id, column index
if (!cbState('show_adj'))
return;
if (!t) return;
var table, rows, switching, i, x, y, shouldSwitch;
table = document.getElementById(t);
switching = true;
while (switching) {
switching = false;
rows = table.rows;
// (skip the first row (0), which contains table headers)
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[c];
y = rows[i + 1].getElementsByTagName("TD")[c];
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
}
}
} // end sort table
function sources(d) { // d json type = trunk_update
// stores source IDs and tags and colors in a global object
// USAGE:
// window['tgidc' + tgid] = tgid color
// window['tgidt' + tgid] = tgid tag "talkgroup"
// window['srct' + srcaddr] = srcaddr tag
// window['srcc' + srcaddr] = srcaddr color
if (Object.keys(d).length < 1)
return;
var f, nac, srcaddr, srcaddr_tag;
var ft = 'frequency_tracking';
var calls = 'calls';
for (nac in d) {
if (Number.isInteger(parseInt(nac))) {
for (f in d[nac][ft]) {
srcaddr_tag = d[nac][ft][f]['calls'][0]['srcaddr']['tag'] ? d[nac][ft][f]['calls'][0]['srcaddr'] : null;
srcaddr = d[nac][ft][f]['calls'][0]['srcaddr']['unit_id'] ? d[nac][ft][f]['calls'][0]['srcaddr'] : null;
if (srcaddr && srcaddr_tag) {
window['tgidc' + d[nac][ft][f]['calls'][0]['tgid']['tg_id']] = d[nac][ft][f]['calls'][0]['tgid']['color'];
window['tgidt' + d[nac][ft][f]['calls'][0]['tgid']['tg_id']] = d[nac][ft][f]['calls'][0]['tgid']['tag'];
window['srct' + d[nac][ft][f]['calls'][0]['srcaddr']['unit_id']] = d[nac][ft][f]['calls'][0]['srcaddr']['tag'];
window['srcc' + d[nac][ft][f]['calls'][0]['srcaddr']['unit_id']] = d[nac][ft][f]['calls'][0]['srcaddr']['color'];
}
if (d[nac][ft][f]['tdma'] == true ) {
window[nac + 'flavor'] = "2"; // set it as p2 and leave it that way for the duration
}
} // end for f
} // end if number
} // end for x
} // end function
function getReason(r) {
// denial reason lookup
if (g_reason[r]) {
result = '0x' + r + ' - ' + g_reason[r];
} else {
result = '0x' + r;
}
return result;
}
function saveDisplaySettings() {
var settings = {};
$('#saveSettings').html('Saved');
setTimeout(() => { $('#saveSettings').html('Save Display Settings');}, 2000);
var s = [
"valSystemFont",
"valTagFont",
"valTruncate",
"valFontStyle",
"sc1",
"sc2",
"sc3",
"sc4",
"log_len",
"color_main_tag",
"color_main_sys",
"smartcolors",
"log_cc",
"log_cf",
"log_tu",
"log_rx",
"show_adj",
"je_joins",
"je_calls",
"je_deny",
"je_log",
"hide_enc",
"trailing_zeros",
"selDispMode",
"acc1",
"acc2",
"sysColor",
"valColor",
"sysColor",
"btnColor",
"unk_default",
"ani_speed",
"showBandPlan",
"showSlot",
"oplogip",
"oplogport",
"showLast",
"colSlot2" ];
for (r in s) {
if ($('#' + s[r]).attr('type') == "checkbox") {
settings[s[r]] = $('#' + s[r]).is(':checked') ? true : false;
} else {
settings[s[r]] = $('#' + s[r]).val();
}
} // end for
var settingsJSON = JSON.stringify(settings, null, 2);
send_command('config-savesettings', settingsJSON);
}
function loadHelp(){ // this is also called from editor.js
f = 'help.html';
$.ajax({
url : f,
type : 'GET',
success : popHelp,
error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Settings File Acces Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');}
});
}
function popHelp(h){
$('#div_help').html(h)
}
function beginJsonSettings(){ // this is also called from editor.js
f = 'ui-settings.json';
$.ajax({
url : f,
type : 'GET',
success : loadJsonDisplaySettings,
error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Settings File Acces Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');}
});
}
function loadJsonDisplaySettings(settings) {
$('#loadSettings').html('Loaded');
setTimeout(() => { $('#loadSettings').html('Load Settings'); }, 2000);
var ele, m;
for (item in settings) {
ele = document.getElementById(item);
if (ele) {
if (ele.type == "checkbox") {
ele.checked = settings[item] == true ? true : false;
continue;
}
if (ele.type == "select") {
ele.value = settings[item];
continue;
}
ele.value = settings[item];
}
if (item == "selDispMode") {
m = settings[item];
document.documentElement.setAttribute('data-theme', m);
sdmode();
}
// populate url into oplog url field
var x = window.location.origin.split( ':' );
// var y = x[0] + ':' + x[1] + ':5000';
// $('#oplogUrl').val(y);
$('#oplogip').val(x[1].substring(2));
$('#oplogport').val('5000');
if (item == "oplogip") {
$('#oplogip').val(settings[item]);
}
if (item == "oplogport") {
$('#oplogport').val(settings[item]);
}
$('#oplogUrl').val('http://' + $('#oplogip').val() + ':' + $('#oplogport').val());
} // end for item
uiColorRefresh();
}
function uiColorRefresh() { // this is also called from editor.js
$('#acc1').trigger('change');
$('#valColor').trigger('change');
$('#sysColor').trigger('change');
$('#btnColor').trigger('change');
$('#ani_speed').trigger('change');
}
function changeCss(className, classValue) {
// we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container');
if (cssMainContainer.length == 0) {
cssMainContainer = $('');
cssMainContainer.hide();
cssMainContainer.appendTo($('body'));
}
// and we need one div for each class
var classContainer = cssMainContainer.find('div[data-class="' + className + '"]');
if (classContainer.length == 0) {
classContainer = $('');
classContainer.appendTo(cssMainContainer);
}
// append additional style
classContainer.html('');
}
function getSiteAlias() {
if (localStorage.AliasTableUpdated == false)
return;
if (event_source == null || event_source.readyState != 1) // the server has gone out to lunch
return;
$.ajax({
url : 'site-alias.json',
type : 'GET',
success : loadSiteAlias,
error : function(XMLHttpRequest, textStatus, errorThrown) {console.log('site-alias.json file not found. \n\nFile:' + url + '\n\n' + errorThrown + '\n\n');}
});
}
function loadSiteAlias(json) {
window.siteAlias = json;
localStorage.AliasTableUpdated == false;
}
function accColorSel() { // this is also called from editor.js
$('.accColor').on('change', function() {
this.blur();
var c1 = $('#acc1').val();
var c2 = $('#acc2').val();
var z = 'linear-gradient(' + c1 + ', ' + c2 +')';
$('.controlsDisplay').css('background', z);
});
$('#valColor').on('change', function() {
this.blur();
var c1 = $('#valColor').val();
changeCss('.value', 'color: ' + c1 + ';');
changeCss('.nac', 'color: ' + c1 + ';');
});
$('#sysColor').on('change', function() {
this.blur();
var c1 = $('#sysColor').val();
changeCss('.systgid', 'color: ' + c1 + ';');
});
$('#btnColor').on('change', function() {
this.blur();
var c1 = $('#btnColor').val();
changeCss('button', 'color: ' + c1 + ';');
changeCss('.nav-button-active', 'color: ' + c1 + ';');
changeCss('.nav-button:hover', 'color: ' + c1 + ';');
changeCss('.nav-button-active:hover', 'color: ' + c1 + ';');
changeCss('.btn', 'color: ' + c1 + ';');
changeCss('.control-button', 'color: ' + c1 + ';');
changeCss('.control-button:hover', 'color: ' + c1 + ';');
});
$('#ani_speed').on('change', function() {
this.blur();
window.animateSpeed = parseInt(this.value);
});
}
// color, animation, backgroundColor - used for main talkgroup/system display
// returns the property value of the selector supplied. sheet is document.styleSheets[index].title
function getProperty(selector, property, sheet) {
for (y in document.styleSheets) {
if (document.styleSheets[y].title == sheet) // this title is set in the json color map builder func
break;
}
rules = document.styleSheets[y].cssRules;
for(i in rules) {
if(rules[i].selectorText == selector)
return rules[i]['style'][property];
}
return false;
}
function getStyle(className) { //test, not used
var cssText = "";
var classes = document.styleSheets[1].rules || document.styleSheets[0].cssRules;
for (var x = 0; x < classes.length; x++) {
if (classes[x].selectorText == className) {
cssText += classes[x].cssText || classes[x].style.cssText;
}
}
return cssText;
}
var g_opcode = { // global opcodes
'0': 'Call', // Group Voice Grant grp_v_ch_grant dec=0
'1': 'Reserved', // REserved 0x01
'2': 'Update 0x02',
'3': 'Update 0x03',
'4': 'UnitVoiceGrant',
'5': 'UU_ANS_RSP', // unit to unit answer response
'6': 'UnitVoiceUpdate',
'8': 'PhoneGrant',
'9': 'TELE_INT_PSTN_AEQ',
'0a': 'Phone Alert', // tele interconnect
'0b': 'Reserved',
'10': 'UnitDataGrant',
'11': 'GroupDataGrant',
'12': 'GroupDataUpdate',
'13': 'GroupDataUpdateExplicit',
'18': 'UnitStatusUpdate',
'1a': 'UnitStatusQuery',
'1c': 'UnitShortMessage',
'1d': 'UnitMonitor',
'1f': 'UnitCallAlert', // Call Alert
'20': 'Ack Response', // AckResponse - ACK_RESP_U - This is the generic response supplied by a unit to acknowledge an action when there is no other expected response. Response from radio to system poll ("are you still there?")
'21': 'QueuedResponse',
'24': 'ExtFunctionCommand',
'27': 'Denied', // DenyResponse
'28': 'Joins', // GroupAffiliationResponse - GRP_AFF_RSP - This is the response to the request for group affiliation by a unit. This will present the necessary information to the requesting unit to allow it to perform group operations for the indicated group identity.
'29': 'Ack Response FNE', // This is the generic response supplied to a unit to acknowledge an action when there is no other expected response. This response is sent to a subscriber unit in response to an earlier action or service request.
'2a': 'Group Aff Q', // GRP_AFF_Q - This transaction is to be used to determine what a targeted subscriber unit maintains as the group affiliation data for the unit. The Query will usually originate in the system, but the standard enables other originators.
'2b': 'Joins', // LocRegResponse - This transaction is to be used to respond to a Location Registration Request. The response indicates that the subscriber is registered in the new location area.
'2c': 'Login', // UnitRegResponse
'2d': 'Force SU Reg', // UnitRegCommand - U_REG_CMD - This transaction is to be used to force an SU to initiate Unit Registration
'2e': 'UnitAuthCommand',
'2f': 'Logout', // UnitDeregAck
'31': 'Auth Demand',
'36': 'RoamingAddrCommand',
'37': 'RoamingAddrUpdate',
'38': 'SystemServiceBroadcast', // This broadcast will inform the subscriber units of the current system services supported and currently offered on the Primary control channel of this site.
'39': 'AltControlChannel',
'3a': 'RfssStatusBroadcast',
'3b': 'NetworkStatusBroadcast',
'3c': 'AdjacentSite', // Adjacent Site Broadcast
'3d': 'ChannelParamsUpdate', // IDEN_UP
'3e': 'ProtectionParamBroadcast',
'3f': 'ProtectionParamUpdate'
};
var g_reason = { // TIA-10.AABC-B Annex B Deny Response Reason Codes
'10': 'Unit not valid',
'11': 'Unit not authoirized',
'20': 'Target unit not valid',
'21': 'Target unit not authorized',
'2f': 'Target unit refused',
'30': 'Target group invalid',
'31': 'Target group not authoirzed',
'40': 'Invalid dialing',
'41': 'Telephone number not authroized',
'42': 'PSTN address invalid',
'50': 'Call time-out',
'51': 'Call terminated by landline',
'52': 'Call terminated by subscriber',
'5f': 'Call pre-empted',
'60': 'Site access denial',
'61': 'User/system def', // 0x61 - 0xEF per standard
'67': 'User/sys def',
'77': 'User/sys def',
'c0': 'User/sys def', // seen on MOT system
'f0': 'Call options invalid',
'f1': 'Protection service option invalid',
'f2': 'Duples service option invalid',
'f3': 'circuit/packet mode service option invalid',
'ff': 'Service not supported by system'
};
var g_moto_opcode = { // opcodes when mfrid is MOT (0x90, 144)
'0': 'MOT Add Patch Group',
'1': 'MOT Del Patch Group',
'3': 'MOT Patch Voice Channel Grant Update',
'4': 'MOT Unknown',
'5': 'MOT Traffic Chan Stn ID',
'6': 'MOT Unknown',
'7': 'MOT Unknown',
'8': 'MOT Unknown',
'9': 'MOT System Load',
'0a': 'MOT Unknown',
'0b': 'MOT Control Chan Base Stn ID',
'0c': 'MOT Unknown',
'0d': 'MOT Unknown',
'0e': 'MOT Planned Control Channel Shutdown',
// 0f through 3f = unknown
};
var g_serviceOption = {
// TODO - populate this, not used currently.
// bits 0-2 priority
'0': 'reserved',
'1': 'Lowest',
'2': 'User/system def',
'3': 'User/system def',
'4': 'Default',
'5': 'User/system def',
'6': 'User/system def',
'7': 'Highest',
// bit 3 - reserved
// bit 4 - mode
};
// const value_string MFIDS[] = {
// { 0x00, "Standard MFID (pre-2001)" },
// { 0x01, "Standard MFID (post-2001)" },
// { 0x09, "Aselsan Inc." },
// { 0x10, "Relm / BK Radio" },
// { 0x18, "EADS Public Safety Inc." },
// { 0x20, "Cycomm" },
// { 0x28, "Efratom Time and Frequency Products, Inc" },
// { 0x30, "Com-Net Ericsson" },
// { 0x34, "Etherstack" },
// { 0x38, "Datron" },
// { 0x40, "Icom" },
// { 0x48, "Garmin" },
// { 0x50, "GTE" },
// { 0x55, "IFR Systems" },
// { 0x5A, "INIT Innovations in Transportation, Inc" },
// { 0x60, "GEC-Marconi" },
// { 0x64, "Harris Corp." },
// { 0x68, "Kenwood Communications" },
// { 0x70, "Glenayre Electronics" },
// { 0x74, "Japan Radio Co." },
// { 0x78, "Kokusai" },
// { 0x7C, "Maxon" },
// { 0x80, "Midland" },
// { 0x86, "Daniels Electronics Ltd." },
// { 0x90, "Motorola" },
// { 0xA0, "Thales" },
// { 0xA4, "M/A-COM" },
// { 0xB0, "Raytheon" },
// { 0xC0, "SEA" },
// { 0xC8, "Securicor" },
// { 0xD0, "ADI" },
// { 0xD8, "Tait Electronics" },
// { 0xE0, "Teletec" },
// { 0xF0, "Transcrypt International" },
// { 0xF8, "Vertex Standard" },
// { 0xFC, "Zetron, Inc" },
// };
// const value_string ALGIDS[] = {
// /* Type I */
// { 0x00, "ACCORDION 1.3" },
// { 0x01, "BATON (Auto Even)" },
// { 0x02, "FIREFLY Type 1" },
// { 0x03, "MAYFLY Type 1" },
// { 0x04, "SAVILLE" },
// { 0x05, "Motorola Assigned - PADSTONE" },
// { 0x41, "BATON (Auto Odd)" },
// /* Type III */
// { 0x80, "Unencrypted" },
// { 0x81, "DES-OFB, 56 bit key" },
// { 0x83, "3 key Triple DES, 168 bit key" },
// { 0x84, "AES-256-OFB" },
// { 0x85, "AES-128-ECB"},
// { 0x88, "AES-CBC"},
// { 0x89, "AES-128-OFB"},
// /* Motorola proprietary - some of these have been observed over the air,
// some have been taken from firmware dumps on various devices, others
// have come from the TIA's FTP website while it was still public,
// from document "ALGID Guide 2015-04-15.pdf", and others have been
// have been worked out with a little bit of "guesswork" ;) */
// { 0x9F, "Motorola DES-XL 56-bit key" },
// { 0xA0, "Motorola DVI-XL" },
// { 0xA1, "Motorola DVP-XL" },
// { 0xA2, "Motorola DVI-SPFL"},
// { 0xA3, "Motorola HAYSTACK" },
// { 0xA4, "Motorola Assigned - Unknown" },
// { 0xA5, "Motorola Assigned - Unknown" },
// { 0xA6, "Motorola Assigned - Unknown" },
// { 0xA7, "Motorola Assigned - Unknown" },
// { 0xA8, "Motorola Assigned - Unknown" },
// { 0xA9, "Motorola Assigned - Unknown" },
// { 0xAA, "Motorola ADP (40 bit RC4)" },
// { 0xAB, "Motorola CFX-256" },
// { 0xAC, "Motorola Assigned - Unknown" },
// { 0xAD, "Motorola Assigned - Unknown" },
// { 0xAE, "Motorola Assigned - Unknown" },
// { 0xAF, "Motorola AES-256-GCM (possibly)" },
// { 0xB0, "Motorola DVP"},
// };