/**
* 平台适配器实现
*/
import * as os from 'os';
import { SerialPort } from 'serialport';
import { logger } from '@/utils/logger';
import { IPlatformAdapter, SerialPortInfo, SerialError, PortConfig } from '@/types';
import { ErrorCode } from '@/utils/error-codes';
/**
* 平台信息
*/
export interface PlatformInfo {
platform: string;
arch: string;
version: string;
hasPermissions: boolean;
supportedFeatures: string[];
}
/**
* 端口列表缓存项
*/
interface PortListCache {
ports: SerialPortInfo[];
timestamp: number;
ttl: number;
}
/**
* 平台适配器实现类
*/
export class PlatformAdapter implements IPlatformAdapter {
private platformInfo?: PlatformInfo;
private permissionCache: Map<string, boolean> = new Map();
private portListCache?: PortListCache;
private readonly DEFAULT_CACHE_TTL = 5000; // 5秒缓存
constructor() {
// 初始化缓存
this.initializeCache();
}
/**
* 初始化缓存
*/
private initializeCache(): void {
// 预加载平台信息
this.getPlatformInfo();
// 设置端口列表缓存
this.portListCache = {
ports: [],
timestamp: 0,
ttl: this.DEFAULT_CACHE_TTL
};
logger.debug('PlatformAdapter initialized with caching');
}
/**
* 列出所有可用串口
*/
async listPorts(): Promise<SerialPortInfo[]> {
try {
// 检查缓存
if (this.isPortListCacheValid()) {
logger.debug('Returning cached port list');
return this.portListCache!.ports;
}
logger.debug('Listing available serial ports...');
const ports = await SerialPort.list();
// 转换为标准格式并过滤
const portInfos: SerialPortInfo[] = ports
.filter(port => this.isValidPort(port))
.map(port => ({
path: port.path,
manufacturer: port.manufacturer || 'Unknown',
serialNumber: port.serialNumber || 'Unknown',
pnpId: port.pnpId,
locationId: port.locationId,
vendorId: port.vendorId,
productId: port.productId
}));
// 更新缓存
this.updatePortListCache(portInfos);
logger.info(`Found ${portInfos.length} serial ports`, {
ports: portInfos.map(p => p.path)
});
return portInfos;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error('Failed to list serial ports', errorObj);
throw new SerialError(ErrorCode.SYSTEM_ERROR, 'Failed to list serial ports', undefined, errorObj);
}
}
/**
* 检查端口列表缓存是否有效
*/
private isPortListCacheValid(): boolean {
if (!this.portListCache) {
return false;
}
const now = Date.now();
return (now - this.portListCache.timestamp) < this.portListCache.ttl;
}
/**
* 更新端口列表缓存
*/
private updatePortListCache(ports: SerialPortInfo[]): void {
if (!this.portListCache) {
this.portListCache = {
ports: [],
timestamp: 0,
ttl: this.DEFAULT_CACHE_TTL
};
}
this.portListCache.ports = ports;
this.portListCache.timestamp = Date.now();
logger.debug('Port list cache updated', {
portCount: ports.length,
cacheAge: 0
});
}
/**
* 验证端口是否有效
*/
private isValidPort(port: any): boolean {
// TODO: 实现更严格的端口验证
// 例如:检查路径格式、排除虚拟端口等
// 基本验证
if (!port || !port.path) {
return false;
}
// 验证路径格式
return this.validatePortPath(port.path);
}
/**
* 检查串口访问权限
*/
async checkPermissions(): Promise<boolean> {
try {
const platform = os.platform();
const cacheKey = `permissions_${platform}`;
// 检查缓存
if (this.permissionCache.has(cacheKey)) {
const cached = this.permissionCache.get(cacheKey)!;
logger.debug('Returning cached permission check result', {
platform,
hasPermissions: cached
});
return cached;
}
let hasPermissions = true;
if (platform === 'linux') {
hasPermissions = await this.checkLinuxPermissions();
} else if (platform === 'win32') {
hasPermissions = await this.checkWindowsPermissions();
} else if (platform === 'darwin') {
hasPermissions = await this.checkMacOSPermissions();
} else {
logger.warn(`Unsupported platform for permission check: ${platform}`);
hasPermissions = false;
}
// 缓存结果
this.permissionCache.set(cacheKey, hasPermissions);
// 更新平台信息
if (this.platformInfo) {
this.platformInfo.hasPermissions = hasPermissions;
}
logger.debug(`Serial port permissions check: ${hasPermissions}`, { platform });
return hasPermissions;
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error('Failed to check permissions', errorObj);
return false;
}
}
/**
* 检查Linux权限
*/
private async checkLinuxPermissions(): Promise<boolean> {
try {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
// 检查用户组
const { stdout: groups } = await execAsync('groups');
const isInDialoutGroup = groups.includes('dialout') || groups.includes('root');
if (!isInDialoutGroup) {
logger.warn('User is not in dialout group', { groups: groups.trim() });
// 检查是否有sudo权限
try {
await execAsync('sudo -n true', { timeout: 1000 });
logger.info('User has sudo privileges');
return true;
} catch {
logger.warn('User does not have sudo privileges');
return false;
}
}
// 检查特定设备权限
const { stdout: devices } = await execAsync('ls -la /dev/tty* | head -5');
logger.debug('Sample device permissions', { devices: devices.trim() });
return true;
} catch (error) {
logger.warn('Linux permission check failed', error);
return false;
}
}
/**
* 检查Windows权限
*/
private async checkWindowsPermissions(): Promise<boolean> {
try {
// Windows下通常有权限,但可以检查管理员权限
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
try {
// 尝试检查是否是管理员
await execAsync('net session', { timeout: 1000 });
logger.info('User has administrator privileges');
} catch {
logger.debug('User does not have administrator privileges');
}
return true;
} catch (error) {
logger.warn('Windows permission check failed', error);
return true; // Windows下默认允许
}
}
/**
* 检查macOS权限
*/
private async checkMacOSPermissions(): Promise<boolean> {
try {
// macOS下检查是否在dialout组
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const { stdout: groups } = await execAsync('groups');
const hasPermission = groups.includes('dialout') || groups.includes('admin') || groups.includes('wheel');
if (!hasPermission) {
logger.warn('User lacks necessary groups for serial access', { groups: groups.trim() });
}
return hasPermission;
} catch (error) {
logger.warn('macOS permission check failed', error);
return false;
}
}
/**
* 获取平台信息
*/
getPlatformInfo(): PlatformInfo {
if (!this.platformInfo) {
const platform = os.platform();
const arch = os.arch();
const version = os.release();
const supportedFeatures: string[] = [];
// 根据平台确定支持的功能
if (platform === 'win32') {
supportedFeatures.push('COM Ports', 'Windows Driver Management');
} else if (platform === 'linux') {
supportedFeatures.push('TTY Ports', 'udev Rules', 'Group Permissions');
} else if (platform === 'darwin') {
supportedFeatures.push('TTY Ports', 'macOS Driver Management');
}
this.platformInfo = {
platform,
arch,
version,
hasPermissions: false, // 将在checkPermissions中更新
supportedFeatures
};
logger.debug('Platform information collected', this.platformInfo);
}
return this.platformInfo;
}
/**
* 获取平台特定的串口路径格式
*/
getPortPathFormat(): string {
const platform = os.platform();
switch (platform) {
case 'win32':
return '\\\\.\\COM{number}'; // COM10及以上需要这种格式
case 'linux':
return '/dev/tty{name}';
case 'darwin':
return '/dev/cu.{name}';
default:
return '{path}';
}
}
/**
* 标准化端口路径
*/
normalizePortPath(port: string): string {
const platform = os.platform();
if (platform === 'win32') {
// Windows下处理COM端口
const comMatch = port.match(/^COM(\d+)$/i);
if (comMatch) {
const comNumber = parseInt(comMatch[1]);
// COM10及以上需要特殊格式
return comNumber >= 10 ? `\\\\.\\COM${comNumber}` : port.toUpperCase();
}
// 已经是完整路径
if (port.startsWith('\\\\.')) {
return port;
}
} else if (platform === 'linux' || platform === 'darwin') {
// Unix-like系统
if (!port.startsWith('/dev/')) {
return `/dev/${port}`;
}
}
return port;
}
/**
* 验证端口路径格式
*/
validatePortPath(port: string): boolean {
const platform = os.platform();
if (platform === 'win32') {
// Windows COM端口格式
return /^\\\\\.\\COM\d+$|^COM\d+$/i.test(port);
} else if (platform === 'linux') {
// Linux TTY端口格式
return /^\/dev\/(ttyS|ttyUSB|ttyACM|ttyAMA|ttymxc)\d+$/i.test(port);
} else if (platform === 'darwin') {
// macOS串口格式
return /^\/dev\/(cu|tty)\..+$/i.test(port);
}
// 其他平台,简单检查
return port.length > 0;
}
/**
* 获取推荐的波特率列表
*/
getStandardBaudRates(): number[] {
return [
110, 300, 600, 1200, 2400, 4800, 9600, 14400,
19200, 28800, 38400, 57600, 115200, 128000,
153600, 230400, 256000, 460800, 500000, 576000,
921600, 1000000, 1152000, 1500000, 2000000, 2500000,
3000000, 3500000, 4000000
];
}
/**
* 获取支持的数据位
*/
getSupportedDataBits(): (5 | 6 | 7 | 8)[] {
return [5, 6, 7, 8];
}
/**
* 获取支持的停止位
*/
getSupportedStopBits(): (1 | 1.5 | 2)[] {
return [1, 1.5, 2];
}
/**
* 获取支持的校验位
*/
getSupportedParity(): ('none' | 'even' | 'odd')[] {
return ['none', 'even', 'odd'];
}
/**
* 获取支持的流控制
*/
getSupportedFlowControl(): ('none' | 'rts_cts' | 'xon_xoff')[] {
return ['none', 'rts_cts', 'xon_xoff'];
}
/**
* 清除缓存
*/
clearCache(): void {
this.permissionCache.clear();
if (this.portListCache) {
this.portListCache.timestamp = 0;
}
logger.debug('Platform adapter cache cleared');
}
/**
* 设置缓存TTL
*/
setCacheTTL(ttlMs: number): void {
if (this.portListCache) {
this.portListCache.ttl = ttlMs;
}
logger.debug(`Cache TTL set to ${ttlMs}ms`);
}
/**
* 获取平台特定的设备类型
*/
getDeviceType(portPath: string): string {
const platform = os.platform();
if (platform === 'linux') {
if (portPath.includes('ttyUSB')) return 'USB Serial';
if (portPath.includes('ttyACM')) return 'USB CDC ACM';
if (portPath.includes('ttyS')) return 'Built-in Serial';
if (portPath.includes('ttyAMA')) return 'ARM Serial';
if (portPath.includes('ttymxc')) return 'i.MX Serial';
} else if (platform === 'win32') {
if (portPath.includes('COM')) return 'COM Port';
} else if (platform === 'darwin') {
if (portPath.includes('usbserial')) return 'USB Serial';
if (portPath.includes('cu.')) return 'Callout Device';
if (portPath.includes('tty.')) return 'TTY Device';
}
return 'Unknown';
}
/**
* 获取端口详细信息
*/
async getPortDetails(portPath: string): Promise<Partial<SerialPortInfo> & { deviceType?: string; friendlyName?: string }> {
try {
const ports = await this.listPorts();
const port = ports.find(p => p.path === portPath);
if (!port) {
throw new SerialError(ErrorCode.PORT_NOT_FOUND, `Port not found: ${portPath}`, portPath);
}
const deviceType = this.getDeviceType(portPath);
const friendlyName = this.generateFriendlyName(port, deviceType);
return {
...port,
deviceType,
friendlyName
};
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error));
logger.error(`Failed to get port details: ${portPath}`, errorObj);
throw error;
}
}
/**
* 生成友好的端口名称
*/
private generateFriendlyName(port: SerialPortInfo, deviceType: string): string {
const parts: string[] = [];
if (port.manufacturer && port.manufacturer !== 'Unknown') {
parts.push(port.manufacturer);
}
parts.push(deviceType);
if (port.serialNumber && port.serialNumber !== 'Unknown') {
parts.push(`(${port.serialNumber})`);
}
return parts.join(' ');
}
/**
* 检查端口是否被占用
*/
async isPortInUse(portPath: string): Promise<boolean> {
try {
const platform = os.platform();
if (platform === 'linux') {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
try {
const { stdout } = await execAsync(`lsof ${portPath} 2>/dev/null`);
return stdout.trim().length > 0;
} catch {
return false;
}
} else if (platform === 'win32') {
// Windows下检查端口占用比较复杂,这里简化处理
return false;
}
return false;
} catch (error) {
logger.warn(`Failed to check if port is in use: ${portPath}`, error);
return false;
}
}
/**
* 获取推荐的端口配置
*/
getRecommendedConfig(portPath: string): Partial<PortConfig> {
const deviceType = this.getDeviceType(portPath);
// 根据设备类型返回推荐配置
switch (deviceType) {
case 'USB Serial':
case 'USB CDC ACM':
return {
baudrate: 115200,
dataBits: 8,
parity: 'none',
stopBits: 1,
flowControl: 'none'
};
case 'Built-in Serial':
return {
baudrate: 9600,
dataBits: 8,
parity: 'none',
stopBits: 1,
flowControl: 'none'
};
default:
return {
baudrate: 9600,
dataBits: 8,
parity: 'none',
stopBits: 1,
flowControl: 'none'
};
}
}
}