diff --git a/README.md b/README.md index 3c11365..afe0ea7 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ The dashboard provides several tabs: 1. Navigate to the **Messages** tab 2. Type your message in the text field -3. Select the channel (0-7) +3. Choose a device to send **From** 4. Click **Send** Messages will be broadcast to the mesh network via MQTT. @@ -269,6 +269,12 @@ Log level can be configured with `LOG_LEVEL` in `.env` (debug, info, warn, error 3. Check that there is active mesh traffic on the topic 4. Review logs for any error messages +### Messages sent over MQTT aren't sent over the radio + +1. Ensure the sending radio (node) has a channel called "mqtt" +2. Ensure the node has Uplink and Downlink enabled on both the default channel (LongFast, etc) and the mqtt channel +3. Ensure the node's setting under LoRa is "Ok to MQTT" + ### Database errors 1. Ensure the `data/` directory is writable diff --git a/public/index.html b/public/index.html index c033224..55a04fa 100644 --- a/public/index.html +++ b/public/index.html @@ -118,6 +118,11 @@ + @@ -159,13 +164,18 @@

Purge old data from the database to free up space.

- - + + + + + + + + + +
diff --git a/public/js/app.js b/public/js/app.js index 3905b40..11c456e 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -173,17 +173,17 @@ const api = { return this.request(`/api/messages?limit=${limit}`); }, - async sendMessage(from, text) { + async sendMessage(from, text, channel) { return this.request('/api/messages/send', { method: 'POST', - body: JSON.stringify({ text, from }) + body: JSON.stringify({ from, text, channel }) }); }, - async purgeData(days) { + async purgeData(hours) { return this.request('/api/purge', { method: 'POST', - body: JSON.stringify({ days }) + body: JSON.stringify({ hours }) }); }, @@ -547,9 +547,10 @@ document.getElementById('send-message-form')?.addEventListener('submit', async ( const text = document.getElementById('message-text').value; const from = parseInt(document.getElementById('message-from').value); + const channel = parseInt(document.getElementById('message-channel').value); try { - await api.sendMessage(text, from); + await api.sendMessage(from, text, channel); document.getElementById('message-text').value = ''; // Reload messages after a short delay @@ -1045,14 +1046,23 @@ document.getElementById('refresh-map-btn')?.addEventListener('click', loadMap); document.getElementById('purge-form')?.addEventListener('submit', async (e) => { e.preventDefault(); - const days = parseInt(document.getElementById('purge-days').value); + const hours = parseInt(document.getElementById('purge-hours').value); - if (!confirm(`Are you sure you want to delete data older than ${days} days? This cannot be undone.`)) { + // Format time period for display + let timePeriod; + if (hours < 24) { + timePeriod = `${hours} hour${hours !== 1 ? 's' : ''}`; + } else { + const days = hours / 24; + timePeriod = `${days} day${days !== 1 ? 's' : ''}`; + } + + if (!confirm(`Are you sure you want to delete data older than ${timePeriod}? This cannot be undone.`)) { return; } try { - const result = await api.purgeData(days); + const result = await api.purgeData(hours); alert(`Successfully purged:\n${result.deleted.messages} messages\n${result.deleted.positions} positions\n${result.deleted.telemetry} telemetry records`); // Reload stats diff --git a/src/database/queries.js b/src/database/queries.js index 2d62a04..e9dbc93 100644 --- a/src/database/queries.js +++ b/src/database/queries.js @@ -95,7 +95,7 @@ const positionQueries = { `), deleteOldPositions: db.prepare(` - DELETE FROM positions WHERE timestamp < datetime('now', '-' || ? || ' days') + DELETE FROM positions WHERE timestamp < datetime('now', '-' || ? || ' hours') `), getPositionTrails: db.prepare(` @@ -170,7 +170,7 @@ const messageQueries = { `), deleteOldMessages: db.prepare(` - DELETE FROM messages WHERE created_at < datetime('now', '-' || ? || ' days') + DELETE FROM messages WHERE created_at < datetime('now', '-' || ? || ' hours') `) }; @@ -199,7 +199,7 @@ const telemetryQueries = { `), deleteOldTelemetry: db.prepare(` - DELETE FROM telemetry WHERE timestamp < datetime('now', '-' || ? || ' days') + DELETE FROM telemetry WHERE timestamp < datetime('now', '-' || ? || ' hours') `) }; diff --git a/src/mqtt/client.js b/src/mqtt/client.js index 80a2ed9..0bbd327 100644 --- a/src/mqtt/client.js +++ b/src/mqtt/client.js @@ -344,10 +344,11 @@ class MeshtasticMQTTClient { } // Send a text message - async sendTextMessage(from, text) { + async sendTextMessage(from, text, channel=0) { try { const message = JSON.stringify({ from: from, + channel: channel, type: 'sendtext', payload: text }); diff --git a/src/routes/api.js b/src/routes/api.js index d45deee..66cb56d 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -169,13 +169,13 @@ router.get('/messages/node/:nodeId', requireAuth, (req, res) => { // Send a message router.post('/messages/send', requireAuth, async (req, res) => { try { - const { text, from } = req.body; + const { from, text, channel } = req.body; if (!text) { return res.status(400).json({ error: 'Message text required' }); } - await mqttClient.sendTextMessage(text, from || 0); + await mqttClient.sendTextMessage(from, text, channel); // Log activity logActivity(req.session.userId, 'send_message', text, req.ip); @@ -224,20 +224,24 @@ router.get('/stats', requireAuth, (req, res) => { // Purge old data router.post('/purge', requireAuth, (req, res) => { try { - const { days } = req.body; - const daysToKeep = days || 30; + const { hours } = req.body; + const hoursToKeep = hours || 720; // Default to 30 days (720 hours) - const messagesDeleted = messageQueries.deleteOldMessages.run(daysToKeep); - const positionsDeleted = positionQueries.deleteOldPositions.run(daysToKeep); - const telemetryDeleted = telemetryQueries.deleteOldTelemetry.run(daysToKeep); + const messagesDeleted = messageQueries.deleteOldMessages.run(hoursToKeep); + const positionsDeleted = positionQueries.deleteOldPositions.run(hoursToKeep); + const telemetryDeleted = telemetryQueries.deleteOldTelemetry.run(hoursToKeep); logger.info(`Data purged: ${messagesDeleted.changes} messages, ${positionsDeleted.changes} positions, ${telemetryDeleted.changes} telemetry records`); // Log activity + const timePeriod = hoursToKeep < 24 + ? `${hoursToKeep} hour${hoursToKeep !== 1 ? 's' : ''}` + : `${hoursToKeep / 24} day${hoursToKeep / 24 !== 1 ? 's' : ''}`; + logActivity( req.session.userId, 'purge_data', - `Purged data older than ${daysToKeep} days`, + `Purged data older than ${timePeriod}`, req.ip );