/**
* RTK Settings UI Component
*
* This component provides a user interface for configuring NTRIP RTK correction settings
* and managing connections to NTRIP casters.
*/
export class RtkSettings {
/**
* Create an RTK settings component
* @param {Object} options - Configuration options
* @param {EventEmitter} options.events - Event emitter for communication
* @param {Settings} options.settings - Settings manager
* @param {string} options.selector - CSS selector for the container element
*/
constructor(options = {}) {
this.events = options.events;
this.settings = options.settings;
this.isConnected = false;
this.isConnecting = false;
// Find container element if selector provided
if (options.selector) {
this.container = document.querySelector(options.selector);
}
// If no container, don't initialize UI
if (!this.container) {
console.warn('RtkSettings: No container element found. UI will not be initialized.');
return;
}
// Cache frequently used elements
this.elements = {};
// Add CSS for proper styling
this.addStyles();
// Create UI elements
this.initializeUI();
// Set up event listeners
this.setupEventListeners();
// Listen for RTK events
this.registerEventListeners();
// Load saved configuration
this.loadSavedConfig();
}
/**
* Add required CSS styles to the document
*/
addStyles() {
// Check if styles already exist
if (document.getElementById('rtk-settings-styles')) {
return;
}
// Create style element
const style = document.createElement('style');
style.id = 'rtk-settings-styles';
style.textContent = `
.rtk-settings-container {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
margin: 0 auto;
padding: 15px;
background-color: #f7f7f7;
border-radius: 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.rtk-settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #e0e0e0;
}
.rtk-settings-title {
font-size: 18px;
font-weight: 500;
margin: 0;
}
.rtk-settings-enable {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.rtk-settings-form {
display: grid;
grid-gap: 10px;
}
.form-group {
display: flex;
flex-direction: column;
}
.form-group label {
font-size: 14px;
color: #555;
margin-bottom: 5px;
}
.form-group input, .form-group select {
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #4285F4;
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.25);
}
.rtk-settings-actions {
display: flex;
justify-content: space-between;
margin-top: 15px;
}
.rtk-status-display {
display: flex;
align-items: center;
margin-top: 15px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
.rtk-status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.rtk-status-indicator.disconnected { background-color: #9e9e9e; }
.rtk-status-indicator.connecting { background-color: #ff9800; }
.rtk-status-indicator.connected { background-color: #4caf50; }
.rtk-status-indicator.error { background-color: #f44336; }
.rtk-button {
padding: 8px 16px;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.rtk-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.rtk-button.primary {
background-color: #4285F4;
color: white;
}
.rtk-button.secondary {
background-color: #f1f1f1;
color: #333;
}
.rtk-button.danger {
background-color: #f44336;
color: white;
}
.rtk-button.primary:hover { background-color: #3367d6; }
.rtk-button.secondary:hover { background-color: #e0e0e0; }
.rtk-button.danger:hover { background-color: #d32f2f; }
.rtk-toggle {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
margin-right: 8px;
}
.rtk-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.rtk-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 20px;
}
.rtk-toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
.rtk-toggle input:checked + .rtk-toggle-slider {
background-color: #4285F4;
}
.rtk-toggle input:focus + .rtk-toggle-slider {
box-shadow: 0 0 1px #4285F4;
}
.rtk-toggle input:checked + .rtk-toggle-slider:before {
transform: translateX(20px);
}
.rtk-correction-stats {
margin-top: 10px;
font-size: 12px;
color: #666;
}
.rtk-correction-stats div {
margin-bottom: 4px;
}
@media (min-width: 768px) {
.rtk-settings-form {
grid-template-columns: 1fr 1fr;
grid-gap: 15px;
}
}
`;
// Add style to document
document.head.appendChild(style);
}
/**
* Initialize the UI elements
*/
initializeUI() {
if (!this.container) return;
// Clear container
this.container.innerHTML = '';
// Create main container
const settingsContainer = document.createElement('div');
settingsContainer.className = 'rtk-settings-container';
// Create header
const header = document.createElement('div');
header.className = 'rtk-settings-header';
const title = document.createElement('h2');
title.className = 'rtk-settings-title';
title.textContent = 'RTK Correction Settings';
header.appendChild(title);
settingsContainer.appendChild(header);
// Create enable toggle
const enableContainer = document.createElement('div');
enableContainer.className = 'rtk-settings-enable';
const toggleLabel = document.createElement('label');
toggleLabel.className = 'rtk-toggle';
const toggleInput = document.createElement('input');
toggleInput.type = 'checkbox';
toggleInput.id = 'rtk-enable';
const toggleSlider = document.createElement('span');
toggleSlider.className = 'rtk-toggle-slider';
toggleLabel.appendChild(toggleInput);
toggleLabel.appendChild(toggleSlider);
const toggleText = document.createElement('span');
toggleText.textContent = 'Enable RTK Corrections';
enableContainer.appendChild(toggleLabel);
enableContainer.appendChild(toggleText);
settingsContainer.appendChild(enableContainer);
// Create form
const form = document.createElement('form');
form.className = 'rtk-settings-form';
// Connection method
const connectionMethodGroup = document.createElement('div');
connectionMethodGroup.className = 'form-group';
const connectionMethodLabel = document.createElement('label');
connectionMethodLabel.htmlFor = 'rtk-connection-mode';
connectionMethodLabel.textContent = 'Connection Method';
const connectionMethodSelect = document.createElement('select');
connectionMethodSelect.id = 'rtk-connection-mode';
const autoOption = document.createElement('option');
autoOption.value = 'auto';
autoOption.textContent = 'Auto (WebSocket preferred)';
const websocketOption = document.createElement('option');
websocketOption.value = 'websocket';
websocketOption.textContent = 'WebSocket';
const directOption = document.createElement('option');
directOption.value = 'direct';
directOption.textContent = 'Direct';
const proxyOption = document.createElement('option');
proxyOption.value = 'proxy';
proxyOption.textContent = 'HTTP Proxy';
connectionMethodSelect.appendChild(autoOption);
connectionMethodSelect.appendChild(websocketOption);
connectionMethodSelect.appendChild(directOption);
connectionMethodSelect.appendChild(proxyOption);
connectionMethodGroup.appendChild(connectionMethodLabel);
connectionMethodGroup.appendChild(connectionMethodSelect);
form.appendChild(connectionMethodGroup);
// Caster host
const hostGroup = document.createElement('div');
hostGroup.className = 'form-group';
const hostLabel = document.createElement('label');
hostLabel.htmlFor = 'rtk-caster-host';
hostLabel.textContent = 'NTRIP Caster Host';
const hostInput = document.createElement('input');
hostInput.type = 'text';
hostInput.id = 'rtk-caster-host';
hostInput.placeholder = 'caster.example.com';
hostGroup.appendChild(hostLabel);
hostGroup.appendChild(hostInput);
form.appendChild(hostGroup);
// Caster port
const portGroup = document.createElement('div');
portGroup.className = 'form-group';
const portLabel = document.createElement('label');
portLabel.htmlFor = 'rtk-caster-port';
portLabel.textContent = 'NTRIP Caster Port';
const portInput = document.createElement('input');
portInput.type = 'number';
portInput.id = 'rtk-caster-port';
portInput.value = 2101;
portGroup.appendChild(portLabel);
portGroup.appendChild(portInput);
form.appendChild(portGroup);
// Mountpoint
const mountpointGroup = document.createElement('div');
mountpointGroup.className = 'form-group';
const mountpointLabel = document.createElement('label');
mountpointLabel.htmlFor = 'rtk-mountpoint';
mountpointLabel.textContent = 'Mountpoint';
const mountpointInput = document.createElement('input');
mountpointInput.type = 'text';
mountpointInput.id = 'rtk-mountpoint';
mountpointInput.placeholder = 'MOUNTPOINT';
mountpointGroup.appendChild(mountpointLabel);
mountpointGroup.appendChild(mountpointInput);
form.appendChild(mountpointGroup);
// Username
const usernameGroup = document.createElement('div');
usernameGroup.className = 'form-group';
const usernameLabel = document.createElement('label');
usernameLabel.htmlFor = 'rtk-username';
usernameLabel.textContent = 'Username';
const usernameInput = document.createElement('input');
usernameInput.type = 'text';
usernameInput.id = 'rtk-username';
usernameInput.placeholder = 'username (optional)';
usernameGroup.appendChild(usernameLabel);
usernameGroup.appendChild(usernameInput);
form.appendChild(usernameGroup);
// Password
const passwordGroup = document.createElement('div');
passwordGroup.className = 'form-group';
const passwordLabel = document.createElement('label');
passwordLabel.htmlFor = 'rtk-password';
passwordLabel.textContent = 'Password';
const passwordInput = document.createElement('input');
passwordInput.type = 'password';
passwordInput.id = 'rtk-password';
passwordInput.placeholder = 'password (optional)';
passwordGroup.appendChild(passwordLabel);
passwordGroup.appendChild(passwordInput);
form.appendChild(passwordGroup);
// Send GGA toggle
const ggaGroup = document.createElement('div');
ggaGroup.className = 'form-group';
const ggaLabel = document.createElement('label');
ggaLabel.htmlFor = 'rtk-send-gga';
ggaLabel.className = 'rtk-toggle';
const ggaInput = document.createElement('input');
ggaInput.type = 'checkbox';
ggaInput.id = 'rtk-send-gga';
ggaInput.checked = true;
const ggaSlider = document.createElement('span');
ggaSlider.className = 'rtk-toggle-slider';
ggaLabel.appendChild(ggaInput);
ggaLabel.appendChild(ggaSlider);
const ggaText = document.createElement('span');
ggaText.textContent = 'Send position to caster (GGA)';
ggaGroup.appendChild(ggaLabel);
ggaGroup.appendChild(ggaText);
form.appendChild(ggaGroup);
// GGA update interval
const ggaIntervalGroup = document.createElement('div');
ggaIntervalGroup.className = 'form-group';
const ggaIntervalLabel = document.createElement('label');
ggaIntervalLabel.htmlFor = 'rtk-gga-interval';
ggaIntervalLabel.textContent = 'GGA Update Interval (seconds)';
const ggaIntervalInput = document.createElement('input');
ggaIntervalInput.type = 'number';
ggaIntervalInput.id = 'rtk-gga-interval';
ggaIntervalInput.min = 1;
ggaIntervalInput.max = 60;
ggaIntervalInput.value = 10;
ggaIntervalGroup.appendChild(ggaIntervalLabel);
ggaIntervalGroup.appendChild(ggaIntervalInput);
form.appendChild(ggaIntervalGroup);
// Auto reconnect toggle
const reconnectGroup = document.createElement('div');
reconnectGroup.className = 'form-group';
const reconnectLabel = document.createElement('label');
reconnectLabel.htmlFor = 'rtk-auto-reconnect';
reconnectLabel.className = 'rtk-toggle';
const reconnectInput = document.createElement('input');
reconnectInput.type = 'checkbox';
reconnectInput.id = 'rtk-auto-reconnect';
reconnectInput.checked = true;
const reconnectSlider = document.createElement('span');
reconnectSlider.className = 'rtk-toggle-slider';
reconnectLabel.appendChild(reconnectInput);
reconnectLabel.appendChild(reconnectSlider);
const reconnectText = document.createElement('span');
reconnectText.textContent = 'Auto reconnect on disconnect';
reconnectGroup.appendChild(reconnectLabel);
reconnectGroup.appendChild(reconnectText);
form.appendChild(reconnectGroup);
settingsContainer.appendChild(form);
// Create status display
const statusDisplay = document.createElement('div');
statusDisplay.className = 'rtk-status-display';
const statusIndicator = document.createElement('div');
statusIndicator.className = 'rtk-status-indicator disconnected';
const statusText = document.createElement('div');
statusText.textContent = 'Not connected';
statusDisplay.appendChild(statusIndicator);
statusDisplay.appendChild(statusText);
// Correction statistics
const correctionStats = document.createElement('div');
correctionStats.className = 'rtk-correction-stats';
correctionStats.style.display = 'none';
const messagesReceived = document.createElement('div');
messagesReceived.textContent = 'Messages received: 0';
const correctionAge = document.createElement('div');
correctionAge.textContent = 'Correction age: N/A';
const bytesReceived = document.createElement('div');
bytesReceived.textContent = 'Bytes received: 0';
correctionStats.appendChild(messagesReceived);
correctionStats.appendChild(correctionAge);
correctionStats.appendChild(bytesReceived);
statusDisplay.appendChild(correctionStats);
settingsContainer.appendChild(statusDisplay);
// Create action buttons
const actions = document.createElement('div');
actions.className = 'rtk-settings-actions';
const saveButton = document.createElement('button');
saveButton.type = 'button';
saveButton.className = 'rtk-button secondary';
saveButton.textContent = 'Save Settings';
const connectButton = document.createElement('button');
connectButton.type = 'button';
connectButton.className = 'rtk-button primary';
connectButton.textContent = 'Connect';
const disconnectButton = document.createElement('button');
disconnectButton.type = 'button';
disconnectButton.className = 'rtk-button danger';
disconnectButton.textContent = 'Disconnect';
disconnectButton.style.display = 'none';
actions.appendChild(saveButton);
actions.appendChild(connectButton);
actions.appendChild(disconnectButton);
settingsContainer.appendChild(actions);
// Add to container
this.container.appendChild(settingsContainer);
// Store references to elements
this.elements = {
enableToggle: toggleInput,
connectionMethod: connectionMethodSelect,
casterHost: hostInput,
casterPort: portInput,
mountpoint: mountpointInput,
username: usernameInput,
password: passwordInput,
sendGga: ggaInput,
ggaInterval: ggaIntervalInput,
autoReconnect: reconnectInput,
saveButton,
connectButton,
disconnectButton,
statusIndicator,
statusText,
correctionStats,
messagesReceived,
correctionAge,
bytesReceived
};
// Disable form if RTK is disabled
this.updateFormState();
}
/**
* Set up UI event listeners
*/
setupEventListeners() {
if (!this.elements) {
console.warn('RtkSettings: No UI elements found. Event listeners not set up.');
return;
}
// Safely add event listener to an element if it exists
const safeAddListener = (elementKey, eventType, handler) => {
const element = this.elements[elementKey];
if (element) {
element.addEventListener(eventType, handler);
} else {
console.warn(`RtkSettings: Element ${elementKey} not found`);
}
};
// Enable/disable toggle
safeAddListener('enableToggle', 'change', () => {
this.updateFormState();
});
// Connect button
safeAddListener('connectButton', 'click', () => {
this.connect();
});
// Disconnect button
safeAddListener('disconnectButton', 'click', () => {
this.disconnect();
});
// Save settings button
safeAddListener('saveButton', 'click', () => {
this.saveConfig();
});
}
/**
* Register event listeners for GNSS events
*/
registerEventListeners() {
if (!this.events) {
console.warn('RtkSettings: No events emitter provided. Settings will not update.');
return;
}
// Connection status change events
this.events.on('ntrip:connecting', this.handleConnecting.bind(this));
this.events.on('ntrip:connected', this.handleConnected.bind(this));
this.events.on('ntrip:disconnected', this.handleDisconnected.bind(this));
this.events.on('ntrip:error', this.handleError.bind(this));
// Data statistics
this.events.on('ntrip:rtcm', this.handleRtcmData.bind(this));
// Handle position updates
this.events.on('position', this.handlePosition.bind(this));
// Update status periodically
setInterval(() => {
this.updateStats();
}, 1000);
}
/**
* Handle connecting event
* @param {Object} data - Event data
*/
handleConnecting(data) {
this.isConnecting = true;
this.isConnected = false;
this.elements.statusIndicator.className = 'rtk-status-indicator connecting';
this.elements.statusText.textContent = 'Connecting...';
this.elements.connectButton.disabled = true;
this.elements.disconnectButton.disabled = true;
this.updateButtonVisibility();
}
/**
* Handle connected event
* @param {Object} data - Event data
*/
handleConnected(data) {
this.isConnecting = false;
this.isConnected = true;
this.elements.statusIndicator.className = 'rtk-status-indicator connected';
// Check if GGA position is required
if (data.requiresGga) {
this.elements.statusText.textContent = `Connected to ${data.mountpoint} (${data.mode}) - GGA position required`;
// Make sure GGA is enabled
if (!this.elements.sendGga.checked) {
this.elements.sendGga.checked = true;
// Update the config
if (this.gnss.ntripClient) {
this.gnss.ntripClient.config.sendGga = true;
}
// Show info message
console.log('GGA position sharing enabled automatically because the NTRIP caster requires it');
}
// Force a position update if we have one
if (this.gnss.lastPosition) {
setTimeout(() => {
console.log('Sending initial GGA position to NTRIP caster');
this.gnss.updateNtripPosition(this.gnss.lastPosition);
}, 500);
}
} else {
this.elements.statusText.textContent = `Connected to ${data.mountpoint} (${data.mode})`;
}
this.elements.connectButton.disabled = false;
this.elements.disconnectButton.disabled = false;
this.elements.correctionStats.style.display = 'block';
this.updateButtonVisibility();
}
/**
* Handle disconnected event
* @param {Object} data - Event data
*/
handleDisconnected(data) {
this.isConnecting = false;
this.isConnected = false;
this.elements.statusIndicator.className = 'rtk-status-indicator disconnected';
this.elements.statusText.textContent = `Disconnected: ${data.reason || 'Unknown reason'}`;
this.elements.connectButton.disabled = false;
this.elements.disconnectButton.disabled = true;
this.updateButtonVisibility();
}
/**
* Handle error event
* @param {Object} data - Event data
*/
handleError(data) {
this.elements.statusIndicator.className = 'rtk-status-indicator error';
this.elements.statusText.textContent = `Error: ${data.message}`;
if (this.isConnecting) {
this.isConnecting = false;
this.elements.connectButton.disabled = false;
this.elements.disconnectButton.disabled = true;
this.updateButtonVisibility();
}
}
/**
* Handle RTCM data event
* @param {Object} data - Event data
*/
handleRtcmData(data) {
if (!data.stats) return;
const stats = data.stats;
this.elements.messagesReceived.textContent = `Messages received: ${stats.messagesReceived}`;
this.elements.bytesReceived.textContent = `Bytes received: ${this.formatBytes(stats.bytesReceived)}`;
if (stats.correctionAge !== null) {
this.elements.correctionAge.textContent = `Correction age: ${stats.correctionAge.toFixed(1)}s`;
}
}
/**
* Handle position update
* @param {Object} position - Position data
*/
handlePosition(position) {
// Nothing to do for now, but we might want to update something in the UI later
}
/**
* Format bytes to human-readable format
* @param {number} bytes - Number of bytes
* @returns {string} Formatted string
*/
formatBytes(bytes) {
if (bytes < 1024) {
return `${bytes} B`;
} else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(1)} KB`;
} else {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
}
/**
* Update RTCM statistics
*/
updateStats() {
// Only update if connected
if (!this.isConnected || !this.gnss.ntripClient) return;
const stats = this.gnss.ntripClient.getRtcmStats();
if (stats) {
// Update correction age if available
if (stats.correctionAge !== null) {
this.elements.correctionAge.textContent = `Correction age: ${stats.correctionAge.toFixed(1)}s`;
// Highlight old corrections
if (stats.correctionAge > 10) {
this.elements.correctionAge.style.color = '#f44336';
} else if (stats.correctionAge > 5) {
this.elements.correctionAge.style.color = '#ff9800';
} else {
this.elements.correctionAge.style.color = '#666';
}
}
}
}
/**
* Update form state based on enable toggle
*/
updateFormState() {
const enabled = this.elements.enableToggle.checked;
// Enable/disable form elements
const formElements = [
this.elements.connectionMethod,
this.elements.casterHost,
this.elements.casterPort,
this.elements.mountpoint,
this.elements.username,
this.elements.password,
this.elements.sendGga,
this.elements.ggaInterval,
this.elements.autoReconnect
];
formElements.forEach(element => {
element.disabled = !enabled;
});
// Enable/disable buttons
this.elements.saveButton.disabled = !enabled;
this.elements.connectButton.disabled = !enabled || this.isConnected;
// Update button visibility
this.updateButtonVisibility();
}
/**
* Update button visibility based on connection state
*/
updateButtonVisibility() {
// Show/hide connect/disconnect buttons
if (this.isConnected) {
this.elements.connectButton.style.display = 'none';
this.elements.disconnectButton.style.display = 'block';
} else {
this.elements.connectButton.style.display = 'block';
this.elements.disconnectButton.style.display = 'none';
}
}
/**
* Load saved configuration from settings
*/
loadSavedConfig() {
if (!this.settings || !this.settings.rtk || !this.elements) return;
const rtkSettings = this.settings.rtk;
// Safely set value if the element exists
const safeSetValue = (elementKey, value) => {
const element = this.elements[elementKey];
if (element) {
if (typeof element.checked !== 'undefined') {
element.checked = Boolean(value);
} else {
element.value = value || '';
}
}
};
// Update UI elements
safeSetValue('enableToggle', rtkSettings.enabled);
safeSetValue('connectionMethod', rtkSettings.connectionMode || 'auto');
safeSetValue('casterHost', rtkSettings.casterHost || '');
safeSetValue('casterPort', rtkSettings.casterPort || 2101);
safeSetValue('mountpoint', rtkSettings.mountpoint || '');
safeSetValue('username', rtkSettings.username || '');
// We don't restore password for security reasons
safeSetValue('sendGga', rtkSettings.sendGga !== undefined ? rtkSettings.sendGga : true);
safeSetValue('ggaInterval', rtkSettings.ggaUpdateInterval || 10);
safeSetValue('autoReconnect', rtkSettings.autoReconnect !== undefined ? rtkSettings.autoReconnect : true);
// Update form state
this.updateFormState();
}
/**
* Save configuration to settings
*/
saveConfig() {
if (!this.settings || !this.elements) return;
// Safely get value from an element if it exists
const safeGetValue = (elementKey, defaultValue = '') => {
const element = this.elements[elementKey];
if (!element) return defaultValue;
if (typeof element.checked !== 'undefined') {
return element.checked;
} else {
return element.value || defaultValue;
}
};
// Get values from form
const config = {
enabled: safeGetValue('enableToggle', false),
connectionMode: safeGetValue('connectionMethod', 'auto'),
casterHost: safeGetValue('casterHost', ''),
casterPort: parseInt(safeGetValue('casterPort', '2101')) || 2101,
mountpoint: safeGetValue('mountpoint', ''),
username: safeGetValue('username', ''),
password: safeGetValue('password', ''),
sendGga: safeGetValue('sendGga', true),
ggaUpdateInterval: parseInt(safeGetValue('ggaInterval', '10')) || 10,
autoReconnect: safeGetValue('autoReconnect', true)
};
// Save to settings
if (this.settings) {
this.settings.rtk = config;
this.settings.save();
}
// Emit settings update event
if (this.events) {
this.events.emit('rtk:settings:update', config);
}
// If RTK is disabled but we're connected, disconnect
if (!config.enabled && this.isConnected) {
this.disconnect();
}
}
/**
* Connect to NTRIP caster
*/
async connect() {
if (this.isConnected || this.isConnecting) return;
// Get config from form
const config = {
connectionMode: this.elements.connectionMethod.value,
casterHost: this.elements.casterHost.value,
casterPort: parseInt(this.elements.casterPort.value) || 2101,
mountpoint: this.elements.mountpoint.value,
username: this.elements.username.value,
password: this.elements.password.value,
sendGga: this.elements.sendGga.checked,
ggaUpdateInterval: parseInt(this.elements.ggaInterval.value) || 10,
autoReconnect: this.elements.autoReconnect.checked
};
// Validate config
if (!config.casterHost) {
this.handleError({ message: 'Caster host is required' });
return;
}
if (!config.mountpoint) {
this.handleError({ message: 'Mountpoint is required' });
return;
}
// Emit connect event
if (this.events) {
this.events.emit('rtk:connect', config);
}
}
/**
* Disconnect from NTRIP caster
*/
disconnect() {
if (!this.isConnected) return;
// Emit disconnect event
if (this.events) {
this.events.emit('rtk:disconnect');
}
}
}
export default RtkSettings;