• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stephendade / Rpanion-server / 19923581221

04 Dec 2025 09:10AM UTC coverage: 35.579% (+1.0%) from 34.54%
19923581221

push

github

stephendade
Server: Add seperate dflogger instead of using mavlink-router for logging

331 of 1209 branches covered (27.38%)

Branch coverage included in aggregate %.

9 of 32 new or added lines in 2 files covered. (28.13%)

589 existing lines in 4 files now uncovered.

1058 of 2695 relevant lines covered (39.26%)

3.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.92
/server/pppConnection.js
1
/*
2
    * PPPConnection.js
3
    * This module manages a PPP connection using pppd.
4
    * It allows setting device, baud rate, local and remote IPs,
5
    * starting and stopping the PPP connection, and retrieving data transfer stats.
6
    * Used for the PPP feature in ArduPilot
7
*/
8
const { spawn, execSync } = require('child_process');
3✔
9
const { detectSerialDevices, getSerialPathFromValue } = require('./serialDetection.js')
3✔
10

11
class PPPConnection {
12
    constructor(settings) {
13
        this.settings = settings
69✔
14
        this.isConnected = this.settings.value('ppp.enabled', false);
69✔
15
        this.pppProcess = null;
69✔
16
        this.device = this.settings.value('ppp.uart', null);
69✔
17
        this.baudRate = this.settings.value('ppp.baud', 921600)
69✔
18
        this.localIP = this.settings.value('ppp.localIP', '192.168.144.14');  // default local IP
69✔
19
        this.remoteIP = this.settings.value('ppp.remoteIP', '192.168.144.15'); // default remote IP
69✔
20
        this.baudRates = [
69✔
21
            { value: 115200, label: '115200' },
22
            { value: 230400, label: '230400' },
23
            { value: 460800, label: '460800' },
24
            { value: 921600, label: '921600' },
25
            { value: 1500000, label: '1.5 MBaud' },
26
            { value: 2000000, label: '2 MBaud' },
27
            { value: 12500000, label: '12.5 MBaud' }];
28
        this.serialDevices = [];
69✔
29
        this.badbaudRate = false; // flag to indicate if the baud rate is not supported
69✔
30
        this.prevdata = null; // previous data for comparison
69✔
31
        this.isQuitting  = false;
69✔
32
        this.isManualStop = false; // flag to distinguish manual stop from process crash
69✔
33

34
        if (this.isConnected) {
69✔
35
            // populate serial devices list and start PPP connection
36
            this.getDevices((err, devices) => {
3✔
37
                this.devices = devices;
3✔
38
                const attemptPPPStart = () => {
3✔
39
                    this.startPPP(this.device, this.baudRate, this.localIP, this.remoteIP, (err, result) => {
6✔
40
                        if (err) {
6!
41
                            if (err.message.includes('already connected')) {
6✔
42
                                console.log('PPP connection is already established. Retrying in 1 second...');
3✔
43
                                this.isConnected = false;
3✔
44
                                this.setSettings();
3✔
45
                                setTimeout(attemptPPPStart, 1000); // Retry after 1 second
3✔
46
                            } else {
47
                                console.error('Error starting PPP connection:', err);
3✔
48
                                this.isConnected = false;
3✔
49
                                this.setSettings();
3✔
50
                            }
51
                        } else {
UNCOV
52
                            console.log('PPP connection started successfully');
×
53
                        }
54
                    });
55
                };
56

57
                attemptPPPStart();
3✔
58
            });
59
        }
60
    }
61

62
    setSettings() {
63
        this.settings.setValue('ppp.uart', this.device);
9✔
64
        this.settings.setValue('ppp.baud', this.baudRate);
9✔
65
        this.settings.setValue('ppp.localIP', this.localIP);
9✔
66
        this.settings.setValue('ppp.remoteIP', this.remoteIP);
9✔
67
        this.settings.setValue('ppp.enabled', this.isConnected);
9✔
68
    }
69

70
    quitting() {
71
        // stop the PPP connection if rpanion is quitting
72
        this.isQuitting = true;
9✔
73
        if (this.pppProcess) {
9!
UNCOV
74
            console.log('Stopping PPP connection on quit...');
×
75
            // Remove all event listeners to prevent close handler from firing
UNCOV
76
            this.pppProcess.removeAllListeners();
×
UNCOV
77
            this.pppProcess.kill();
×
UNCOV
78
            this.pppProcess = null;
×
UNCOV
79
            try {
×
UNCOV
80
                execSync('sudo pkill -SIGTERM pppd && sleep 1');
×
81
            } catch (error) {
UNCOV
82
                console.error('Error stopping PPP connection on shutdown:', error);
×
83
            }
84
        }
85
    }
86

87
    async getDevices (callback) {
88
        // get all serial devices using hardwareDetection module
89
        try {
6✔
90
            this.serialDevices = await detectSerialDevices()
6✔
91
            return callback(null, this.serialDevices);
6✔
92
        } catch (error) {
UNCOV
93
            console.error('Error detecting serial devices:', error)
×
UNCOV
94
            return callback(error, []);
×
95
        }
96
    }
97

98
    startPPP(device, baudRate, localIP, remoteIP, callback) {
99
        this.badbaudRate = false;
15✔
100
        if (this.isConnected) {
15✔
101
            return callback(new Error('PPP is already connected'), {
6✔
102
                selDevice: this.device,
103
                selBaudRate: this.baudRate,
104
                localIP: this.localIP,
105
                remoteIP: this.remoteIP,
106
                enabled: this.isConnected,
107
                baudRates: this.baudRates,
108
                serialDevices: this.serialDevices,
109
            });
110
        }
111
        if (!device) {
9✔
112
            return callback(new Error('Device is required'), {
3✔
113
                selDevice: this.device,
114
                selBaudRate: this.baudRate,
115
                localIP: this.localIP,
116
                remoteIP: this.remoteIP,
117
                enabled: this.isConnected,
118
                baudRates: this.baudRates,
119
                serialDevices: this.serialDevices,
120
            });
121
        }
122
        if (this.pppProcess) {
6!
UNCOV
123
            return callback(new Error('PPP still running. Please wait for it to finish.'), {
×
124
                selDevice: this.device,
125
                selBaudRate: this.baudRate,
126
                localIP: this.localIP,
127
                remoteIP: this.remoteIP,
128
                enabled: this.isConnected,
129
                baudRates: this.baudRates,
130
                serialDevices: this.serialDevices,
131
            });
132
        }
133

134
        //ensure device string is valid in the serialdevices list
135
        const devicePath = getSerialPathFromValue(device, this.serialDevices);
6✔
136
        if (!devicePath) {
6!
137
            return callback(new Error('Invalid device selected'), {
6✔
138
                selDevice: this.device,
139
                selBaudRate: this.baudRate,
140
                localIP: this.localIP,
141
                remoteIP: this.remoteIP,
142
                enabled: this.isConnected,
143
                baudRates: this.baudRates,
144
                serialDevices: this.serialDevices,
145
            });
146
        }
147

148
        this.device = device;
×
UNCOV
149
        this.baudRate = baudRate;
×
UNCOV
150
        this.localIP = localIP;
×
151
        this.remoteIP = remoteIP;
×
152
        
UNCOV
153
        const args = [
×
154
            "pppd",
155
            devicePath,
156
            this.baudRate, // baud rate
157
            //'persist',          // enables faster termination
158
            //'holdoff', '1',     // minimum delay of 1 second between connection attempts
159
            this.localIP + ':' + this.remoteIP, // local and remote IPs
160
            'local',
161
            'noauth',
162
            //'debug',
163
            'crtscts',
164
            'nodetach',
165
            'ktune'
166
        ];
167
        // if running in dev env, need to preload sudo login
UNCOV
168
        if (process.env.NODE_ENV === 'development') {
×
UNCOV
169
            execSync('sudo -v');
×
170
        }
UNCOV
171
        console.log(`Starting PPP with args: ${args.join(' ')}`);
×
UNCOV
172
        this.pppProcess = spawn('sudo', args, {
×
173
        //detached: true,
174
        stdio: ['ignore', 'pipe', 'pipe'] // or 'ignore' for all three to fully detach
175
        });
UNCOV
176
        this.pppProcess.stdout.on('data', (data) => {
×
177
            console.log("PPP Output: ", data.toString().trim());
×
178
            // Check for non support baud rates "speed <baud> not supported"
UNCOV
179
            if (data.toString().includes('speed') && data.toString().includes('not supported')) {
×
180
                this.pppProcess.kill();
×
UNCOV
181
                this.pppProcess = null; // reset the process reference
×
UNCOV
182
                this.isConnected = false;
×
UNCOV
183
                this.badbaudRate = true;
×
184
            }
185
        });
UNCOV
186
        this.pppProcess.stderr.on('data', (data) => {
×
UNCOV
187
            console.log("PPP Error: ", data.toString().trim());
×
188
        });
UNCOV
189
        this.pppProcess.on('close', (code, signal) => {
×
UNCOV
190
            console.log(`PPP process exited with code: ${code}, signal: ${signal} (isQuitting: ${this.isQuitting}, isManualStop: ${this.isManualStop})`);
×
191
            // Don't treat signal-based terminations as unexpected (code 5 is typical for SIGTERM/SIGINT)
192
            // These usually happen during application shutdown when Ctrl+C is pressed
UNCOV
193
            const isSignalTermination = signal !== null || code === 5 || code === 2;
×
UNCOV
194
            if (!this.isQuitting && !this.isManualStop && !isSignalTermination) {
×
UNCOV
195
                this.isConnected = false;
×
UNCOV
196
                this.setSettings();
×
197
            }
UNCOV
198
            this.pppProcess = null; // reset the process reference
×
UNCOV
199
            this.isManualStop = false; // reset flag for next connection
×
200
        });
UNCOV
201
        this.isConnected = true;
×
UNCOV
202
        this.setSettings();
×
UNCOV
203
        return callback(null, {
×
204
            selDevice: this.device,
205
            selBaudRate: this.baudRate,
206
            localIP: this.localIP,
207
            remoteIP: this.remoteIP,
208
            enabled: this.isConnected,
209
            baudRates: this.baudRates,
210
            serialDevices: this.serialDevices,
211
        });
212
    }
213

214
    stopPPP(callback) {
215
        if (!this.isConnected) {
6✔
216
            return callback(new Error('PPP is not connected'), {
3✔
217
                selDevice: this.device,
218
                selBaudRate: this.baudRate,
219
                localIP: this.localIP,
220
                remoteIP: this.remoteIP,
221
                enabled: this.isConnected,
222
                baudRates: this.baudRates,
223
                serialDevices: this.serialDevices,
224
            });
225
        }
226
        if (this.pppProcess) {
3!
227
            // Gracefully kill the PPP process
UNCOV
228
            console.log('Stopping PPP connection...');
×
229
            // Set flag to prevent the close event handler from updating state
UNCOV
230
            this.isManualStop = true;
×
UNCOV
231
            this.pppProcess.kill();
×
UNCOV
232
            execSync('sudo pkill -SIGTERM pppd');
×
UNCOV
233
            this.isConnected = false;
×
UNCOV
234
            this.setSettings();
×
235
        }
236
        return callback(null, {
3✔
237
            selDevice: this.device,
238
            selBaudRate: this.baudRate,
239
            localIP: this.localIP,
240
            remoteIP: this.remoteIP,
241
            enabled: this.isConnected,
242
            baudRates: this.baudRates,
243
            serialDevices: this.serialDevices,
244
        });
245
    }
246

247
    getPPPdatarate(callback) {
UNCOV
248
        if (!this.isConnected) {
×
UNCOV
249
            return callback(new Error('PPP is not connected'));
×
250
        }
251
        // get current data transfer stats for connected PPP session
UNCOV
252
        return new Promise((resolve, reject) => {
×
UNCOV
253
            exec('ifconfig ppp0', (error, stdout, stderr) => {
×
UNCOV
254
                if (error) {
×
255
                    reject(`Error getting PPP data rate: ${stderr}`);
×
256
                } else {
257
                    // match format RX packets 110580  bytes 132651067 (132.6 MB)
258
                    const match = stdout.match(/RX packets \d+  bytes (\d+) \(\d+\.\d+ MB\).*TX packets \d+  bytes (\d+) \(\d+\.\d+ MB\)/);
×
259
                    if (match) {
×
UNCOV
260
                        const rxBytes = parseInt(match[1], 10);
×
UNCOV
261
                        const txBytes = parseInt(match[5], 10);
×
UNCOV
262
                        resolve({ rxBytes, txBytes });
×
263
                    } else {
264
                        reject('Could not parse PPP data rate');
×
265
                    }
266
                }
267
            });
268
        });
269
    }
270

271
    getPPPSettings(callback) {
UNCOV
272
        this.getDevices((err, devices) => {
×
273
            if (err) {
×
274
                console.error('Error fetching serial devices:', err);
×
UNCOV
275
                return callback(err, {
×
276
                    selDevice: null,
277
                    selBaudRate: this.baudRate,
278
                    localIP: this.localIP,
279
                    remoteIP: this.remoteIP,
280
                    enabled: this.isConnected,
281
                    baudRates: this.baudRates,
282
                    serialDevices: [],
283
                });
284
            }
285
            
UNCOV
286
            this.serialDevices = devices;
×
287
            
288
            // Set default device if not already set
UNCOV
289
            if (!this.device && this.serialDevices.length > 0) {
×
UNCOV
290
                this.device = this.serialDevices[0].value;
×
291
            }
292
            
293
            // if this.device is not in the list, set it to first available device
UNCOV
294
            if (this.device && !this.serialDevices.some(d => d.value === this.device)) {
×
UNCOV
295
                this.device = this.serialDevices[0].value;
×
296
            }
297
            
298
            // Always return callback
UNCOV
299
            return callback(null, {
×
300
                selDevice: this.device,
301
                selBaudRate: this.baudRate,
302
                localIP: this.localIP,
303
                remoteIP: this.remoteIP,
304
                enabled: this.isConnected,
305
                baudRates: this.baudRates,
306
                serialDevices: this.serialDevices,
307
            });
308
        });
309
    }
310

311
    // uses ifconfig to get the PPP connection datarate
312
    getPPPDataRate() {
313
        if (!this.isConnected) {
9✔
314
            return { rxRate: 0, txRate: 0 };
3✔
315
        }
316
        // get current data transfer stats for connected PPP session
317
        try {
6✔
318
            let stdout = execSync('ifconfig ppp0 | grep packets', { encoding: 'utf8' }).toString().trim();
6✔
UNCOV
319
            if (!stdout) {
×
UNCOV
320
                return { rxRate: 0, txRate: 0, percentusedRx: 0, percentusedTx: 0 };
×
321
            }
322
            // match format :
323
            //        RX packets 0  bytes 0 (0.0 B)
324
            //        TX packets 118  bytes 12232 (12.2 KB)
UNCOV
325
            const [ , matchRX, matchTX ] = stdout.match(/RX\s+packets\s+\d+\s+bytes\s+(\d+).*TX\s+packets\s+\d+\s+bytes\s+(\d+)/s);
×
UNCOV
326
            if (matchRX && matchTX) {
×
327
                const rxBytes = parseInt(matchRX);
×
328
                const txBytes = parseInt(matchTX);
×
329
                // calculate the data rate in bytes per second
UNCOV
330
                if (this.prevdata) {
×
331
                    const elapsed = Date.now() - this.prevdata.timestamp; // in milliseconds
×
332
                    const rxRate = (rxBytes - this.prevdata.rxBytes) / (elapsed / 1000); // bytes per second
×
333
                    const txRate = (txBytes - this.prevdata.txBytes) / (elapsed / 1000); // bytes per second
×
334
                    const percentusedRx = rxRate / (this.baudRate / 8); // percent of baud rate used
×
UNCOV
335
                    const percentusedTx = txRate / (this.baudRate / 8); // percent of baud rate used
×
UNCOV
336
                    this.prevdata = { rxBytes, txBytes, timestamp: Date.now() };
×
337
                    return { rxRate, txRate, percentusedRx, percentusedTx };
×
338
                }
339
                this.prevdata = { rxBytes, txBytes, timestamp: Date.now() };
×
340
                return { rxRate: 0, txRate: 0, percentusedRx: 0, percentusedTx: 0 };
×
341
            } else {
UNCOV
342
                return { rxRate: 0, txRate: 0, percentusedRx: 0, percentusedTx: 0};
×
343
            }
344
        } catch (error) {
345
            console.error('Error getting PPP data rate:', error.message);
6✔
346
            return { rxRate: 0, txRate: 0, percentusedRx: 0, percentusedTx: 0 };
6✔
347
        }
348
    }
349

350
    // Returns a string representation of the PPP connection status for use by socket.io
351
    conStatusStr () {
352
        //format the connection status string
353
        if (this.badbaudRate) {
9✔
354
            return 'Disconnected (Baud rate not supported)';
3✔
355
        }
356
        if (!this.isConnected) {
6✔
357
            return 'Disconnected';
3✔
358
        }
359
        if (this.pppProcess && this.pppProcess.pid) {
3!
360
            //get datarate
361
            const { rxRate, txRate, percentusedRx, percentusedTx } = this.getPPPDataRate();
3✔
362
            let status = 'Connected';
3✔
363
            if (this.pppProcess.pid) {
3!
364
                status += ` (PID: ${this.pppProcess.pid})`;
3✔
365
            }
366
            if (rxRate > 0 || txRate > 0) {
3!
UNCOV
367
                status += `, RX: ${rxRate.toFixed(2)} B/s (${(percentusedRx * 100).toFixed(2)}%), TX: ${txRate.toFixed(2)} B/s (${(percentusedTx * 100).toFixed(2)}%)`;
×
368
            } else {
369
                status += ', No data transfer';
3✔
370
            }
371
            return status;
3✔
372
        }
373
        else {
374
            return 'Disconnected';
×
375
        }
376
    }
377
}
378

379

380
module.exports = PPPConnection;
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc