diff --git a/node/tessel-export.js b/node/tessel-export.js index 7e0f861..6e21eb2 100644 --- a/node/tessel-export.js +++ b/node/tessel-export.js @@ -1,27 +1,33 @@ 'use strict'; -var cp = require('child_process'); -var Duplex = require('stream').Duplex; -var EventEmitter = require('events').EventEmitter; -var fs = require('fs'); -var net = require('net'); -var util = require('util'); - -var defOptions = { +// System Objects +const cp = require('child_process'); +const Duplex = require('stream').Duplex; +const EventEmitter = require('events').EventEmitter; +const fs = require('fs'); +const net = require('net'); +const util = require('util'); + +const defOptions = { ports: { A: true, B: true, } }; +const pwmBankSettings = { + period: 0, + prescalarIndex: 0, +}; + const reusableNoOp = () => {}; const enforceCallback = callback => typeof callback === 'function' ? callback : reusableNoOp; - // Port Name Constants const A = 'A'; const B = 'B'; +const ANALOG_RESOLUTION = 4096; // Maximum number of ticks before period completes const PWM_MAX_PERIOD = 0xFFFF; // Actual lowest frequency is ~0.72Hz but 1Hz is easier to remember. @@ -35,1131 +41,1162 @@ const SAMD21_TICKS_PER_SECOND = 48000000; // GPIO number of RESET pin const SAMD21_RESET_GPIO = 39; -function Tessel(options) { - if (Tessel.instance) { - return Tessel.instance; - } else { - Tessel.instance = this; - } - - // If the user program has provided a _valid_ options object, or use default - options = typeof options === 'object' && options !== null ? options : defOptions; +// Per pin capabilities +const ADC_CAPABLE_PINS = [4, 7]; +const PULL_CAPABLE_PINS = [2, 3, 4, 5, 6, 7]; +const INT_CAPABLE_PINS = [2, 5, 6, 7]; +const PWM_CAPABLE_PINS = [5, 6]; - // If the user program has passed an options object that doesn't - // contain a `ports` property, or the value of the `ports` property - // is null or undefined: use the default. - if (options.ports == null) { - options.ports = defOptions.ports; - } - - // For compatibility with T1 code, ensure that all ports are initialized by default. - // This means that only an explicit `A: false` or `B: false` will result in a - // port not being initialized. If the property is not present, null or undefined, - // it will be set to `true`. - // - // ONLY a value of `false` can prevent the port from being initialized! - // - if (options.ports.A == null) { - options.ports.A = true; - } - - if (options.ports.B == null) { - options.ports.B = true; - } +const INT_MODES = { + rise: 1, + fall: 2, + change: 3, + high: 4, + low: 5, +}; - this.ports = { - A: options.ports.A ? new Tessel.Port(A, Tessel.Port.PATH.A, this) : null, - B: options.ports.B ? new Tessel.Port(B, Tessel.Port.PATH.B, this) : null, - }; +const PULL_MODES = { + pulldown: 0, + pullup: 1, + none: 2, +}; - this.port = this.ports; - - this.led = new Tessel.LEDs([{ - color: 'red', - type: 'error' - }, { - color: 'amber', - type: 'wlan' - }, { - color: 'green', - type: 'user1' - }, { - color: 'blue', - type: 'user2' - }, ]); - - this.leds = this.led; - - this.network = { - wifi: new Tessel.Wifi(), - ap: new Tessel.AP() - }; +const CMD = { + NOP: 0, + FLUSH: 1, + ECHO: 2, + GPIO_IN: 3, + GPIO_HIGH: 4, + GPIO_LOW: 5, + GPIO_TOGGLE: 21, + GPIO_CFG: 6, + GPIO_WAIT: 7, + GPIO_INT: 8, + GPIO_INPUT: 22, + GPIO_RAW_READ: 23, + GPIO_PULL: 26, + ANALOG_READ: 24, + ANALOG_WRITE: 25, + ENABLE_SPI: 10, + DISABLE_SPI: 11, + ENABLE_I2C: 12, + DISABLE_I2C: 13, + ENABLE_UART: 14, + DISABLE_UART: 15, + TX: 16, + RX: 17, + TXRX: 18, + START: 19, + STOP: 20, + PWM_DUTY_CYCLE: 27, + PWM_PERIOD: 28, +}; - // tessel v1 does not have this version number - // this is useful for libraries to adapt to changes - // such as all pin reads/writes becoming async in version 2 - this.version = 2; -} +const REPLY = { + ACK: 0x80, + NACK: 0x81, + HIGH: 0x82, + LOW: 0x83, + DATA: 0x84, -var pwmBankSettings = { - period: 0, - prescalarIndex: 0, + MIN_ASYNC: 0xA0, + ASYNC_PIN_CHANGE_N: 0xC0, // c0 to c8 is all async pin assignments + ASYNC_UART_RX: 0xD0 }; -Tessel.prototype.close = function(portName) { - if (portName !== undefined) { - // This _could_ be combined with the above condition, - // but is separate since the open() method has a - // necessarily nested condition and this _may_ require - // further conditional restrictions in the future. - /* istanbul ignore else */ - if (this.port[portName]) { - this.port[portName].close(); +class Tessel { + constructor(options) { + if (Tessel.instance) { + return Tessel.instance; + } else { + Tessel.instance = this; } - } else { - [A, B].forEach(name => this.close(name)); - } - return this; -}; -Tessel.prototype.open = function(portName) { - if (portName !== undefined) { - // If there _is not_ a port created with this port name; - // Or there _is_, but the socket was previously destroyed... - if (!this.port[portName] || - (this.port[portName] && this.port[portName].sock.destroyed)) { - this.port[portName] = new Tessel.Port(portName, Tessel.Port.PATH[portName], this); + // If the user program has provided a _valid_ options object, or use default + options = typeof options === 'object' && options !== null ? options : defOptions; + + // If the user program has passed an options object that doesn't + // contain a `ports` property, or the value of the `ports` property + // is null or undefined: use the default. + if (options.ports == null) { + options.ports = defOptions.ports; } - } else { - [A, B].forEach(name => this.open(name)); - } - return this; -}; -Tessel.prototype.reboot = function() { + // For compatibility with T1 code, ensure that all ports are initialized by default. + // This means that only an explicit `A: false` or `B: false` will result in a + // port not being initialized. If the property is not present, null or undefined, + // it will be set to `true`. + // + // ONLY a value of `false` can prevent the port from being initialized! + // + if (options.ports.A == null) { + options.ports.A = true; + } - this.close(); + if (options.ports.B == null) { + options.ports.B = true; + } - // When attempting to reboot, if the sockets - // are left open at the moment that `reboot` - // is executed, there will be a substantial - // delay before the actual reboot occurs. - // Polling for `destroyed` signals ensures - // that the sockets are closed before - // `reboot` is executed. - var pollUntilSocketsDestroyed = () => { - /* istanbul ignore else */ - if (this.port.A.sock.destroyed && - this.port.B.sock.destroyed) { + this.ports = { + A: options.ports.A ? new Tessel.Port(A, Tessel.Port.PATH.A, this) : null, + B: options.ports.B ? new Tessel.Port(B, Tessel.Port.PATH.B, this) : null, + }; - // Stop SPI communication between SAMD21 and MediaTek - cp.execSync('/etc/init.d/spid stop'); + this.port = this.ports; + + this.led = new Tessel.LEDs([{ + color: 'red', + type: 'error' + }, { + color: 'amber', + type: 'wlan' + }, { + color: 'green', + type: 'user1' + }, { + color: 'blue', + type: 'user2' + }, ]); + + this.leds = this.led; + + this.network = { + wifi: new Tessel.Wifi(), + ap: new Tessel.AP() + }; - // Create a GPIO entry for the SAMD21 RESET pin - cp.execSync(`echo "${SAMD21_RESET_GPIO}" > /sys/class/gpio/export`); - // Make that GPIO an output - cp.execSync(`echo "out" > /sys/class/gpio/gpio${SAMD21_RESET_GPIO}/direction`); - // Pull the output low to reset the SAMD21 - cp.execSync(`echo "0" > /sys/class/gpio/gpio${SAMD21_RESET_GPIO}/value`); + // tessel v1 does not have this version number + // this is useful for libraries to adapt to changes + // such as all pin reads/writes becoming async in version 2 + this.version = 2; + } - // Reboot the MediaTek - cp.execSync('reboot'); + close(portName) { + if (portName !== undefined) { + // This _could_ be combined with the above condition, + // but is separate since the open() method has a + // necessarily nested condition and this _may_ require + // further conditional restrictions in the future. + /* istanbul ignore else */ + if (this.port[portName]) { + this.port[portName].close(); + } } else { - setImmediate(pollUntilSocketsDestroyed); + [A, B].forEach(name => this.close(name)); } - }; - - pollUntilSocketsDestroyed(); -}; - -Tessel.prototype.pwmFrequency = function(frequency, callback) { - if (frequency < PWM_MIN_FREQUENCY || - frequency > PWM_MAX_FREQUENCY) { - throw new RangeError(`PWM Frequency value must be between ${PWM_MIN_FREQUENCY} and ${PWM_MAX_FREQUENCY}`); + return this; } - var results = determineDutyCycleAndPrescalar(frequency); + open(portName) { + if (portName !== undefined) { + // If there _is not_ a port created with this port name; + // Or there _is_, but the socket was previously destroyed... + if (!this.port[portName] || + (this.port[portName] && this.port[portName].sock.destroyed)) { + this.port[portName] = new Tessel.Port(portName, Tessel.Port.PATH[portName], this); + } + } else { + [A, B].forEach(name => this.open(name)); + } + return this; + } - pwmBankSettings.period = results.period; - pwmBankSettings.prescalarIndex = results.prescalarIndex; + reboot() { - // We are currently only using TCC Bank 0 - // This may be expanded in the future to enable PWM on more pins - const TCC_ID = 0; + this.close(); - var packet = new Buffer(4); - // Write the command id first - packet.writeUInt8(CMD.PWM_PERIOD, 0); - // Write our prescalar to the top 4 bits and TCC id to the bottom 4 bits - packet.writeUInt8((pwmBankSettings.prescalarIndex << 4) | TCC_ID, 1); - // Write our period (16 bits) - packet.writeUInt16BE(pwmBankSettings.period, 2); + // When attempting to reboot, if the sockets + // are left open at the moment that `reboot` + // is executed, there will be a substantial + // delay before the actual reboot occurs. + // Polling for `destroyed` signals ensures + // that the sockets are closed before + // `reboot` is executed. + const pollUntilSocketsDestroyed = () => { + /* istanbul ignore else */ + if (this.port.A.sock.destroyed && + this.port.B.sock.destroyed) { - // Send the packet off to the samd21 - // on the first available port object (regardless of name) - this.port[[A, B].find(name => this.ports[name] !== null)].sock.write(packet, callback); -}; + // Stop SPI communication between SAMD21 and MediaTek + cp.execSync('/etc/init.d/spid stop'); -/* - Takes in a desired frequency setting and outputs the - necessary prescalar and duty cycle settings based on set period. - Outputs an object in the form of: - { - prescalar: number (0-7), - period: number (0-0xFFFF) - } -*/ -function determineDutyCycleAndPrescalar(frequency) { - // Current setting for the prescalar - var prescalarIndex = 0; - // Current period setting - var period = 0; + // Create a GPIO entry for the SAMD21 RESET pin + cp.execSync(`echo "${SAMD21_RESET_GPIO}" > /sys/class/gpio/export`); + // Make that GPIO an output + cp.execSync(`echo "out" > /sys/class/gpio/gpio${SAMD21_RESET_GPIO}/direction`); + // Pull the output low to reset the SAMD21 + cp.execSync(`echo "0" > /sys/class/gpio/gpio${SAMD21_RESET_GPIO}/value`); - // If the current frequency would require a period greater than the max - while ((period = Math.floor((SAMD21_TICKS_PER_SECOND / PWM_PRESCALARS[prescalarIndex]) / frequency)) > PWM_MAX_PERIOD) { - // Increase our clock prescalar - prescalarIndex++; + // Reboot the MediaTek + cp.execSync('reboot'); + } else { + setImmediate(pollUntilSocketsDestroyed); + } + }; - // If we have no higher prescalars - if (prescalarIndex === PWM_PRESCALARS.length) { - // Throw an error because this frequency is too low for our possible parameters - throw new Error('Unable to find prescalar/duty cycle parameter match for frequency'); - } + pollUntilSocketsDestroyed(); } - // We have found a period inside a suitable prescalar, return results - return { - period: period, - prescalarIndex: prescalarIndex - }; -} + pwmFrequency(frequency, callback) { + if (frequency < PWM_MIN_FREQUENCY || + frequency > PWM_MAX_FREQUENCY) { + throw new RangeError(`PWM Frequency value must be between ${PWM_MIN_FREQUENCY} and ${PWM_MAX_FREQUENCY}`); + } -Tessel.Port = function(name, path, board) { - var port = this; + const results = determineDutyCycleAndPrescalar(frequency); - this.name = name; - this.board = board; - // Connection to the SPI daemon - this.sock = net.createConnection({ - path - }, error => { - /* istanbul ignore else */ - if (error) { - throw error; - } - }); + pwmBankSettings.period = results.period; + pwmBankSettings.prescalarIndex = results.prescalarIndex; - // Number of tasks occupying the socket - this._pendingTasks = 0; + // We are currently only using TCC Bank 0 + // This may be expanded in the future to enable PWM on more pins + const TCC_ID = 0; - // Unreference this socket so that the script will exit - // if nothing else is waiting in the event queue. - this.unref(); + const packet = new Buffer(4); + // Write the command id first + packet.writeUInt8(CMD.PWM_PERIOD, 0); + // Write our prescalar to the top 4 bits and TCC id to the bottom 4 bits + packet.writeUInt8((pwmBankSettings.prescalarIndex << 4) | TCC_ID, 1); + // Write our period (16 bits) + packet.writeUInt16BE(pwmBankSettings.period, 2); - this.sock.on('error', error => { - console.log(`Socket: Error occurred: ${error.toString()}`); - }); + // Send the packet off to the samd21 + // on the first available port object (regardless of name) + this.port[[A, B].find(name => this.ports[name] !== null)].sock.write(packet, callback); + } +} - this.sock.on('end', () => { - console.log('Socket: The other end sent FIN packet.'); - }); +class Port extends EventEmitter { + constructor(name, path, board) { + super(); - this.sock.on('close', () => { - if (!this.sock.isAllowedToClose) { - throw new Error('Socket: The Port socket has closed.'); - } - }); + const port = this; - // Track whether the port should treat closing - // as an error. This will be set to true when `tessel.close()` - // is called, to indicate that the closing is intentional and - // therefore should be allow to proceed. - this.sock.isAllowedToClose = false; - - var replyBuf = new Buffer(0); - - this.sock.on('readable', function() { - var queued; - // This value can potentially be `null`. - var available = new Buffer(this.sock.read() || 0); - - // Copy incoming data into the reply buffer - replyBuf = Buffer.concat([replyBuf, available]); - - // While we still have data to process in the buffer - while (replyBuf.length !== 0) { - // Grab the next byte - var byte = replyBuf[0]; - // If the next byte equals the marker for a uart incoming - if (byte === REPLY.ASYNC_UART_RX) { - // Get the next byte which is the number of bytes - var rxNum = replyBuf[1]; - // As long as the number of bytes of rx buffer exists - // and we have at least the number of bytes needed for a uart rx packet - if (rxNum !== undefined && replyBuf.length >= 2 + rxNum) { - // Read the incoming data - var rxData = replyBuf.slice(2, 2 + rxNum); - // Cut those bytes out of the reply buf packet so we don't - // process them again - replyBuf = replyBuf.slice(2 + rxNum); - - // If a uart port was instantiated - /* istanbul ignore else */ - if (this._uart) { - // Push this data into the buffer - this._uart.push(rxData); - } - // Something went wrong and the packet is malformed - } else { - break; - } - // This is some other async transaction - } else if (byte >= REPLY.MIN_ASYNC) { - // If this is a pin change - if (byte >= REPLY.ASYNC_PIN_CHANGE_N && byte < REPLY.ASYNC_PIN_CHANGE_N + 16) { - // Pull out the pin number (requires clearing the value bit) - var pin = this.pin[(byte - REPLY.ASYNC_PIN_CHANGE_N) & ~(1 << 3)]; - // Get the mode change - var mode = pin.interruptMode; - // Get the pin value - var pinValue = (byte >> 3) & 1; - - // For one-time 'low' or 'high' event - if (mode === 'low' || mode === 'high') { - pin.emit(mode); - // Reset the pin interrupt state (prevent constant interrupts) - pin.interruptMode = null; - // Decrement the number of tasks waiting on the socket - this.unref(); - } else { - // Emit the change and rise or fall - pin.emit('change', pinValue); - pin.emit(pinValue ? 'rise' : 'fall'); - } + this.name = name; + this.board = board; + // Connection to the SPI daemon + this.sock = net.createConnection({ + path + }, error => { + /* istanbul ignore else */ + if (error) { + throw error; + } + }); - } else { - // Some other async event - this.emit('async-event', byte); - } + // Number of tasks occupying the socket + this.pending = 0; - // Cut this byte off of the reply buffer - replyBuf = replyBuf.slice(1); - } else { - // If there are no commands awaiting a response - if (this.replyQueue.length === 0) { - // Throw an error... something went wrong - throw new Error('Received unexpected response with no commands pending: ' + byte); - } + // Unreference this socket so that the script will exit + // if nothing else is waiting in the event queue. + this.unref(); - // Get the size if the incoming packet - var size = this.replyQueue[0].size; + this.sock.on('error', error => { + console.log(`Socket: Error occurred: ${error.toString()}`); + }); - // If we have reply data - if (byte === REPLY.DATA) { - // Ensure that the packet size agrees - if (!size) { - throw new Error('Received unexpected data packet'); - } + this.sock.on('end', () => { + console.log('Socket: The other end sent FIN packet.'); + }); - // The number of data bytes expected have been received. - if (replyBuf.length >= 1 + size) { - // Extract the data - var data = replyBuf.slice(1, 1 + size); - // Slice this data off of the buffer - replyBuf = replyBuf.slice(1 + size); - // Get the queued command - queued = this.dequeue(); + this.sock.on('close', () => { + if (!this.sock.isAllowedToClose) { + throw new Error('Socket: The Port socket has closed.'); + } + }); - // If there is a callback for th ecommand + // Track whether the port should treat closing + // as an error. This will be set to true when `tessel.close()` + // is called, to indicate that the closing is intentional and + // therefore should be allow to proceed. + this.sock.isAllowedToClose = false; + + let replyBuf = new Buffer(0); + + this.sock.on('readable', () => { + let queued; + // This value can potentially be `null`. + const available = new Buffer(this.sock.read() || 0); + + // Copy incoming data into the reply buffer + replyBuf = Buffer.concat([replyBuf, available]); + + // While we still have data to process in the buffer + while (replyBuf.length !== 0) { + // Grab the next byte + const byte = replyBuf[0]; + // If the next byte equals the marker for a uart incoming + if (byte === REPLY.ASYNC_UART_RX) { + // Get the next byte which is the number of bytes + const rxNum = replyBuf[1]; + // As long as the number of bytes of rx buffer exists + // and we have at least the number of bytes needed for a uart rx packet + if (rxNum !== undefined && replyBuf.length >= 2 + rxNum) { + // Read the incoming data + const rxData = replyBuf.slice(2, 2 + rxNum); + // Cut those bytes out of the reply buf packet so we don't + // process them again + replyBuf = replyBuf.slice(2 + rxNum); + + // If a uart port was instantiated /* istanbul ignore else */ - if (queued.callback) { - // Return the data in the callback - queued.callback.call(this, null, data); + if (this._uart) { + // Push this data into the buffer + this._uart.push(rxData); } + // Something went wrong and the packet is malformed } else { - // The buffer does not have the correct number of - // date bytes to fulfill the requirements of the - // reply queue's next registered handler. break; } - // If it's just one byte being returned + // This is some other async transaction + } else if (byte >= REPLY.MIN_ASYNC) { + // If this is a pin change + if (byte >= REPLY.ASYNC_PIN_CHANGE_N && byte < REPLY.ASYNC_PIN_CHANGE_N + 16) { + // Pull out the pin number (requires clearing the value bit) + const pin = this.pin[(byte - REPLY.ASYNC_PIN_CHANGE_N) & ~(1 << 3)]; + // Get the mode change + const mode = pin.interruptMode; + // Get the pin value + const pinValue = (byte >> 3) & 1; + + // For one-time 'low' or 'high' event + if (mode === 'low' || mode === 'high') { + pin.emit(mode); + // Reset the pin interrupt state (prevent constant interrupts) + pin.interruptMode = null; + // Decrement the number of tasks waiting on the socket + this.unref(); + } else { + // Emit the change and rise or fall + pin.emit('change', pinValue); + pin.emit(pinValue ? 'rise' : 'fall'); + } + + } else { + // Some other async event + this.emit('async-event', byte); + } + + // Cut this byte off of the reply buffer + replyBuf = replyBuf.slice(1); } else { - /* istanbul ignore else */ - if (byte === REPLY.HIGH || byte === REPLY.LOW) { - // Slice it off - replyBuf = replyBuf.slice(1); - // Get the callback in the queue - queued = this.dequeue(); - - // If a callback was provided + // If there are no commands awaiting a response + if (this.replyQueue.length === 0) { + // Throw an error... something went wrong + throw new Error(`Received unexpected response with no commands pending: ${byte}`); + } + + // Get the size if the incoming packet + const size = this.replyQueue[0].size; + + // If we have reply data + if (byte === REPLY.DATA) { + // Ensure that the packet size agrees + if (!size) { + throw new Error('Received unexpected data packet'); + } + + // The number of data bytes expected have been received. + if (replyBuf.length >= 1 + size) { + // Extract the data + const data = replyBuf.slice(1, 1 + size); + // Slice this data off of the buffer + replyBuf = replyBuf.slice(1 + size); + // Get the queued command + queued = this.dequeue(); + + // If there is a callback for th ecommand + /* istanbul ignore else */ + if (queued.callback) { + // Return the data in the callback + queued.callback.call(this, null, data); + } + } else { + // The buffer does not have the correct number of + // date bytes to fulfill the requirements of the + // reply queue's next registered handler. + break; + } + // If it's just one byte being returned + } else { /* istanbul ignore else */ - if (queued.callback) { - // Return the byte in the callback - queued.callback.call(this, null, byte); + if (byte === REPLY.HIGH || byte === REPLY.LOW) { + // Slice it off + replyBuf = replyBuf.slice(1); + // Get the callback in the queue + queued = this.dequeue(); + + // If a callback was provided + /* istanbul ignore else */ + if (queued.callback) { + // Return the byte in the callback + queued.callback.call(this, null, byte); + } } } } } - } - }.bind(this)); + }); - // Active peripheral: 'none', 'i2c', 'spi', 'uart' - this.mode = 'none'; + // Active peripheral: 'none', 'i2c', 'spi', 'uart' + this.mode = 'none'; - // Array of {size, callback} used to dispatch replies - this.replyQueue = []; + // Array of {size, callback} used to dispatch replies + this.replyQueue = []; - this.pin = []; - for (var i = 0; i < 8; i++) { - var interruptSupported = Tessel.Pin.interruptCapablePins.indexOf(i) !== -1; - var adcSupported = (name === B || Tessel.Pin.adcCapablePins.indexOf(i) !== -1); - var pullSupported = Tessel.Pin.pullCapablePins.indexOf(i) !== -1; - var pwmSupported = Tessel.Pin.pwmCapablePins.indexOf(i) !== -1; - this.pin.push(new Tessel.Pin(i, this, interruptSupported, adcSupported, pullSupported, pwmSupported)); - } + this.pin = []; + for (let i = 0; i < 8; i++) { + // const interruptSupported = INT_CAPABLE_PINS.indexOf(i) !== -1; + // const adcSupported = (name === B || ADC_CAPABLE_PINS.indexOf(i)) !== -1; + // const pullSupported = PULL_CAPABLE_PINS.indexOf(i) !== -1; + // const pwmSupported = PWM_CAPABLE_PINS.indexOf(i) !== -1; + // this.pin.push(new Tessel.Pin(i, this, interruptSupported, adcSupported, pullSupported, pwmSupported)); + this.pin.push(new Tessel.Pin(i, this)); + } - // Deprecated properties for Tessel 1 backwards compatibility: - this.pin.G1 = this.pin.g1 = this.pin[5]; - this.pin.G2 = this.pin.g2 = this.pin[6]; - this.pin.G3 = this.pin.g3 = this.pin[7]; - this.digital = [this.pin[5], this.pin[6], this.pin[7]]; + // Deprecated properties for Tessel 1 backwards compatibility: + this.pin.G1 = this.pin.g1 = this.pin[5]; + this.pin.G2 = this.pin.g2 = this.pin[6]; + this.pin.G3 = this.pin.g3 = this.pin[7]; + this.digital = [this.pin[5], this.pin[6], this.pin[7]]; - this.pwm = [this.pin[5], this.pin[6]]; + this.pwm = [this.pin[5], this.pin[6]]; - this.I2C = function(address) { - var options = {}; + this.I2C = function(address) { + const options = {}; - if (typeof address === 'object' && address != null) { - /* - { - addr: address, - freq: frequency, - port: port, - } - */ - Object.assign(options, address); - } else { - /* - (address) - */ - options.address = address; - } + if (typeof address === 'object' && address != null) { + /* + { + addr: address, + freq: frequency, + port: port, + } + */ + Object.assign(options, address); + } else { + /* + (address) + */ + options.address = address; + } - /* - Always ensure that the options - object contains a port property - with this port as its value. - */ - if (!options.port) { - options.port = port; - } else { /* - When receiving an object containing - options information, it's possible that - the calling code accidentally sends - a "port" that isn't this port. + Always ensure that the options + object contains a port property + with this port as its value. */ - /* istanbul ignore else */ - if (options.port !== port) { + if (!options.port) { options.port = port; + } else { + /* + When receiving an object containing + options information, it's possible that + the calling code accidentally sends + a "port" that isn't this port. + */ + /* istanbul ignore else */ + if (options.port !== port) { + options.port = port; + } } - } - return new Tessel.I2C(options); - }; + return new Tessel.I2C(options); + }; - this.I2C.enabled = false; + this.I2C.enabled = false; - this.SPI = function(options) { - if (port._spi) { - port._spi.disable(); - } + this.SPI = function(options) { + if (port._spi) { + port._spi.disable(); + } - port._spi = new Tessel.SPI(options || {}, port); + port._spi = new Tessel.SPI(options || {}, port); - return port._spi; - }; + return port._spi; + }; - this.UART = function(options) { - if (port._uart) { - port._uart.disable(); - } + this.UART = function(options) { + if (port._uart) { + port._uart.disable(); + } - port._uart = new Tessel.UART(options || {}, port); - // Grab a reference to this socket so it doesn't close - // if we're waiting for UART data - port.ref(); + port._uart = new Tessel.UART(options || {}, port); + // Grab a reference to this socket so it doesn't close + // if we're waiting for UART data + port.ref(); - return port._uart; - }; -}; + return port._uart; + }; + } -util.inherits(Tessel.Port, EventEmitter); + close() { + /* istanbul ignore else */ + if (!this.sock.destroyed) { + this.sock.isAllowedToClose = true; + this.sock.destroy(); + } + } -Tessel.Port.prototype.close = function() { - /* istanbul ignore else */ - if (!this.sock.destroyed) { - this.sock.isAllowedToClose = true; - this.sock.destroy(); + ref() { + // Increase the number of pending tasks + this.pending++; + // Ensure this socket stays open until unref'ed + this.sock.ref(); } -}; -Tessel.Port.prototype.ref = function() { - // Increase the number of pending tasks - this._pendingTasks++; - // Ensure this socket stays open until unref'ed - this.sock.ref(); -}; + unref() { + // If we have pending tasks to complete + if (this.pending > 0) { + // Subtract the one that is being unref'ed + this.pending--; + } -Tessel.Port.prototype.unref = function() { - // If we have pending tasks to complete - if (this._pendingTasks > 0) { - // Subtract the one that is being unref'ed - this._pendingTasks--; + // If this was the last task + if (this.pending === 0) { + // Unref the socket so the process doesn't hang open + this.sock.unref(); + } } - // If this was the last task - if (this._pendingTasks === 0) { - // Unref the socket so the process doesn't hang open - this.sock.unref(); + enqueue(reply) { + this.ref(); + this.replyQueue.push(reply); } -}; -Tessel.Port.prototype.enqueue = function(reply) { - this.ref(); - this.replyQueue.push(reply); -}; + dequeue() { + this.unref(); + return this.replyQueue.shift(); + } + cork() { + this.sock.cork(); + } -Tessel.Port.prototype.dequeue = function() { - this.unref(); - return this.replyQueue.shift(); -}; + uncork() { + this.sock.uncork(); + } -Tessel.Port.prototype.cork = function() { - this.sock.cork(); -}; + sync(callback) { + if (callback) { + this.sock.write(new Buffer([CMD.ECHO, 1, 0x88])); + this.enqueue({ + size: 1, + callback + }); + } + } -Tessel.Port.prototype.uncork = function() { - this.sock.uncork(); -}; + command(data, callback) { + this.cork(); + this.sock.write(new Buffer(data)); + this.sync(callback); + this.uncork(); + } -Tessel.Port.prototype.sync = function(callback) { - if (callback) { - this.sock.write(new Buffer([CMD.ECHO, 1, 0x88])); + status(data, callback) { + this.sock.write(new Buffer(data)); this.enqueue({ - size: 1, - callback: callback + size: 0, + callback, }); } -}; -Tessel.Port.prototype._simple_cmd = function(buf, callback) { - this.cork(); - this.sock.write(new Buffer(buf)); - this.sync(callback); - this.uncork(); -}; + tx(data, callback) { + let offset = 0; + let chunk; -Tessel.Port.prototype._status_cmd = function(buf, callback) { - this.sock.write(new Buffer(buf)); - this.enqueue({ - size: 0, - callback: callback, - }); -}; + if (data.length === 0) { + throw new RangeError('Buffer size must be non-zero'); + } -Tessel.Port.prototype._tx = function(buf, callback) { - var offset = 0, - chunk; + this.cork(); - if (buf.length === 0) { - throw new RangeError('Buffer size must be non-zero'); - } + // The protocol only supports <256 byte transfers, chunk if data is bigger + while (offset < data.length) { + chunk = data.slice(offset, offset + 255); - this.cork(); + this.sock.write(new Buffer([CMD.TX, chunk.length])); + this.sock.write(chunk); - // The protocol only supports <256 byte transfers, chunk if buf is bigger - while (offset < buf.length) { - chunk = buf.slice(offset, offset + 255); - - this.sock.write(new Buffer([CMD.TX, chunk.length])); - this.sock.write(chunk); + offset += 255; + } - offset += 255; + this.sync(callback); + this.uncork(); } - this.sync(callback); - this.uncork(); -}; + rx(len, callback) { + if (len === 0 || len > 255) { + throw new RangeError('Buffer size must be within 1-255'); + } -Tessel.Port.prototype._rx = function(len, callback) { - if (len === 0 || len > 255) { - throw new RangeError('Buffer size must be within 1-255'); + this.sock.write(new Buffer([CMD.RX, len])); + this.enqueue({ + size: len, + callback, + }); } - this.sock.write(new Buffer([CMD.RX, len])); - this.enqueue({ - size: len, - callback: callback, - }); -}; + txrx(buf, callback) { + const len = buf.length; -Tessel.Port.prototype._txrx = function(buf, callback) { - var len = buf.length; + if (len === 0 || len > 255) { + throw new RangeError('Buffer size must be within 1-255'); + } - if (len === 0 || len > 255) { - throw new RangeError('Buffer size must be within 1-255'); + this.cork(); + this.sock.write(new Buffer([CMD.TXRX, len])); + this.sock.write(buf); + this.enqueue({ + size: len, + callback, + }); + this.uncork(); } +} - this.cork(); - this.sock.write(new Buffer([CMD.TXRX, len])); - this.sock.write(buf); - this.enqueue({ - size: len, - callback: callback, - }); - this.uncork(); -}; - -Tessel.Port.PATH = { +Port.PATH = { A: '/var/run/tessel/port_a', B: '/var/run/tessel/port_b' }; -Tessel.Pin = function(pin, port, interruptSupported, analogSupported, pullSupported, pwmSupported) { - this.pin = pin; - this._port = port; - this.interruptSupported = interruptSupported || false; - this.analogSupported = analogSupported || false; - this.pullSupported = pullSupported || false; - this.pwmSupported = pwmSupported || false; - this.interruptMode = null; - this.isPWM = false; -}; -util.inherits(Tessel.Pin, EventEmitter); -Tessel.Pin.adcCapablePins = [4, 7]; -Tessel.Pin.pullCapablePins = [2, 3, 4, 5, 6, 7]; -Tessel.Pin.interruptCapablePins = [2, 5, 6, 7]; -Tessel.Pin.pwmCapablePins = [5, 6]; -Tessel.Pin.interruptModes = { - rise: 1, - fall: 2, - change: 3, - high: 4, - low: 5, -}; -Tessel.Pin.pullModes = { - pulldown: 0, - pullup: 1, - none: 2, -}; -Tessel.Pin.prototype.removeListener = function(event, listener) { - // If it's an interrupt event, remove as necessary - var emitter = Tessel.Pin.super_.prototype.removeListener.call(this, event, listener); - if (event === this.interruptMode && this.listenerCount(event) === 0) { - this._setInterruptMode(null); + + +/* + Takes in a desired frequency setting and outputs the + necessary prescalar and duty cycle settings based on set period. + Outputs an object in the form of: + { + prescalar: number (0-7), + period: number (0-0xFFFF) + } +*/ +function determineDutyCycleAndPrescalar(frequency) { + // Current setting for the prescalar + let prescalarIndex = 0; + // Current period setting + let period = 0; + + // If the current frequency would require a period greater than the max + while ((period = Math.floor((SAMD21_TICKS_PER_SECOND / PWM_PRESCALARS[prescalarIndex]) / frequency)) > PWM_MAX_PERIOD) { + // Increase our clock prescalar + prescalarIndex++; + + // If we have no higher prescalars + if (prescalarIndex === PWM_PRESCALARS.length) { + // Throw an error because this frequency is too low for our possible parameters + throw new Error('Unable to find prescalar/duty cycle parameter match for frequency'); + } } - return emitter; -}; + // We have found a period inside a suitable prescalar, return results + return { + period, + prescalarIndex + }; +} + +class Pin extends EventEmitter { + constructor(pin, port, interruptSupported, analogSupported, pullSupported, pwmSupported) { + super(); + + this.pin = pin; + this.port = port; + + console.log(port.name); -Tessel.Pin.prototype.removeAllListeners = function(event) { - /* istanbul ignore else */ - if (!event || event === this.interruptMode) { - this._setInterruptMode(null); + + this.interruptSupported = interruptSupported || false; + this.analogSupported = analogSupported || false; + this.pullSupported = pullSupported || false; + this.pwmSupported = pwmSupported || false; + this.interruptMode = null; + this.isPWM = false; } - return Tessel.Pin.super_.prototype.removeAllListeners.apply(this, arguments); -}; + get resolution() { + return ANALOG_RESOLUTION; + } -Tessel.Pin.prototype.addListener = function(mode, callback) { - // Check for valid pin event mode - if (typeof Tessel.Pin.interruptModes[mode] !== 'undefined') { - if (!this.interruptSupported) { - throw new Error(`Interrupts are not supported on pin ${this.pin}. Pins 2, 5, 6, and 7 on either port support interrupts.`); - } + removeListener(event, listener) { + // If it's an interrupt event, remove as necessary + super.removeListener(event, listener); - // For one-time 'low' or 'high' event - if ((mode === 'low' || mode === 'high') && !callback.listener) { - throw new Error('Cannot use "on" with level interrupts. You can only use "once".'); + if (event === this.interruptMode && this.listenerCount(event) === 0) { + this._setInterruptMode(null); } - // Can't set multiple listeners when using 'low' or 'high' - if (this.interruptMode) { - var singleEventModes = ['low', 'high']; - if (singleEventModes.some(value => mode === value || this.interruptMode === value)) { - throw new Error(`Cannot set pin interrupt mode to "${mode}"; already listening for "${this.interruptMode}". Can only set multiple listeners with "change", "rise" & "fall".`); - } + return this; + } + + removeAllListeners(event) { + /* istanbul ignore else */ + if (!event || event === this.interruptMode) { + this._setInterruptMode(null); } - // Set the socket reference so the script doesn't exit - this._port.ref(); - this._setInterruptMode(mode); + super.removeAllListeners.apply(this, arguments); - // Add the event listener - Tessel.Pin.super_.prototype.on.call(this, mode, callback); - } else { - throw new Error(`Invalid pin event mode "${mode}". Valid modes are "change", "rise", "fall", "high" and "low".`); + return this; } -}; -Tessel.Pin.prototype.on = Tessel.Pin.prototype.addListener; -Tessel.Pin.prototype._setInterruptMode = function(mode) { - // rise and fall events will be emitted by change event - this.interruptMode = (mode === 'rise' || mode === 'fall') ? 'change' : mode; - var bits = mode ? Tessel.Pin.interruptModes[mode] << 4 : 0; - this._port._simple_cmd([CMD.GPIO_INT, this.pin | bits]); -}; + addListener(mode, callback) { + // Check for valid pin event mode + if (typeof INT_MODES[mode] !== 'undefined') { + if (!this.interruptSupported) { + throw new Error(`Interrupts are not supported on pin ${this.pin}. Pins 2, 5, 6, and 7 on either port support interrupts.`); + } -Tessel.Pin.prototype.high = function(callback) { - this._port._simple_cmd([CMD.GPIO_HIGH, this.pin], callback); - return this; -}; + // For one-time 'low' or 'high' event + if ((mode === 'low' || mode === 'high') && !callback.listener) { + throw new Error('Cannot use "on" with level interrupts. You can only use "once".'); + } -Tessel.Pin.prototype.low = function(callback) { - this._port._simple_cmd([CMD.GPIO_LOW, this.pin], callback); - return this; -}; + // Can't set multiple listeners when using 'low' or 'high' + if (this.interruptMode) { + const singleEventModes = ['low', 'high']; + if (singleEventModes.some(value => mode === value || this.interruptMode === value)) { + throw new Error(`Cannot set pin interrupt mode to "${mode}"; already listening for "${this.interruptMode}". Can only set multiple listeners with "change", "rise" & "fall".`); + } + } -Tessel.Pin.prototype.rawWrite = util.deprecate(function(value) { - if (value) { - this.high(); - } else { - this.low(); + // Set the socket reference so the script doesn't exit + this.port.ref(); + this._setInterruptMode(mode); + + // Add the event listener + super.on(mode, callback); + } else { + throw new Error(`Invalid pin event mode "${mode}". Valid modes are "change", "rise", "fall", "high" and "low".`); + } } - return this; -}, 'pin.rawWrite is deprecated. Use .high() or .low()'); -Tessel.Pin.prototype.toggle = function(callback) { - this._port._simple_cmd([CMD.GPIO_TOGGLE, this.pin], callback); - return this; -}; + _setInterruptMode(mode) { + // rise and fall events will be emitted by change event + this.interruptMode = (mode === 'rise' || mode === 'fall') ? 'change' : mode; + const bits = mode ? INT_MODES[mode] << 4 : 0; + this.port.command([CMD.GPIO_INT, this.pin | bits]); + } -Tessel.Pin.prototype.output = function(value, callback) { - if (value) { - this.high(callback); - } else { - this.low(callback); + high(callback) { + this.port.command([CMD.GPIO_HIGH, this.pin], callback); + return this; } - return this; -}; -Tessel.Pin.prototype.write = function(value, callback) { - // same as .output - return this.output(value, callback); -}; + low(callback) { + this.port.command([CMD.GPIO_LOW, this.pin], callback); + return this; + } -Tessel.Pin.prototype.rawDirection = function() { - throw new Error('pin.rawDirection is not supported on Tessel 2. Use .input() or .output()'); -}; + toggle(callback) { + this.port.command([CMD.GPIO_TOGGLE, this.pin], callback); + return this; + } -Tessel.Pin.prototype._readPin = function(cmd, callback) { - this._port.cork(); - this._port.sock.write(new Buffer([cmd, this.pin])); - this._port.enqueue({ - size: 0, - callback: (error, data) => callback(error, data === REPLY.HIGH ? 1 : 0), - }); - this._port.uncork(); -}; + output(value, callback) { + if (value) { + this.high(callback); + } else { + this.low(callback); + } + return this; + } -Tessel.Pin.prototype.rawRead = function(callback) { - if (typeof callback !== 'function') { - throw new Error('pin.rawRead is async, pass in a callback to get the value'); + write(value, callback) { + // same as .output + return this.output(value, callback); } - this._readPin(CMD.GPIO_RAW_READ, callback); - return this; -}; -Tessel.Pin.prototype.input = function(callback) { - this._port._simple_cmd([CMD.GPIO_INPUT, this.pin], callback); - return this; -}; + rawDirection() { + throw new Error('pin.rawDirection is not supported on Tessel 2. Use .input() or .output()'); + } -Tessel.Pin.prototype.read = function(callback) { - if (typeof callback !== 'function') { - throw new Error('pin.read is async, pass in a callback to get the value'); + _readPin(cmd, callback) { + this.port.cork(); + this.port.sock.write(new Buffer([cmd, this.pin])); + this.port.enqueue({ + size: 0, + callback: (error, data) => callback(error, data === REPLY.HIGH ? 1 : 0), + }); + this.port.uncork(); } - this._readPin(CMD.GPIO_IN, callback); - return this; -}; -Tessel.Pin.prototype.pull = function(pullType, callback) { + rawRead(callback) { + if (typeof callback !== 'function') { + throw new Error('pin.rawRead is async, pass in a callback to get the value'); + } + this._readPin(CMD.GPIO_RAW_READ, callback); + return this; + } + + input(callback) { + this.port.command([CMD.GPIO_INPUT, this.pin], callback); + return this; + } + + read(callback) { + if (typeof callback !== 'function') { + throw new Error('pin.read is async, pass in a callback to get the value'); + } + this._readPin(CMD.GPIO_IN, callback); + return this; + } + + pull(pullType, callback) { + + // Ensure this pin supports being pulled + if (!this.pullSupported) { + throw new Error('Internal pull resistors are not available on this pin. Please use pins 2-7.'); + } + + // Set a default value to 'none'; + if (pullType === undefined) { + pullType = 'none'; + } + + const mode = PULL_MODES[pullType]; - // Ensure this pin supports being pulled - if (!this.pullSupported) { - throw new Error('Internal pull resistors are not available on this pin. Please use pins 2-7.'); + // Ensure a valid mode was requested + if (mode === undefined) { + throw new Error('Invalid pull type. Must be one of: "pullup", "pulldown", or "none"'); + } + + // Send the command to the coprocessor + this.port.command([CMD.GPIO_PULL, (this.pin | (mode << 4))], callback); } - // Set a default value to 'none'; - if (pullType === undefined) { - pullType = 'none'; + readPulse() { + throw new Error('Pin.readPulse is not yet implemented'); } - var mode = Tessel.Pin.pullModes[pullType]; + analogRead(callback) { + if (!this.analogSupported) { + throw new RangeError('pin.analogRead is not supported on this pin. Analog read is supported on port A pins 4 and 7 and on all pins on port B'); + } + + if (typeof callback !== 'function') { + throw new Error('analogPin.read is async, pass in a callback to get the value'); + } + + this.port.sock.write(new Buffer([CMD.ANALOG_READ, this.pin])); + this.port.enqueue({ + size: 2, + callback(err, data) { + callback(err, (data[0] + (data[1] << 8)) / ANALOG_RESOLUTION); + }, + }); - // Ensure a valid mode was requested - if (mode === undefined) { - throw new Error('Invalid pull type. Must be one of: "pullup", "pulldown", or "none"'); + return this; } - // Send the command to the coprocessor - this._port._simple_cmd([CMD.GPIO_PULL, (this.pin | (mode << 4))], callback); -}; - -Tessel.Pin.prototype.readPulse = function( /* type, timeout, callback */ ) { - throw new Error('Pin.readPulse is not yet implemented'); -}; + analogWrite(val) { + // throw an error if this isn't the adc pin (port b, pin 7) + if (this.port.name !== 'B' || this.pin !== 7) { + throw new RangeError('Analog write can only be used on Pin 7 (G3) of Port B.'); + } -var ANALOG_RESOLUTION = 4096; -Tessel.Pin.prototype.resolution = ANALOG_RESOLUTION; + const data = val * 0x3ff; + if (data > 0x3ff || data < 0) { + throw new RangeError('Analog write must be between 0 and 1'); + } -Tessel.Pin.prototype.analogRead = function(callback) { - if (!this.analogSupported) { - throw new RangeError('pin.analogRead is not supported on this pin. Analog read is supported on port A pins 4 and 7 and on all pins on port B'); + this.port.sock.write(new Buffer([CMD.ANALOG_WRITE, data >> 8, data & 0xff])); + return this; } - if (typeof callback !== 'function') { - throw new Error('analogPin.read is async, pass in a callback to get the value'); - } + // Duty cycle should be a value between 0 and 1 + pwmDutyCycle(dutyCycle, callback) { + // throw an error if this pin doesn't support PWM + if (!this.pwmSupported) { + throw new RangeError('PWM can only be used on TX (pin 5) and RX (pin 6) of either module port.'); + } - this._port.sock.write(new Buffer([CMD.ANALOG_READ, this.pin])); - this._port.enqueue({ - size: 2, - callback: function(err, data) { - callback(err, (data[0] + (data[1] << 8)) / ANALOG_RESOLUTION); - }, - }); + if (typeof dutyCycle !== 'number' || dutyCycle < 0 || dutyCycle > 1) { + throw new RangeError('PWM duty cycle must be a number between 0 and 1'); + } - return this; -}; + // The frequency must be set prior to setting the duty cycle + if (pwmBankSettings.period === 0) { + throw new Error('PWM Frequency is not configured. You must call Tessel.pwmFrequency before setting duty cycle.'); + } -Tessel.Pin.prototype.analogWrite = function(val) { - // throw an error if this isn't the adc pin (port b, pin 7) - if (this._port.name !== 'B' || this.pin !== 7) { - throw new RangeError('Analog write can only be used on Pin 7 (G3) of Port B.'); - } + // Calculate number of ticks for specified duty cycle + const dutyCycleTicks = Math.floor(dutyCycle * pwmBankSettings.period); + // Construct packet + const packet = new Buffer([CMD.PWM_DUTY_CYCLE, this.pin, dutyCycleTicks >> 8, dutyCycleTicks & 0xff]); - var data = val * 0x3ff; - if (data > 0x3ff || data < 0) { - throw new RangeError('Analog write must be between 0 and 1'); + // Write it to the socket + this.port.sock.write(packet, callback); + + return this; } - this._port.sock.write(new Buffer([CMD.ANALOG_WRITE, data >> 8, data & 0xff])); - return this; -}; -// Duty cycle should be a value between 0 and 1 -Tessel.Pin.prototype.pwmDutyCycle = function(dutyCycle, callback) { - // throw an error if this pin doesn't support PWM - if (!this.pwmSupported) { - throw new RangeError('PWM can only be used on TX (pin 5) and RX (pin 6) of either module port.'); + static get ANALOG_RESOLUTION() { + return ANALOG_RESOLUTION; } +} - if (typeof dutyCycle !== 'number' || dutyCycle < 0 || dutyCycle > 1) { - throw new RangeError('PWM duty cycle must be a number between 0 and 1'); - } - // The frequency must be set prior to setting the duty cycle - if (pwmBankSettings.period === 0) { - throw new Error('PWM Frequency is not configured. You must call Tessel.pwmFrequency before setting duty cycle.'); +Pin.prototype.rawWrite = util.deprecate(function(value) { + if (value) { + this.high(); + } else { + this.low(); } + return this; +}, 'pin.rawWrite is deprecated. Use .high() or .low()'); - // Calculate number of ticks for specified duty cycle - var dutyCycleTicks = Math.floor(dutyCycle * pwmBankSettings.period); - // Construct packet - var packet = new Buffer([CMD.PWM_DUTY_CYCLE, this.pin, dutyCycleTicks >> 8, dutyCycleTicks & 0xff]); - // Write it to the socket - this._port.sock.write(packet, callback); +Pin.prototype.on = Pin.prototype.addListener; - return this; -}; -Tessel.I2C = function(params) { - var frequency = 1e5; +class I2C { + constructor(params) { + let frequency = 1e5; - if (params.address == null) { - throw new Error('Tessel.I2C expected an address'); - } + if (params.address == null) { + throw new Error('I2C expected an address'); + } - Object.defineProperties(this, { - frequency: { - get() { - return frequency; + Object.defineProperties(this, { + frequency: { + get() { + return frequency; + }, + set(value) { + // Restrict to between 100kHz and 400kHz. + // Can actually go up to 4mhz without clk modification + if (value !== 1e5 && value !== 4e5) { + // http://asf.atmel.com/docs/3.15.0/samd21/html/group__asfdoc__sam0__sercom__i2c__group.html#gace1e0023f2eee92565496a2e30006548 + throw new RangeError('I2C frequency must be 100kHz or 400kHz'); + } + + frequency = value; + } }, - set(value) { - // Restrict to between 100kHz and 400kHz. - // Can actually go up to 4mhz without clk modification - if (value !== 1e5 && value !== 4e5) { - // http://asf.atmel.com/docs/3.15.0/samd21/html/group__asfdoc__sam0__sercom__i2c__group.html#gace1e0023f2eee92565496a2e30006548 - throw new RangeError('I2C frequency must be 100kHz or 400kHz'); + baudrate: { + get() { + return I2C.computeBaud(frequency); } - - frequency = value; - } - }, - baudrate: { - get() { - return Tessel.I2C.computeBaud(frequency); } - } - }); + }); - this._port = params.port; + this.port = params.port; - // For t1-firmware compatibility, this.addr = ... - this.addr = this.address = params.address; + // For t1-firmware compatibility, this.addr = ... + this.addr = this.address = params.address; - // This is setting the accessor defined above - this.frequency = params.frequency || 100000; // 100khz + // This is setting the accessor defined above + this.frequency = params.frequency || 100000; // 100khz - // Send the ENABLE_I2C command when the first I2C device is instantiated - if (!this._port.I2C.enabled) { - this._port._simple_cmd([CMD.ENABLE_I2C, this.baudrate]); - // Note that this bus is enabled now - this._port.I2C.enabled = true; + // Send the ENABLE_I2C command when the first I2C device is instantiated + if (!this.port.I2C.enabled) { + this.port.command([CMD.ENABLE_I2C, this.baudrate]); + // Note that this bus is enabled now + this.port.I2C.enabled = true; + } } -}; - -Tessel.I2C.computeBaud = function(frequency) { - // 15ns is max scl rise time - // f = (48e6)/(2*(5+baud)+48e6*1.5e-8) - var baud = Math.floor(((48e6 / frequency) - 48e6 * (1.5e-8)) / 2 - 5); - - return Math.max(0, Math.min(baud, 255)); -}; - -Tessel.I2C.prototype.send = function(data, callback) { - this._port.cork(); - this._port._simple_cmd([CMD.START, this.address << 1]); - this._port._tx(data); - this._port._simple_cmd([CMD.STOP], callback); - this._port.uncork(); -}; - -Tessel.I2C.prototype.read = function(length, callback) { - this._port.cork(); - this._port._simple_cmd([CMD.START, this.address << 1 | 1]); - this._port._rx(length, callback); - this._port._simple_cmd([CMD.STOP]); - this._port.uncork(); -}; -Tessel.I2C.prototype.transfer = function(txbuf, rxlen, callback) { - this._port.cork(); - /* istanbul ignore else */ - if (txbuf.length > 0) { - this._port._simple_cmd([CMD.START, this.address << 1]); - this._port._tx(txbuf); - } - this._port._simple_cmd([CMD.START, this.address << 1 | 1]); - this._port._rx(rxlen, callback); - this._port._simple_cmd([CMD.STOP]); - this._port.uncork(); -}; + static computeBaud(frequency) { + // 15ns is max scl rise time + // f = (48e6)/(2*(5+baud)+48e6*1.5e-8) + const baud = Math.floor(((48e6 / frequency) - 48e6 * (1.5e-8)) / 2 - 5); -Tessel.SPI = function(params, port) { - this._port = port; - // Default the params if none were provided - params = params || {}; - // default to pin 5 of the module port as cs - this.chipSelect = params.chipSelect || this._port.digital[0]; - this.chipSelectActive = params.chipSelectActive === 'high' || params.chipSelectActive === 1 ? 1 : 0; + return Math.max(0, Math.min(baud, 255)); + } - if (this.chipSelectActive) { - // active high, pull low for now - this.chipSelect.low(); - } else { - // active low, pull high for now - this.chipSelect.high(); + send(data, callback) { + this.port.cork(); + this.port.command([CMD.START, this.address << 1]); + this.port.tx(data); + this.port.command([CMD.STOP], callback); + this.port.uncork(); } - /* spi baud rate is set by the following equation: - * f_baud = f_ref/(2*(baud_reg+1)) - * max baud rate is 24MHz for the SAMD21, min baud rate is 93750 without a clock divisor - * with a max clock divisor of 255, slowest clock is 368Hz unless we switch from 48MHz xtal to 32KHz xtal - */ - // default is 2MHz - this.clockSpeed = params.clockSpeed || 2e6; + read(length, callback) { + this.port.cork(); + this.port.command([CMD.START, this.address << 1 | 1]); + this.port.rx(length, callback); + this.port.command([CMD.STOP]); + this.port.uncork(); + } - // if speed is slower than 93750 then we need a clock divisor - if (this.clockSpeed < 368 || this.clockSpeed > 24e6) { - throw new RangeError('SPI clock must be between 368Hz and 24MHz'); + transfer(txbuf, rxlen, callback) { + this.port.cork(); + /* istanbul ignore else */ + if (txbuf.length > 0) { + this.port.command([CMD.START, this.address << 1]); + this.port.tx(txbuf); + } + this.port.command([CMD.START, this.address << 1 | 1]); + this.port.rx(rxlen, callback); + this.port.command([CMD.STOP]); + this.port.uncork(); } +} - this._clockReg = Math.floor(48e6 / (2 * this.clockSpeed) - 1); - // Find the smallest clock divider such that clockReg is <=255 - if (this._clockReg > 255) { - // Find the clock divider, make sure its at least 1 - this._clockDiv = Math.floor(48e6 / (this.clockSpeed * (2 * 255 + 2))); +class SPI { + constructor(params, port) { + this.port = port; + // Default the params if none were provided + params = params || {}; + // default to pin 5 of the module port as cs + this.chipSelect = params.chipSelect || this.port.digital[0]; + this.chipSelectActive = params.chipSelectActive === 'high' || params.chipSelectActive === 1 ? 1 : 0; - // if the speed is still too low, set the clock divider to max and set baud accordingly - // This condition will only be met when the clockSpeed parameter - // is <= 366Hz, which is not possible given the Range condition - // above: (368Hz-24MHz) - /* istanbul ignore if*/ - if (this._clockDiv > 255) { - this._clockReg = Math.floor(this._clockReg / 255) || 1; - this._clockDiv = 255; + if (this.chipSelectActive) { + // active high, pull low for now + this.chipSelect.low(); } else { - // if we can set a clock divider <255, max out clockReg - this._clockReg = 255; + // active low, pull high for now + this.chipSelect.high(); } - } else { - this._clockDiv = 1; - } - if (typeof params.dataMode === 'number') { - params.cpol = params.dataMode & 0x1; - params.cpha = params.dataMode & 0x2; - } + /* spi baud rate is set by the following equation: + * f_baud = f_ref/(2*(baud_reg+1)) + * max baud rate is 24MHz for the SAMD21, min baud rate is 93750 without a clock divisor + * with a max clock divisor of 255, slowest clock is 368Hz unless we switch from 48MHz xtal to 32KHz xtal + */ + // default is 2MHz + this.clockSpeed = params.clockSpeed || 2e6; + + // if speed is slower than 93750 then we need a clock divisor + if (this.clockSpeed < 368 || this.clockSpeed > 24e6) { + throw new RangeError('SPI clock must be between 368Hz and 24MHz'); + } - this.cpol = params.cpol === 'high' || params.cpol === 1 ? 1 : 0; - this.cpha = params.cpha === 'second' || params.cpha === 1 ? 1 : 0; + this._clockReg = Math.floor(48e6 / (2 * this.clockSpeed) - 1); + + // Find the smallest clock divider such that clockReg is <=255 + if (this._clockReg > 255) { + // Find the clock divider, make sure its at least 1 + this._clockDiv = Math.floor(48e6 / (this.clockSpeed * (2 * 255 + 2))); + + // if the speed is still too low, set the clock divider to max and set baud accordingly + // This condition will only be met when the clockSpeed parameter + // is <= 366Hz, which is not possible given the Range condition + // above: (368Hz-24MHz) + /* istanbul ignore if*/ + if (this._clockDiv > 255) { + this._clockReg = Math.floor(this._clockReg / 255) || 1; + this._clockDiv = 255; + } else { + // if we can set a clock divider <255, max out clockReg + this._clockReg = 255; + } + } else { + this._clockDiv = 1; + } - this._port._simple_cmd([CMD.ENABLE_SPI, this.cpol + (this.cpha << 1), this._clockReg, this._clockDiv]); -}; + if (typeof params.dataMode === 'number') { + params.cpol = params.dataMode & 0x1; + params.cpha = params.dataMode & 0x2; + } -Tessel.SPI.prototype.send = function(data, callback) { - this._port.cork(); - this.chipSelect.low(); - this._port._tx(data, callback); - this.chipSelect.high(); - this._port.uncork(); -}; + this.cpol = params.cpol === 'high' || params.cpol === 1 ? 1 : 0; + this.cpha = params.cpha === 'second' || params.cpha === 1 ? 1 : 0; -Tessel.SPI.prototype.disable = function() { - // Tell the coprocessor to disable this interface - this._port._simple_cmd([CMD.CMD_DISABLE_SPI]); - // Unreference the previous SPI object - this._port._spi = undefined; -}; + this.port.command([CMD.ENABLE_SPI, this.cpol + (this.cpha << 1), this._clockReg, this._clockDiv]); + } -Tessel.SPI.prototype.receive = function(length, callback) { - this._port.cork(); - this.chipSelect.low(); - this._port._rx(length, callback); - this.chipSelect.high(); - this._port.uncork(); -}; + send(data, callback) { + this.port.cork(); + this.chipSelect.low(); + this.port.tx(data, callback); + this.chipSelect.high(); + this.port.uncork(); + } -Tessel.SPI.prototype.transfer = function(data, callback) { - this._port.cork(); - this.chipSelect.low(); - this._port._txrx(data, callback); - this.chipSelect.high(); - this._port.uncork(); -}; + disable() { + // Tell the coprocessor to disable this interface + this.port.command([CMD.CMD_DISABLE_SPI]); + // Unreference the previous SPI object + this.port._spi = undefined; + } -Tessel.UART = function(options, port) { - Duplex.call(this, {}); + receive(length, callback) { + this.port.cork(); + this.chipSelect.low(); + this.port.rx(length, callback); + this.chipSelect.high(); + this.port.uncork(); + } - var baudrate = 9600; + transfer(data, callback) { + this.port.cork(); + this.chipSelect.low(); + this.port.txrx(data, callback); + this.chipSelect.high(); + this.port.uncork(); + } +} - Object.defineProperties(this, { - baudrate: { - get: () => { - return baudrate; - }, - set: (value) => { - // baud is given by the following: - // baud = 65536*(1-(samples_per_bit)*(f_wanted/f_ref)) - // samples_per_bit = 16, 8, or 3 - // f_ref = 48e6 - - if (value < 9600 || value > 115200) { - throw new Error('UART baudrate must be between 9600 and 115200'); - } +class UART extends Duplex { + constructor(options, port) { + super({}); + + let baudrate = 9600; + + Object.defineProperties(this, { + baudrate: { + get: () => { + return baudrate; + }, + set: (value) => { + // baud is given by the following: + // baud = 65536*(1-(samples_per_bit)*(f_wanted/f_ref)) + // samples_per_bit = 16, 8, or 3 + // f_ref = 48e6 + + if (value < 9600 || value > 115200) { + throw new Error('UART baudrate must be between 9600 and 115200'); + } - baudrate = value; + baudrate = value; - var computed = Math.floor(65536 * (1 - 16 * (baudrate / 48e6))); + const computed = Math.floor(65536 * (1 - 16 * (baudrate / 48e6))); - this._port._simple_cmd([CMD.ENABLE_UART, computed >> 8, computed & 0xFF]); + this.port.command([CMD.ENABLE_UART, computed >> 8, computed & 0xFF]); + } } - } - }); - - this._port = port; - this.baudrate = options.baudrate || 9600; -}; - -util.inherits(Tessel.UART, Duplex); - -Tessel.UART.prototype._write = function(chunk, encoding, callback) { - // It appears that UART _write has always ignored the encoding argument. - // This function is unused by this library code and appears only for - // compatibility with T1 module code. + }); - if (!this._port._uart) { - throw new Error('UART is not enabled on this port'); + this.port = port; + this.baudrate = options.baudrate || 9600; } - this._port._tx(chunk, callback); -}; -Tessel.UART.prototype._read = function() {}; + _write(chunk, encoding, callback) { + // It appears that UART _write has always ignored the encoding argument. + // This function is unused by this library code and appears only for + // compatibility with T1 module code. -Tessel.UART.prototype.disable = function() { - // Tell the coprocessor to disable this interface - this._port._simple_cmd([CMD.DISABLE_UART, 0, 0]); - // Unreference this socket if there are no more items waiting on it - // Specifically because it is asynchronous - this._port.unref(); - // Unreference the previous uart object - this._port._uart = undefined; -}; - -var CMD = { - NOP: 0, - FLUSH: 1, - ECHO: 2, - GPIO_IN: 3, - GPIO_HIGH: 4, - GPIO_LOW: 5, - GPIO_TOGGLE: 21, - GPIO_CFG: 6, - GPIO_WAIT: 7, - GPIO_INT: 8, - GPIO_INPUT: 22, - GPIO_RAW_READ: 23, - GPIO_PULL: 26, - ANALOG_READ: 24, - ANALOG_WRITE: 25, - ENABLE_SPI: 10, - DISABLE_SPI: 11, - ENABLE_I2C: 12, - DISABLE_I2C: 13, - ENABLE_UART: 14, - DISABLE_UART: 15, - TX: 16, - RX: 17, - TXRX: 18, - START: 19, - STOP: 20, - PWM_DUTY_CYCLE: 27, - PWM_PERIOD: 28, -}; + if (!this.port._uart) { + throw new Error('UART is not enabled on this port'); + } + this.port.tx(chunk, callback); + } -var REPLY = { - ACK: 0x80, - NACK: 0x81, - HIGH: 0x82, - LOW: 0x83, - DATA: 0x84, + _read() {} - MIN_ASYNC: 0xA0, - ASYNC_PIN_CHANGE_N: 0xC0, // c0 to c8 is all async pin assignments - ASYNC_UART_RX: 0xD0 -}; + disable() { + // Tell the coprocessor to disable this interface + this.port.command([CMD.DISABLE_UART, 0, 0]); + // Unreference this socket if there are no more items waiting on it + // Specifically because it is asynchronous + this.port.unref(); + // Unreference the previous uart object + this.port._uart = undefined; + } +} // Currently unused. Uncomment when ready to implement // var SPISettings = { @@ -1170,7 +1207,7 @@ var REPLY = { const prefix = '/sys/devices/leds/leds/tessel:'; const suffix = '/brightness'; -Tessel.LEDs = function(defs) { +function LEDs(defs) { const descriptors = {}; const leds = []; @@ -1198,10 +1235,10 @@ Tessel.LEDs = function(defs) { }; Object.defineProperties(this, descriptors); -}; +} for (const operation of ['on', 'off', 'toggle']) { - Tessel.LEDs.prototype[operation] = function() { + LEDs.prototype[operation] = function() { for (let i = 0; i < this.length; i++) { this[i][operation](); } @@ -1209,236 +1246,219 @@ for (const operation of ['on', 'off', 'toggle']) { }; } -Tessel.LED = function(color, path) { - var state = { - color, - path, - value: 0, - }; +class LED { + constructor(color, path) { + const state = { + color, + path, + value: 0, + }; - // Define data properties that enforce - // restricted write privileges. - Object.defineProperties(this, { - color: { - value: state.color - }, - path: { - value: state.path - }, - value: { - get() { - return state.value; + // Define data properties that enforce + // restricted write privileges. + Object.defineProperties(this, { + color: { + value: state.color }, - set(value) { - // Treat any truthiness as "high" - state.value = value ? 1 : 0; - } - }, - isOn: { - get() { - return state.value === 1; + path: { + value: state.path + }, + value: { + get() { + return state.value; + }, + set(value) { + // Treat any truthiness as "high" + state.value = value ? 1 : 0; + } + }, + isOn: { + get() { + return state.value === 1; + } } - } - }); -}; + }); + } -Tessel.LED.prototype.high = function(callback) { - this.write(1, callback); -}; + high(callback) { + this.write(1, callback); + } -Tessel.LED.prototype.low = function(callback) { - this.write(0, callback); -}; + low(callback) { + this.write(0, callback); + } -Tessel.LED.prototype.on = function() { - this.write(1); - return this; -}; + on() { + this.write(1); + return this; + } -Tessel.LED.prototype.off = function() { - this.write(0); - return this; -}; + off() { + this.write(0); + return this; + } -Tessel.LED.prototype.toggle = function(callback) { - this.write(this.value ? 0 : 1, callback); -}; + toggle(callback) { + this.write(this.value ? 0 : 1, callback); + } -Tessel.LED.prototype.write = function(value, callback) { - callback = enforceCallback(callback); + write(value, callback) { + callback = enforceCallback(callback); - // Setting this.value will invoke the setter - // defined inside the constructor body. That - // ensure that any truthy value will result - // in a value of 1 and falsy results in 0. - this.value = value; + // Setting this.value will invoke the setter + // defined inside the constructor body. That + // ensure that any truthy value will result + // in a value of 1 and falsy results in 0. + this.value = value; - fs.writeFile(this.path, String(this.value), callback); -}; + fs.writeFile(this.path, String(this.value), callback); + } + + read(callback) { + const value = this.value; + callback = enforceCallback(callback); + setImmediate(() => callback(null, value)); + } +} // Define backward compatibility alias -Tessel.LED.prototype.output = Tessel.LED.prototype.write; +LED.prototype.output = LED.prototype.write; -Tessel.LED.prototype.read = function(callback) { - var value = this.value; - setImmediate(() => { - callback(null, value); - }); -}; +class Wifi extends EventEmitter { + constructor() { + super(); -Tessel.Wifi = function() { - var state = { - settings: {} - }; + const state = { + settings: {} + }; - Object.defineProperties(this, { - settings: { - get: () => state.settings, - set: (settings) => { - state.settings = Object.assign(state.settings, settings); + Object.defineProperties(this, { + settings: { + get: () => state.settings, + set: (settings) => { + state.settings = Object.assign(state.settings, settings); + } } - } - }); -}; - -util.inherits(Tessel.Wifi, EventEmitter); - -Tessel.Wifi.prototype.enable = function(callback) { - callback = enforceCallback(callback); - - turnOnWifi() - .then(commitWireless) - .then(restartWifi) - .then(getWifiInfo) - .then((network) => { - Object.assign(this.settings, network); - this.emit('connect', this.settings); - callback(); - }) - .catch((error) => { - this.connected = false; - this.emit('error', error); - callback(error); }); -}; + } -Tessel.Wifi.prototype.disable = function(callback) { - callback = enforceCallback(callback); - - turnOffWifi() - .then(commitWireless) - .then(restartWifi) - .then(() => { - this.emit('disconnect'); - callback(); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + enable(callback) { + callback = enforceCallback(callback); + + turnOnWifi() + .then(commitWireless) + .then(restartWifi) + .then(getWifiInfo) + .then(network => emitConnectCallback(this, Object.assign(this.settings, network), callback)) + .catch(error => { + this.connected = false; + emitErrorCallback(this, error, callback); + }); + } -Tessel.Wifi.prototype.reset = function(callback) { - callback = enforceCallback(callback); - - this.emit('disconnect', 'Resetting connection'); - restartWifi() - .then(getWifiInfo) - .then((network) => { - Object.assign(this.settings, network); - this.emit('connect', this.settings); - callback(); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + disable(callback) { + callback = enforceCallback(callback); + + turnOffWifi() + .then(commitWireless) + .then(restartWifi) + .then(() => { + this.emit('disconnect'); + callback(); + }) + .catch(error => emitErrorCallback(this, error, callback)); + } -Tessel.Wifi.prototype.connection = function(callback) { - callback = enforceCallback(callback); + reset(callback) { + callback = enforceCallback(callback); - isEnabled() - .then((enabled) => { - if (enabled) { - getWifiInfo() - .then((network) => { - delete network.password; + this.emit('disconnect', 'Resetting connection'); + restartWifi() + .then(getWifiInfo) + .then(network => emitConnectCallback(this, Object.assign(this.settings, network), callback)) + .catch(error => emitErrorCallback(this, error, callback)); + } - this.settings = network; + connection(callback) { + callback = enforceCallback(callback); - callback(null, network); - }); - } else { - callback(null, null); - } - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + isEnabled() + .then((enabled) => { + if (enabled) { + getWifiInfo() + .then(network => { + delete network.password; -Tessel.Wifi.prototype.connect = function(settings, callback) { - if (typeof settings !== 'object' || settings.ssid.length === 0) { - throw new Error('Wifi settings must be an object with at least a "ssid" property.'); + this.settings = network; + + callback(null, network); + }); + } else { + callback(null, null); + } + }) + .catch(error => emitErrorCallback(this, error, callback)); } - callback = enforceCallback(callback); + connect(settings, callback) { + if (typeof settings !== 'object' || settings.ssid.length === 0) { + throw new Error('Wifi settings must be an object with at least a "ssid" property.'); + } - if (settings.password && !settings.security) { - settings.security = 'psk2'; - } + callback = enforceCallback(callback); - if (!settings.password && (!settings.security || settings.security === 'none')) { - settings.password = ''; - settings.security = 'none'; - } + if (settings.password && !settings.security) { + settings.security = 'psk2'; + } - connectToNetwork(settings) - .then(turnOnWifi) - .then(commitWireless) - .then(restartWifi) - .then(getWifiInfo) - .then((network) => { - delete settings.password; + if (!settings.password && (!settings.security || settings.security === 'none')) { + settings.password = ''; + settings.security = 'none'; + } - this.settings = Object.assign(network, settings); - this.emit('connect', this.settings); + connectToNetwork(settings) + .then(turnOnWifi) + .then(commitWireless) + .then(restartWifi) + .then(getWifiInfo) + .then(network => { + delete settings.password; + emitConnectCallback(this, Object.assign(this.settings, network, settings), callback); + }) + .catch(error => emitErrorCallback(this, error, callback)); + } - callback(null, this.settings); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + findAvailableNetworks(callback) { + callback = enforceCallback(callback); + + isEnabled() + .then(enabled => { + if (enabled) { + return scanWifi(); + } else { + return turnOnWifi() + .then(commitWireless) + .then(restartWifi) + .then(scanWifi); + } + }) + .then(networks => callback(null, networks)) + .catch(error => emitErrorCallback(this, error, callback)); + } +} -Tessel.Wifi.prototype.findAvailableNetworks = function(callback) { - callback = enforceCallback(callback); +function emitConnectCallback(instance, data, callback) { + instance.emit('connect', data); + callback(null, data); +} - isEnabled() - .then((enabled) => { - if (enabled) { - return scanWifi(); - } else { - return turnOnWifi() - .then(commitWireless) - .then(restartWifi) - .then(scanWifi); - } - }) - .then((networks) => { - callback(null, networks); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; +function emitErrorCallback(instance, error, callback) { + instance.emit('error', error); + callback(error); +} function connectToNetwork(settings) { - var commands = ` + const commands = ` uci batch < { - cp.exec(commands, (error) => { + return new Promise(resolve => { + cp.exec(commands, error => { if (error) { throw error; } @@ -1458,8 +1478,8 @@ function connectToNetwork(settings) { } function turnOnWifi() { - return new Promise((resolve) => { - cp.exec('uci set wireless.@wifi-iface[0].disabled=0', (error) => { + return new Promise(resolve => { + cp.exec('uci set wireless.@wifi-iface[0].disabled=0', error => { if (error) { throw error; } @@ -1469,8 +1489,8 @@ function turnOnWifi() { } function turnOffWifi() { - return new Promise((resolve) => { - cp.exec('uci set wireless.@wifi-iface[0].disabled=1', (error) => { + return new Promise(resolve => { + cp.exec('uci set wireless.@wifi-iface[0].disabled=1', error => { if (error) { throw error; } @@ -1480,8 +1500,8 @@ function turnOffWifi() { } function commitWireless() { - return new Promise((resolve) => { - cp.exec('uci commit wireless', (error) => { + return new Promise(resolve => { + cp.exec('uci commit wireless', error => { if (error) { throw error; } @@ -1491,8 +1511,8 @@ function commitWireless() { } function restartWifi() { - return new Promise((resolve) => { - cp.exec('wifi', (error) => { + return new Promise(resolve => { + cp.exec('wifi', error => { if (error) { throw error; } @@ -1503,7 +1523,7 @@ function restartWifi() { } function isEnabled() { - return new Promise((resolve) => { + return new Promise(resolve => { cp.exec('uci get wireless.@wifi-iface[0].disabled', (error, result) => { if (error) { throw error; @@ -1516,8 +1536,8 @@ function isEnabled() { function getWifiInfo() { return new Promise((resolve, reject) => { - var checkCount = 0; - var inetRegex = /(inet addr):([\w\.]+)/; + let checkCount = 0; + const inetRegex = /(inet addr):([\w\.]+)/; function recursiveWifi() { cp.exec(`ubus call iwinfo info '{"device":"wlan0"}'`, (error, results) => { @@ -1525,7 +1545,7 @@ function getWifiInfo() { recursiveWifi(); } else { try { - var network = JSON.parse(results); + const network = JSON.parse(results); if (network.ssid === undefined) { // using 6 because it's the lowest count with accurate results after testing @@ -1533,7 +1553,7 @@ function getWifiInfo() { checkCount++; recursiveWifi(); } else { - var msg = 'Tessel is unable to connect, please check your credentials or list of available networks (using tessel.network.wifi.findAvailableNetworks()) and try again.'; + const msg = 'Tessel is unable to connect, please check your credentials or list of available networks (using tessel.network.wifi.findAvailableNetworks()) and try again.'; throw msg; } } else { @@ -1553,7 +1573,7 @@ function getWifiInfo() { if (error) { reject(error); } else { - var inetMatches = ipResults.match(inetRegex); + const inetMatches = ipResults.match(inetRegex); if (inetMatches === null) { recursiveWifi(network); @@ -1590,8 +1610,8 @@ function getWifiInfo() { } function scanWifi() { - return new Promise((resolve) => { - var checkCount = 0; + return new Promise(resolve => { + let checkCount = 0; function recursiveScan() { setImmediate(() => { @@ -1601,13 +1621,13 @@ function scanWifi() { return; } - var ssidRegex = /ESSID: "(.*)"/; - var qualityRegex = /Quality: (.*)/; - var encryptionRegex = /Encryption: (.*)/; + const ssidRegex = /ESSID: "(.*)"/; + const qualityRegex = /Quality: (.*)/; + const encryptionRegex = /Encryption: (.*)/; - var networks = results.trim().split('\n\n').reduce((networks, entry) => { + const networks = results.trim().split('\n\n').reduce((networks, entry) => { try { - var networkInfo = { + const networkInfo = { // Parse out the SSID ssid: ssidRegex.exec(entry)[1], // Parse out the quality of the connection @@ -1651,8 +1671,8 @@ function scanWifi() { } function safeQualityExprEvaluation(expr) { - var parsed = /(\d.*)(?:\/)(\d.*)/.exec(expr); - var isNumber = parsed === null && typeof + expr === 'number' && !Number.isNaN(+expr); + const parsed = /(\d.*)(?:\/)(\d.*)/.exec(expr); + const isNumber = parsed === null && typeof + expr === 'number' && !Number.isNaN(+expr); // If the expression doesn't match "\d.*/\d.*", // but IS a number, then return the number. Otherwise, @@ -1663,8 +1683,8 @@ function safeQualityExprEvaluation(expr) { } function compareBySignal(a, b) { - var ae = safeQualityExprEvaluation(a.quality); - var be = safeQualityExprEvaluation(b.quality); + const ae = safeQualityExprEvaluation(a.quality); + const be = safeQualityExprEvaluation(b.quality); if (ae > be) { return -1; @@ -1676,112 +1696,102 @@ function compareBySignal(a, b) { } // Access Point -Tessel.AP = function() { - var state = { - settings: {} - }; +class AP extends EventEmitter { + constructor() { + super(); - Object.defineProperties(this, { - settings: { - get: () => state.settings, - set: (settings) => { - state.settings = Object.assign(state.settings, settings); - } - } - }); -}; + const state = { + settings: {} + }; -util.inherits(Tessel.AP, EventEmitter); - -Tessel.AP.prototype.enable = function(callback) { - callback = enforceCallback(callback); - - turnOnAP() - .then(commitWireless) - .then(restartWifi) - .then(() => { - this.emit('on', this.settings); - this.emit('enable', this.settings); - callback(); - }) - .catch((error) => { - this.emit('error', error); - callback(error); + Object.defineProperties(this, { + settings: { + get: () => state.settings, + set: (settings) => { + state.settings = Object.assign(state.settings, settings); + } + } }); -}; + } -Tessel.AP.prototype.disable = function(callback) { - callback = enforceCallback(callback); - - turnOffAP() - .then(commitWireless) - .then(restartWifi) - .then(() => { - this.emit('off'); - this.emit('disable'); - callback(); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + enable(callback) { + callback = enforceCallback(callback); + + turnOnAP() + .then(commitWireless) + .then(restartWifi) + .then(() => { + this.emit('on', this.settings); + this.emit('enable', this.settings); + callback(); + }) + .catch(error => emitErrorCallback(this, error, callback)); + } -Tessel.AP.prototype.reset = function(callback) { - callback = enforceCallback(callback); - - this.emit('reset', 'Resetting connection'); - this.emit('off'); - this.emit('disable'); - restartWifi() - .then(() => { - this.emit('on', this.settings); - this.emit('enable', this.settings); - callback(); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + disable(callback) { + callback = enforceCallback(callback); + + turnOffAP() + .then(commitWireless) + .then(restartWifi) + .then(() => { + this.emit('off'); + this.emit('disable'); + callback(); + }) + .catch(error => emitErrorCallback(this, error, callback)); + } -Tessel.AP.prototype.create = function(settings, callback) { - if (typeof settings !== 'object' || settings.ssid.length === 0) { - throw new Error('Access point settings must be an object with at least a "ssid" property.'); + reset(callback) { + callback = enforceCallback(callback); + + this.emit('reset', 'Resetting connection'); + this.emit('off'); + this.emit('disable'); + restartWifi() + .then(() => { + this.emit('on', this.settings); + this.emit('enable', this.settings); + callback(); + }) + .catch(error => emitErrorCallback(this, error, callback)); } - callback = enforceCallback(callback); + create(settings, callback) { + if (typeof settings !== 'object' || settings.ssid.length === 0) { + throw new Error('Access point settings must be an object with at least a "ssid" property.'); + } - if (settings.password && !settings.security) { - settings.security = 'psk2'; - } + callback = enforceCallback(callback); - if (!settings.password && (!settings.security || settings.security === 'none')) { - settings.password = ''; - settings.security = 'none'; - } + if (settings.password && !settings.security) { + settings.security = 'psk2'; + } - createNetwork(settings) - .then(turnOnAP) - .then(commitWireless) - .then(restartWifi) - .then(getAccessPointIP) - .then((ip) => { - this.settings = Object.assign(settings, { - ip - }); - this.emit('create', this.settings); + if (!settings.password && (!settings.security || settings.security === 'none')) { + settings.password = ''; + settings.security = 'none'; + } - callback(null, this.settings); - }) - .catch((error) => { - this.emit('error', error); - callback(error); - }); -}; + createNetwork(settings) + .then(turnOnAP) + .then(commitWireless) + .then(restartWifi) + .then(getAccessPointIP) + .then((ip) => { + this.settings = Object.assign(settings, { + ip + }); + this.emit('create', this.settings); + + callback(null, this.settings); + }) + .catch(error => emitErrorCallback(this, error, callback)); + } +} function createNetwork(settings) { - var commands = ` + const commands = ` uci batch < { - cp.exec(commands, (error) => { + return new Promise(resolve => { + cp.exec(commands, error => { if (error) { throw error; } @@ -1801,7 +1811,7 @@ function createNetwork(settings) { } function getAccessPointIP() { - return new Promise((resolve) => { + return new Promise(resolve => { cp.exec('uci get network.lan.ipaddr', (error, ip) => { if (error) { throw error; @@ -1814,8 +1824,8 @@ function getAccessPointIP() { } function turnOnAP() { - return new Promise((resolve) => { - cp.exec('uci set wireless.@wifi-iface[1].disabled=0', (error) => { + return new Promise(resolve => { + cp.exec('uci set wireless.@wifi-iface[1].disabled=0', error => { if (error) { throw error; } @@ -1825,8 +1835,8 @@ function turnOnAP() { } function turnOffAP() { - return new Promise((resolve) => { - cp.exec('uci set wireless.@wifi-iface[1].disabled=1', (error) => { + return new Promise(resolve => { + cp.exec('uci set wireless.@wifi-iface[1].disabled=1', error => { if (error) { throw error; } @@ -1835,6 +1845,25 @@ function turnOffAP() { }); } + +Tessel.AP = AP; +Tessel.I2C = I2C; +Tessel.LED = LED; +Tessel.LEDs = LEDs; +Tessel.Port = Port; +Tessel.Pin = Pin; +Tessel.SPI = SPI; +Tessel.UART = UART; +Tessel.Wifi = Wifi; + + +Tessel.Pin.adcCapablePins = ADC_CAPABLE_PINS; +Tessel.Pin.interruptCapablePins = INT_CAPABLE_PINS; +Tessel.Pin.interruptModes = INT_MODES; +Tessel.Pin.pullCapablePins = PULL_CAPABLE_PINS; +Tessel.Pin.pullModes = PULL_MODES; +Tessel.Pin.pwmCapablePins = PWM_CAPABLE_PINS; + /* istanbul ignore else*/ if (process.env.IS_TEST_MODE) { Tessel.CMD = CMD; @@ -1847,7 +1876,7 @@ if (process.env.IS_TEST_MODE) { } -process.on('exit', function() { +process.on('exit', () => { /* istanbul ignore if*/ if (Tessel.instance) { Tessel.instance.close();