@ralphwetzel/node-red-ds2482-mcu
Version:
DS2482 I2C 1-Wire Master controller for Node-RED w/ MCU support
711 lines (525 loc) • 16.9 kB
JavaScript
/*
This code is based on DS2482 Onewire Bridge
Copyright (c) 2017 Ian Metcalf
https://github.com/ianmetcalf/node-ds2482
License: MIT
Adaptations to node-red-mcu by @ralphwetzel
https://github.com/ralphwetzel/node-red-ds2482-mcu
License: MIT
*/
'use strict';
import Modules from "modules";
import Timer from "timer";
// https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/tools/manifest.md#config
import config_from_manifest from "mc/config";
import { cmds } from "commands";
import { Buffer } from "buffer";
// import { trace } from "console";
//import { trace } from "console";
// import { trace } from "console";
// const utils = require('./utils');
const ROM_SIZE = 8;
const _utils = {
checkCRC: function (buffer, length) {
if (!(buffer instanceof Buffer)) {
// eslint-disable-next-line no-param-reassign
buffer = Buffer.from(Array.isArray(buffer) ? buffer : [buffer]);
}
length = length ?? buffer.length;
let crc = 0;
for (let i = 0; i < length; i += 1) {
crc ^= buffer.readUInt8(i);
for (let j = 0; j < 8; j += 1) {
if (crc & 0x01) {
crc = (crc >> 1) ^ 0x8C;
} else {
crc >>= 1;
}
}
}
return (crc === 0);
},
// delay: function (duration) {
// return new Promise(resolve => {
// setTimeout(() => {
// trace(duration + "\n");
// resolve();
// }, duration)
// })
// }
}
const utils = Object.freeze(_utils);
// const owFamilyCode = {
// "26": "ds2438"
// }
// The generic (= not implemented) 1-wire device
class owGeneric {
paths = { "not_implemented": [] }
}
// class owDevice {
// #id;
// #paths = {};
// add_path(name, read, write) {
// if (name in this.#paths) {
// throw new RangeError(`Path labeled "${name}" already exists.`)
// }
// this.#paths[name] = [read, write];
// }
// constructor(id) {
// this.#id = id;
// }
// get id() {
// return this.#id;
// }
// read(bridge, id, path) {
// if (path in this.#paths) {
// return this.#paths[path][0](bridge, id);
// }
// }
// write(bridge, id, path, data) {
// if (path in this.#paths) {
// this.#paths[path][1](bridge, id, data);
// return true;
// }
// return;
// }
// get paths() {
// return Object.keys(this.#paths).sort();
// }
// }
// class ds2438 extends owDevice {
// static {
// super.#add("temperature", function(bridge, id, path){}, function(bridge, id, path, data){})
// }
// }
class DS2482 {
/* the table to translate family codes to module names shall be defined in the
* manifest.json:
* {
* config: {
* ds2482: {
* <family_code>: <module_name>
* }
* }
* }
*/
#modules4devices = config_from_manifest?.ds2482?.devices;
#modules = {};
#devices = [];
#workers = {};
constructor(i2c) {
// eslint-disable-next-line no-param-reassign
// options = options || {};
this.i2c = i2c;
this.channel = null;
}
/*
* Main API
*/
init() {
return this.reset();
}
reset() {
this.lastFound = null;
this.lastConflict = 0;
this._resetBridge();
return this._resetWire();
}
configureBridge(options, confirm) {
// let config = 0;
options ??= {};
this._wait(true);
this._i2cWrite(cmds.SET_READ_POINTER, [cmds.REGISTERS.CONFIG]);
let config = this._i2cRead() & 0x0F;
if (options.activePullup) {
config = (config & ~cmds.CONFIG.ACTIVE) | ((options.activePullup ? 1 : 0) * cmds.CONFIG.ACTIVE)
}
if (options.strongPullup) {
config = (config & ~cmds.CONFIG.STRONG) | ((options.strongPullup ? 1 : 0) * cmds.CONFIG.STRONG)
}
if (options.overdrive) {
config = (config & ~cmds.CONFIG.OVERDRIVE) | ((options.overdrive ? 1 : 0) * cmds.CONFIG.OVERDRIVE)
}
this._i2cWrite(cmds.WRITE_CONFIG, [((~config & 0x0F) << 4) | config]);
let resp;
if (confirm) {
resp = this._readBridge();
if (config !== resp) {
throw new Error('Failed to configure bridge');
}
}
return resp;
}
strongPullup() {
this.configureBridge({strongPullup: true});
}
selectChannel(num) {
const ch = cmds.SELECTION_CODES[num || 0];
if (!ch) {
throw new Error('Invalid channel');
}
if (this.channel === num) {
return ch.read;
}
this._wait(true);
this.writeData(cmds.CHANNEL_SELECT, [ch.write]);
let resp = this._readBridge();
if (ch.read !== resp) {
throw new Error('Failed to select channel');
}
this.channel = num;
return resp;
}
sendCommand(cmd, rom) {
rom ? this.matchROM(rom) : this.skipROM();
this.writeData(cmd);
}
search() {
this.lastFound = null;
this.lastConflict = 0;
const found = [];
const searchNext = () => {
let resp = this.searchROM();
found.push(resp);
if (this.lastConflict) {
return searchNext();
}
this.lastFound = null;
this.lastConflict = 0;
return found;
};
return searchNext();
}
searchByFamily(family) {
if (typeof family === 'string') {
// eslint-disable-next-line no-param-reassign
family = parseInt(family, 16);
}
this.lastFound = Buffer.from([family, 0, 0, 0, 0, 0, 0, 0]);
this.lastConflict = 64;
const found = [];
const searchNext = () => {
let resp = this.searchROM()
if (this.lastFound.readUInt8(0) === family) {
found.push(resp);
}
if (this.lastConflict > 7 && found.length) {
return searchNext();
}
this.lastFound = null;
this.lastConflict = 0;
return found;
};
return searchNext();
}
/*
* Onewire ROM API
*/
searchROM() {
const rom = Buffer.alloc(ROM_SIZE);
let offset = 0;
let mask = 0x01;
let bit = 1;
let lastConflict = 0;
const direction = () => {
if (this.lastFound && bit < this.lastConflict) {
return this.lastFound.readUInt8(offset) & mask;
}
return bit === this.lastConflict ? 1 : 0;
};
const searchNextBit = () => {
let d = direction();
// trace("dir: " + d + "\n");
let resp = this.triplet(d);
const sbr = (resp & cmds.STATUS.SINGLE_BIT);
const tsb = (resp & cmds.STATUS.TRIPLE_BIT);
const dir = (resp & cmds.STATUS.BRANCH_DIR);
// trace(resp + "\n");
if (sbr && tsb) {
throw new Error('Bad search result');
}
if (!sbr && !tsb && !dir) {
lastConflict = bit;
}
const part = rom.readUInt8(offset);
rom.writeUInt8(dir ? part | mask : part & ~mask, offset);
mask <<= 1;
bit += 1;
if (mask > 128) {
offset += 1;
mask = 0x01;
}
if (offset < rom.length) {
return searchNextBit();
}
if (rom[0] === 0) {
throw new Error('ROM invalid');
}
if (!utils.checkCRC(rom)) {
throw new Error('CRC mismatch');
}
this.lastFound = rom;
this.lastConflict = lastConflict;
return rom.toString('hex');
};
this._resetWire();
this.writeData(cmds.ONE_WIRE_SEARCH_ROM);
return searchNextBit();
}
readROM() {
this._resetWire();
this.writeData(cmds.ONE_WIRE_READ_ROM);
let rom = this.readData(ROM_SIZE);
if (rom[0] === 0) {
throw new Error('ROM invalid');
}
if (!utils.checkCRC(rom)) {
throw new Error('CRC mismatch');
}
return rom.toString('hex');
}
matchROM(rom) {
// trace(rom + "\n");
if (typeof rom === 'string') {
// eslint-disable-next-line no-param-reassign
rom = Buffer.from(rom, "hex");
}
if (rom[0] === 0 || rom.length !== ROM_SIZE) {
throw new Error(`${rom}: ROM invalid.`);
}
this._resetWire();
this.writeData(cmds.ONE_WIRE_MATCH_ROM);
return this.writeData(rom);
}
skipROM() {
this._resetWire();
return this.writeData(cmds.ONE_WIRE_SKIP_ROM);
}
strongPullup
/*
* Onewire read/write API
*/
writeData(data, length) {
if (!(data instanceof Buffer)) {
// eslint-disable-next-line no-param-reassign
data = Buffer.from(Array.isArray(data) ? data : [data]);
}
length = length ?? data.byteLength;
let offset = 0;
const writeNextByte = () => {
this._i2cWrite(cmds.ONE_WIRE_WRITE_BYTE, data.slice(offset, offset + 1));
let resp = this._wait();
offset += 1;
if (offset < length) {
return writeNextByte();
}
return resp;
};
this._wait(true);
return writeNextByte();
}
readData(size) {
const data = Buffer.alloc(size);
let offset = 0;
const readNextByte = () => {
this._i2cWrite(cmds.ONE_WIRE_READ_BYTE);
this._wait();
let resp = this._readBridge(cmds.REGISTERS.DATA);
data.writeUInt8(resp, offset);
offset += 1;
if (offset < data.length) {
return readNextByte();
}
return data;
}
this._wait(true);
return readNextByte();
}
bit(setHigh) {
this._wait(true);
this._i2cWrite(cmds.ONE_WIRE_SINGLE_BIT, [setHigh ? 0x80 : 0]);
let resp = this._wait();
return (resp & cmds.STATUS.SINGLE_BIT ? 1 : 0);
}
triplet(dir) {
this._wait(true);
this._i2cWrite(cmds.ONE_WIRE_TRIPLET, [dir ? 0x80 : 0]);
return this._wait();
}
/*
* Private Methods
*/
_resetBridge() {
this._i2cWrite(cmds.DEVICE_RESET);
let resp = this._wait();
this.channel = 0;
return resp;
}
_resetWire() {
this._wait(true);
this._i2cWrite(cmds.ONE_WIRE_RESET);
let resp = this._wait();
// trace("_resetWire: " + JSON.stringify(resp) + "\n");
if (resp & cmds.STATUS.SHORT) {
throw new Error('Detected onewire short');
}
if (!(resp & cmds.STATUS.PRESENCE)) {
throw new Error('Failed to detected any onewire devices');
}
return resp;
}
_wait(setPointer) {
let reg = setPointer ? cmds.REGISTERS.STATUS : null;
let resp;
let t = Timer.set(() => {
throw new Error('Wait timeout');
}, 200);
do {
if (resp) {
reg = null;
Timer.delay(10);
}
try {
resp = this._readBridge(reg);
} catch (err) {
Timer.clear(t);
throw err;
}
} while (resp & cmds.STATUS.BUSY)
Timer.clear(t);
return resp;
}
_readBridge(reg) {
if (reg) {
this._i2cWrite(cmds.SET_READ_POINTER, [reg]);
}
let r = this._i2cRead();
return ((r >>> 0) & 0xFF)
}
_i2cWrite(cmd, bytes) {
// trace("write: " + cmd + ", " + JSON.stringify(bytes) + "\n");
let args = [cmd];
if (bytes) {
if (Array.isArray(bytes)) {
args = args.concat(bytes);
} else if (bytes instanceof Buffer) {
args = args.concat(...bytes);
} else {
throw new TypeError("'bytes' of _i2cWrite is of type " + typeof (bytes) + ".");
}
}
// trace("write: " + JSON.stringify(Uint8Array.from(args)) + "\n");
// from: Moddable/moddable/modules/io/expander/expander.js
try {
this.i2c.write(Uint8Array.from(args).buffer, true);
} catch (e) {
throw new Error("I2C: Write failed.", e.fileName, e.lineNumber);
}
}
_i2cRead() {
let res;
try {
res = new Uint8Array(this.i2c.read(1, true));
} catch (e) {
throw new Error("I2C: Read failed.", e.fileName, e.lineNumber);
}
// trace("read: " + JSON.stringify(res) + "\n");
return res[0];
}
/*
1-Wire device access API by path
*/
// class generic {
// paths = {
// "not_implemented": []
// }
// }
#get_worker(family_code) {
if (!this.#workers[family_code]) {
// check if there's a module defined for this family code
if (family_code in this.#modules4devices) {
// if so: import it!
this.#modules[family_code] = Modules.importNow(this.#modules4devices[family_code]);
// invoke the default function
this.#workers[family_code] = new this.#modules[family_code]()
} else {
// No device manager available to support this family code!
if (!this.#modules['*generic*']) {
// create a single worker instance (and store this in #modules)!
this.#modules['*generic*'] = new owGeneric();
}
// create a reference to the generic worker instance
this.#workers[family_code] = this.#modules['*generic*'];
}
}
return this.#workers[family_code];
}
#get_paths(worker) {
return Object.keys(worker?.paths)?.sort();
}
get paths() {
let p = [];
this.#devices = this.search();
for (let d = 0, dl = this.#devices.length; d < dl; d++) {
let id = this.#devices[d];
// trace("id: " + id + "\n");
let family = id.substring(0, 2);
let worker = this.#get_worker(family);
let paths = this.#get_paths(worker) || [];
for (let i = 0, l = paths.length; i < l; i++) {
p.push(`${id.substring(0,2)}.${id.substring(2)}/${paths[i]}`);
}
}
return p.sort();
}
readPath(path) {
if (typeof (path) !== "string") {
throw new RangeError(`'${path}' is an invalid path.`)
}
path = path.toLowerCase();
let pp = path.split("/");
let id = pp.shift();
if (!id || id.length < 16) {
throw new RangeError(`'${path}' is an invalid path.`)
}
let family = id.substring(0, 2);
// accept 28.FFFFFFF...
// as well as 28FFFFFF...
if (id[2] === ".") {
id = family + id.substring(3);
}
trace(id + "\n");
let worker = this.#get_worker(family);
let read_fn = worker?.paths?.[pp.join("/")]?.[0];
return read_fn?.call(worker, this, id);
}
writePath(path, data) {
if (typeof (path) !== "string") {
throw new RangeError(`'${path}' is an invalid path.`)
}
path = path.toLowerCase();
let pp = path.split("/");
let id = pp.shift();
if (!id || id.length < 8) {
throw new RangeError(`'${path}' is an invalid path.`)
}
let family = id.substring(0, 2);
// accept 28.FFFFFFF...
// as well as 28FFFFFF...
if (id[2] === ".") {
id = family + id.substring(3);
}
let worker = this.#get_worker(family);
let write_fn = worker?.paths?.[pp.join("/")]?.[1];
return write_fn?.call(worker, this, id, data);
}
checkCRC(buffer, length) {
return utils.checkCRC(buffer, length);
}
}
// Object.assign(DS2482, {
// ROM_SIZE,
// checkCRC: utils.checkCRC,
// });
// module.exports = DS2482;
export { DS2482 };