TinyGS - Advanced Hex Transmit
HTML
<h2>TinyGS Advanced Dashboard</h2>
<div class="card">
<strong>Station Status</strong>
<ul id="gsstatus" class="status-list">
<li>
<span class="status-label">Frequency:</span>
<span id="stat-freq" class="status-value">433.000 MHz</span>
</li>
<li>
<span class="status-label">Mode:</span>
<span id="stat-mode" class="status-value">LoRa</span>
</li>
<li>
<span class="status-label">Uptime:</span>
<span id="stat-uptime" class="status-value">00:42:15</span>
</li>
</ul>
</div>
<div class="card" style="min-width: 400px; max-width: 500px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<strong>Transaction Log (Local)</strong>
<button onclick="exportTxLog()"
style="font-size: 0.8em; padding: 3px 8px; cursor: pointer; background: #28a745; color: white; border: none; border-radius: 3px;">Export
JSON</button>
</div>
<div
style="max-height: 120px; overflow-y: auto; margin-top: 10px; font-size: 0.85em; border-top: 1px solid #ddd; padding-top: 5px;">
<table style="width: 100%; margin: 0; text-align: left;">
<thead>
<tr style="color: #666;">
<th style="padding-bottom: 5px;">Timestamp (ISO)</th>
<th style="padding-bottom: 5px;">Payload (Hex)</th>
</tr>
</thead>
<tbody id="txLogBody">
<tr>
<td colspan="2" style="text-align: center; color: #999;">No local logs found.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="t1" style="height:50px;"></div>
...
CSS
/* Reusing styles from IOTWEBCONF_DASHBOARD_STYLE_INNER in html.h */
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
text-align: center;
padding: 20px;
}
table {
margin: 20px auto;
border-collapse: collapse;
}
h3 {
text-align: center;
}
.card {
margin: 10px;
text-align: left;
font-family: Arial;
border: 3px groove;
border-radius: 0.3rem;
display: inline-block;
padding: 15px;
min-width: 300px;
background: #fff;
vertical-align: top;
}
td {
padding: 0 10px;
}
.G {
color: green;
font-weight: bold;
}
.R {
color: red;
font-weight: bold;
}
/* Specific styles for the Advanced Hex Tool */
.tx-input {
width: 100%;
font-family: monospace;
padding: 8px;
margin: 10px 0;
box-sizing: border-box;
text-transform: uppercase;
}
.legal-box {
font-size: 0.85em;
background: #fff3cd;
padding: 10px;
border: 1px solid #ffeeba;
border-radius: 4px;
margin-bottom: 10px;
}
.btn-send {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.btn-send:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.timer-display {
font-size: 0.9em;
color: #666;
margin-top: 5px;
}
select {
width: 100%;
padding: 5px;
margin-bottom: 10px;
}
.byte-counter {
float: right;
font-size: 0.9em;
color: #666;
font-weight: normal;
}
.status-list {
list-style: none;
padding: 0;
margin: 10px 0;
text-align: left;
}
.status-list li {
display: flex;
justify-content: space-between;
padding: 6px 0;
border-bottom: 1px dashed #eee;
font-size: 0.95em;
}
.status-label {
font-weight: bold;
color: #444;
}
...
JavaScript
let currentCallsignHex = '';
function injectTemplate() {
const template = document.getElementById('template').value;
if (template) {
document.getElementById('hexInput').value = template;
validateHex();
}
}
// --- LOGGING UTILITIES ---
const LOG_KEY = 'tinygs_tx_history';
const MAX_LOGS = 100; // Keep the last 100 TXs to prevent unlimited growth
function loadTxLogs() {
try {
const logs = localStorage.getItem(LOG_KEY);
return logs ? JSON.parse(logs) : [];
} catch (e) {
return [];
}
}
function saveTxLog(hexPayload) {
let logs = loadTxLogs();
const entry = {
timestamp: new Date().toISOString(),
payload: hexPayload,
};
logs.unshift(entry); // Add to the beginning of the array
if (logs.length > MAX_LOGS) {
logs.pop(); // Remove oldest to stay within limit
}
localStorage.setItem(LOG_KEY, JSON.stringify(logs));
renderTxLog();
}
function renderTxLog() {
const logs = loadTxLogs();
const tbody = document.getElementById('txLogBody');
if (logs.length === 0) {
tbody.innerHTML =
'<tr><td colspan="2" style="text-align: center; color: #999;">No local logs found.</td></tr>';
return;
}
tbody.innerHTML = '';
logs.forEach((log) => {
const tr = document.createElement('tr');
// Format Timestamp (Show just time & date for space, hover for full ISO)
const tdTime = document.createElement('td');
const d = new Date(log.timestamp);
tdTime.innerText = d.toISOString().replace('T', ' ').substring(0, 19);
tdTime.title = log.timestamp;
tdTime.style.paddingRight = '10px';
// Format Payload (Truncate to 36 chars)
const tdPayload = document.createElement('td');
tdPayload.style.fontFamily = 'monospace';
if (log.payload.length > 36) {
tdPayload.innerText = log.payload.substring(0, 36) + '...';
tdPayload.title = log.payload; // Show full hex on hover
} else {
tdPayload.innerText...