<commit_before>const _ = require('lodash');
const {
	readFile,
	delay,
	asyncMapSeries,
	enforceTimeout
} = require('./utilities');
const log = require('./log');

const ymodem = {
	// 128 byte blocks
	SOH: 0x01,
	// 1024 byte blocks
	STX: 0x02,
	EOT: 0x04,
	ACK: 0x06,
	NAK: 0x15,
	CA: 0x18,
	CRC16: 0x43,
	ABORT1: 0x41,
	ABORT2: 0x61
};
const ymodemLookup = _.invert(ymodem);


module.exports = class YModem {
	constructor(serialPort, options){
		this.options = _.defaults(options, {
			packetLength: 128,
			debug: false,
			inListeningMode: true
		});
		this.port = serialPort;

		if (this.options.packetLength === 128){
			this.mark = ymodem.SOH;
		} else if (this.options.packetLength === 1024){
			this.mark = ymodem.STX;
		} else {
			throw new Error('invalid packet length');
		}

		serialPort.once('close', this._close.bind(this));
	}

	send(filenames){
		let self = this;

		if (!_.isArray(filenames)){
			filenames = [filenames];
		}

		return this._open(this.options.inListeningMode)
			.then(() => {
				return asyncMapSeries(filenames, (filename) => {
					console.log('sending file:', filename);
					return readFile(filename)
						.then((buffer) => self._sendFile(buffer));
				});
			})
			.then(() => this._endTransfer())
			.catch((err) => {
				self.errored = true;
				return Promise.reject(err);
			})
			.finally(() => self._close());
	}

	_logData(data){
		if (data.length <= 2){
			for (let i=0; i < data.length; i++){
				let chr = data[i];
				log.verbose(ymodemLookup[chr] || chr);
			}
			return;
		}
		log.verbose(data, data.toString());
	}

	_close(){
		if (this.port.isOpen){
			if (this.errored){
				// ignore error here
				this.port.write([ymodem.ABORT2], () => {});
			}
			this.port.close();
		}
	}

	_open(inListeningMode){
		let self = this;

		if (inListeningMode === undefined){
			inListeningMode = true;
		}

		const promise = new Promise((resolve, reject) => {
			self.port.open((err) => {
				if (err){
					return reject(err);
				}

				// wait for initial response
				let line = '';
				function cmdResponse(){
					let data = self.port.read();
					self._logData(data);
					line += data.toString();
					// if not in listening mode, we get CRC16 back
					// if in listening mode, we get this string
					if (data[0] === ymodem.CRC16 || line.trim() === "Waiting for the binary file to be sent ... (press 'a' to abort)"){
						self.port.removeListener('readable', cmdResponse);
						return resolve();
					}
				}
				self.port.on('readable', cmdResponse);
				self.port.write('f');
			});
		});

		return enforceTimeout(promise, 10000)
			.catch((error) => {
				if (error && error.isTimeout){
					return Promise.reject('Timed out waiting for initial response from device');
				}
				throw error;
			});
	}

	_endTransfer(){
		this.seq = 0;
		this.ending = true;
		return this._sendFileHeader('', 0);
	}<commit_msg>ensure line-break is received when opening a serial connection to a device<commit_after>const _ = require('lodash');
const {
	readFile,
	delay,
	asyncMapSeries,
	enforceTimeout
} = require('./utilities');
const log = require('./log');

const ymodem = {
	// 128 byte blocks
	SOH: 0x01,
	// 1024 byte blocks
	STX: 0x02,
	EOT: 0x04,
	ACK: 0x06,
	NAK: 0x15,
	CA: 0x18,
	CRC16: 0x43,
	ABORT1: 0x41,
	ABORT2: 0x61
};
const ymodemLookup = _.invert(ymodem);


module.exports = class YModem {
	constructor(serialPort, options){
		this.options = _.defaults(options, {
			packetLength: 128,
			debug: false,
			inListeningMode: true
		});
		this.port = serialPort;

		if (this.options.packetLength === 128){
			this.mark = ymodem.SOH;
		} else if (this.options.packetLength === 1024){
			this.mark = ymodem.STX;
		} else {
			throw new Error('invalid packet length');
		}

		serialPort.once('close', this._close.bind(this));
	}

	send(filenames){
		let self = this;

		if (!_.isArray(filenames)){
			filenames = [filenames];
		}

		return this._open(this.options.inListeningMode)
			.then(() => {
				return asyncMapSeries(filenames, (filename) => {
					console.log('sending file:', filename);
					return readFile(filename)
						.then((buffer) => self._sendFile(buffer));
				});
			})
			.then(() => this._endTransfer())
			.catch((err) => {
				self.errored = true;
				return Promise.reject(err);
			})
			.finally(() => self._close());
	}

	_logData(data){
		if (data.length <= 2){
			for (let i=0; i < data.length; i++){
				let chr = data[i];
				log.verbose(ymodemLookup[chr] || chr);
			}
			return;
		}
		log.verbose(data, data.toString());
	}

	_close(){
		if (this.port.isOpen){
			if (this.errored){
				// ignore error here
				this.port.write([ymodem.ABORT2], () => {});
			}
			this.port.close();
		}
	}

	_open(inListeningMode){
		let self = this;

		if (inListeningMode === undefined){
			inListeningMode = true;
		}

		const promise = new Promise((resolve, reject) => {
			self.port.open((err) => {
				if (err){
					return reject(err);
				}

				// wait for initial response
				const readyMsg = "Waiting for the binary file to be sent ... (press 'a' to abort)";
				const linebreakPtn = /\r?\n/;
				let line = '';
				function cmdResponse(){
					let data = self.port.read();
					self._logData(data);
					line += data.toString();
					const lines = line.split(linebreakPtn);
					// if not in listening mode, we get CRC16 back
					// if in listening mode, we get the ready message
					if (data[0] === ymodem.CRC16 || (lines.length > 1 && lines.some(l => l === readyMsg))){
						self.port.removeListener('readable', cmdResponse);
						return resolve();
					}
				}
				self.port.on('readable', cmdResponse);
				self.port.write('f');
			});
		});

		return enforceTimeout(promise, 10000)
			.catch((error) => {
				if (error && error.isTimeout){
					return Promise.reject('Timed out waiting for initial response from device');
				}
				throw error;
			});
	}

	_endTransfer(){
		this.seq = 0;
		this.ending = true;
		return this._sendFileHeader('', 0);
	}
{'fraction_non_alphanumeric': 0.16588235294117648, 'fraction_numerical': 0.02134453781512605, 'mean_word_length': 2.985934360348292, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 25, 'https://': 0, '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': '7441274', 'n_tokens_mistral': 2605, 'n_tokens_neox': 2070, 'n_words': 486}