<commit_before>const fs = require('fs');
const _ = require('lodash');
const path = require('path');
const chalk = require('chalk');
const VError = require('verror');
const inquirer = require('inquirer');
const prompt = require('inquirer').prompt;
const wifiScan = require('node-wifiscanner2').scan;
const SerialPort = require('../lib/require-optional')('serialport');
const log = require('../lib/log');
const specs = require('../lib/deviceSpecs');
const ApiClient = require('../lib/api-client');
const settings = require('../../settings');
const DescribeParser = require('binary-version-reader').HalDescribeParser;
const YModem = require('../lib/ymodem');
const SerialBatchParser = require('../lib/serial-batch-parser');
const SerialTrigger = require('../lib/serial-trigger');
const spinnerMixin = require('../lib/spinner-mixin');
const ensureError = require('../lib/utilities').ensureError;

// TODO: DRY this up somehow
// The categories of output will be handled via the log class, and similar for protip.
const cmd = path.basename(process.argv[1]);
const arrow = chalk.green('>');
const alert = chalk.yellow('!');
const timeoutError = 'Serial timed out';

function protip(){
	const args = Array.prototype.slice.call(arguments);
	args.unshift(chalk.cyan('!'), chalk.bold.white('PROTIP:'));
	console.log.apply(null, args);
}


// An LTE device may take up to 18 seconds to power up the modem
const MODULE_INFO_COMMAND_TIMEOUT = 20000;
const IDENTIFY_COMMAND_TIMEOUT = 20000;

const SERIAL_PORT_DEFAULTS = {
	baudRate: 9600,
	autoOpen: false
};

module.exports = class SerialCommand {
	constructor(){
		spinnerMixin(this);
	}

	findDevices(){
		return SerialPort.list()
			.then(ports => {
				const devices = [];

				ports.forEach((port) => {
					// manufacturer value
					// Mac - Spark devices
					// Devices on old driver - Spark Core, Photon
					// Devices on new driver - Particle IO (https://github.com/spark/firmware/pull/447)
					// Windows only contains the pnpId field

					let device;
					const serialDeviceSpec = _.find(specs, (deviceSpec) => {
						if (!deviceSpec.serial){
							return false;
						}
						const vid = deviceSpec.serial.vid;
						const pid = deviceSpec.serial.pid;
						const serialNumber = deviceSpec.serial.serialNumber;

						const usbMatches = (port.vendorId === vid.toLowerCase() && port.productId === pid.toLowerCase());
						const pnpMatches = !!(port.pnpId && (port.pnpId.indexOf('VID_' + vid.toUpperCase()) >= 0) && (port.pnpId.indexOf('PID_' + pid.toUpperCase()) >= 0));
						const serialNumberMatches = port.serialNumber && port.serialNumber.indexOf(serialNumber) >= 0;

						return !!(usbMatches || pnpMatches || serialNumberMatches);

					});

					if (serialDeviceSpec){
						device = {
							port: port.path,
							type: serialDeviceSpec.productName,
							deviceId: serialDeviceSpec.serial.deviceId && serialDeviceSpec.serial.deviceId(port.serialNumber || port.pnpId),
							specs: serialDeviceSpec
						};
					}

					// Populate the Device ID based on the ports serial number:
					if (device && port.serialNumber && typeof device.deviceId == 'undefined') {
						device.deviceId = port.serialNumber;
					}

					const matchesManufacturer = port.manufacturer && (port.manufacturer.indexOf('Particle') >= 0 || port.manufacturer.indexOf('Spark') >= 0 || port.manufacturer.indexOf('Photon') >= 0);

					if (!device && matchesManufacturer){
						device = { port: port.path, type: 'Core' };
					}

					if (device){
						devices.push(device);
					}
				});

				//if I didn't find anything, grab any 'ttyACM's
				if (devices.length === 0){
					ports.forEach((port) => {
						//if it doesn't have a manufacturer or pnpId set, but it's a ttyACM port, then lets grab it.
						if (port.path.indexOf('/dev/ttyACM') === 0){
							devices.push({ port: port.path, type: '' });
						} else if (port.path.indexOf('/dev/cuaU') === 0){
							devices.push({ port: port.path, type: '' });
						}
					});
				}

				return devices;
			})
			.catch(err => {
				throw new VError(ensureError(err), 'Error listing serial ports');
			});
	}

	listDevices(){
		return this.findDevices()
			.then(devices => {
				if (devices.length === 0){
					console.log(chalk.bold.white('No devices available via serial'));
					return;
				}

				console.log('Found', chalk.cyan(devices.length), (devices.length > 1 ? 'devices' : 'device'), 'connected via serial:');
				devices.forEach((device) => console.log(`${device.port} - ${device.type} - ${device.deviceId}`));
			});
	}

	monitorPort({ port, follow }){
		let cleaningUp = false;
		let selectedDevice;
		let serialPort;

		const displayError = (err) => {
			if (err){
				console.error('Serial err: ' + err);
				console.error('Serial problems, please reconnect the device.');
			}
		};

		// Called when port closes
		const handleClose = () => {
			if (follow && !cleaningUp){
				console.log(chalk.bold.white('Serial connection closed.  Attempting to reconnect...'));
				return reconnect();
			}
			console.log(chalk.bold.white('Serial connection closed.'));
		};

		// Handle interrupts and close the port gracefully
		const handleInterrupt = (silent) => {
			if (!cleaningUp){
				if (!silent){
					console.log(chalk.bold.red('Caught Interrupt.  Cleaning up.'));<commit_msg>add line-break to serial close message so output isn't mangled<commit_after>const os = require('os');
const fs = require('fs');
const _ = require('lodash');
const path = require('path');
const chalk = require('chalk');
const VError = require('verror');
const inquirer = require('inquirer');
const prompt = require('inquirer').prompt;
const wifiScan = require('node-wifiscanner2').scan;
const SerialPort = require('../lib/require-optional')('serialport');
const log = require('../lib/log');
const specs = require('../lib/deviceSpecs');
const ApiClient = require('../lib/api-client');
const settings = require('../../settings');
const DescribeParser = require('binary-version-reader').HalDescribeParser;
const YModem = require('../lib/ymodem');
const SerialBatchParser = require('../lib/serial-batch-parser');
const SerialTrigger = require('../lib/serial-trigger');
const spinnerMixin = require('../lib/spinner-mixin');
const ensureError = require('../lib/utilities').ensureError;

// TODO: DRY this up somehow
// The categories of output will be handled via the log class, and similar for protip.
const cmd = path.basename(process.argv[1]);
const arrow = chalk.green('>');
const alert = chalk.yellow('!');
const timeoutError = 'Serial timed out';

function protip(){
	const args = Array.prototype.slice.call(arguments);
	args.unshift(chalk.cyan('!'), chalk.bold.white('PROTIP:'));
	console.log.apply(null, args);
}


// An LTE device may take up to 18 seconds to power up the modem
const MODULE_INFO_COMMAND_TIMEOUT = 20000;
const IDENTIFY_COMMAND_TIMEOUT = 20000;

const SERIAL_PORT_DEFAULTS = {
	baudRate: 9600,
	autoOpen: false
};

module.exports = class SerialCommand {
	constructor(){
		spinnerMixin(this);
	}

	findDevices(){
		return SerialPort.list()
			.then(ports => {
				const devices = [];

				ports.forEach((port) => {
					// manufacturer value
					// Mac - Spark devices
					// Devices on old driver - Spark Core, Photon
					// Devices on new driver - Particle IO (https://github.com/spark/firmware/pull/447)
					// Windows only contains the pnpId field

					let device;
					const serialDeviceSpec = _.find(specs, (deviceSpec) => {
						if (!deviceSpec.serial){
							return false;
						}
						const vid = deviceSpec.serial.vid;
						const pid = deviceSpec.serial.pid;
						const serialNumber = deviceSpec.serial.serialNumber;

						const usbMatches = (port.vendorId === vid.toLowerCase() && port.productId === pid.toLowerCase());
						const pnpMatches = !!(port.pnpId && (port.pnpId.indexOf('VID_' + vid.toUpperCase()) >= 0) && (port.pnpId.indexOf('PID_' + pid.toUpperCase()) >= 0));
						const serialNumberMatches = port.serialNumber && port.serialNumber.indexOf(serialNumber) >= 0;

						return !!(usbMatches || pnpMatches || serialNumberMatches);

					});

					if (serialDeviceSpec){
						device = {
							port: port.path,
							type: serialDeviceSpec.productName,
							deviceId: serialDeviceSpec.serial.deviceId && serialDeviceSpec.serial.deviceId(port.serialNumber || port.pnpId),
							specs: serialDeviceSpec
						};
					}

					// Populate the Device ID based on the ports serial number:
					if (device && port.serialNumber && typeof device.deviceId == 'undefined') {
						device.deviceId = port.serialNumber;
					}

					const matchesManufacturer = port.manufacturer && (port.manufacturer.indexOf('Particle') >= 0 || port.manufacturer.indexOf('Spark') >= 0 || port.manufacturer.indexOf('Photon') >= 0);

					if (!device && matchesManufacturer){
						device = { port: port.path, type: 'Core' };
					}

					if (device){
						devices.push(device);
					}
				});

				//if I didn't find anything, grab any 'ttyACM's
				if (devices.length === 0){
					ports.forEach((port) => {
						//if it doesn't have a manufacturer or pnpId set, but it's a ttyACM port, then lets grab it.
						if (port.path.indexOf('/dev/ttyACM') === 0){
							devices.push({ port: port.path, type: '' });
						} else if (port.path.indexOf('/dev/cuaU') === 0){
							devices.push({ port: port.path, type: '' });
						}
					});
				}

				return devices;
			})
			.catch(err => {
				throw new VError(ensureError(err), 'Error listing serial ports');
			});
	}

	listDevices(){
		return this.findDevices()
			.then(devices => {
				if (devices.length === 0){
					console.log(chalk.bold.white('No devices available via serial'));
					return;
				}

				console.log('Found', chalk.cyan(devices.length), (devices.length > 1 ? 'devices' : 'device'), 'connected via serial:');
				devices.forEach((device) => console.log(`${device.port} - ${device.type} - ${device.deviceId}`));
			});
	}

	monitorPort({ port, follow }){
		let cleaningUp = false;
		let selectedDevice;
		let serialPort;

		const displayError = (err) => {
			if (err){
				console.error('Serial err: ' + err);
				console.error('Serial problems, please reconnect the device.');
			}
		};

		// Called when port closes
		const handleClose = () => {
			console.log(os.EOL);
			if (follow && !cleaningUp){
				console.log(chalk.bold.white('Serial connection closed.  Attempting to reconnect...'));
				return reconnect();
			}
			console.log(chalk.bold.white('Serial connection closed.'));
		};

		// Handle interrupts and close the port gracefully
		const handleInterrupt = (silent) => {
			if (!cleaningUp){
				if (!silent){
					console.log(chalk.bold.red('Caught Interrupt.  Cleaning up.'));
{'fraction_non_alphanumeric': 0.1588802546578036, 'fraction_numerical': 0.005991948319445745, 'mean_word_length': 3.4268545379196023, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 39, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1500912', 'n_tokens_mistral': 3992, 'n_tokens_neox': 3238, 'n_words': 810}