homebridge-bondbridge
Version:
Plugin to integrate BondBridge units by Bond to Homekit
298 lines (260 loc) • 8.89 kB
JavaScript
// This script is to generate a complete configuration file needed for the bondbridge plugin
// This script can handle up to 3 independent BondBridge (BB) systems
//
// This script is invoked from the plugin when homebridge restart
// Read CLI args
const args = process.argv.slice(2);
const BBIP1 = args[0];
const BBtoken1 = args[1];
const CFsetupOption1 = args[2];
const CFtimerSetup1 = args[3];
const BBdebug1 = args[4];
const BBIP2 = args[5];
const BBtoken2 = args[6];
const CFsetupOption2 = args[7];
const CFtimerSetup2 = args[8];
const BBdebug2 = args[9];
const BBIP3 = args[10];
const BBtoken3 = args[11];
const CFsetupOption3 = args[12];
const CFtimerSetup3 = args[13];
const BBdebug3 = args[14];
const BONDBRIDGE_SH_PATH = args[15];
// Globals for config parts
let bondbridgeBasicKeys = {};
let bondbridgeModelQueue = {};
let bondbridgeConstants = { constants: [] };
let bondbridgeQueueTypes = { queueTypes: [] };
let bondbridgeAccessories = { accessories: [] };
// Helper: IP validation
function isValidIp(ip) {
return /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(ip);
}
if (!isValidIp(BBIP1)) {
console.warn(`WARNING: the specified IP address ${BBIP1} is in wrong format`);
process.exit(1);
}
if (BBIP2 && BBIP2 !== "undefined" && !isValidIp(BBIP2)) {
console.warn(`WARNING: the specified IP address ${BBIP2} is in wrong format`);
process.exit(1);
}
if (BBIP3 && BBIP3 !== "undefined" && !isValidIp(BBIP3)) {
console.warn(`WARNING: the specified IP address ${BBIP3} is in wrong format`);
process.exit(1);
}
const noOfBondBridges = [BBIP1, BBIP2, BBIP3].filter(ip => ip && ip !== "undefined").length;
// Functions creating config parts
function createBasicKeys(debugFlag) {
bondbridgeBasicKeys = {
title: "BondBridge config auto-generated by ConfigCreator",
debug: debugFlag,
outputConstants: false,
statusMsg: true,
timeout: 60000,
stateChangeResponseTime: 0
};
}
function createModelQueue(model, bondid, queue) {
bondbridgeModelQueue = {
manufacturer: "OLIBRA",
model,
serialNumber: bondid,
queue
};
}
function createConstants(IPA, ip, debug) {
const debugA = debug === "true" ? "-debug" : "";
const constant = {
key: IPA,
value: `${ip}${debugA}`
};
bondbridgeConstants.constants.push(constant);
}
function createQueueTypes(queue) {
queueType = {
queue: queue,
queueType: "WoRm2"
};
bondbridgeQueueTypes.queueTypes.push(queueType);
}
function createFan(name, minStep, modelQueue, bondToken, device, IPA) {
const fan = {
type: "Fan",
displayName: name,
on: false,
rotationSpeed: 25,
name,
...modelQueue,
polling: [
{ characteristic: "on" },
{ characteristic: "rotationSpeed" }
],
props: {
rotationSpeed: { minStep }
},
state_cmd: `'${BONDBRIDGE_SH_PATH}'`,
state_cmd_suffix: `fan 'token:${bondToken}' 'device:${device}' ${IPA}`
};
bondbridgeAccessories.accessories.push(fan);
}
function createLightbulbNoDimmer(name, accType, modelQueue, bondToken, device, IPA) {
const lightbulb = {
type: "Lightbulb",
displayName: name,
on: false,
name,
...modelQueue,
polling: [{ characteristic: "on" }],
state_cmd: `'${BONDBRIDGE_SH_PATH}'`,
state_cmd_suffix: `${accType} 'token:${bondToken}' 'device:${device}' ${IPA}`
};
bondbridgeAccessories.accessories.push(lightbulb);
}
function createLightbulbWithDimmer(name, accType, minStep, modelQueue, bondToken, device, IPA) {
const lightbulb = {
type: "Lightbulb",
displayName: name,
on: false,
brightness: 80,
name,
...modelQueue,
polling: [
{ characteristic: "on" },
{ characteristic: "brightness" }
],
props: {
brightness: { minStep }
},
state_cmd: `'${BONDBRIDGE_SH_PATH}'`,
state_cmd_suffix: `${accType} 'token:${bondToken}' 'device:${device}' ${IPA}`
};
bondbridgeAccessories.accessories.push(lightbulb);
}
function createTimerLightbulb(name, accType, deviceType, modelQueue, bondToken, timerDevice, device, IPA) {
const timerLightbulb = {
type: "Lightbulb",
displayName: name,
on: false,
brightness: 0,
name,
...modelQueue,
polling: [
{ characteristic: "on" },
{ characteristic: "brightness" }
],
props: { brightness: { minStep: 1 } },
state_cmd: `'${BONDBRIDGE_SH_PATH}'`,
state_cmd_suffix: `${accType} 'token:${bondToken}' 'device:${timerDevice}' '${deviceType}:${device}' ${IPA}`
};
bondbridgeAccessories.accessories.push(timerLightbulb);
}
function assembleBondBridgeConfig() {
return {
name: "BondBridge",
...bondbridgeBasicKeys,
...bondbridgeConstants,
...bondbridgeQueueTypes,
...bondbridgeAccessories,
platform: "BondBridge"
};
}
async function main() {
const debugBondBridge = [BBdebug1, BBdebug2, BBdebug3].some(d => d === "true");
for (let n = 1; n <= noOfBondBridges; n++) {
const IPA = `\${BBIP${n}}`;
const ip = eval(`BBIP${n}`);
const bondToken = eval(`BBtoken${n}`);
const CFsetupOption = eval(`CFsetupOption${n}`);
const CFtimerSetup = eval(`CFtimerSetup${n}`);
const debug = eval(`BBdebug${n}`);
const queue = ['BBA', 'BBB', 'BBC'][n - 1];
// Fetch version info
let version;
try {
const res = await fetch(`http://${ip}/v2/sys/version`);
if (!res.ok) throw new Error(`HTTP error ${res.status}`);
version = await res.json();
} catch (err) {
console.error(`ERROR: BondBridge device is inaccessible or IP ${ip} is invalid!`);
process.exit(1);
}
const bondid = version.bondid;
if (!bondid) {
console.error("ERROR: Missing bondid in response!");
process.exit(1);
}
const model = version.model || "";
// Create config parts
createBasicKeys(debugBondBridge);
createModelQueue(model, bondid, queue);
if (CFsetupOption !== "doNotConfigure") {
createConstants(IPA, ip, debug);
createQueueTypes(queue);
}
// Fetch devices
let devicesData;
try {
const devRes = await fetch(`http://${ip}/v2/devices`, {
headers: { "BOND-Token": bondToken }
});
if (!devRes.ok) throw new Error(`HTTP error ${devRes.status}`);
devicesData = await devRes.json();
} catch (err) {
console.error(`ERROR: Failed to fetch devices from ${ip}`, err);
continue;
}
const deviceKeys = Object.keys(devicesData);
for (const device of deviceKeys) {
if (!/^[a-f0-9]+$/.test(device)) continue;
const timerDevice = device.split('').reverse().join('');
// Get max_speed
let maxSpeed = 0;
try {
const propRes = await fetch(`http://${ip}/v2/devices/${device}/properties`, {
headers: { "BOND-Token": bondToken }
});
if (!propRes.ok) throw new Error(`HTTP error ${propRes.status}`);
const propJson = await propRes.json();
maxSpeed = propJson.max_speed || 0;
} catch {}
const speedInterval = maxSpeed ? Math.floor(100 / maxSpeed) : 0;
// Get device name
let name = "";
try {
const nameRes = await fetch(`http://${ip}/v2/devices/${device}`, {
headers: { "BOND-Token": bondToken }
});
if (!nameRes.ok) throw new Error(`HTTP error ${nameRes.status}`);
const nameJson = await nameRes.json();
name = nameJson.name || "";
} catch {}
if (CFsetupOption !== "doNotConfigure") {
if (/^[a-zA-Z0-9 ]*Fan$/.test(name)) {
if (CFsetupOption !== "lightDimmer") {
createFan(name, speedInterval, bondbridgeModelQueue, bondToken, device, IPA);
}
if (CFtimerSetup === "includeTimers") {
createTimerLightbulb(`${name} Timer`, "fanTimer", "fanDevice", bondbridgeModelQueue, bondToken, timerDevice, device, IPA);
}
}
if (/^[a-zA-Z0-9 ]*Light$/.test(name)) {
if (CFsetupOption === "fanLight") {
createLightbulbNoDimmer(name, "light", bondbridgeModelQueue, bondToken, device, IPA);
} else if (CFsetupOption === "fanLightDimmer") {
createLightbulbWithDimmer(name, "light", speedInterval, bondbridgeModelQueue, bondToken, device, IPA);
} else if (CFsetupOption === "lightDimmer") {
createLightbulbWithDimmer(`${name} Dimmer`, "dimmer", speedInterval, bondbridgeModelQueue, bondToken, device, IPA);
}
if (CFtimerSetup === "includeTimers" && CFsetupOption !== "fan") {
createTimerLightbulb(`${name} Timer`, "lightTimer", "lightDevice", bondbridgeModelQueue, bondToken, timerDevice, device, IPA);
}
}
}
}
}
const bondbridgeConfig = assembleBondBridgeConfig();
console.log("DONE! createBondBridgeConfig completed successfully!");
console.log(JSON.stringify(bondbridgeConfig));
process.exit(0);
}
main();