brewstack/nodeDrivers/i2c/i2c_raspi.js

/**
 * I2C Driver.
 * @module i2c
 * @author Dave Leitch
 * @requires brewlog
 * @requires node-mcp23017
 * @requires child_process
 * @desc This module provides access to all I2C GPIO expansion pins via a 32 bit bus.
 * The expansion module has 2 ports and each port has 2 banks which are 8 bits wide. 
 * These are mapped onto a single 32 bit value that can be read and written.
 * http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf

Usage: i2cset [-f] [-y] [-m MASK] [-r] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE]
  I2CBUS is an integer or an I2C bus name
  ADDRESS is an integer (0x03 - 0x77)
  MODE is one of:
    c (byte, no value)
    b (byte data, default)
    w (word data)
    i (I2C block data)
    s (SMBus block data)

BANK0:
	IO_DIR_A @ 0x0
	IO_DIR_B @ 0x1
BANK1:
	IO_DIR_A @ 0x0
	IO_DIR_B @ 0x10


	E.g. Switch on watchdog led  =>  
	DIR:0=out: 
	i2cset -y 1 0x21 0x0  0xBF
	i2cset -y 1 0x21 0x12 0x40
     Close Valve 0
	DIR:0=out:
        i2cset -y 1 0x20 0x1  0xFE
	i2cset -y 1 0x20 0x13 0x1
     Switch On Heater
	DIR:0=out:
	i2cset -y 1 0x21 0x0 0xFD   
        i2cset -y 1 0x21 0x12 0x2	

        	chip    data    bit   pin
	same as defs
Valve Ferment(2)0x20	0x13	7	15	no,4
Hood		0x20	0x13	6	14	no,13
Pump?		0x20	0x13 	5	13	no,12
Valve MashIn(7)	0x20 	0x13 	3	11	yes
Relay 5		0x20	0x13 	2	10
Valve ChillIn(3)0x20 	0x13 	1	9	yes
Chill Pump	0x20	0x13 	0	8	yes	

Relay 4		0x20	0x12	6	6`
Valve Kettle In	0x20	0x12	5	5	yes
Relay 8		0x20	0x12	4	4`
Pump1?		0x20	0x12 	3	3	yes
Heater		0x20	0x12	2	2	yes	


Flow 3		0x21	0x13	0x8	27	In	
Flow 1		0x21	0x13	0x2	25	In
Flow 0		0x21	0x13	0x1	24	In	
Watchdog Halt	0x21	0x12	0x80	23	In(1)	
Watchdog LED	0x21	0x12	0x40	22	Out(0)	1 = On
 */

const brewlog = require('../../../common/brewlog.js');
const brewdefs = require('../../../common/brewdefs.js');
let mraa;
let i2c;

const REG20 = 0x20;
const REG21 = 0x21;

const DIR_INPUT =  1;
const DIR_OUTPUT = 0;

const BYTE_INPUT = (DIR_INPUT << 0) | (DIR_INPUT << 1) | (DIR_INPUT << 2) | (DIR_INPUT << 3) | (DIR_INPUT << 4) | (DIR_INPUT << 5) | (DIR_INPUT << 6) | (DIR_INPUT << 7);

/**
 * Raspi I2C is part of the Raspi.js suite that provides access to the hardware I2C 
 * on pins 3 (SDA0) and 5 (SCL0).
 */

let I2C;
let raspi;
let _opt;

//IN=1, OUT=0
let dataDir  = [BYTE_INPUT, BYTE_INPUT, BYTE_INPUT, BYTE_INPUT];
let dataByte = [0x00, 0x00, 0x00, 0x00];

/**
 * Write a bit into the register
 | @param {Number} bankAddr 
 * @param {Number} currentByte 
 * @param {Number} bit 
 * @param {Number} value 
 */	
function writeReg(chipAddress, address, currentByte, bit, value){
	//brewlog.debug("writeReg", `0x${chipAddress.toString(16)}, 0x${address.toString(16)}, Bit ${bit}=${value}`);

	const mask = 1 << bit;
	let result;
	if (value == 1){
	  result = (currentByte | mask);
	}else if (value == 0){
	  result = (currentByte & ~mask);
	}else{
	  brewlog.critical("writeReg", `${value}`)
	}

	//setDir(bit, DIR_OUTPUT);
	i2c.writeByteSync(chipAddress, address, result);
	//setDir(bit, DIR_INPUT);


	return result;
}

/**
 * 
 * @param {Number} byte 
 * @param {Number} bit 
 */
function getBit(byte, bit){
	const result = (byte & (1 << bit)) >> bit;
	//console.log("bit",bit,"of",byte,"=",result)
	return result; 
	
}

function init(i2c){
	try {
	//Initialise all as inputs 
	brewlog.debug("INIT I2C")
	i2c.writeByteSync(REG20, 0x0, dataDir[0]);
	i2c.writeByteSync(REG20, 0x1, dataDir[1]);

	i2c.writeByteSync(REG21, 0x0, dataDir[2]);
	i2c.writeByteSync(REG21, 0x1, dataDir[3]);
	}
	catch (err) {
		if (err.errno == 121){
			brewlog.critical("Looks like there is no I2C hardware", err.message)
		}else{
			brewlog.critical("I2C init error", err.message)
		}
	}
	brewlog.debug("INIT I2C DONE");
}

function toString(bytes){
  const toHex = byte => `${byte.toString(16)}`;
  return bytes.reduce((acc, byte) => toHex(byte) + acc, '');
}

module.exports = { 
	start(opt) {
		return new Promise((resolve, reject) => {
			_opt = opt;
			if (brewdefs.isRaspPi() && (opt.sim.simulate === false)) {
				raspi = require('raspi');
				I2C = require('raspi-i2c').I2C;
				i2c = new I2C();
				raspi.init(()=>init(i2c));
			}else{
				i2c = require('../../../sim/raspi-i2c.js');
				init(i2c);
			}
			module.exports.DIR_INPUT =  DIR_INPUT;
 			module.exports.DIR_OUTPUT = DIR_OUTPUT;
			resolve(opt);
		})
	},
	
	/** Set or clear any bit
	 * @param {number} bit - Bit number [0:31]
	 * @param {number} value - 0 or 1
	 */
    writeBit(bit, value) {
    	// Suspect this is causing i2c errors.
		// brewlog.debug("writeBit", `${bit}, ${value}`);
	  	// brewlog.debug("BEFORE dataBytes=\t\t", `${toString(dataByte)}`);
	
	  if (bit < 16){
	    if (bit < 8){
	      dataByte[0] = writeReg(REG20, 0x12, dataByte[0], bit-(0*8), value);	
	    }else{		
	      dataByte[1] = writeReg(REG20, 0x13, dataByte[1], bit-(1*8), value);	
	    }
	  }else{	
	    if (bit < 24){
	      dataByte[2] = writeReg(REG21, 0x12, dataByte[2], bit-(2*8), value);		
	    }else{		
	      dataByte[3] = writeReg(REG21, 0x13, dataByte[3], bit-(3*8), value);		
	    }
	  }

	//   brewlog.debug("AFTER  result=\t", `${toString(dataByte)}`);
	},
	
	/** Read any single bit
	 * @param {number} bit - Bit number [0:31]
	 */
    readBit(bit) {
	  // brewlog.debug("Read bit=>"+bit)
	  let result;
	  if (bit < 16){
	    if (bit < 8){
	      dataByte[0] = i2c.readByteSync(REG20, 0x12);
	    //   brewlog.debug("Read [byte0]=>"+dataByte[0])
	      result = getBit(dataByte[0], bit); 
	    }else{		
	      dataByte[1] = i2c.readByteSync(REG20, 0x13);
	    //   brewlog.debug("Read [byte1]=>"+dataByte[1])
	      result = getBit(dataByte[1], bit-8); 
	    }
	  }else{
	    if (bit < 24){
	      dataByte[2] = i2c.readByteSync(REG21, 0x12);		
	        // brewlog.debug("Read [byte2]=>"+dataByte[2])
		result = getBit(dataByte[2], bit-16); 
	    }else{		
	      dataByte[3] = i2c.readByteSync(REG21, 0x13);		
	    //   brewlog.debug("Read [byte3]=>"+dataByte[3])
	      result = getBit(dataByte[3], bit-24); 
	    }
	  }
		
	  return result;
	},
	
	/** Toggle any bit
	 * @param {number} bit - Bit number [0:31]
	 */
	toggleBit(bit) {
		this.writeBit(bit, (this.readBit(bit) === 0) ? 1 : 0);
	},
	
	/** Set direction of a single bit
	 * @param {number} bit - Bit number [0:31]
	 * @param {number} dir - DIR_INPUT | DIR_OUTPUT
	 * 
	BANK0:
	IO_DIR_A @ 0x0
	IO_DIR_B @ 0x1
	BANK1:
	IO_DIR_A @ 0x0
	IO_DIR_B @ 0x10
	 */
  	setDir,
  
    /**
     * @desc Initialise an individual bit.
	 * {number, dir, value} bitInfo
	 */
    init(bitInfo) {
		this.setDir(bitInfo.number, bitInfo.dir);
		brewlog.debug("INIT", `${JSON.stringify(bitInfo)}`);
		this.setDir(bitInfo.number, bitInfo.dir);

		if (bitInfo.dir === this.DIR_OUTPUT){
			this.writeBit(bitInfo.number, bitInfo.value);
		}	
	},
	
	getWord() {
		try {
			const byte0 = i2c.readByteSync(REG20, 0x12);
			const byte1 = i2c.readByteSync(REG20, 0x13);
			const byte2 = i2c.readByteSync(REG21, 0x12);		
			const byte3 = i2c.readByteSync(REG21, 0x13);		
			
		//console.log(byte3,byte2,byte1,byte0);
			const word = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
			
			return word;
		} catch (err) {
			brewlog.critical("getWord Error", `${err}`)
			//restart
			this.start(_opt)
		}

	}
}

function setDir(bit, dir) {
	// brewlog.debug(`BEFORE bit ${bit}=${dir} I2C DIR BITS`, `${toString(dataDir)}`);
	if (bit < 16){
		if (bit < 8){
			dataDir[0] = writeReg(REG20, 0x0, dataDir[0], bit-(0*8), dir); 
		}else{		
			dataDir[1] = writeReg(REG20, 0x1, dataDir[1], bit-(1*8), dir); 
		}
	} else {
		if (bit < 24){
			dataDir[2] = writeReg(REG21, 0x0, dataDir[2], bit-(2*8), dir); 
		}else{		
			dataDir[3] = writeReg(REG21, 0x1, dataDir[3], bit-(3*8), dir); 
		}
	}
	// brewlog.debug("AFTER I2C DIR BITS", `${toString(dataDir)}`);
}