/**
* GNSS Module - Main entry point
*
* This module provides a JavaScript interface for connecting to GNSS RTK rovers
* via Web Bluetooth or Web Serial, parsing NMEA data, and managing NTRIP correction data.
*/
import { EventEmitter } from './event-emitter.js';
import { BluetoothConnection } from './bluetooth.js';
import { NmeaParser } from './nmea-parser.js';
import { NtripClient } from './ntrip-client.js';
import { Settings } from './settings.js';
import { ConnectionManager } from './connection/connection-manager.js';
import { BluetoothHandler } from './connection/bluetooth-handler.js';
import { SerialHandler } from './connection/serial-handler.js';
import { RtkSettings } from './ui/rtk-settings.js';
import { RtkStatus } from './ui/rtk-status.js';
import { DeviceSettings } from './ui/device-settings.js';
/**
* Main GNSS Module class
* Manages device connections, NMEA parsing, and NTRIP client
*/
class GnssModule {
/**
* Create a new GNSS module instance
* @param {Object} options - Configuration options
*/
constructor(options = {}) {
// Initialize event system
this.events = new EventEmitter();
// Initialize settings
this.settings = new Settings(options.settings);
// Initialize other properties
this.debugSettings = options.debugSettings || {
info: false,
debug: false,
errors: true,
parsedSentences: false,
rtcmMessages: false
};
// Initialize connection manager
this.connectionManager = new ConnectionManager(this.events, {
debug: this.debugSettings,
settings: this.settings
});
// Create and register connection handlers
this.bluetoothHandler = new BluetoothHandler(this.events, {
debug: this.debugSettings
});
this.serialHandler = new SerialHandler(this.events, {
debug: this.debugSettings
});
// Register connection handlers with the connection manager
this.connectionManager.registerConnectionMethod(this.bluetoothHandler);
this.connectionManager.registerConnectionMethod(this.serialHandler);
// Initialize NMEA parser
this.nmeaParser = new NmeaParser({
events: this.events
});
// Initialize NTRIP client
this.ntripClient = new NtripClient({
events: this.events,
settings: this.settings
});
// Initialize UI components if enabled
if (options.ui !== false) {
this.rtkSettings = new RtkSettings({
events: this.events,
settings: this.settings,
selector: options.rtkSettingsSelector
});
this.rtkStatus = new RtkStatus({
events: this.events,
selector: options.rtkStatusSelector
});
this.deviceSettings = new DeviceSettings({
events: this.events,
settings: this.settings,
selector: options.deviceSettingsSelector
});
}
// Last known position
this.currentPosition = null;
// Setup internal event listeners
this._setupEventListeners();
}
/**
* Set up internal event listeners
* @private
*/
_setupEventListeners() {
// Listen for position updates from NMEA parser
this.events.on('nmea:position', (position) => {
this.currentPosition = position;
this.events.emit('position', position);
});
// Listen for satellite updates from NMEA parser
this.events.on('nmea:satellites', (satellites) => {
this.satellites = satellites;
this.events.emit('satellites', satellites);
});
// Forward RTCM data to device when connected
this.events.on('ntrip:rtcm', (rtcmData) => {
if (this.connectionManager.isConnected()) {
this.connectionManager.sendData(rtcmData.data);
}
});
// Handle device settings application request
this.events.on('device:apply:settings', (settings) => {
this.configureDevice(settings);
});
}
/**
* Connect to a GNSS device using available methods
* @param {Object} options - Connection options
* @returns {Promise<boolean>} Connection success
*/
async connectDevice(options = {}) {
try {
const connected = await this.connectionManager.connect(options);
if (connected) {
// Setup data flow from device to NMEA parser
// We need to listen for both generic and specific device data events
this.events.on('device:data', (data) => {
this.nmeaParser.parse(data);
});
// Also listen for bluetooth-specific data
this.events.on('bluetooth:data', (data) => {
this.nmeaParser.parse(data);
});
// Also listen for serial-specific data
this.events.on('serial:data', (data) => {
this.nmeaParser.parse(data);
});
}
return connected;
} catch (error) {
this.events.emit('connection:error', { message: error.message });
return false;
}
}
/**
* Connect specifically via Bluetooth
* @param {Object} options - Bluetooth connection options
* @returns {Promise<boolean>} Connection success
*/
async connectBluetooth(options = {}) {
try {
// Important: This must be called directly in response to a user gesture
// Create the options for requestDevice
const requestOptions = {
acceptAllDevices: !options.filters,
filters: options.filters || [],
optionalServices: [
// Common UART services
'6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART Service
'0000ffe0-0000-1000-8000-00805f9b34fb', // Nordic UART Service (alternate)
'49535343-fe7d-4ae5-8fa9-9fafd205e455', // HM-10/HM-16/HM-17 Service
'0000fff0-0000-1000-8000-00805f9b34fb', // HC-08/HC-10 Service
// Generic services
'00001800-0000-1000-8000-00805f9b34fb', // Generic Access
'00001801-0000-1000-8000-00805f9b34fb', // Generic Attribute
// SparkFun specific
'0000fe9a-0000-1000-8000-00805f9b34fb', // Custom service
// Classic Bluetooth services
'00001101-0000-1000-8000-00805f9b34fb', // SPP
]
};
// Request device directly (this must happen in direct response to user gesture)
const device = await navigator.bluetooth.requestDevice(requestOptions);
// Now pass the selected device to the connection manager
return this.connectDevice({
...options,
method: 'bluetooth',
deviceObj: device // Pass the selected device object
});
} catch (error) {
// Handle the case where user cancels the dialog
if (error.name === 'NotFoundError') {
this.events.emit('connection:error', { message: 'No device selected' });
return false;
}
this.events.emit('connection:error', { message: error.message });
return false;
}
}
/**
* Connect specifically via Serial
* @param {Object} options - Serial connection options
* @returns {Promise<boolean>} Connection success
*/
async connectSerial(options = {}) {
return this.connectDevice({
...options,
method: 'serial'
});
}
/**
* Connect to NTRIP caster
* @param {Object} options - Connection options
* @returns {Promise<boolean>} Connection success
*/
async connectNtrip(options = {}) {
try {
return await this.ntripClient.connect({
...options,
position: this.currentPosition
});
} catch (error) {
this.events.emit('ntrip:error', { message: error.message });
return false;
}
}
/**
* Disconnect from device
* @returns {Promise<void>}
*/
async disconnectDevice() {
return this.connectionManager.disconnect();
}
/**
* Disconnect from NTRIP
* @returns {Promise<void>}
*/
async disconnectNtrip() {
return this.ntripClient.disconnect();
}
/**
* Get current position
* @returns {Object|null} Current position
*/
getPosition() {
return this.currentPosition;
}
/**
* Get satellite information
* @returns {Array|null} Satellite information
*/
getSatellites() {
return this.satellites || [];
}
/**
* Get the settings manager
* @returns {Settings} Settings manager
*/
getSettings() {
return this.settings;
}
/**
* Configure the device with specified settings
* @param {Object} settings - Device settings to apply
* @returns {Promise<boolean>} Success flag
*/
async configureDevice(settings = {}) {
try {
// Check if connected
if (!this.connectionManager.isConnected()) {
this.events.emit('device:error', { message: 'Not connected to any device' });
return false;
}
// If no settings provided, use saved device settings
const deviceSettings = settings.gnssSystems ?
settings :
this.settings.getSection('device');
// Emit event before applying settings
this.events.emit('device:configuring', { settings: deviceSettings });
// Currently this is a placeholder - in a real implementation,
// you would send configuration commands to the device
// For example, with u-blox devices, you'd send UBX configuration messages
// For now, we'll just simulate a successful configuration
setTimeout(() => {
this.events.emit('device:configured', {
settings: deviceSettings,
success: true
});
}, 1000);
return true;
} catch (error) {
this.events.emit('device:error', {
message: 'Error configuring device',
error
});
return false;
}
}
/**
* Subscribe to an event
* @param {string} event - Event name
* @param {Function} callback - Callback function
* @returns {Function} Unsubscribe function
*/
on(event, callback) {
return this.events.on(event, callback);
}
/**
* Unsubscribe from an event
* @param {string} event - Event name
* @param {Function} callback - Callback function
*/
off(event, callback) {
this.events.off(event, callback);
}
}
// Export the main module class
export { GnssModule };
// Export other classes for extensibility
export { EventEmitter };
export { BluetoothConnection };
export { NmeaParser };
export { NtripClient };
export { Settings };
export { ConnectionManager };
export { BluetoothHandler };
export { SerialHandler };
export { RtkSettings };
export { RtkStatus };
export { DeviceSettings };
// Export default GnssModule for backward compatibility
export default GnssModule;