diff --git a/README.md b/README.md index 5fd33f0..4e50c43 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # zigbee-OTA -A collection of Zigbee OTA files +A collection of Zigbee OTA files. diff --git a/images/Hue/WhiteLamp-Atmel-Target_0105_5.130.1.30000_0012.sbl-ota b/images/Hue/WhiteLamp-Atmel-Target_0105_5.130.1.30000_0012.sbl-ota new file mode 100644 index 0000000..594653e Binary files /dev/null and b/images/Hue/WhiteLamp-Atmel-Target_0105_5.130.1.30000_0012.sbl-ota differ diff --git a/index.json b/index.json new file mode 100644 index 0000000..9b43353 --- /dev/null +++ b/index.json @@ -0,0 +1,10 @@ +[ + { + "fileVersion": 1107326256, + "fileSize": 256632, + "manufacturerCode": 4107, + "imageType": 261, + "url": "TODO", + "path": "images/Hue/WhiteLamp-Atmel-Target_0105_5.130.1.30000_0012.sbl-ota" + } +] \ No newline at end of file diff --git a/lib/ota.js b/lib/ota.js new file mode 100644 index 0000000..de81648 --- /dev/null +++ b/lib/ota.js @@ -0,0 +1,58 @@ +const assert = require('assert'); +const upgradeFileIdentifier = Buffer.from([0x1E, 0xF1, 0xEE, 0x0B]); + +function parseSubElement(buffer, position) { + const tagID = buffer.readUInt16LE(position); + const length = buffer.readUInt32LE(position + 2); + const data = buffer.slice(position + 6, position + 6 + length); + return {tagID, length, data}; +} + +function parseImage(buffer) { + const header = { + otaUpgradeFileIdentifier: buffer.subarray(0, 4), + otaHeaderVersion: buffer.readUInt16LE(4), + otaHeaderLength: buffer.readUInt16LE(6), + otaHeaderFieldControl: buffer.readUInt16LE(8), + manufacturerCode: buffer.readUInt16LE(10), + imageType: buffer.readUInt16LE(12), + fileVersion: buffer.readUInt32LE(14), + zigbeeStackVersion: buffer.readUInt16LE(18), + otaHeaderString: buffer.toString('utf8', 20, 52), + totalImageSize: buffer.readUInt32LE(52), + }; + let headerPos = 56; + if (header.otaHeaderFieldControl & 1) { + header.securityCredentialVersion = buffer.readUInt8(headerPos); + headerPos += 1; + } + if (header.otaHeaderFieldControl & 2) { + header.upgradeFileDestination = buffer.subarray(headerPos, headerPos + 8); + headerPos += 8; + } + if (header.otaHeaderFieldControl & 4) { + header.minimumHardwareVersion = buffer.readUInt16LE(headerPos); + headerPos += 2; + header.maximumHardwareVersion = buffer.readUInt16LE(headerPos); + headerPos += 2; + } + + const raw = buffer.slice(0, header.totalImageSize); + + assert(Buffer.compare(header.otaUpgradeFileIdentifier, upgradeFileIdentifier) === 0, 'Not an OTA file'); + + let position = header.otaHeaderLength; + const elements = []; + while (position < header.totalImageSize) { + const element = parseSubElement(buffer, position); + elements.push(element); + position += element.data.length + 6; + } + + assert(position === header.totalImageSize, 'Size mismatch'); + return {header, elements, raw}; +} + +module.exports = { + parseImage +}; \ No newline at end of file diff --git a/scripts/add.js b/scripts/add.js new file mode 100644 index 0000000..4367dfc --- /dev/null +++ b/scripts/add.js @@ -0,0 +1,58 @@ +const path = require('path'); +const fs = require('fs'); +const ota = require('../lib/ota'); +const filename = process.argv[2]; + +const manufacturerNameLookup = { + 4107: 'Hue', +}; + +if (!filename) { + throw new Error('Please provide a filename'); +} + +const file = path.resolve(filename); +if (!fs.existsSync(file)) { + throw new Error(`${file} does not exist`); +} + +const buffer = fs.readFileSync(file); +const parsed = ota.parseImage(buffer); + +if (!manufacturerNameLookup[parsed.header.manufacturerCode]) { + throw new Error(`${parsed.header.manufacturerCode} not in manufacturerNameLookup (please add it)`); +} + +const manufacturerName = manufacturerNameLookup[parsed.header.manufacturerCode]; +const indexJSON = JSON.parse(fs.readFileSync('index.json')); +const destination = path.join('images', manufacturerName, path.basename(file)); + +const entry = { + fileVersion: parsed.header.fileVersion, + fileSize: parsed.header.totalImageSize, + manufacturerCode: parsed.header.manufacturerCode, + imageType: parsed.header.imageType, + url: 'TODO', + path: destination, +}; + +const index = indexJSON.findIndex((i) => { + return i.manufacturerCode === entry.manufacturerCode && i.imageType === entry.imageType +}); + +if (index !== -1) { + console.log(`Updated existing entry (${JSON.stringify(entry)})`); + indexJSON[index] = entry; + fs.unlinkSync(entry.path) +} else { + console.log(`Added new entry (${JSON.stringify(entry)})`); + indexJSON.push(entry); +} + +if (!fs.existsSync(path.dirname(destination))) { + fs.mkdirSync(path.dirname(destination)); +} + +fs.copyFileSync(file, destination); + +fs.writeFileSync('index.json', JSON.stringify(indexJSON, null, ' ')); \ No newline at end of file