765 lines
28 KiB
JavaScript
Executable File
765 lines
28 KiB
JavaScript
Executable File
/** @odoo-module **/
|
|
|
|
/**
|
|
* Bluetooth Printer Manager
|
|
*
|
|
* Manages bluetooth connections to thermal printers using the Web Bluetooth API.
|
|
* Handles device discovery, connection management, auto-reconnection, and data transmission.
|
|
*/
|
|
|
|
// Custom error classes for better error handling
|
|
export class BluetoothNotAvailableError extends Error {
|
|
constructor(message = 'Web Bluetooth API is not available') {
|
|
super(message);
|
|
this.name = 'BluetoothNotAvailableError';
|
|
}
|
|
}
|
|
|
|
export class UserCancelledError extends Error {
|
|
constructor(message = 'User cancelled device selection') {
|
|
super(message);
|
|
this.name = 'UserCancelledError';
|
|
}
|
|
}
|
|
|
|
export class DeviceNotFoundError extends Error {
|
|
constructor(message = 'Bluetooth device not found') {
|
|
super(message);
|
|
this.name = 'DeviceNotFoundError';
|
|
}
|
|
}
|
|
|
|
export class ConnectionFailedError extends Error {
|
|
constructor(message = 'Failed to connect to bluetooth device') {
|
|
super(message);
|
|
this.name = 'ConnectionFailedError';
|
|
}
|
|
}
|
|
|
|
export class PrinterNotConnectedError extends Error {
|
|
constructor(message = 'Printer is not connected') {
|
|
super(message);
|
|
this.name = 'PrinterNotConnectedError';
|
|
}
|
|
}
|
|
|
|
export class TransmissionError extends Error {
|
|
constructor(message = 'Failed to transmit data to printer') {
|
|
super(message);
|
|
this.name = 'TransmissionError';
|
|
}
|
|
}
|
|
|
|
export class PrinterBusyError extends Error {
|
|
constructor(message = 'Printer is busy processing another job') {
|
|
super(message);
|
|
this.name = 'PrinterBusyError';
|
|
}
|
|
}
|
|
|
|
export class TimeoutError extends Error {
|
|
constructor(message = 'Operation timed out') {
|
|
super(message);
|
|
this.name = 'TimeoutError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bluetooth Printer Manager Service
|
|
*/
|
|
export class BluetoothPrinterManager {
|
|
constructor(errorNotificationService = null) {
|
|
// Debug logging
|
|
this.debugMode = true; // Set to false to disable verbose logging
|
|
|
|
// Connection state
|
|
this.device = null;
|
|
this.server = null;
|
|
this.service = null;
|
|
this.characteristic = null;
|
|
this.connectionStatus = 'disconnected';
|
|
|
|
// Reconnection state
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 3;
|
|
this.reconnectDelays = [1000, 2000, 4000]; // Exponential backoff in milliseconds
|
|
this.isReconnecting = false;
|
|
this.autoReconnectEnabled = true;
|
|
|
|
// Event listeners
|
|
this.eventListeners = {
|
|
'connection-status-changed': [],
|
|
'print-completed': [],
|
|
'print-failed': [],
|
|
'reconnection-attempt': [],
|
|
'reconnection-success': [],
|
|
'reconnection-failure': []
|
|
};
|
|
|
|
// Printer state
|
|
this.lastError = null;
|
|
this.isPrinting = false;
|
|
|
|
// Error notification service
|
|
this.errorNotificationService = errorNotificationService;
|
|
|
|
// Bluetooth service UUID for serial port profile (commonly used by thermal printers)
|
|
// Using the standard Serial Port Profile UUID (SPP)
|
|
// Most thermal printers use SPP for communication
|
|
this.serviceUUID = '00001101-0000-1000-8000-00805f9b34fb'; // Serial Port Profile
|
|
this.characteristicUUID = '00002af1-0000-1000-8000-00805f9b34fb';
|
|
}
|
|
|
|
/**
|
|
* Set error notification service
|
|
* @param {Object} errorNotificationService - Error notification service instance
|
|
*/
|
|
setErrorNotificationService(errorNotificationService) {
|
|
this.errorNotificationService = errorNotificationService;
|
|
}
|
|
|
|
/**
|
|
* Debug log helper
|
|
* @private
|
|
* @param {string} message - Log message
|
|
* @param {any} data - Optional data to log
|
|
*/
|
|
_log(message, data = null) {
|
|
if (this.debugMode) {
|
|
if (data) {
|
|
console.log(`[BluetoothPrinter] ${message}`, data);
|
|
} else {
|
|
console.log(`[BluetoothPrinter] ${message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if Web Bluetooth API is available
|
|
* @returns {boolean} True if available
|
|
*/
|
|
isBluetoothAvailable() {
|
|
const available = typeof navigator !== 'undefined' &&
|
|
navigator.bluetooth !== undefined;
|
|
this._log(`Bluetooth API available: ${available}`);
|
|
return available;
|
|
}
|
|
|
|
/**
|
|
* Scan for available bluetooth devices
|
|
* @returns {Promise<Array>} Array of bluetooth devices
|
|
* @throws {BluetoothNotAvailableError} If Web Bluetooth API is not available
|
|
* @throws {UserCancelledError} If user cancels device selection
|
|
*/
|
|
async scanDevices() {
|
|
if (!this.isBluetoothAvailable()) {
|
|
const error = new BluetoothNotAvailableError();
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(error, { operation: 'scanDevices' });
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
try {
|
|
this._log('Starting device scan...');
|
|
|
|
// Request bluetooth device with filters for thermal printers
|
|
// Many thermal printers advertise with specific name patterns
|
|
const device = await navigator.bluetooth.requestDevice({
|
|
filters: [
|
|
{ namePrefix: 'RPP' }, // RPP02 and similar
|
|
{ namePrefix: 'Printer' }, // Generic printer names
|
|
{ namePrefix: 'POS' }, // POS printers
|
|
{ namePrefix: 'TM-' }, // Epson TM series
|
|
{ namePrefix: 'SM-' }, // Star Micronics
|
|
{ namePrefix: 'BlueTooth' },// Generic bluetooth printers
|
|
{ namePrefix: 'BT-' }, // BT prefix printers
|
|
{ namePrefix: 'MTP' }, // Mobile thermal printers
|
|
{ namePrefix: 'SPP' }, // Serial Port Profile devices
|
|
],
|
|
optionalServices: [
|
|
this.serviceUUID, // Serial Port Profile
|
|
'000018f0-0000-1000-8000-00805f9b34fb', // Alternative service
|
|
'49535343-fe7d-4ae5-8fa9-9fafd205e455', // Microchip transparent UART
|
|
'0000ffe0-0000-1000-8000-00805f9b34fb', // Common serial service
|
|
'battery_service'
|
|
]
|
|
});
|
|
|
|
this._log('Device found via filtered scan', { name: device.name, id: device.id });
|
|
return [device];
|
|
} catch (error) {
|
|
this._log('Filtered scan failed, trying acceptAllDevices fallback', error.name);
|
|
|
|
// If filtered search fails, try acceptAllDevices as fallback
|
|
if (error.name === 'NotFoundError') {
|
|
try {
|
|
this._log('Attempting acceptAllDevices scan...');
|
|
const device = await navigator.bluetooth.requestDevice({
|
|
acceptAllDevices: true,
|
|
optionalServices: [
|
|
this.serviceUUID,
|
|
'000018f0-0000-1000-8000-00805f9b34fb',
|
|
'49535343-fe7d-4ae5-8fa9-9fafd205e455',
|
|
'0000ffe0-0000-1000-8000-00805f9b34fb',
|
|
'battery_service'
|
|
]
|
|
});
|
|
this._log('Device found via acceptAllDevices', { name: device.name, id: device.id });
|
|
return [device];
|
|
} catch (fallbackError) {
|
|
this._log('AcceptAllDevices fallback also failed', fallbackError.name);
|
|
error = fallbackError;
|
|
}
|
|
}
|
|
if (error.name === 'NotFoundError') {
|
|
const cancelError = new UserCancelledError();
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(cancelError, { operation: 'scanDevices' });
|
|
}
|
|
throw cancelError;
|
|
}
|
|
|
|
// Log unexpected errors
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.logError(error, { operation: 'scanDevices' });
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect to a bluetooth printer
|
|
* @param {string} deviceId - Bluetooth device ID (or device object)
|
|
* @returns {Promise<Object>} Connection object with device info
|
|
* @throws {ConnectionFailedError} If connection fails
|
|
* @throws {BluetoothNotAvailableError} If Web Bluetooth API is not available
|
|
*/
|
|
async connectToPrinter(deviceId) {
|
|
if (!this.isBluetoothAvailable()) {
|
|
const error = new BluetoothNotAvailableError();
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(error, { operation: 'connectToPrinter' });
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
try {
|
|
this._log('Attempting to connect to printer...', deviceId);
|
|
this._setConnectionStatus('connecting');
|
|
|
|
// If deviceId is actually a device object from scanDevices, use it directly
|
|
let device;
|
|
if (typeof deviceId === 'object' && deviceId.gatt) {
|
|
this._log('Using device object directly');
|
|
device = deviceId;
|
|
} else {
|
|
// Try to get previously paired device
|
|
this._log('Looking for previously paired device...');
|
|
const devices = await navigator.bluetooth.getDevices();
|
|
this._log(`Found ${devices.length} previously paired devices`);
|
|
device = devices.find(d => d.id === deviceId || d.name === deviceId);
|
|
|
|
if (!device) {
|
|
this._log('Device not found in paired devices');
|
|
|
|
const error = new DeviceNotFoundError(`Device ${deviceId} not found`);
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(error, {
|
|
operation: 'connectToPrinter',
|
|
deviceId: deviceId
|
|
});
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
this.device = device;
|
|
this._log('Device selected', { name: this.device.name, id: this.device.id });
|
|
|
|
// Set up disconnect handler
|
|
this.device.addEventListener('gattserverdisconnected', () => {
|
|
this._log('GATT server disconnected event fired');
|
|
this._onDisconnected();
|
|
});
|
|
|
|
// Connect to GATT server
|
|
this._log('Connecting to GATT server...');
|
|
this.server = await this.device.gatt.connect();
|
|
this._log('GATT server connected successfully');
|
|
|
|
// Try to get the printer service - try multiple common UUIDs
|
|
const serviceUUIDs = [
|
|
this.serviceUUID, // Serial Port Profile
|
|
'000018f0-0000-1000-8000-00805f9b34fb', // Alternative serial service
|
|
'49535343-fe7d-4ae5-8fa9-9fafd205e455', // Microchip transparent UART
|
|
'0000ffe0-0000-1000-8000-00805f9b34fb', // Common serial service
|
|
'6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART Service
|
|
];
|
|
|
|
const characteristicUUIDs = [
|
|
this.characteristicUUID, // Default characteristic
|
|
'00002af1-0000-1000-8000-00805f9b34fb', // Write characteristic
|
|
'49535343-8841-43f4-a8d4-ecbe34729bb3', // Microchip TX
|
|
'0000ffe1-0000-1000-8000-00805f9b34fb', // Common write characteristic
|
|
'6e400002-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART TX
|
|
];
|
|
|
|
let serviceFound = false;
|
|
|
|
for (const serviceUUID of serviceUUIDs) {
|
|
try {
|
|
console.log(`Trying service UUID: ${serviceUUID}`);
|
|
this.service = await this.server.getPrimaryService(serviceUUID);
|
|
|
|
// Try to find a writable characteristic
|
|
for (const charUUID of characteristicUUIDs) {
|
|
try {
|
|
this.characteristic = await this.service.getCharacteristic(charUUID);
|
|
console.log(`Found characteristic: ${charUUID}`);
|
|
serviceFound = true;
|
|
break;
|
|
} catch (charError) {
|
|
// Try next characteristic
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (serviceFound) {
|
|
break;
|
|
}
|
|
} catch (serviceError) {
|
|
// Try next service UUID
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!serviceFound) {
|
|
console.warn('Standard printer services not found, attempting to use any available writable characteristic');
|
|
|
|
// Last resort: try to find any writable characteristic
|
|
try {
|
|
const services = await this.server.getPrimaryServices();
|
|
for (const service of services) {
|
|
const characteristics = await service.getCharacteristics();
|
|
for (const char of characteristics) {
|
|
if (char.properties.write || char.properties.writeWithoutResponse) {
|
|
this.service = service;
|
|
this.characteristic = char;
|
|
console.log(`Using fallback characteristic: ${char.uuid}`);
|
|
serviceFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (serviceFound) break;
|
|
}
|
|
} catch (fallbackError) {
|
|
console.error('Failed to find any writable characteristic:', fallbackError);
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.logError(fallbackError, {
|
|
operation: 'findWritableCharacteristic',
|
|
deviceName: this.device.name
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
this._setConnectionStatus('connected');
|
|
this.reconnectAttempts = 0;
|
|
this.lastError = null;
|
|
|
|
return {
|
|
deviceId: this.device.id,
|
|
deviceName: this.device.name,
|
|
connected: true
|
|
};
|
|
} catch (error) {
|
|
this._setConnectionStatus('error');
|
|
this.lastError = error.message;
|
|
|
|
if (error instanceof DeviceNotFoundError) {
|
|
throw error;
|
|
}
|
|
|
|
const connectionError = new ConnectionFailedError(`Failed to connect: ${error.message}`);
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(connectionError, {
|
|
operation: 'connectToPrinter',
|
|
originalError: error.message
|
|
});
|
|
}
|
|
throw connectionError;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disconnect from the bluetooth printer
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async disconnect() {
|
|
this.autoReconnectEnabled = false;
|
|
|
|
if (this.server && this.server.connected) {
|
|
try {
|
|
await this.server.disconnect();
|
|
} catch (error) {
|
|
console.error('Error during disconnect:', error);
|
|
}
|
|
}
|
|
|
|
this.device = null;
|
|
this.server = null;
|
|
this.service = null;
|
|
this.characteristic = null;
|
|
|
|
this._setConnectionStatus('disconnected');
|
|
this.lastError = null;
|
|
}
|
|
|
|
/**
|
|
* Send ESC/POS data to the printer (OPTIMIZED FOR SPEED)
|
|
* @param {Uint8Array} escposData - ESC/POS command bytes
|
|
* @returns {Promise<boolean>} True if transmission successful
|
|
* @throws {PrinterNotConnectedError} If printer is not connected
|
|
* @throws {TransmissionError} If transmission fails
|
|
* @throws {PrinterBusyError} If printer is busy
|
|
*/
|
|
async sendData(escposData) {
|
|
if (!this.server || !this.server.connected) {
|
|
const error = new PrinterNotConnectedError();
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(error, { operation: 'sendData' });
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
if (this.isPrinting) {
|
|
const error = new PrinterBusyError();
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(error, { operation: 'sendData' });
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
if (!this.characteristic) {
|
|
const error = new TransmissionError('Printer characteristic not available');
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(error, {
|
|
operation: 'sendData',
|
|
reason: 'characteristic_unavailable'
|
|
});
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
try {
|
|
this.isPrinting = true;
|
|
const startTime = performance.now();
|
|
|
|
// OPTIMIZED: Use larger chunks for graphics data (faster transmission)
|
|
// Graphics data can handle larger chunks than text commands
|
|
const isLargeData = escposData.length > 1000;
|
|
const chunkSize = isLargeData ? 512 : 20; // Much larger chunks for graphics
|
|
|
|
const chunks = [];
|
|
for (let i = 0; i < escposData.length; i += chunkSize) {
|
|
chunks.push(escposData.slice(i, i + chunkSize));
|
|
}
|
|
|
|
console.log(`[Bluetooth] Sending ${chunks.length} chunks (${escposData.length} bytes, ${chunkSize} bytes/chunk)`);
|
|
|
|
// Determine write method based on characteristic properties
|
|
const useWriteWithoutResponse = this.characteristic.properties.writeWithoutResponse;
|
|
const useWrite = this.characteristic.properties.write;
|
|
|
|
if (!useWrite && !useWriteWithoutResponse) {
|
|
throw new Error('Characteristic does not support write operations');
|
|
}
|
|
|
|
// OPTIMIZED: Reduce delays for faster transmission
|
|
const delay = isLargeData ?
|
|
(useWriteWithoutResponse ? 10 : 5) : // Much shorter delays for graphics
|
|
(useWriteWithoutResponse ? 50 : 25); // Normal delays for text
|
|
|
|
// Send each chunk
|
|
for (let i = 0; i < chunks.length; i++) {
|
|
const chunk = chunks[i];
|
|
|
|
try {
|
|
if (useWriteWithoutResponse) {
|
|
// Faster but no acknowledgment
|
|
await this.characteristic.writeValueWithoutResponse(chunk);
|
|
} else {
|
|
// Slower but with acknowledgment
|
|
await this.characteristic.writeValue(chunk);
|
|
}
|
|
|
|
// OPTIMIZED: Minimal delay between chunks
|
|
if (delay > 0) {
|
|
await this._sleep(delay);
|
|
}
|
|
|
|
// Progress logging every 20%
|
|
if (i % Math.ceil(chunks.length / 5) === 0) {
|
|
const progress = Math.round((i / chunks.length) * 100);
|
|
console.log(`[Bluetooth] Progress: ${progress}%`);
|
|
}
|
|
} catch (chunkError) {
|
|
console.error(`Failed to send chunk ${i + 1}/${chunks.length}:`, chunkError);
|
|
throw chunkError;
|
|
}
|
|
}
|
|
|
|
const endTime = performance.now();
|
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
const speed = (escposData.length / 1024 / (duration || 1)).toFixed(2);
|
|
|
|
console.log(`[Bluetooth] Transmission complete in ${duration}s (${speed} KB/s)`);
|
|
this.isPrinting = false;
|
|
this._emit('print-completed', { success: true });
|
|
|
|
return true;
|
|
} catch (error) {
|
|
this.isPrinting = false;
|
|
this._emit('print-failed', { error: error.message });
|
|
|
|
const transmissionError = new TransmissionError(`Failed to send data: ${error.message}`);
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleError(transmissionError, {
|
|
operation: 'sendData',
|
|
dataSize: escposData.length,
|
|
originalError: error.message
|
|
});
|
|
}
|
|
throw transmissionError;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current connection status
|
|
* @returns {string} Connection status: 'connected', 'disconnected', 'connecting', 'error'
|
|
*/
|
|
getConnectionStatus() {
|
|
return this.connectionStatus;
|
|
}
|
|
|
|
/**
|
|
* Get detailed connection information
|
|
* @returns {Object} Connection status object
|
|
*/
|
|
getConnectionInfo() {
|
|
return {
|
|
status: this.connectionStatus,
|
|
deviceName: this.device ? this.device.name : null,
|
|
deviceId: this.device ? this.device.id : null,
|
|
lastError: this.lastError,
|
|
reconnectAttempts: this.reconnectAttempts,
|
|
isReconnecting: this.isReconnecting,
|
|
timestamp: Date.now()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Attempt automatic reconnection with exponential backoff
|
|
* @returns {Promise<boolean>} True if reconnection successful
|
|
*/
|
|
async autoReconnect() {
|
|
if (!this.autoReconnectEnabled || this.isReconnecting) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.device) {
|
|
console.warn('Cannot reconnect: no device information available');
|
|
return false;
|
|
}
|
|
|
|
this.isReconnecting = true;
|
|
this._setConnectionStatus('connecting');
|
|
|
|
for (let attempt = 0; attempt < this.maxReconnectAttempts; attempt++) {
|
|
this.reconnectAttempts = attempt + 1;
|
|
|
|
// Emit reconnection attempt event
|
|
this._emit('reconnection-attempt', {
|
|
attempt: this.reconnectAttempts,
|
|
maxAttempts: this.maxReconnectAttempts
|
|
});
|
|
|
|
// Notify via error service
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleReconnectionAttempt(
|
|
this.reconnectAttempts,
|
|
this.maxReconnectAttempts
|
|
);
|
|
}
|
|
|
|
try {
|
|
console.log(`Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
|
|
|
// Try to reconnect
|
|
await this.connectToPrinter(this.device);
|
|
|
|
this.isReconnecting = false;
|
|
this.reconnectAttempts = 0;
|
|
|
|
console.log('Reconnection successful');
|
|
|
|
// Emit reconnection success event
|
|
this._emit('reconnection-success', {
|
|
deviceName: this.device.name
|
|
});
|
|
|
|
// Notify via error service
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleReconnectionSuccess(this.device.name);
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`Reconnection attempt ${this.reconnectAttempts} failed:`, error);
|
|
|
|
// Log the error
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.logError(error, {
|
|
operation: 'autoReconnect',
|
|
attempt: this.reconnectAttempts,
|
|
maxAttempts: this.maxReconnectAttempts
|
|
});
|
|
}
|
|
|
|
// Wait before next attempt (exponential backoff)
|
|
if (attempt < this.maxReconnectAttempts - 1) {
|
|
const delay = this.reconnectDelays[attempt];
|
|
console.log(`Waiting ${delay}ms before next attempt`);
|
|
await this._sleep(delay);
|
|
}
|
|
}
|
|
}
|
|
|
|
// All reconnection attempts failed
|
|
this.isReconnecting = false;
|
|
this._setConnectionStatus('error');
|
|
this.lastError = 'Reconnection failed after maximum attempts';
|
|
|
|
console.error('All reconnection attempts failed');
|
|
|
|
// Emit reconnection failure event
|
|
this._emit('reconnection-failure', {
|
|
attempts: this.maxReconnectAttempts
|
|
});
|
|
|
|
// Notify via error service
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleReconnectionFailure();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Enable or disable automatic reconnection
|
|
* @param {boolean} enabled - Whether to enable auto-reconnect
|
|
*/
|
|
setAutoReconnect(enabled) {
|
|
this.autoReconnectEnabled = enabled;
|
|
}
|
|
|
|
/**
|
|
* Add event listener
|
|
* @param {string} event - Event name
|
|
* @param {Function} callback - Callback function
|
|
*/
|
|
addEventListener(event, callback) {
|
|
if (this.eventListeners[event]) {
|
|
this.eventListeners[event].push(callback);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove event listener
|
|
* @param {string} event - Event name
|
|
* @param {Function} callback - Callback function
|
|
*/
|
|
removeEventListener(event, callback) {
|
|
if (this.eventListeners[event]) {
|
|
const index = this.eventListeners[event].indexOf(callback);
|
|
if (index > -1) {
|
|
this.eventListeners[event].splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle disconnection event
|
|
* @private
|
|
*/
|
|
_onDisconnected() {
|
|
console.log('Bluetooth device disconnected');
|
|
this._setConnectionStatus('disconnected');
|
|
|
|
// Attempt auto-reconnection if enabled
|
|
if (this.autoReconnectEnabled) {
|
|
console.log('Starting auto-reconnection...');
|
|
this.autoReconnect().catch(error => {
|
|
console.error('Auto-reconnection failed:', error);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set connection status and emit event
|
|
* @private
|
|
* @param {string} status - New connection status
|
|
*/
|
|
_setConnectionStatus(status) {
|
|
const oldStatus = this.connectionStatus;
|
|
this.connectionStatus = status;
|
|
|
|
if (oldStatus !== status) {
|
|
const statusData = {
|
|
oldStatus,
|
|
newStatus: status,
|
|
deviceName: this.device ? this.device.name : null,
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
this._emit('connection-status-changed', statusData);
|
|
|
|
// Notify error service about status change
|
|
if (this.errorNotificationService) {
|
|
this.errorNotificationService.handleStatusChange(statusData);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Emit event to all listeners
|
|
* @private
|
|
* @param {string} event - Event name
|
|
* @param {Object} data - Event data
|
|
*/
|
|
_emit(event, data) {
|
|
if (this.eventListeners[event]) {
|
|
this.eventListeners[event].forEach(callback => {
|
|
try {
|
|
callback(data);
|
|
} catch (error) {
|
|
console.error(`Error in event listener for ${event}:`, error);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sleep for specified milliseconds
|
|
* @private
|
|
* @param {number} ms - Milliseconds to sleep
|
|
* @returns {Promise<void>}
|
|
*/
|
|
_sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
}
|
|
|
|
export default BluetoothPrinterManager;
|