UNPKG

raspi-pcf8574

Version:

Control each pin of a PCF8574/PCF8574A I2C port expander IC.

327 lines (326 loc) 14.6 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); /* * Node.js PCF8574/PCF8574A * * Copyright (c) 2017 Peter Müller <peter@crycode.de> (https://crycode.de) * * Node.js module for controlling each pin of a PCF8574/PCF8574A I2C port expander IC. */ var events_1 = require("events"); var Promise = require("bluebird"); var onoff_1 = require("onoff"); /** * Class for handling a PCF8574/PCF8574A IC. */ var PCF8574 = /** @class */ (function (_super) { __extends(PCF8574, _super); /** * Constructor for a new PCF8574/PCF8574A instance. * If you use this IC with one or more input pins, you have to call ... * a) enableInterrupt(gpioPin) to detect interrupts from the IC using a GPIO pin, or * b) doPoll() frequently enough to detect input changes with manually polling. * @param {I2cBus} i2cBus Instance of an opened i2c-bus. * @param {number} address The address of the PCF8574/PCF8574A IC. * @param {boolean|number} initialState The initial state of the pins of this IC. You can set a bitmask to define each pin seprately, or use true/false for all pins at once. */ function PCF8574(i2cBus, address, initialState) { var _this = _super.call(this) || this; /** Direction of each pin. By default all pin directions are undefined. */ _this._directions = [ PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF, PCF8574.DIR_UNDEF ]; /** Bitmask for all input pins. Used to set all input pins to high on the PCF8574/PCF8574A IC. */ _this._inputPinBitmask = 0; /** Flag if we are currently polling changes from the PCF8574/PCF8574A IC. */ _this._currentlyPolling = false; /** Instance of the used GPIO to detect interrupts, or null if no interrupt is used. */ _this._gpio = null; // bind the _handleInterrupt method strictly to this instance _this._handleInterrupt = _this._handleInterrupt.bind(_this); _this._i2cBus = i2cBus; if (address < 0 || address > 255) { throw new Error('Address out of range'); } _this._address = address; // nothing inverted by default _this._inverted = 0; if (initialState === true) { initialState = 255; } else if (initialState === false) { initialState = 0; } else if (typeof (initialState) !== 'number' || initialState < 0 || initialState > 255) { throw new Error('InitalState bitmask out of range'); } // save the inital state as current sate and write it to the IC _this._currentState = initialState; _this._i2cBus.sendByteSync(_this._address, _this._currentState); return _this; } /** * Enable the interrupt detection on the specified GPIO pin. * You can use one GPIO pin for multiple instances of the PCF8574 class. * @param {number} gpioPin BCM number of the pin, which will be used for the interrupts from the PCF8574/8574A IC. */ PCF8574.prototype.enableInterrupt = function (gpioPin) { if (PCF8574._allInstancesUsedGpios[gpioPin] != null) { // use already initalized GPIO this._gpio = PCF8574._allInstancesUsedGpios[gpioPin]; this._gpio['pcf8574UseCount']++; } else { // init the GPIO as input with falling edge, // because the PCF8574/PCF8574A will lower the interrupt line on changes this._gpio = new onoff_1.Gpio(gpioPin, 'in', 'falling'); this._gpio['pcf8574UseCount'] = 1; } this._gpio.watch(this._handleInterrupt); }; /** * Internal function to handle a GPIO interrupt. */ PCF8574.prototype._handleInterrupt = function () { // poll the current state and ignore any rejected promise this._poll().catch(function () { }); }; /** * Disable the interrupt detection. * This will unexport the interrupt GPIO, if it is not used by an other instance of this class. */ PCF8574.prototype.disableInterrupt = function () { // release the used GPIO if (this._gpio !== null) { // remove the interrupt handling this._gpio.unwatch(this._handleInterrupt); // decrease the use count of the GPIO and unexport it if not used anymore this._gpio['pcf8574UseCount']--; if (this._gpio['pcf8574UseCount'] === 0) { this._gpio.unexport(); } this._gpio = null; } }; /** * Helper function to set/clear one bit in a bitmask. * @param {number} current The current bitmask. * @param {PCF8574.PinNumber} pin The bit-number in the bitmask. * @param {boolean} value The new value for the bit. (true=set, false=clear) * @return {number} The new (modified) bitmask. */ PCF8574.prototype._setStatePin = function (current, pin, value) { if (value) { // set the bit return current | 1 << pin; } else { // clear the bit return current & ~(1 << pin); } }; /** * Write the current stateto the IC. * @param {number} newState (optional) The new state which will be set. If omitted the current state will be used. * @return {Promise} Promise which gets resolved when the state is written to the IC, or rejected in case of an error. */ PCF8574.prototype._setNewState = function (newState) { var _this = this; return new Promise(function (resolve, reject) { if (typeof (newState) === 'number') { _this._currentState = newState; } // repect inverted with bitmask using XOR var newIcState = _this._currentState ^ _this._inverted; // set all input pins to high newIcState = newIcState | _this._inputPinBitmask; _this._i2cBus.sendByte(_this._address, newIcState, function (err) { if (err) { reject(err); } else { resolve(); } }); }); }; /** * Manually poll changed inputs from the PCF8574/PCF8574A IC. * If a change on an input is detected, an "input" Event will be emitted with a data object containing the "pin" and the new "value". * This have to be called frequently enough if you don't use a GPIO for interrupt detection. * If you poll again before the last poll was completed, the promise will be rejected with an error. * @return {Promise} */ PCF8574.prototype.doPoll = function () { return this._poll(); }; /** * Internal function to poll the changes from the PCF8574/PCF8574A IC. * If a change on an input is detected, an "input" Event will be emitted with a data object containing the "pin" and the new "value". * This is called if an interrupt occured, or if doPoll() is called manually. * Additionally this is called if a new input is defined to read the current state of this pin. * @param {PCF8574.PinNumber} noEmit (optional) Pin number of a pin which should not trigger an event. (used for getting the current state while defining a pin as input) * @return {Promise} */ PCF8574.prototype._poll = function (noEmit) { var _this = this; if (this._currentlyPolling) { return Promise.reject('An other poll is in progress'); } this._currentlyPolling = true; return new Promise(function (resolve, reject) { // read from the IC _this._i2cBus.receiveByte(_this._address, function (err, readState) { _this._currentlyPolling = false; if (err) { reject(err); return; } // repect inverted with bitmask using XOR readState = readState ^ _this._inverted; // check each input for changes for (var pin = 0; pin < 8; pin++) { if (_this._directions[pin] !== PCF8574.DIR_IN) { continue; // isn't an input pin } if ((_this._currentState >> pin) % 2 !== (readState >> pin) % 2) { // pin changed var value = ((readState >> pin) % 2 !== 0); _this._currentState = _this._setStatePin(_this._currentState, pin, value); if (noEmit !== pin) { _this.emit('input', { pin: pin, value: value }); } } } resolve(); }); }); }; /** * Define a pin as an output. * This marks the pin to be used as an output pin. * @param {PCF8574.PinNumber} pin The pin number. (0 to 7) * @param {boolean} inverted true if this pin should be handled inverted (true=low, false=high) * @param {boolean} initialValue (optional) The initial value of this pin, which will be set immediatly. * @return {Promise} */ PCF8574.prototype.outputPin = function (pin, inverted, initialValue) { if (pin < 0 || pin > 7) { return Promise.reject(new Error('Pin out of range')); } this._inverted = this._setStatePin(this._inverted, pin, inverted); this._inputPinBitmask = this._setStatePin(this._inputPinBitmask, pin, false); this._directions[pin] = PCF8574.DIR_OUT; // set the initial value only if it is defined, otherwise keep the last value (probably from the initial state) if (typeof (initialValue) === 'undefined') { return Promise.resolve(null); } else { return this._setPinInternal(pin, initialValue); } }; /** * Define a pin as an input. * This marks the pin for input processing and activates the high level on this pin. * @param {PCF8574.PinNumber} pin The pin number. (0 to 7) * @param {boolean} inverted true if this pin should be handled inverted (high=false, low=true) * @return {Promise} */ PCF8574.prototype.inputPin = function (pin, inverted) { var _this = this; if (pin < 0 || pin > 7) { return Promise.reject(new Error('Pin out of range')); } this._inverted = this._setStatePin(this._inverted, pin, inverted); this._inputPinBitmask = this._setStatePin(this._inputPinBitmask, pin, true); this._directions[pin] = PCF8574.DIR_IN; // call _setNewState() to activate the high level on the input pin ... return this._setNewState() // ... and then poll all current inputs with noEmit on this pin to suspress the event .then(function () { return _this._poll(pin); }); }; /** * Set the value of an output pin. * If no value is given, the pin will be toggled. * @param {PCF8574.PinNumber} pin The pin number. (0 to 7) * @param {boolean} value The new value for this pin. * @return {Promise} */ PCF8574.prototype.setPin = function (pin, value) { if (pin < 0 || pin > 7) { return Promise.reject(new Error('Pin out of range')); } if (this._directions[pin] !== PCF8574.DIR_OUT) { return Promise.reject(new Error('Pin is not defined as output')); } if (typeof (value) == 'undefined') { // set value dependend on current state to toggle value = !((this._currentState >> pin) % 2 !== 0); } return this._setPinInternal(pin, value); }; /** * Internal function to set the state of a pin, regardless its direction. * @param {PCF8574.PinNumber} pin The pin number. (0 to 7) * @param {boolean} value The new value. * @return {Promise} */ PCF8574.prototype._setPinInternal = function (pin, value) { var newState = this._setStatePin(this._currentState, pin, value); return this._setNewState(newState); }; /** * Set the given value to all output pins. * @param {boolean} value The new value for all output pins. * @return {Promise} */ PCF8574.prototype.setAllPins = function (value) { var newState = this._currentState; for (var pin = 0; pin < 8; pin++) { if (this._directions[pin] !== PCF8574.DIR_OUT) { continue; // isn't an output pin } newState = this._setStatePin(newState, pin, value); } return this._setNewState(newState); }; /** * Returns the current value of a pin. * This returns the last saved value, not the value currently returned by the PCF8574/PCF9574A IC. * To get the current value call doPoll() first, if you're not using interrupts. * @param {PCF8574.PinNumber} pin The pin number. (0 to 7) * @return {boolean} The current value. */ PCF8574.prototype.getPinValue = function (pin) { if (pin < 0 || pin > 7) { return false; } return ((this._currentState >> pin) % 2 !== 0); }; /** Constant for undefined pin direction (unused pin). */ PCF8574.DIR_UNDEF = -1; /** Constant for input pin direction. */ PCF8574.DIR_IN = 1; /** Constant for output pin direction. */ PCF8574.DIR_OUT = 0; /** Object containing all GPIOs used by any PCF8574 instance. */ PCF8574._allInstancesUsedGpios = {}; return PCF8574; }(events_1.EventEmitter)); exports.PCF8574 = PCF8574;